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
![]() |
|||||
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
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
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. ![]() |
|||||
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 |