FieldRendererComplexText   A
last analyzed

Complexity

Total Complexity 22

Size/Duplication

Total Lines 196
Duplicated Lines 0 %

Test Coverage

Coverage 84.62%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 76
dl 0
loc 196
ccs 66
cts 78
cp 0.8462
rs 10
c 1
b 0
f 0
wmc 22

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A factory() 0 4 1
B wrap() 0 25 7
B renderLine() 0 56 6
B render() 0 57 7
1
<?php
2
3
namespace Graze\CiffRenderer\Renderer\FieldRenderer;
4
5
use Graze\CiffRenderer\Parser\FieldParser\FieldParserComplexText;
6
use Graze\CiffRenderer\Parser\FieldParser\FieldParserInterface;
7
use Intervention\Image\Gd\Font;
8
use Intervention\Image\ImageManager;
9
10
class FieldRendererComplexText implements FieldRendererInterface
11
{
12
    /**
13
     * @var float
14
     */
15
    const LETTER_SPACING_ADJUSTMENT = 0.97;
16
17
    /**
18
     * @var Font
19
     */
20
    private $font;
21
22
    /**
23
     * @param Font $font
24
     */
25 2
    public function __construct(Font $font)
26
    {
27 2
        $this->font = $font;
28 2
    }
29
30
    /**
31
     * @param ImageManager $imageManager
32
     * @param FieldParserInterface $parser
33
     * @param null|callable $fontResolver
34
     * @param null|callable $graphicResolver
35
     * @return \Intervention\Image\Image
36
     */
37 1
    public function render(
38
        ImageManager $imageManager,
39
        FieldParserInterface $parser,
40
        callable $fontResolver = null,
41
        callable $graphicResolver = null
42
    ) {
43 1
        $fontPath = $fontResolver($parser->getFontFace());
44
45
        // Input may be multiline so grab the raw lines but preserve empty lines by injecting spaces
46 1
        $rawLines = explode("\n", str_replace("\n\n", "\n \n", $parser->getText()));
47 1
        $lines = [];
48 1
        foreach ($rawLines as $rawLine) {
49
            // Pass each raw line through the wrapping function
50 1
            $lines = array_merge($lines, explode("\n", $this->wrap($parser->getFontSize(), $fontPath, $rawLine, $parser->getWidth())));
51
        }
52
53 1
        $renderedTexts = [];
54 1
        $width = 0;
55 1
        $height = 0;
56
57 1
        foreach ($lines as $line) {
58 1
            $renderedText = $this->renderLine($imageManager, $parser, $fontPath, $line);
59 1
            if (is_null($renderedText)) {
60
                continue;
61
            }
62
63
            // store dimensions ready to create a canvas
64 1
            $width = max($width, $renderedText->getWidth());
65 1
            $height += $renderedText->getHeight();
66
67 1
            $renderedTexts[] = $renderedText;
68
        }
69
70 1
        if (empty($renderedTexts)) {
71
            // nothing to render
72
            return null;
73
        }
74
75
        // create a canvas to add each line of text to
76 1
        $canvas = $imageManager->canvas($width, $height);
77
78 1
        $offsetX = 0;
79 1
        $offsetY = 0;
80 1
        foreach ($renderedTexts as $renderedText) {
81
            // add the text to the canvas
82 1
            $canvas->insert($renderedText, 'top-left', (int) $offsetX, (int) $offsetY);
83
84
            // increase the offset for the next line
85 1
            $offsetY += $renderedText->getHeight();
86
        }
87
88 1
        $orientation = $parser->getOrientation();
89 1
        if ($orientation) {
90 1
            $canvas->rotate(360 - $orientation);
91
        }
92
93 1
        return $canvas;
94
    }
95
96
    /**
97
     * @param ImageManager $imageManager
98
     * @param FieldParserInterface $parser
99
     * @param string $fontPath
100
     * @param string $text
101
     * @return \Intervention\Image\Image
102
     */
103 1
    private function renderLine(ImageManager $imageManager, FieldParserInterface $parser, $fontPath, $text)
104
    {
105
        // create the text here so it can be measured
106 1
        $this->font->text($text);
107
108 1
        $this->font->file($fontPath);
109 1
        $this->font->size($parser->getFontSize());
110 1
        $this->font->color('#000');
111 1
        $this->font->align($parser->getTextAlignment());
112 1
        $this->font->valign('top');
113
114 1
        $size = $this->font->getBoxSize();
115
116
        // If we are trying to render a single space, it will have zero height and be skipped. Temporarily use the
117
        // dimensions of the letter 'l' to ensure it isn't skipped.
118 1
        if ($text == ' ') {
119
            $this->font->text('l');
120
            $size = $this->font->getBoxSize();
121
            $this->font->text($text);
122
        }
123
124
        // if text consists of invisible chars, measured size will be zero, nothing to print
125 1
        if ($size['width'] < 1 || $size['height'] < 1) {
126
            return null;
127
        }
128
129 1
        $fontCallback = function (&$font) {
130
            // override the font created by Canvas::text()
131 1
            $font = $this->font;
132 1
        };
133
134 1
        $canvas = $imageManager->canvas($parser->getWidth(), $size['height']);
135
136
        // figure out the correct reference point for this alignment
137 1
        switch ($parser->getTextAlignment()) {
138 1
            case FieldParserComplexText::ALIGN_CENTRE:
139
                $posX = round($parser->getWidth()/2);
140
                break;
141
142 1
            case FieldParserComplexText::ALIGN_RIGHT:
143
                $posX = $parser->getWidth();
144
                break;
145
146
            default:
147 1
                $posX = 0;
148 1
                break;
149
        }
150
151
        // no need to pass the text in here as we override the font in the callback
152 1
        $canvas->text('', $posX, 0, $fontCallback);
0 ignored issues
show
Bug introduced by
It seems like $posX can also be of type double; however, parameter $x of Intervention\Image\Image::text() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

152
        $canvas->text('', /** @scrutinizer ignore-type */ $posX, 0, $fontCallback);
Loading history...
153
154
        // attempt to correct differences between this and Clarisoft
155 1
        $width = (int) round($parser->getWidth() * self::LETTER_SPACING_ADJUSTMENT);
156 1
        $canvas->resize($width, $size['height']);
157
158 1
        return $canvas;
159
    }
160
161
    /**
162
     * Wrap the given text such that it doesn't exceed the given width using the given font and size.
163
     *
164
     * Derived from https://www.php.net/manual/en/function.imagettftext.php#89505
165
     *
166
     * @param string $fontSize
167
     * @param string $fontFace
168
     * @param string $string
169
     * @param int $width
170
     * @return string
171
     */
172 1
    private function wrap($fontSize, $fontFace, $string, $width)
173
    {
174
        // Font size needs to be scaled a bit to render to the same width as in Clarisoft.
175 1
        $fontSize = $fontSize * 0.8;
176 1
        $wrappedString = "";
177
178 1
        if ($string == ' ') {
179
            return $string;
180
        }
181
182 1
        $words = explode(' ', $string);
183
184 1
        foreach ($words as $word) {
185 1
            $teststring = $wrappedString=="" ? $word : $wrappedString.' '.$word;
186 1
            $testbox = imagettfbbox($fontSize, 0, $fontFace, $teststring);
0 ignored issues
show
Unused Code introduced by
The call to Graze\CiffRenderer\Rende...Renderer\imagettfbbox() has too many arguments starting with $fontSize. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

186
            $testbox = /** @scrutinizer ignore-call */ imagettfbbox($fontSize, 0, $fontFace, $teststring);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
187
            // Check that adding the new word does not exceed the width we want.
188
            // This isn't exact for various (unknown) reasons, so there is a 10 pixel buffer.
189 1
            if ($testbox[2] > ($width+10)) {
190
                $wrappedString.=($wrappedString=="" ? "" : "\n").$word;
191
            } else {
192 1
                $wrappedString.=($wrappedString=="" ? "" : ' ').$word;
193
            }
194
        }
195
196 1
        return $wrappedString;
197
    }
198
199
    /**
200
     * @return FieldRendererInterface
201
     */
202 1
    public static function factory()
203
    {
204 1
        return new static(
205 1
            new Font()
206
        );
207
    }
208
}
209