Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
26.06% |
37 / 142 |
|
14.29% |
3 / 21 |
CRAP | |
0.00% |
0 / 1 |
Stream | |
26.06% |
37 / 142 |
|
14.29% |
3 / 21 |
2800.51 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
setStartX | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
setStartY | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
setEdgeX | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
setEdgeY | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
setCurrentX | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
setCurrentY | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getStartX | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getStartY | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getEdgeX | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getEdgeY | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getCurrentX | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getCurrentY | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
addText | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
setCurrentStyle | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
getTextStreams | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
20 | |||
getStream | |
0.00% |
0 / 47 |
|
0.00% |
0 / 1 |
992 | |||
getOrphanStream | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
hasOrphans | |
80.00% |
28 / 35 |
|
0.00% |
0 / 1 |
28.61 | |||
getColorStream | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
hasOrphanIndex | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * Pop PHP Framework (http://www.popphp.org/) |
4 | * |
5 | * @link https://github.com/popphp/popphp-framework |
6 | * @author Nick Sagona, III <dev@nolainteractive.com> |
7 | * @copyright Copyright (c) 2009-2023 NOLA Interactive, LLC. (http://www.nolainteractive.com) |
8 | * @license http://www.popphp.org/license New BSD License |
9 | */ |
10 | |
11 | /** |
12 | * @namespace |
13 | */ |
14 | namespace Pop\Pdf\Document\Page\Text; |
15 | |
16 | use Pop\Pdf\Document\Page\Color; |
17 | |
18 | /** |
19 | * Pdf page text stream class |
20 | * |
21 | * @category Pop |
22 | * @package Pop\Pdf |
23 | * @author Nick Sagona, III <dev@nolainteractive.com> |
24 | * @copyright Copyright (c) 2009-2023 NOLA Interactive, LLC. (http://www.nolainteractive.com) |
25 | * @license http://www.popphp.org/license New BSD License |
26 | * @version 4.2.0 |
27 | */ |
28 | class Stream |
29 | { |
30 | |
31 | /** |
32 | * Start X |
33 | * @var int |
34 | */ |
35 | protected $startX = null; |
36 | |
37 | /** |
38 | * Start Y |
39 | * @var int |
40 | */ |
41 | protected $startY = null; |
42 | |
43 | /** |
44 | * Edge X boundary |
45 | * @var int |
46 | */ |
47 | protected $edgeX = null; |
48 | |
49 | /** |
50 | * Edge Y boundary |
51 | * @var int |
52 | */ |
53 | protected $edgeY = null; |
54 | /** |
55 | * Current X |
56 | * @var int |
57 | */ |
58 | protected $currentX = null; |
59 | |
60 | /** |
61 | * Current Y |
62 | * @var int |
63 | */ |
64 | protected $currentY = null; |
65 | |
66 | /** |
67 | * Text streams |
68 | * @var array |
69 | */ |
70 | protected $streams = []; |
71 | |
72 | /** |
73 | * Text styles |
74 | * @var array |
75 | */ |
76 | protected $styles = []; |
77 | |
78 | /** |
79 | * Orphan index |
80 | * @var array |
81 | */ |
82 | protected $orphanIndex = []; |
83 | |
84 | /** |
85 | * Constructor |
86 | * |
87 | * Instantiate a PDF text stream object. |
88 | * |
89 | * @param int $startX |
90 | * @param int $startY |
91 | * @param int $edgeX |
92 | * @param int $edgeY |
93 | */ |
94 | public function __construct($startX, $startY, $edgeX, $edgeY = null) |
95 | { |
96 | $this->startX = $startX; |
97 | $this->startY = $startY; |
98 | $this->edgeX = $edgeX; |
99 | $this->edgeY = $edgeY; |
100 | } |
101 | |
102 | /** |
103 | * Set start X |
104 | * |
105 | * @param int $startX |
106 | * @return Stream |
107 | */ |
108 | public function setStartX($startX) |
109 | { |
110 | $this->startX = $startX; |
111 | return $this; |
112 | } |
113 | |
114 | /** |
115 | * Set start Y |
116 | * |
117 | * @param int $startY |
118 | * @return Stream |
119 | */ |
120 | public function setStartY($startY) |
121 | { |
122 | $this->startY = $startY; |
123 | return $this; |
124 | } |
125 | |
126 | /** |
127 | * Set edge X boundary |
128 | * |
129 | * @param int $edgeX |
130 | * @return Stream |
131 | */ |
132 | public function setEdgeX($edgeX) |
133 | { |
134 | $this->edgeX = $edgeX; |
135 | return $this; |
136 | } |
137 | |
138 | /** |
139 | * Set edge Y boundary |
140 | * |
141 | * @param int $edgeY |
142 | * @return Stream |
143 | */ |
144 | public function setEdgeY($edgeY) |
145 | { |
146 | $this->edgeY = $edgeY; |
147 | return $this; |
148 | } |
149 | |
150 | /** |
151 | * Set current X |
152 | * |
153 | * @param int $currentX |
154 | * @return Stream |
155 | */ |
156 | public function setCurrentX($currentX) |
157 | { |
158 | $this->currentX = $currentX; |
159 | return $this; |
160 | } |
161 | |
162 | /** |
163 | * Set current Y |
164 | * |
165 | * @param int $currentY |
166 | * @return Stream |
167 | */ |
168 | public function setCurrentY($currentY) |
169 | { |
170 | $this->currentY = $currentY; |
171 | return $this; |
172 | } |
173 | |
174 | /** |
175 | * Get start X |
176 | * |
177 | * @return int |
178 | */ |
179 | public function getStartX() |
180 | { |
181 | return $this->startX; |
182 | } |
183 | |
184 | /** |
185 | * Get start Y |
186 | * |
187 | * @return int |
188 | */ |
189 | public function getStartY() |
190 | { |
191 | return $this->startY; |
192 | } |
193 | |
194 | /** |
195 | * Get edge X boundary |
196 | * |
197 | * @return int |
198 | */ |
199 | public function getEdgeX() |
200 | { |
201 | return $this->edgeX; |
202 | } |
203 | |
204 | /** |
205 | * Get edge Y boundary |
206 | * |
207 | * @return int |
208 | */ |
209 | public function getEdgeY() |
210 | { |
211 | return $this->edgeY; |
212 | } |
213 | |
214 | /** |
215 | * Get current X |
216 | * |
217 | * @return int |
218 | */ |
219 | public function getCurrentX() |
220 | { |
221 | return $this->currentX; |
222 | } |
223 | |
224 | /** |
225 | * Get current Y |
226 | * |
227 | * @return int |
228 | */ |
229 | public function getCurrentY() |
230 | { |
231 | return $this->currentY; |
232 | } |
233 | |
234 | /** |
235 | * Add text to the stream |
236 | * |
237 | * @param string $string |
238 | * @param int $y |
239 | * @return Stream |
240 | */ |
241 | public function addText($string, $y = null) |
242 | { |
243 | $this->streams[] = [ |
244 | 'string' => $string, |
245 | 'y' => $y |
246 | ]; |
247 | |
248 | return $this; |
249 | } |
250 | |
251 | /** |
252 | * Set the current style |
253 | * |
254 | * @param string $font |
255 | * @param int $size |
256 | * @param Color\ColorInterface $color |
257 | * @return Stream |
258 | */ |
259 | public function setCurrentStyle($font, $size, Color\ColorInterface $color = null) |
260 | { |
261 | $key = (!empty($this->streams)) ? count($this->streams) : 0; |
262 | $this->styles[$key] = [ |
263 | 'font' => $font, |
264 | 'size' => $size, |
265 | 'color' => $color |
266 | ]; |
267 | |
268 | return $this; |
269 | } |
270 | |
271 | /** |
272 | * Get text stream |
273 | * |
274 | * @return array |
275 | */ |
276 | public function getTextStreams() |
277 | { |
278 | $streams = $this->streams; |
279 | $currentFont = 'Arial'; |
280 | $currentSize = 10; |
281 | $currentColor = new Color\Rgb(0, 0, 0); |
282 | |
283 | if (isset($this->styles[0])) { |
284 | $currentFont = $this->styles[0]['font'] ?? 'Arial'; |
285 | $currentSize = $this->styles[0]['size'] ?? 10; |
286 | $currentColor = $this->styles[0]['color'] ?? new Color\Rgb(0, 0, 0); |
287 | } |
288 | |
289 | foreach ($streams as $i => $stream) { |
290 | if (isset($this->styles[$i])) { |
291 | $currentFont = $this->styles[$i]['font'] ?? $currentFont; |
292 | $currentSize = $this->styles[$i]['size'] ?? $currentSize; |
293 | $currentColor = $this->styles[$i]['color'] ?? $currentColor; |
294 | } |
295 | $streams[$i]['font'] = $currentFont; |
296 | $streams[$i]['size'] = $currentSize; |
297 | $streams[$i]['color'] = $currentColor; |
298 | } |
299 | |
300 | return $streams; |
301 | } |
302 | |
303 | /** |
304 | * Get stream |
305 | * |
306 | * @param array $fonts |
307 | * @param array $fontReferences |
308 | * @return string |
309 | */ |
310 | public function getStream(array $fonts, array $fontReferences) |
311 | { |
312 | if (null === $this->currentX) { |
313 | $this->currentX = $this->startX; |
314 | } |
315 | if (null === $this->currentY) { |
316 | $this->currentY = $this->startY; |
317 | } |
318 | $fontName = null; |
319 | $fontReference = null; |
320 | $fontSize = null; |
321 | $curFont = null; |
322 | |
323 | foreach ($this->styles as $style) { |
324 | if ((null === $fontReference) && !empty($style['font']) && isset($fontReferences[$style['font']])) { |
325 | $fontName = $style['font']; |
326 | $fontReference = substr($fontReferences[$fontName], 0, strpos($fontReferences[$fontName], ' ')); |
327 | $curFont = $fonts[$fontName] ?? null; |
328 | } |
329 | if ((null === $fontSize) && !empty($style['size'])) { |
330 | $fontSize = $style['size']; |
331 | } |
332 | } |
333 | |
334 | $stream = "\nBT\n {$fontReference} {$fontSize} Tf\n 1 0 0 1 {$this->currentX} {$this->currentY} Tm\n 0 Tc 0 Tw 0 Tr\n"; |
335 | |
336 | foreach ($this->streams as $i => $str) { |
337 | if (isset($this->styles[$i]) && !empty($this->styles[$i]['font']) && isset($fontReferences[$this->styles[$i]['font']])) { |
338 | $fontName = $this->styles[$i]['font']; |
339 | $fontReference = substr($fontReferences[$fontName], 0, strpos($fontReferences[$fontName], ' ')); |
340 | $fontSize = (!empty($this->styles[$i]['size'])) ? $this->styles[$i]['size'] : $fontSize; |
341 | $curFont = $fonts[$fontName] ?? null; |
342 | $stream .= " {$fontReference} {$fontSize} Tf\n"; |
343 | } |
344 | if (isset($this->styles[$i]) && !empty($this->styles[$i]['color'])) { |
345 | $stream .= $this->getColorStream($this->styles[$i]['color']); |
346 | } |
347 | $curString = explode(' ', $str['string']); |
348 | |
349 | foreach ($curString as $j => $string) { |
350 | if ((null !== $this->edgeX) && ($this->currentX >= $this->edgeX)) { |
351 | $nextY = (null !== $str['y']) ? $str['y'] : $fontSize; |
352 | $stream .= " 0 -" . $nextY . " Td\n"; |
353 | $this->currentX = $this->startX; |
354 | $this->currentY -= $nextY; |
355 | if ((null !== $this->edgeY) && ($this->currentY <= $this->edgeY) && ($this->currentX == $this->startX)) { |
356 | break; |
357 | } |
358 | } |
359 | |
360 | if (!isset($curString[$j + 1])) { |
361 | if (isset($this->streams[$i + 1]) && |
362 | preg_match('/[a-zA-Z0-9]/', substr($this->streams[$i + 1]['string'], 0, 1))) { |
363 | $string .= ' '; |
364 | } |
365 | } else { |
366 | $string .= ' '; |
367 | } |
368 | |
369 | $stream .= " (" . $string . ")Tj\n"; |
370 | if (null !== $curFont) { |
371 | $this->currentX += $curFont->getStringWidth($string, $fontSize); |
372 | } |
373 | } |
374 | if ((null !== $this->edgeY) && ($this->currentY <= $this->edgeY) && ($this->currentX == $this->startX)) { |
375 | $this->orphanIndex = (isset($j)) ? [$i, $j] : [$i, 0]; |
376 | break; |
377 | } |
378 | } |
379 | |
380 | $stream .= "ET\n"; |
381 | |
382 | return $stream; |
383 | } |
384 | |
385 | /** |
386 | * Resume stream from orphaned index |
387 | * |
388 | * @return Stream |
389 | */ |
390 | public function getOrphanStream() |
391 | { |
392 | $offset = array_search($this->orphanIndex[0], array_keys($this->streams)); |
393 | $this->streams = array_slice($this->streams, $offset, null, true); |
394 | |
395 | if ($this->orphanIndex[1] > 0) { |
396 | $strings = array_slice(explode(' ', $this->streams[$this->orphanIndex[0]]['string']), $this->orphanIndex[1], null, true); |
397 | $this->streams[$this->orphanIndex[0]]['string'] = implode(' ', $strings); |
398 | } |
399 | |
400 | $this->orphanIndex = []; |
401 | return $this; |
402 | } |
403 | |
404 | /** |
405 | * Prepare stream |
406 | * |
407 | * @param array $fonts |
408 | * @return boolean |
409 | */ |
410 | public function hasOrphans(array $fonts) |
411 | { |
412 | $this->currentX = $this->startX; |
413 | $this->currentY = $this->startY; |
414 | $fontName = null; |
415 | $fontSize = null; |
416 | $curFont = null; |
417 | |
418 | foreach ($this->styles as $style) { |
419 | if (!empty($style['font'])) { |
420 | $fontName = $style['font']; |
421 | $curFont = $fonts[$fontName] ?? null; |
422 | } |
423 | if ((null === $fontSize) && !empty($style['size'])) { |
424 | $fontSize = $style['size']; |
425 | } |
426 | } |
427 | |
428 | foreach ($this->streams as $i => $str) { |
429 | if (isset($this->styles[$i]) && !empty($this->styles[$i]['font'])) { |
430 | $fontName = $this->styles[$i]['font']; |
431 | $fontSize = (!empty($this->styles[$i]['size'])) ? $this->styles[$i]['size'] : $fontSize; |
432 | $curFont = $fonts[$fontName] ?? null; |
433 | } |
434 | |
435 | $curString = explode(' ', $str['string']); |
436 | |
437 | foreach ($curString as $j => $string) { |
438 | if ((null !== $this->edgeX) && ($this->currentX >= $this->edgeX)) { |
439 | $nextY = (null !== $str['y']) ? $str['y'] : $fontSize; |
440 | $this->currentX = $this->startX; |
441 | $this->currentY -= $nextY; |
442 | if ((null !== $this->edgeY) && ($this->currentY <= $this->edgeY) && ($this->currentX == $this->startX)) { |
443 | break; |
444 | } |
445 | } |
446 | |
447 | if (!isset($curString[$j + 1])) { |
448 | if (isset($this->streams[$i + 1]) && |
449 | preg_match('/[a-zA-Z0-9]/', substr($this->streams[$i + 1]['string'], 0, 1))) { |
450 | $string .= ' '; |
451 | } |
452 | } else { |
453 | $string .= ' '; |
454 | } |
455 | |
456 | if (null !== $curFont) { |
457 | $this->currentX += $curFont->getStringWidth($string, $fontSize); |
458 | } |
459 | } |
460 | if ((null !== $this->edgeY) && ($this->currentY <= $this->edgeY) && ($this->currentX == $this->startX)) { |
461 | $this->orphanIndex = (isset($j)) ? [$i, $j] : [$i, 0]; |
462 | break; |
463 | } |
464 | } |
465 | |
466 | return (!empty($this->orphanIndex)); |
467 | } |
468 | |
469 | /** |
470 | * Get the partial color stream |
471 | * |
472 | * @param Color\ColorInterface $color |
473 | * @return string |
474 | */ |
475 | public function getColorStream(Color\ColorInterface $color) |
476 | { |
477 | $stream = ''; |
478 | |
479 | if ($color instanceof Color\Rgb) { |
480 | $stream .= ' ' . $color . " rg\n"; |
481 | } else if ($color instanceof Color\Cmyk) { |
482 | $stream .= ' ' . $color . " k\n"; |
483 | } else if ($color instanceof Color\Gray) { |
484 | $stream .= ' ' . $color . " g\n"; |
485 | } |
486 | |
487 | return $stream; |
488 | } |
489 | |
490 | /** |
491 | * Check if the text stream has orphan streams due to the page bottom |
492 | * |
493 | * @return boolean |
494 | */ |
495 | public function hasOrphanIndex() |
496 | { |
497 | return (null !== $this->orphanIndex); |
498 | } |
499 | |
500 | } |