1 | <?php |
||||
2 | |||||
3 | declare(strict_types=1); |
||||
4 | /** |
||||
5 | * Document class. |
||||
6 | * |
||||
7 | * @package YetiForcePDF |
||||
8 | * |
||||
9 | * @copyright YetiForce Sp. z o.o |
||||
10 | * @license MIT |
||||
11 | * @author Rafal Pospiech <[email protected]> |
||||
12 | * @author Mariusz Krzaczkowski <[email protected]> |
||||
13 | */ |
||||
14 | |||||
15 | namespace YetiForcePDF; |
||||
16 | |||||
17 | use Exception; |
||||
18 | use YetiForcePDF\Html\Parser; |
||||
19 | use YetiForcePDF\Layout\FooterBox; |
||||
20 | use YetiForcePDF\Layout\HeaderBox; |
||||
21 | use YetiForcePDF\Layout\WatermarkBox; |
||||
22 | use YetiForcePDF\Objects\Meta; |
||||
23 | use YetiForcePDF\Objects\PdfObject; |
||||
24 | |||||
25 | /** |
||||
26 | * Class Document. |
||||
27 | */ |
||||
28 | class Document |
||||
29 | { |
||||
30 | /** |
||||
31 | * Actual id auto incremented. |
||||
32 | * |
||||
33 | * @var int |
||||
34 | */ |
||||
35 | protected $actualId = 0; |
||||
36 | |||||
37 | /** |
||||
38 | * Main output buffer / content for pdf file. |
||||
39 | * |
||||
40 | * @var string |
||||
41 | */ |
||||
42 | protected $buffer = ''; |
||||
43 | |||||
44 | /** |
||||
45 | * Main entry point - root element. |
||||
46 | * |
||||
47 | * @var \YetiForcePDF\Catalog |
||||
48 | */ |
||||
49 | protected $catalog; |
||||
50 | |||||
51 | /** |
||||
52 | * Pages dictionary. |
||||
53 | * |
||||
54 | * @var Pages |
||||
55 | */ |
||||
56 | protected $pagesObject; |
||||
57 | |||||
58 | /** |
||||
59 | * Current page object. |
||||
60 | * |
||||
61 | * @var Page |
||||
62 | */ |
||||
63 | protected $currentPageObject; |
||||
64 | |||||
65 | /** |
||||
66 | * @var string default page format |
||||
67 | */ |
||||
68 | protected $defaultFormat = 'A4'; |
||||
69 | |||||
70 | /** |
||||
71 | * @var string default page orientation |
||||
72 | */ |
||||
73 | protected $defaultOrientation = \YetiForcePDF\Page::ORIENTATION_PORTRAIT; |
||||
74 | |||||
75 | /** |
||||
76 | * @var Page[] all pages in the document |
||||
77 | */ |
||||
78 | protected $pages = []; |
||||
79 | |||||
80 | /** |
||||
81 | * Default page margins. |
||||
82 | * |
||||
83 | * @var array |
||||
84 | */ |
||||
85 | protected $defaultMargins = [ |
||||
86 | 'left' => 40, |
||||
87 | 'top' => 40, |
||||
88 | 'right' => 40, |
||||
89 | 'bottom' => 40, |
||||
90 | ]; |
||||
91 | |||||
92 | /** |
||||
93 | * All objects inside document. |
||||
94 | * |
||||
95 | * @var \YetiForcePDF\Objects\PdfObject[] |
||||
96 | */ |
||||
97 | protected $objects = []; |
||||
98 | |||||
99 | /** |
||||
100 | * @var Parser |
||||
101 | */ |
||||
102 | protected $htmlParser; |
||||
103 | |||||
104 | /** |
||||
105 | * Fonts data. |
||||
106 | * |
||||
107 | * @var array |
||||
108 | */ |
||||
109 | protected $fontsData = []; |
||||
110 | |||||
111 | /** |
||||
112 | * @var array |
||||
113 | */ |
||||
114 | protected $fontInstances = []; |
||||
115 | |||||
116 | /** |
||||
117 | * Actual font id. |
||||
118 | * |
||||
119 | * @var int |
||||
120 | */ |
||||
121 | protected $actualFontId = 0; |
||||
122 | |||||
123 | /** |
||||
124 | * Actual graphic state id. |
||||
125 | * |
||||
126 | * @var int |
||||
127 | */ |
||||
128 | protected $actualGraphicStateId = 0; |
||||
129 | |||||
130 | /** |
||||
131 | * @var bool |
||||
132 | */ |
||||
133 | protected $debugMode = false; |
||||
134 | |||||
135 | /** |
||||
136 | * @var HeaderBox|null |
||||
137 | */ |
||||
138 | protected $header; |
||||
139 | |||||
140 | /** |
||||
141 | * @var FooterBox|null |
||||
142 | */ |
||||
143 | protected $footer; |
||||
144 | |||||
145 | /** |
||||
146 | * @var WatermarkBox|null |
||||
147 | */ |
||||
148 | protected $watermark; |
||||
149 | |||||
150 | /** |
||||
151 | * @var Meta |
||||
152 | */ |
||||
153 | protected $meta; |
||||
154 | |||||
155 | /** |
||||
156 | * @var bool |
||||
157 | */ |
||||
158 | protected $parsed = false; |
||||
159 | |||||
160 | /** |
||||
161 | * Characters int values cache for fonts. |
||||
162 | * |
||||
163 | * @var array |
||||
164 | */ |
||||
165 | public $ordCache = []; |
||||
166 | |||||
167 | /** |
||||
168 | * Css selectors like classes ids. |
||||
169 | * |
||||
170 | * @var array |
||||
171 | */ |
||||
172 | protected $cssSelectors = []; |
||||
173 | |||||
174 | /** |
||||
175 | * Are we debugging? |
||||
176 | * |
||||
177 | * @return bool |
||||
178 | */ |
||||
179 | public function inDebugMode() |
||||
180 | { |
||||
181 | return $this->debugMode; |
||||
182 | } |
||||
183 | |||||
184 | /** |
||||
185 | * Is document already parsed? |
||||
186 | * |
||||
187 | * @return bool |
||||
188 | */ |
||||
189 | public function isParsed() |
||||
190 | { |
||||
191 | return $this->parsed; |
||||
192 | } |
||||
193 | |||||
194 | /** |
||||
195 | * Initialisation. |
||||
196 | * |
||||
197 | * @return $this |
||||
198 | */ |
||||
199 | public function init() |
||||
200 | { |
||||
201 | $this->catalog = (new \YetiForcePDF\Catalog())->setDocument($this)->init(); |
||||
202 | $this->pagesObject = $this->catalog->addChild((new Pages())->setDocument($this)->init()); |
||||
203 | $this->meta = (new Meta())->setDocument($this)->init(); |
||||
204 | |||||
205 | return $this; |
||||
206 | } |
||||
207 | |||||
208 | /** |
||||
209 | * Set default page format. |
||||
210 | * |
||||
211 | * @param string $defaultFormat |
||||
212 | * |
||||
213 | * @return $this |
||||
214 | */ |
||||
215 | public function setDefaultFormat(string $defaultFormat) |
||||
216 | { |
||||
217 | $this->defaultFormat = $defaultFormat; |
||||
218 | foreach ($this->pages as $page) { |
||||
219 | $page->setFormat($defaultFormat); |
||||
220 | } |
||||
221 | |||||
222 | return $this; |
||||
223 | } |
||||
224 | |||||
225 | /** |
||||
226 | * Set default page orientation. |
||||
227 | * |
||||
228 | * @param string $defaultOrientation |
||||
229 | * |
||||
230 | * @return $this |
||||
231 | */ |
||||
232 | public function setDefaultOrientation(string $defaultOrientation) |
||||
233 | { |
||||
234 | $this->defaultOrientation = $defaultOrientation; |
||||
235 | foreach ($this->pages as $page) { |
||||
236 | $page->setOrientation($defaultOrientation); |
||||
237 | } |
||||
238 | |||||
239 | return $this; |
||||
240 | } |
||||
241 | |||||
242 | /** |
||||
243 | * Set default page margins. |
||||
244 | * |
||||
245 | * @param float $left |
||||
246 | * @param float $top |
||||
247 | * @param float $right |
||||
248 | * @param float $bottom |
||||
249 | * |
||||
250 | * @return $this |
||||
251 | */ |
||||
252 | public function setDefaultMargins(float $left, float $top, float $right, float $bottom) |
||||
253 | { |
||||
254 | $this->defaultMargins = [ |
||||
255 | 'left' => $left, |
||||
256 | 'top' => $top, |
||||
257 | 'right' => $right, |
||||
258 | 'bottom' => $bottom, |
||||
259 | 'horizontal' => $left + $right, |
||||
260 | 'vertical' => $top + $bottom, |
||||
261 | ]; |
||||
262 | foreach ($this->pages as $page) { |
||||
263 | $page->setMargins($left, $top, $right, $bottom); |
||||
264 | } |
||||
265 | |||||
266 | return $this; |
||||
267 | } |
||||
268 | |||||
269 | /** |
||||
270 | * Set default left margin. |
||||
271 | * |
||||
272 | * @param float $left |
||||
273 | */ |
||||
274 | public function setDefaultLeftMargin(float $left) |
||||
275 | { |
||||
276 | $this->defaultMargins['left'] = $left; |
||||
277 | foreach ($this->pages as $page) { |
||||
278 | $page->setMargins($this->defaultMargins['left'], $this->defaultMargins['top'], $this->defaultMargins['right'], $this->defaultMargins['bottom']); |
||||
279 | } |
||||
280 | |||||
281 | return $this; |
||||
282 | } |
||||
283 | |||||
284 | /** |
||||
285 | * Set default top margin. |
||||
286 | * |
||||
287 | * @param float $left |
||||
288 | * @param float $top |
||||
289 | */ |
||||
290 | public function setDefaultTopMargin(float $top) |
||||
291 | { |
||||
292 | $this->defaultMargins['top'] = $top; |
||||
293 | foreach ($this->pages as $page) { |
||||
294 | $page->setMargins($this->defaultMargins['left'], $this->defaultMargins['top'], $this->defaultMargins['right'], $this->defaultMargins['bottom']); |
||||
295 | } |
||||
296 | |||||
297 | return $this; |
||||
298 | } |
||||
299 | |||||
300 | /** |
||||
301 | * Set default right margin. |
||||
302 | * |
||||
303 | * @param float $left |
||||
304 | * @param float $right |
||||
305 | */ |
||||
306 | public function setDefaultRightMargin(float $right) |
||||
307 | { |
||||
308 | $this->defaultMargins['right'] = $right; |
||||
309 | foreach ($this->pages as $page) { |
||||
310 | $page->setMargins($this->defaultMargins['left'], $this->defaultMargins['top'], $this->defaultMargins['right'], $this->defaultMargins['bottom']); |
||||
311 | } |
||||
312 | |||||
313 | return $this; |
||||
314 | } |
||||
315 | |||||
316 | /** |
||||
317 | * Set default bottom margin. |
||||
318 | * |
||||
319 | * @param float $left |
||||
320 | * @param float $bottom |
||||
321 | */ |
||||
322 | public function setDefaultBottomMargin(float $bottom) |
||||
323 | { |
||||
324 | $this->defaultMargins['bottom'] = $bottom; |
||||
325 | foreach ($this->pages as $page) { |
||||
326 | $page->setMargins($this->defaultMargins['left'], $this->defaultMargins['top'], $this->defaultMargins['right'], $this->defaultMargins['bottom']); |
||||
327 | } |
||||
328 | |||||
329 | return $this; |
||||
330 | } |
||||
331 | |||||
332 | /** |
||||
333 | * Get meta. |
||||
334 | * |
||||
335 | * @return Meta |
||||
336 | */ |
||||
337 | public function getMeta() |
||||
338 | { |
||||
339 | return $this->meta; |
||||
340 | } |
||||
341 | |||||
342 | /** |
||||
343 | * Get actual id for newly created object. |
||||
344 | * |
||||
345 | * @return int |
||||
346 | */ |
||||
347 | public function getActualId() |
||||
348 | { |
||||
349 | return ++$this->actualId; |
||||
350 | } |
||||
351 | |||||
352 | /** |
||||
353 | * Get actual id for newly created font. |
||||
354 | * |
||||
355 | * @return int |
||||
356 | */ |
||||
357 | public function getActualFontId(): int |
||||
358 | { |
||||
359 | return ++$this->actualFontId; |
||||
360 | } |
||||
361 | |||||
362 | /** |
||||
363 | * Get actual id for newly created graphic state. |
||||
364 | * |
||||
365 | * @return int |
||||
366 | */ |
||||
367 | public function getActualGraphicStateId(): int |
||||
368 | { |
||||
369 | return ++$this->actualGraphicStateId; |
||||
370 | } |
||||
371 | |||||
372 | /** |
||||
373 | * Set font. |
||||
374 | * |
||||
375 | * @param string $family |
||||
376 | * @param string $weight |
||||
377 | * @param string $style |
||||
378 | * @param \YetiForcePDF\Objects\Font $fontInstance |
||||
379 | * |
||||
380 | * @return $this |
||||
381 | */ |
||||
382 | public function setFontInstance(string $family, string $weight, string $style, Objects\Font $fontInstance) |
||||
383 | { |
||||
384 | $this->fontInstances[$family][$weight][$style] = $fontInstance; |
||||
385 | |||||
386 | return $this; |
||||
387 | } |
||||
388 | |||||
389 | /** |
||||
390 | * Get font instance. |
||||
391 | * |
||||
392 | * @param string $family |
||||
393 | * @param string $weight |
||||
394 | * @param string $style |
||||
395 | * |
||||
396 | * @return \YetiForcePDF\Objects\Font|null |
||||
397 | */ |
||||
398 | public function getFontInstance(string $family, string $weight, string $style) |
||||
399 | { |
||||
400 | if (!empty($this->fontInstances[$family][$weight][$style])) { |
||||
401 | return $this->fontInstances[$family][$weight][$style]; |
||||
402 | } |
||||
403 | |||||
404 | return null; |
||||
405 | } |
||||
406 | |||||
407 | /** |
||||
408 | * Get all font instances. |
||||
409 | * |
||||
410 | * @return \YetiForcePDF\Objects\Font[] |
||||
411 | */ |
||||
412 | public function getAllFontInstances() |
||||
413 | { |
||||
414 | $instances = []; |
||||
415 | foreach ($this->fontInstances as $weights) { |
||||
416 | foreach ($weights as $styles) { |
||||
417 | foreach ($styles as $instance) { |
||||
418 | $instances[] = $instance; |
||||
419 | } |
||||
420 | } |
||||
421 | } |
||||
422 | |||||
423 | return $instances; |
||||
424 | } |
||||
425 | |||||
426 | /** |
||||
427 | * Set font information. |
||||
428 | * |
||||
429 | * @param string $family |
||||
430 | * @param string $weight |
||||
431 | * @param string $style |
||||
432 | * @param \FontLib\TrueType\File $font |
||||
433 | * |
||||
434 | * @return $this |
||||
435 | */ |
||||
436 | public function setFontData(string $family, string $weight, string $style, \FontLib\TrueType\File $font) |
||||
437 | { |
||||
438 | if (empty($this->fontsData[$family][$weight][$style])) { |
||||
439 | $this->fontsData[$family][$weight][$style] = $font; |
||||
440 | } |
||||
441 | |||||
442 | return $this; |
||||
443 | } |
||||
444 | |||||
445 | /** |
||||
446 | * Get font data. |
||||
447 | * |
||||
448 | * @param string $family |
||||
449 | * @param string $weight |
||||
450 | * @param string $style |
||||
451 | * |
||||
452 | * @return \FontLib\Font|null |
||||
453 | */ |
||||
454 | public function getFontData(string $family, string $weight, string $style) |
||||
455 | { |
||||
456 | if (!empty($this->fontsData[$family][$weight][$style])) { |
||||
457 | return $this->fontsData[$family][$weight][$style]; |
||||
458 | } |
||||
459 | |||||
460 | return null; |
||||
461 | } |
||||
462 | |||||
463 | /** |
||||
464 | * Add fonts from json. |
||||
465 | * |
||||
466 | * @param array $fonts |
||||
467 | */ |
||||
468 | public static function addFonts(array $fonts) |
||||
469 | { |
||||
470 | \YetiForcePDF\Objects\Font::loadFromArray($fonts); |
||||
471 | } |
||||
472 | |||||
473 | /** |
||||
474 | * Get pages object. |
||||
475 | * |
||||
476 | * @return \YetiForcePDF\Pages |
||||
477 | */ |
||||
478 | public function getPagesObject(): Pages |
||||
479 | { |
||||
480 | return $this->pagesObject; |
||||
481 | } |
||||
482 | |||||
483 | /** |
||||
484 | * Get default page format. |
||||
485 | * |
||||
486 | * @return string |
||||
487 | */ |
||||
488 | public function getDefaultFormat() |
||||
489 | { |
||||
490 | return $this->defaultFormat; |
||||
491 | } |
||||
492 | |||||
493 | /** |
||||
494 | * Get default page orientation. |
||||
495 | * |
||||
496 | * @return string |
||||
497 | */ |
||||
498 | public function getDefaultOrientation() |
||||
499 | { |
||||
500 | return $this->defaultOrientation; |
||||
501 | } |
||||
502 | |||||
503 | /** |
||||
504 | * Get default margins. |
||||
505 | * |
||||
506 | * @return array |
||||
507 | */ |
||||
508 | public function getDefaultMargins() |
||||
509 | { |
||||
510 | return $this->defaultMargins; |
||||
511 | } |
||||
512 | |||||
513 | /** |
||||
514 | * Set header. |
||||
515 | * |
||||
516 | * @param HeaderBox $header |
||||
517 | * |
||||
518 | * @return $this |
||||
519 | */ |
||||
520 | public function setHeader(HeaderBox $header) |
||||
521 | { |
||||
522 | if ($header->getParent()) { |
||||
523 | $header = $header->getParent()->removeChild($header); |
||||
524 | } |
||||
525 | $this->header = $header; |
||||
526 | |||||
527 | return $this; |
||||
528 | } |
||||
529 | |||||
530 | /** |
||||
531 | * Get header. |
||||
532 | * |
||||
533 | * @return HeaderBox|null |
||||
534 | */ |
||||
535 | public function getHeader() |
||||
536 | { |
||||
537 | return $this->header; |
||||
538 | } |
||||
539 | |||||
540 | /** |
||||
541 | * Set watermark. |
||||
542 | * |
||||
543 | * @param WatermarkBox $watermark |
||||
544 | * |
||||
545 | * @return $this |
||||
546 | */ |
||||
547 | public function setWatermark(WatermarkBox $watermark) |
||||
548 | { |
||||
549 | if ($watermark->getParent()) { |
||||
550 | $watermark = $watermark->getParent()->removeChild($watermark); |
||||
551 | } |
||||
552 | $this->watermark = $watermark; |
||||
553 | |||||
554 | return $this; |
||||
555 | } |
||||
556 | |||||
557 | /** |
||||
558 | * Get watermark. |
||||
559 | * |
||||
560 | * @return WatermarkBox|null |
||||
561 | */ |
||||
562 | public function getWatermark() |
||||
563 | { |
||||
564 | return $this->watermark; |
||||
565 | } |
||||
566 | |||||
567 | /** |
||||
568 | * Set footer. |
||||
569 | * |
||||
570 | * @param FooterBox $footer |
||||
571 | * |
||||
572 | * @return $this |
||||
573 | */ |
||||
574 | public function setFooter(FooterBox $footer) |
||||
575 | { |
||||
576 | if ($footer->getParent()) { |
||||
577 | $footer = $footer->getParent()->removeChild($footer); |
||||
578 | } |
||||
579 | $this->footer = $footer; |
||||
580 | |||||
581 | return $this; |
||||
582 | } |
||||
583 | |||||
584 | /** |
||||
585 | * Get footer. |
||||
586 | * |
||||
587 | * @return FooterBox|null |
||||
588 | */ |
||||
589 | public function getFooter() |
||||
590 | { |
||||
591 | return $this->footer; |
||||
592 | } |
||||
593 | |||||
594 | /** |
||||
595 | * Add page to the document. |
||||
596 | * |
||||
597 | * @param string $format - optional format 'A4' for example |
||||
598 | * @param string $orientation - optional orientation 'P' or 'L' |
||||
599 | * @param Page|null $page - we can add cloned page or page from other document too |
||||
600 | * @param Page|null $after - add page after this page |
||||
601 | * |
||||
602 | * @return \YetiForcePDF\Page |
||||
603 | */ |
||||
604 | public function addPage(string $format = '', string $orientation = '', Page $page = null, Page $after = null): Page |
||||
605 | { |
||||
606 | if (null === $page) { |
||||
607 | $page = (new Page())->setDocument($this)->init(); |
||||
608 | } |
||||
609 | if (!$format) { |
||||
610 | $format = $this->defaultFormat; |
||||
611 | } |
||||
612 | if (!$orientation) { |
||||
613 | $orientation = $this->defaultOrientation; |
||||
614 | } |
||||
615 | $page->setOrientation($orientation)->setFormat($format); |
||||
616 | $afterIndex = \count($this->pages); |
||||
617 | if ($after) { |
||||
618 | foreach ($this->pages as $afterIndex => $childPage) { |
||||
619 | if ($childPage === $after) { |
||||
620 | break; |
||||
621 | } |
||||
622 | } |
||||
623 | ++$afterIndex; |
||||
624 | } |
||||
625 | $page->setPageNumber($afterIndex); |
||||
626 | if ($after) { |
||||
627 | $merge = array_splice($this->pages, $afterIndex); |
||||
628 | $this->pages[] = $page; |
||||
629 | $this->pages = array_merge($this->pages, $merge); |
||||
630 | } else { |
||||
631 | $this->pages[] = $page; |
||||
632 | } |
||||
633 | $this->currentPageObject = $page; |
||||
634 | |||||
635 | return $page; |
||||
636 | } |
||||
637 | |||||
638 | /** |
||||
639 | * Get current page. |
||||
640 | * |
||||
641 | * @return Page |
||||
642 | */ |
||||
643 | public function getCurrentPage(): Page |
||||
644 | { |
||||
645 | return $this->currentPageObject; |
||||
646 | } |
||||
647 | |||||
648 | /** |
||||
649 | * Set current page. |
||||
650 | * |
||||
651 | * @param Page $page |
||||
652 | */ |
||||
653 | public function setCurrentPage(Page $page) |
||||
654 | { |
||||
655 | $this->currentPageObject = $page; |
||||
656 | } |
||||
657 | |||||
658 | /** |
||||
659 | * Get all pages. |
||||
660 | * |
||||
661 | * @param int|null $groupIndex |
||||
662 | * |
||||
663 | * @return Page[] |
||||
664 | */ |
||||
665 | public function getPages(int $groupIndex = null) |
||||
666 | { |
||||
667 | if ($groupIndex) { |
||||
0 ignored issues
–
show
|
|||||
668 | $pages = []; |
||||
669 | foreach ($this->pages as $page) { |
||||
670 | if ($page->getGroup() === $groupIndex) { |
||||
671 | $pages[] = $page; |
||||
672 | } |
||||
673 | } |
||||
674 | |||||
675 | return $pages; |
||||
676 | } |
||||
677 | |||||
678 | return $this->pages; |
||||
679 | } |
||||
680 | |||||
681 | /** |
||||
682 | * Fix page numbers. |
||||
683 | * |
||||
684 | * pages that are expanded by overflow will have the same unique id - cloned |
||||
685 | * so they are in one group of pages - if some page is added with different unique id |
||||
686 | * then it means that from now on pages are from other group and we should reset page numbers / count |
||||
687 | * |
||||
688 | * @return $this |
||||
689 | */ |
||||
690 | public function fixPageNumbers() |
||||
691 | { |
||||
692 | $groups = []; |
||||
693 | foreach ($this->getPages() as $page) { |
||||
694 | $groups[$page->getGroup()][] = $page; |
||||
695 | } |
||||
696 | foreach ($groups as $pages) { |
||||
697 | $pageCount = \count($pages); |
||||
698 | foreach ($pages as $index => $page) { |
||||
699 | $page->setPageNumber($index + 1); |
||||
700 | $page->setPageCount($pageCount); |
||||
701 | } |
||||
702 | } |
||||
703 | |||||
704 | return $this; |
||||
705 | } |
||||
706 | |||||
707 | /** |
||||
708 | * Get document header. |
||||
709 | * |
||||
710 | * @return string |
||||
711 | */ |
||||
712 | protected function getDocumentHeader(): string |
||||
713 | { |
||||
714 | return "%PDF-1.4\n%âăĎÓ\n"; |
||||
715 | } |
||||
716 | |||||
717 | /** |
||||
718 | * Get document footer. |
||||
719 | * |
||||
720 | * @return string |
||||
721 | */ |
||||
722 | protected function getDocumentFooter(): string |
||||
723 | { |
||||
724 | return '%%EOF'; |
||||
725 | } |
||||
726 | |||||
727 | /** |
||||
728 | * Add object to document. |
||||
729 | * |
||||
730 | * @param PdfObject $object |
||||
731 | * @param PdfObject|null $after - add after this element |
||||
732 | * |
||||
733 | * @return \YetiForcePDF\Document |
||||
734 | */ |
||||
735 | public function addObject(PdfObject $object, $after = null): self |
||||
736 | { |
||||
737 | $afterIndex = \count($this->objects); |
||||
738 | if ($after) { |
||||
739 | foreach ($this->objects as $afterIndex => $obj) { |
||||
740 | if ($after === $obj) { |
||||
741 | break; |
||||
742 | } |
||||
743 | } |
||||
744 | ++$afterIndex; |
||||
745 | } |
||||
746 | if (!$after) { |
||||
747 | $this->objects[] = $object; |
||||
748 | |||||
749 | return $this; |
||||
750 | } |
||||
751 | $merge = array_splice($this->objects, $afterIndex); |
||||
752 | foreach ($this->objects as $obj) { |
||||
753 | if ($obj->getId() === $object->getId()) { |
||||
754 | // id already exists (maybe we are merging with other doc) - generate new one |
||||
755 | $object->setId($this->getActualId()); |
||||
756 | |||||
757 | break; |
||||
758 | } |
||||
759 | } |
||||
760 | $this->objects[] = $object; |
||||
761 | $this->objects = array_merge($this->objects, $merge); |
||||
762 | |||||
763 | return $this; |
||||
764 | } |
||||
765 | |||||
766 | /** |
||||
767 | * Remove object from document. |
||||
768 | * |
||||
769 | * @param \YetiForcePDF\Objects\PdfObject $object |
||||
770 | * |
||||
771 | * @return \YetiForcePDF\Document |
||||
772 | */ |
||||
773 | public function removeObject(PdfObject $object): self |
||||
774 | { |
||||
775 | $objects = []; |
||||
776 | foreach ($this->objects as $currentObject) { |
||||
777 | if ($currentObject !== $object) { |
||||
778 | $objects[] = $currentObject; |
||||
779 | } |
||||
780 | } |
||||
781 | $this->objects = $objects; |
||||
782 | unset($objects); |
||||
783 | |||||
784 | return $this; |
||||
785 | } |
||||
786 | |||||
787 | /** |
||||
788 | * Load html string. |
||||
789 | * |
||||
790 | * @param string $html |
||||
791 | * @param string $fromEncoding |
||||
792 | * |
||||
793 | * @return $this |
||||
794 | * @throws Exception |
||||
795 | */ |
||||
796 | public function loadHtml(string $html, string $fromEncoding = 'UTF-8'): self |
||||
797 | { |
||||
798 | if ($fromEncoding === '') { |
||||
799 | throw new Exception('Encoding can not be empty'); |
||||
800 | } |
||||
801 | |||||
802 | $this->htmlParser = (new Parser())->setDocument($this)->init(); |
||||
803 | $this->htmlParser->loadHtml($html, $fromEncoding); |
||||
804 | |||||
805 | return $this; |
||||
806 | } |
||||
807 | |||||
808 | /** |
||||
809 | * Count objects. |
||||
810 | * |
||||
811 | * @param string $name - object name |
||||
812 | * |
||||
813 | * @return int |
||||
814 | */ |
||||
815 | public function countObjects(string $name = ''): int |
||||
816 | { |
||||
817 | if ('' === $name) { |
||||
818 | return \count($this->objects); |
||||
819 | } |
||||
820 | $typeCount = 0; |
||||
821 | foreach ($this->objects as $object) { |
||||
822 | if ($object->getName() === $name) { |
||||
823 | ++$typeCount; |
||||
824 | } |
||||
825 | } |
||||
826 | |||||
827 | return $typeCount; |
||||
828 | } |
||||
829 | |||||
830 | /** |
||||
831 | * Get objects. |
||||
832 | * |
||||
833 | * @param string $name - object name |
||||
834 | * |
||||
835 | * @return \YetiForcePDF\Objects\PdfObject[] |
||||
836 | */ |
||||
837 | public function getObjects(string $name = ''): array |
||||
838 | { |
||||
839 | if ('' === $name) { |
||||
840 | return $this->objects; |
||||
841 | } |
||||
842 | $objects = []; |
||||
843 | foreach ($this->objects as $object) { |
||||
844 | if ($object->getName() === $name) { |
||||
845 | $objects[] = $object; |
||||
846 | } |
||||
847 | } |
||||
848 | |||||
849 | return $objects; |
||||
850 | } |
||||
851 | |||||
852 | /** |
||||
853 | * Filter text |
||||
854 | * Filter the text, this is applied to all text just before being inserted into the pdf document |
||||
855 | * it escapes the various things that need to be escaped, and so on. |
||||
856 | * |
||||
857 | * @param string $text |
||||
858 | * @param string $encoding |
||||
859 | * @param bool $withParenthesis |
||||
860 | * @param bool $prependBom |
||||
861 | * |
||||
862 | * @return string |
||||
863 | */ |
||||
864 | public function filterText(string $text, string $encoding = 'UTF-16', bool $withParenthesis = true, bool $prependBom = false) |
||||
865 | { |
||||
866 | $text = preg_replace('/[\n\r\t\s]+/u', ' ', mb_convert_encoding($text, 'UTF-8')); |
||||
867 | $text = preg_replace('/^\s+|\s+$/u', '', $text); |
||||
868 | $text = preg_replace('/\s+/u', ' ', $text); |
||||
869 | $text = mb_convert_encoding($text, $encoding, mb_detect_encoding($text)); |
||||
870 | $text = strtr($text, [')' => '\\)', '(' => '\\(', '\\' => '\\\\', \chr(13) => '\r']); |
||||
0 ignored issues
–
show
It seems like
$text can also be of type array ; however, parameter $str of strtr() does only seem to accept string , 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
![]() |
|||||
871 | if ($prependBom) { |
||||
872 | $text = \chr(254) . \chr(255) . $text; |
||||
873 | } |
||||
874 | if ($withParenthesis) { |
||||
875 | return '(' . $text . ')'; |
||||
876 | } |
||||
877 | |||||
878 | return $text; |
||||
879 | } |
||||
880 | |||||
881 | /** |
||||
882 | * Parse html. |
||||
883 | * |
||||
884 | * @return $this |
||||
885 | */ |
||||
886 | public function parse() |
||||
887 | { |
||||
888 | if (!$this->isParsed()) { |
||||
889 | $this->htmlParser->parse(); |
||||
890 | $this->parsed = true; |
||||
891 | } |
||||
892 | |||||
893 | return $this; |
||||
894 | } |
||||
895 | |||||
896 | /** |
||||
897 | * Layout document content to pdf string. |
||||
898 | * |
||||
899 | * @return string |
||||
900 | */ |
||||
901 | public function render(): string |
||||
902 | { |
||||
903 | $xref = ''; |
||||
904 | $this->buffer = ''; |
||||
905 | |||||
906 | $this->buffer .= $this->getDocumentHeader(); |
||||
907 | $this->parse(); |
||||
908 | $objectSize = 0; |
||||
909 | |||||
910 | foreach ($this->objects as $object) { |
||||
911 | if (\in_array($object->getBasicType(), ['Dictionary', 'Stream', 'Array'])) { |
||||
912 | $xref .= sprintf("%010d 00000 n \n", \strlen($this->buffer)); |
||||
913 | $this->buffer .= $object->render() . "\n"; |
||||
914 | ++$objectSize; |
||||
915 | } |
||||
916 | } |
||||
917 | |||||
918 | $offset = \strlen($this->buffer); |
||||
919 | $this->buffer .= implode("\n", [ |
||||
920 | 'xref', |
||||
921 | '0 ' . $objectSize, |
||||
922 | '0000000000 65535 f ', |
||||
923 | $xref, |
||||
924 | ]); |
||||
925 | |||||
926 | $trailer = (new \YetiForcePDF\Objects\Trailer()) |
||||
927 | ->setDocument($this)->setRootObject($this->catalog)->setSize($objectSize); |
||||
928 | |||||
929 | $this->buffer .= $trailer->render() . "\n"; |
||||
930 | $this->buffer .= implode("\n", [ |
||||
931 | 'startxref', |
||||
932 | $offset, |
||||
933 | '', |
||||
934 | ]); |
||||
935 | $this->buffer .= $this->getDocumentFooter(); |
||||
936 | $this->removeObject($trailer); |
||||
937 | |||||
938 | return $this->buffer; |
||||
939 | } |
||||
940 | |||||
941 | /** |
||||
942 | * Get css selector rules. |
||||
943 | * |
||||
944 | * @param string $selector |
||||
945 | * |
||||
946 | * @return array |
||||
947 | */ |
||||
948 | public function getCssSelectorRules(string $selector): array |
||||
949 | { |
||||
950 | $rules = []; |
||||
951 | foreach (explode(' ', $selector) as $className) { |
||||
952 | if ($className && isset($this->cssSelectors[$className])) { |
||||
953 | $rules = array_merge($rules, $this->cssSelectors[$className]); |
||||
954 | } |
||||
955 | } |
||||
956 | |||||
957 | return $rules; |
||||
958 | } |
||||
959 | |||||
960 | /** |
||||
961 | * Get css selectors. |
||||
962 | * |
||||
963 | * @return array |
||||
964 | */ |
||||
965 | public function getCssSelectors() |
||||
966 | { |
||||
967 | return $this->cssSelectors; |
||||
968 | } |
||||
969 | |||||
970 | /** |
||||
971 | * Add css selector rules. |
||||
972 | * |
||||
973 | * @param string $selector .className or #id |
||||
974 | * @param array $rules |
||||
975 | * |
||||
976 | * @return $this |
||||
977 | */ |
||||
978 | public function addCssSelectorRules(string $selector, array $rules): self |
||||
979 | { |
||||
980 | if (isset($this->cssSelectors[$selector])) { |
||||
981 | $this->cssSelectors[$selector] = array_merge($this->cssSelectors[$selector], $rules); |
||||
982 | } else { |
||||
983 | $this->cssSelectors[$selector] = $rules; |
||||
984 | } |
||||
985 | return $this; |
||||
986 | } |
||||
987 | } |
||||
988 |
In PHP, under loose comparison (like
==
, or!=
, orswitch
conditions), values of different types might be equal.For
integer
values, zero is a special case, in particular the following results might be unexpected: