composer package updates
[openemr.git] / vendor / mpdf / mpdf / src / Mpdf.php
blobb6ce0f04d3087e63f17c92b0f0feb55bf7ae9cb2
1 <?php
3 namespace Mpdf;
5 use fpdi_pdf_parser;
6 use pdf_parser;
8 use Mpdf\Config\ConfigVariables;
9 use Mpdf\Config\FontVariables;
11 use Mpdf\Color\ColorConverter;
12 use Mpdf\Color\ColorModeConverter;
13 use Mpdf\Color\ColorSpaceRestrictor;
15 use Mpdf\Conversion;
17 use Mpdf\Css\Border;
18 use Mpdf\Css\TextVars;
20 use Mpdf\Image\ImageProcessor;
22 use Mpdf\Language\LanguageToFont;
23 use Mpdf\Language\ScriptToLanguage;
25 use Mpdf\Log\Context as LogContext;
27 use Mpdf\Fonts\FontCache;
28 use Mpdf\Fonts\FontFileFinder;
29 use Mpdf\Fonts\MetricsGenerator;
31 use Mpdf\Output\Destination;
33 use Mpdf\Pdf\Protection;
34 use Mpdf\Pdf\Protection\UniqidGenerator;
36 use Mpdf\QrCode;
38 use Mpdf\Utils\Arrays;
39 use Mpdf\Utils\PdfDate;
40 use Mpdf\Utils\NumericString;
41 use Mpdf\Utils\UtfString;
43 use Psr\Log\LoggerInterface;
44 use Psr\Log\NullLogger;
46 /**
47 * mPDF, PHP library generating PDF files from UTF-8 encoded HTML
49 * based on FPDF by Olivier Plathey
50 * and HTML2FPDF by Renato Coelho
52 * @version 7.0
53 * @license GPL-2.0
55 class Mpdf implements \Psr\Log\LoggerAwareInterface
58 const VERSION = '7.1.0';
60 const SCALE = 72 / 25.4;
62 var $useFixedNormalLineHeight; // mPDF 6
63 var $useFixedTextBaseline; // mPDF 6
64 var $adjustFontDescLineheight; // mPDF 6
65 var $interpolateImages; // mPDF 6
66 var $defaultPagebreakType; // mPDF 6 pagebreaktype
67 var $indexUseSubentries; // mPDF 6
69 var $autoScriptToLang; // mPDF 6
70 var $baseScript; // mPDF 6
71 var $autoVietnamese; // mPDF 6
72 var $autoArabic; // mPDF 6
74 var $CJKforceend;
75 var $h2bookmarks;
76 var $h2toc;
77 var $decimal_align;
78 var $margBuffer;
79 var $splitTableBorderWidth;
81 var $bookmarkStyles;
82 var $useActiveForms;
84 var $repackageTTF;
85 var $allowCJKorphans;
86 var $allowCJKoverflow;
88 var $useKerning;
89 var $restrictColorSpace;
90 var $bleedMargin;
91 var $crossMarkMargin;
92 var $cropMarkMargin;
93 var $cropMarkLength;
94 var $nonPrintMargin;
96 var $PDFX;
97 var $PDFXauto;
99 var $PDFA;
100 var $PDFAversion = '1-B';
101 var $PDFAauto;
102 var $ICCProfile;
104 var $printers_info;
105 var $iterationCounter;
106 var $smCapsScale;
107 var $smCapsStretch;
109 var $backupSubsFont;
110 var $backupSIPFont;
111 var $fonttrans;
112 var $debugfonts;
113 var $useAdobeCJK;
114 var $percentSubset;
115 var $maxTTFFilesize;
116 var $BMPonly;
118 var $tableMinSizePriority;
120 var $dpi;
121 var $watermarkImgAlphaBlend;
122 var $watermarkImgBehind;
123 var $justifyB4br;
124 var $packTableData;
125 var $pgsIns;
126 var $simpleTables;
127 var $enableImports;
129 var $debug;
131 var $setAutoTopMargin;
132 var $setAutoBottomMargin;
133 var $autoMarginPadding;
134 var $collapseBlockMargins;
135 var $falseBoldWeight;
136 var $normalLineheight;
137 var $incrementFPR1;
138 var $incrementFPR2;
139 var $incrementFPR3;
140 var $incrementFPR4;
142 var $SHYlang;
143 var $SHYleftmin;
144 var $SHYrightmin;
145 var $SHYcharmin;
146 var $SHYcharmax;
147 var $SHYlanguages;
149 // PageNumber Conditional Text
150 var $pagenumPrefix;
151 var $pagenumSuffix;
153 var $nbpgPrefix;
154 var $nbpgSuffix;
155 var $showImageErrors;
156 var $allow_output_buffering;
157 var $autoPadding;
158 var $tabSpaces;
159 var $autoLangToFont;
160 var $watermarkTextAlpha;
161 var $watermarkImageAlpha;
162 var $watermark_size;
163 var $watermark_pos;
164 var $annotSize;
165 var $annotMargin;
166 var $annotOpacity;
167 var $title2annots;
168 var $keepColumns;
169 var $keep_table_proportions;
170 var $ignore_table_widths;
171 var $ignore_table_percents;
172 var $list_number_suffix;
174 var $list_auto_mode; // mPDF 6
175 var $list_indent_first_level; // mPDF 6
176 var $list_indent_default; // mPDF 6
177 var $list_marker_offset; // mPDF 6
178 var $list_symbol_size;
180 var $useSubstitutions;
181 var $CSSselectMedia;
183 var $forcePortraitHeaders;
184 var $forcePortraitMargins;
185 var $displayDefaultOrientation;
186 var $ignore_invalid_utf8;
187 var $allowedCSStags;
188 var $onlyCoreFonts;
189 var $allow_charset_conversion;
191 var $jSWord;
192 var $jSmaxChar;
193 var $jSmaxCharLast;
194 var $jSmaxWordLast;
196 var $max_colH_correction;
198 var $table_error_report;
199 var $table_error_report_param;
200 var $biDirectional;
201 var $text_input_as_HTML;
202 var $anchor2Bookmark;
203 var $shrink_tables_to_fit;
205 var $allow_html_optional_endtags;
207 var $img_dpi;
209 var $defaultheaderfontsize;
210 var $defaultheaderfontstyle;
211 var $defaultheaderline;
212 var $defaultfooterfontsize;
213 var $defaultfooterfontstyle;
214 var $defaultfooterline;
215 var $header_line_spacing;
216 var $footer_line_spacing;
218 var $pregCJKchars;
219 var $pregRTLchars;
220 var $pregCURSchars; // mPDF 6
222 var $mirrorMargins;
223 var $watermarkText;
224 var $watermarkAngle;
225 var $watermarkImage;
226 var $showWatermarkText;
227 var $showWatermarkImage;
229 var $svgAutoFont;
230 var $svgClasses;
232 var $fontsizes;
234 var $defaultPageNumStyle; // mPDF 6
236 //////////////////////
237 // INTERNAL VARIABLES
238 //////////////////////
239 var $extrapagebreak; // mPDF 6 pagebreaktype
241 var $uniqstr; // mPDF 5.7.2
242 var $hasOC;
244 var $textvar; // mPDF 5.7.1
245 var $fontLanguageOverride; // mPDF 5.7.1
246 var $OTLtags; // mPDF 5.7.1
247 var $OTLdata; // mPDF 5.7.1
249 var $writingToC;
250 var $layers;
251 var $layerDetails;
252 var $current_layer;
253 var $open_layer_pane;
254 var $decimal_offset;
255 var $inMeter;
257 var $CJKleading;
258 var $CJKfollowing;
259 var $CJKoverflow;
261 var $textshadow;
263 var $colsums;
264 var $spanborder;
265 var $spanborddet;
267 var $visibility;
269 var $kerning;
270 var $fixedlSpacing;
271 var $minwSpacing;
272 var $lSpacingCSS;
273 var $wSpacingCSS;
275 var $spotColorIDs;
276 var $SVGcolors;
277 var $spotColors;
278 var $defTextColor;
279 var $defDrawColor;
280 var $defFillColor;
282 var $tableBackgrounds;
283 var $inlineDisplayOff;
284 var $kt_y00;
285 var $kt_p00;
286 var $upperCase;
287 var $checkSIP;
288 var $checkSMP;
289 var $checkCJK;
291 var $watermarkImgAlpha;
292 var $PDFAXwarnings;
294 var $MetadataRoot;
295 var $OutputIntentRoot;
296 var $InfoRoot;
297 var $associatedFilesRoot;
299 var $current_filename;
300 var $parsers;
301 var $current_parser;
302 var $_obj_stack;
303 var $_don_obj_stack;
304 var $_current_obj_id;
305 var $tpls;
306 var $tpl;
307 var $tplprefix;
308 var $_res;
310 var $pdf_version;
312 private $fontDir;
314 var $tempDir;
316 var $allowAnnotationFiles;
318 var $fontdata;
320 var $noImageFile;
321 var $lastblockbottommargin;
322 var $baselineC;
324 // mPDF 5.7.3 inline text-decoration parameters
325 var $baselineSup;
326 var $baselineSub;
327 var $baselineS;
328 var $baselineO;
330 var $subPos;
331 var $subArrMB;
332 var $ReqFontStyle;
333 var $tableClipPath;
335 var $fullImageHeight;
337 var $inFixedPosBlock; // Internal flag for position:fixed block
338 var $fixedPosBlock; // Buffer string for position:fixed block
339 var $fixedPosBlockDepth;
340 var $fixedPosBlockBBox;
341 var $fixedPosBlockSave;
342 var $maxPosL;
343 var $maxPosR;
344 var $loaded;
346 var $extraFontSubsets;
348 var $docTemplateStart; // Internal flag for page (page no. -1) that docTemplate starts on
350 var $time0;
352 var $hyphenationDictionaryFile;
354 var $spanbgcolorarray;
355 var $default_font;
356 var $headerbuffer;
357 var $lastblocklevelchange;
358 var $nestedtablejustfinished;
359 var $linebreakjustfinished;
360 var $cell_border_dominance_L;
361 var $cell_border_dominance_R;
362 var $cell_border_dominance_T;
363 var $cell_border_dominance_B;
364 var $table_keep_together;
365 var $plainCell_properties;
366 var $shrin_k1;
367 var $outerfilled;
369 var $blockContext;
370 var $floatDivs;
372 var $patterns;
373 var $pageBackgrounds;
375 var $bodyBackgroundGradient;
376 var $bodyBackgroundImage;
377 var $bodyBackgroundColor;
379 var $writingHTMLheader; // internal flag - used both for writing HTMLHeaders/Footers and FixedPos block
380 var $writingHTMLfooter;
382 var $angle;
384 var $gradients;
386 var $kwt_Reference;
387 var $kwt_BMoutlines;
388 var $kwt_toc;
390 var $tbrot_BMoutlines;
391 var $tbrot_toc;
393 var $col_BMoutlines;
394 var $col_toc;
396 var $floatbuffer;
397 var $floatmargins;
399 var $bullet;
400 var $bulletarray;
402 var $currentLang;
403 var $default_lang;
405 var $default_available_fonts;
407 var $pageTemplate;
408 var $docTemplate;
409 var $docTemplateContinue;
411 var $arabGlyphs;
412 var $arabHex;
413 var $persianGlyphs;
414 var $persianHex;
415 var $arabVowels;
416 var $arabPrevLink;
417 var $arabNextLink;
419 var $formobjects; // array of Form Objects for WMF
420 var $InlineProperties;
421 var $InlineAnnots;
422 var $InlineBDF; // mPDF 6 Bidirectional formatting
423 var $InlineBDFctr; // mPDF 6
425 var $ktAnnots;
426 var $tbrot_Annots;
427 var $kwt_Annots;
428 var $columnAnnots;
429 var $columnForms;
430 var $tbrotForms;
432 var $PageAnnots;
434 var $pageDim; // Keep track of page wxh for orientation changes - set in _beginpage, used in _putannots
436 var $breakpoints;
438 var $tableLevel;
439 var $tbctr;
440 var $innermostTableLevel;
441 var $saveTableCounter;
442 var $cellBorderBuffer;
444 var $saveHTMLFooter_height;
445 var $saveHTMLFooterE_height;
447 var $firstPageBoxHeader;
448 var $firstPageBoxHeaderEven;
449 var $firstPageBoxFooter;
450 var $firstPageBoxFooterEven;
452 var $page_box;
454 var $show_marks; // crop or cross marks
455 var $basepathIsLocal;
457 var $use_kwt;
458 var $kwt;
459 var $kwt_height;
460 var $kwt_y0;
461 var $kwt_x0;
462 var $kwt_buffer;
463 var $kwt_Links;
464 var $kwt_moved;
465 var $kwt_saved;
467 var $PageNumSubstitutions;
469 var $table_borders_separate;
470 var $base_table_properties;
471 var $borderstyles;
473 var $blockjustfinished;
475 var $orig_bMargin;
476 var $orig_tMargin;
477 var $orig_lMargin;
478 var $orig_rMargin;
479 var $orig_hMargin;
480 var $orig_fMargin;
482 var $pageHTMLheaders;
483 var $pageHTMLfooters;
485 var $saveHTMLHeader;
486 var $saveHTMLFooter;
488 var $HTMLheaderPageLinks;
489 var $HTMLheaderPageAnnots;
490 var $HTMLheaderPageForms;
492 // See Config\FontVariables for these next 5 values
493 var $available_unifonts;
494 var $sans_fonts;
495 var $serif_fonts;
496 var $mono_fonts;
497 var $defaultSubsFont;
499 // List of ALL available CJK fonts (incl. styles) (Adobe add-ons) hw removed
500 var $available_CJK_fonts;
502 var $HTMLHeader;
503 var $HTMLFooter;
504 var $HTMLHeaderE;
505 var $HTMLFooterE;
506 var $bufferoutput;
508 // CJK fonts
509 var $Big5_widths;
510 var $GB_widths;
511 var $SJIS_widths;
512 var $UHC_widths;
514 // SetProtection
515 var $encrypted;
517 var $enc_obj_id; // encryption object id
519 // Bookmark
520 var $BMoutlines;
521 var $OutlineRoot;
523 // INDEX
524 var $ColActive;
525 var $Reference;
526 var $CurrCol;
527 var $NbCol;
528 var $y0; // Top ordinate of columns
530 var $ColL;
531 var $ColWidth;
532 var $ColGap;
534 // COLUMNS
535 var $ColR;
536 var $ChangeColumn;
537 var $columnbuffer;
538 var $ColDetails;
539 var $columnLinks;
540 var $colvAlign;
542 // Substitutions
543 var $substitute; // Array of substitution strings e.g. <ttz>112</ttz>
544 var $entsearch; // Array of HTML entities (>ASCII 127) to substitute
545 var $entsubstitute; // Array of substitution decimal unicode for the Hi entities
547 // Default values if no style sheet offered (cf. http://www.w3.org/TR/CSS21/sample.html)
548 var $defaultCSS;
549 var $defaultCssFile;
551 var $lastoptionaltag; // Save current block item which HTML specifies optionsl endtag
552 var $pageoutput;
553 var $charset_in;
554 var $blk;
555 var $blklvl;
556 var $ColumnAdjust;
558 var $ws; // Word spacing
560 var $HREF;
561 var $pgwidth;
562 var $fontlist;
563 var $oldx;
564 var $oldy;
565 var $B;
566 var $I;
568 var $tdbegin;
569 var $table;
570 var $cell;
571 var $col;
572 var $row;
574 var $divbegin;
575 var $divwidth;
576 var $divheight;
577 var $spanbgcolor;
579 // mPDF 6 Used for table cell (block-type) properties
580 var $cellTextAlign;
581 var $cellLineHeight;
582 var $cellLineStackingStrategy;
583 var $cellLineStackingShift;
585 // mPDF 6 Lists
586 var $listcounter;
587 var $listlvl;
588 var $listtype;
589 var $listitem;
591 var $pjustfinished;
592 var $ignorefollowingspaces;
593 var $SMALL;
594 var $BIG;
595 var $dash_on;
596 var $dotted_on;
598 var $textbuffer;
599 var $currentfontstyle;
600 var $currentfontfamily;
601 var $currentfontsize;
602 var $colorarray;
603 var $bgcolorarray;
604 var $internallink;
605 var $enabledtags;
607 var $lineheight;
608 var $basepath;
609 var $textparam;
611 var $specialcontent;
612 var $selectoption;
613 var $objectbuffer;
615 // Table Rotation
616 var $table_rotate;
617 var $tbrot_maxw;
618 var $tbrot_maxh;
619 var $tablebuffer;
620 var $tbrot_align;
621 var $tbrot_Links;
623 var $keep_block_together; // Keep a Block from page-break-inside: avoid
625 var $tbrot_y0;
626 var $tbrot_x0;
627 var $tbrot_w;
628 var $tbrot_h;
630 var $mb_enc;
631 var $originalMbEnc;
632 var $originalMbRegexEnc;
634 var $directionality;
636 var $extgstates; // Used for alpha channel - Transparency (Watermark)
637 var $mgl;
638 var $mgt;
639 var $mgr;
640 var $mgb;
642 var $tts;
643 var $ttz;
644 var $tta;
646 // Best to alter the below variables using default stylesheet above
647 var $page_break_after_avoid;
648 var $margin_bottom_collapse;
649 var $default_font_size; // in pts
650 var $original_default_font_size; // used to save default sizes when using table default
651 var $original_default_font;
652 var $watermark_font;
653 var $defaultAlign;
655 // TABLE
656 var $defaultTableAlign;
657 var $tablethead;
658 var $thead_font_weight;
659 var $thead_font_style;
660 var $thead_font_smCaps;
661 var $thead_valign_default;
662 var $thead_textalign_default;
663 var $tabletfoot;
664 var $tfoot_font_weight;
665 var $tfoot_font_style;
666 var $tfoot_font_smCaps;
667 var $tfoot_valign_default;
668 var $tfoot_textalign_default;
670 var $trow_text_rotate;
672 var $cellPaddingL;
673 var $cellPaddingR;
674 var $cellPaddingT;
675 var $cellPaddingB;
676 var $table_border_attr_set;
677 var $table_border_css_set;
679 var $shrin_k; // factor with which to shrink tables - used internally - do not change
680 var $shrink_this_table_to_fit; // 0 or false to disable; value (if set) gives maximum factor to reduce fontsize
681 var $MarginCorrection; // corrects for OddEven Margins
682 var $margin_footer;
683 var $margin_header;
685 var $tabletheadjustfinished;
686 var $usingCoreFont;
687 var $charspacing;
689 var $js;
692 * Set timeout for cURL
694 * @var int
696 var $curlTimeout;
699 * Set to true to follow redirects with cURL.
701 * @var bool
703 var $curlFollowLocation;
706 * Set to true to allow unsafe SSL HTTPS requests.
708 * Can be useful when using CDN with HTTPS and if you don't want to configure settings with SSL certificates.
710 * @var bool
712 var $curlAllowUnsafeSslRequests;
714 // Private properties FROM FPDF
715 var $DisplayPreferences;
716 var $flowingBlockAttr;
718 var $page; // current page number
720 var $n; // current object number
721 var $n_js; // current object number
723 var $n_ocg_hidden;
724 var $n_ocg_print;
725 var $n_ocg_view;
727 var $offsets; // array of object offsets
728 var $buffer; // buffer holding in-memory PDF
729 var $pages; // array containing pages
730 var $state; // current document state
731 var $compress; // compression flag
733 var $DefOrientation; // default orientation
734 var $CurOrientation; // current orientation
735 var $OrientationChanges; // array indicating orientation changes
737 var $k; // scale factor (number of points in user unit)
739 var $fwPt;
740 var $fhPt; // dimensions of page format in points
741 var $fw;
742 var $fh; // dimensions of page format in user unit
743 var $wPt;
744 var $hPt; // current dimensions of page in points
746 var $w;
747 var $h; // current dimensions of page in user unit
749 var $lMargin; // left margin
750 var $tMargin; // top margin
751 var $rMargin; // right margin
752 var $bMargin; // page break margin
753 var $cMarginL; // cell margin Left
754 var $cMarginR; // cell margin Right
755 var $cMarginT; // cell margin Left
756 var $cMarginB; // cell margin Right
758 var $DeflMargin; // Default left margin
759 var $DefrMargin; // Default right margin
761 var $x;
762 var $y; // current position in user unit for cell positioning
764 var $lasth; // height of last cell printed
765 var $LineWidth; // line width in user unit
767 var $CoreFonts; // array of standard font names
768 var $fonts; // array of used fonts
769 var $FontFiles; // array of font files
771 var $images; // array of used images
772 var $imageVars = []; // array of image vars
774 var $PageLinks; // array of links in pages
775 var $links; // array of internal links
776 var $FontFamily; // current font family
777 var $FontStyle; // current font style
778 var $CurrentFont; // current font info
779 var $FontSizePt; // current font size in points
780 var $FontSize; // current font size in user unit
781 var $DrawColor; // commands for drawing color
782 var $FillColor; // commands for filling color
783 var $TextColor; // commands for text color
784 var $ColorFlag; // indicates whether fill and text colors are different
785 var $autoPageBreak; // automatic page breaking
786 var $PageBreakTrigger; // threshold used to trigger page breaks
787 var $InFooter; // flag set when processing footer
789 var $InHTMLFooter;
790 var $processingFooter; // flag set when processing footer - added for columns
791 var $processingHeader; // flag set when processing header - added for columns
792 var $ZoomMode; // zoom display mode
793 var $LayoutMode; // layout display mode
794 var $title; // title
795 var $subject; // subject
796 var $author; // author
797 var $keywords; // keywords
798 var $creator; // creator
800 var $customProperties; // array of custom document properties
802 var $associatedFiles; // associated files (see SetAssociatedFiles below)
803 var $additionalXmpRdf; // additional rdf added in xmp
805 var $aliasNbPg; // alias for total number of pages
806 var $aliasNbPgGp; // alias for total number of pages in page group
808 var $ispre;
809 var $outerblocktags;
810 var $innerblocktags;
813 * @var string
815 private $fontDescriptor;
818 * @var \Mpdf\Otl
820 private $otl;
823 * @var \Mpdf\CssManager
825 private $cssManager;
828 * @var \Mpdf\Gradient
830 private $gradient;
833 * @var \Mpdf\Image\Bmp
835 private $bmp;
838 * @var \Mpdf\Image\Wmf
840 private $wmf;
843 * @var \Mpdf\TableOfContents
845 private $tableOfContents;
848 * @var \Mpdf\Form
850 private $form;
853 * @var \Mpdf\DirectWrite
855 private $directWrite;
858 * @var \Mpdf\Cache
860 private $cache;
863 * @var \Mpdf\Fonts\FontCache
865 private $fontCache;
868 * @var \Mpdf\Fonts\FontFileFinder
870 private $fontFileFinder;
873 * @var \Mpdf\Tag
875 private $tag;
878 * @var \Mpdf\Barcode
879 * @todo solve Tag dependency and make private
881 public $barcode;
884 * @var \Mpdf\QrCode\QrCode
886 private $qrcode;
889 * @var \Mpdf\SizeConverter
891 private $sizeConverter;
894 * @var \Mpdf\Color\ColorConverter
896 private $colorConverter;
899 * @var \Mpdf\Color\ColorModeConverter
901 private $colorModeConverter;
904 * @var \Mpdf\Color\ColorSpaceRestrictor
906 private $colorSpaceRestrictor;
909 * @var \Mpdf\Hyphenator
911 private $hyphenator;
914 * @var \Mpdf\Pdf\Protection
916 private $protection;
919 * @var \Mpdf\Image\ImageProcessor
921 private $imageProcessor;
924 * @var \Mpdf\Language\LanguageToFontInterface
926 private $languageToFont;
929 * @var \Mpdf\Language\ScriptToLanguageInterface
931 private $scriptToLanguage;
934 * @var \Psr\Log\LoggerInterface
936 private $logger;
939 * @var string[]
941 private $services;
944 * @param mixed[] $config
946 public function __construct(array $config = [])
948 $this->_dochecks();
950 list(
951 $mode,
952 $format,
953 $default_font_size,
954 $default_font,
955 $mgl,
956 $mgr,
957 $mgt,
958 $mgb,
959 $mgh,
960 $mgf,
961 $orientation
962 ) = $this->initConstructorParams($config);
964 $this->logger = new NullLogger();
966 $originalConfig = $config;
967 $config = $this->initConfig($originalConfig);
969 $this->sizeConverter = new SizeConverter($this->dpi, $this->default_font_size, $this->logger);
971 $this->colorModeConverter = new ColorModeConverter();
972 $this->colorSpaceRestrictor = new ColorSpaceRestrictor(
973 $this,
974 $this->colorModeConverter,
975 $this->restrictColorSpace
977 $this->colorConverter = new ColorConverter($this, $this->colorModeConverter, $this->colorSpaceRestrictor);
980 $this->gradient = new Gradient($this, $this->sizeConverter, $this->colorConverter);
981 $this->tableOfContents = new TableOfContents($this, $this->sizeConverter);
983 $this->cache = new Cache($config['tempDir']);
984 $this->fontCache = new FontCache(new Cache($config['tempDir'] . '/ttfontdata'));
986 $this->fontFileFinder = new FontFileFinder($config['fontDir']);
988 $this->cssManager = new CssManager($this, $this->cache, $this->sizeConverter, $this->colorConverter);
990 $this->otl = new Otl($this, $this->fontCache);
992 $this->form = new Form($this, $this->otl, $this->colorConverter);
994 $this->hyphenator = new Hyphenator($this);
996 $this->imageProcessor = new ImageProcessor(
997 $this,
998 $this->otl,
999 $this->cssManager,
1000 $this->sizeConverter,
1001 $this->colorConverter,
1002 $this->colorModeConverter,
1003 $this->cache,
1004 $this->languageToFont,
1005 $this->scriptToLanguage,
1006 $this->logger
1009 $this->tag = new Tag(
1010 $this,
1011 $this->cache,
1012 $this->cssManager,
1013 $this->form,
1014 $this->otl,
1015 $this->tableOfContents,
1016 $this->sizeConverter,
1017 $this->colorConverter,
1018 $this->imageProcessor,
1019 $this->languageToFont
1022 $this->services = [
1023 'otl',
1024 'bmp',
1025 'cache',
1026 'cssManager',
1027 'directWrite',
1028 'fontCache',
1029 'fontFileFinder',
1030 'form',
1031 'gradient',
1032 'tableOfContents',
1033 'tag',
1034 'wmf',
1035 'sizeConverter',
1036 'colorConverter',
1037 'hyphenator',
1038 'imageProcessor',
1039 'protection',
1040 'languageToFont',
1041 'scriptToLanguage',
1044 $this->time0 = microtime(true);
1046 $this->writingToC = false;
1048 $this->layers = [];
1049 $this->current_layer = 0;
1050 $this->open_layer_pane = false;
1052 $this->visibility = 'visible';
1054 $this->tableBackgrounds = [];
1055 $this->uniqstr = '20110230'; // mPDF 5.7.2
1056 $this->kt_y00 = '';
1057 $this->kt_p00 = '';
1058 $this->BMPonly = [];
1059 $this->page = 0;
1060 $this->n = 2;
1061 $this->buffer = '';
1062 $this->objectbuffer = [];
1063 $this->pages = [];
1064 $this->OrientationChanges = [];
1065 $this->state = 0;
1066 $this->fonts = [];
1067 $this->FontFiles = [];
1068 $this->images = [];
1069 $this->links = [];
1070 $this->InFooter = false;
1071 $this->processingFooter = false;
1072 $this->processingHeader = false;
1073 $this->lasth = 0;
1074 $this->FontFamily = '';
1075 $this->FontStyle = '';
1076 $this->FontSizePt = 9;
1078 // Small Caps
1079 $this->inMeter = false;
1080 $this->decimal_offset = 0;
1082 $this->PDFAXwarnings = [];
1084 $this->defTextColor = $this->TextColor = $this->SetTColor($this->colorConverter->convert(0, $this->PDFAXwarnings), true);
1085 $this->defDrawColor = $this->DrawColor = $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings), true);
1086 $this->defFillColor = $this->FillColor = $this->SetFColor($this->colorConverter->convert(255, $this->PDFAXwarnings), true);
1088 $this->upperCase = require __DIR__ . '/../data/upperCase.php';
1090 $this->extrapagebreak = true; // mPDF 6 pagebreaktype
1092 $this->ColorFlag = false;
1093 $this->extgstates = [];
1095 $this->mb_enc = 'windows-1252';
1096 $this->originalMbEnc = mb_internal_encoding();
1097 $this->originalMbRegexEnc = mb_regex_encoding();
1099 $this->directionality = 'ltr';
1100 $this->defaultAlign = 'L';
1101 $this->defaultTableAlign = 'L';
1103 $this->fixedPosBlockSave = [];
1104 $this->extraFontSubsets = 0;
1106 $this->blockContext = 1;
1107 $this->floatDivs = [];
1108 $this->DisplayPreferences = '';
1110 // Tiling patterns used for backgrounds
1111 $this->patterns = [];
1112 $this->pageBackgrounds = [];
1113 $this->gradients = [];
1115 // internal flag - used both for writing HTMLHeaders/Footers and FixedPos block
1116 $this->writingHTMLheader = false;
1117 // internal flag - used both for writing HTMLHeaders/Footers and FixedPos block
1118 $this->writingHTMLfooter = false;
1120 $this->kwt_Reference = [];
1121 $this->kwt_BMoutlines = [];
1122 $this->kwt_toc = [];
1124 $this->tbrot_BMoutlines = [];
1125 $this->tbrot_toc = [];
1127 $this->col_BMoutlines = [];
1128 $this->col_toc = [];
1130 $this->pgsIns = [];
1131 $this->PDFAXwarnings = [];
1132 $this->inlineDisplayOff = false;
1133 $this->lSpacingCSS = '';
1134 $this->wSpacingCSS = '';
1135 $this->fixedlSpacing = false;
1136 $this->minwSpacing = 0;
1138 // Baseline for text
1139 $this->baselineC = 0.35;
1141 // mPDF 5.7.3 inline text-decoration parameters
1142 // Sets default change in baseline for <sup> text as factor of preceeding fontsize
1143 // 0.35 has been recommended; 0.5 matches applications like MS Word
1144 $this->baselineSup = 0.5;
1146 // Sets default change in baseline for <sub> text as factor of preceeding fontsize
1147 $this->baselineSub = -0.2;
1148 // Sets default height for <strike> text as factor of fontsize
1149 $this->baselineS = 0.3;
1150 // Sets default height for overline text as factor of fontsize
1151 $this->baselineO = 1.1;
1153 $this->noImageFile = __DIR__ . '/../data/no_image.jpg';
1154 $this->subPos = 0;
1156 $this->fullImageHeight = false;
1157 $this->floatbuffer = [];
1158 $this->floatmargins = [];
1159 $this->formobjects = []; // array of Form Objects for WMF
1160 $this->InlineProperties = [];
1161 $this->InlineAnnots = [];
1162 $this->InlineBDF = []; // mPDF 6
1163 $this->InlineBDFctr = 0; // mPDF 6
1164 $this->tbrot_Annots = [];
1165 $this->kwt_Annots = [];
1166 $this->columnAnnots = [];
1167 $this->PageLinks = [];
1168 $this->OrientationChanges = [];
1169 $this->pageDim = [];
1170 $this->saveHTMLHeader = [];
1171 $this->saveHTMLFooter = [];
1172 $this->PageAnnots = [];
1173 $this->PageNumSubstitutions = [];
1174 $this->breakpoints = []; // used in columnbuffer
1175 $this->tableLevel = 0;
1176 $this->tbctr = []; // counter for nested tables at each level
1177 $this->page_box = [];
1178 $this->show_marks = ''; // crop or cross marks
1179 $this->kwt = false;
1180 $this->kwt_height = 0;
1181 $this->kwt_y0 = 0;
1182 $this->kwt_x0 = 0;
1183 $this->kwt_buffer = [];
1184 $this->kwt_Links = [];
1185 $this->kwt_moved = false;
1186 $this->kwt_saved = false;
1187 $this->PageNumSubstitutions = [];
1188 $this->base_table_properties = [];
1189 $this->borderstyles = ['inset', 'groove', 'outset', 'ridge', 'dotted', 'dashed', 'solid', 'double'];
1190 $this->tbrot_align = 'C';
1192 $this->pageHTMLheaders = [];
1193 $this->pageHTMLfooters = [];
1194 $this->HTMLheaderPageLinks = [];
1195 $this->HTMLheaderPageAnnots = [];
1197 $this->HTMLheaderPageForms = [];
1198 $this->columnForms = [];
1199 $this->tbrotForms = [];
1201 $this->pageoutput = [];
1203 $this->bufferoutput = false;
1205 $this->encrypted = false;
1207 $this->BMoutlines = [];
1208 $this->ColActive = 0; // Flag indicating that columns are on (the index is being processed)
1209 $this->Reference = []; // Array containing the references
1210 $this->CurrCol = 0; // Current column number
1211 $this->ColL = [0]; // Array of Left pos of columns - absolute - needs Margin correction for Odd-Even
1212 $this->ColR = [0]; // Array of Right pos of columns - absolute pos - needs Margin correction for Odd-Even
1213 $this->ChangeColumn = 0;
1214 $this->columnbuffer = [];
1215 $this->ColDetails = []; // Keeps track of some column details
1216 $this->columnLinks = []; // Cross references PageLinks
1217 $this->substitute = []; // Array of substitution strings e.g. <ttz>112</ttz>
1218 $this->entsearch = []; // Array of HTML entities (>ASCII 127) to substitute
1219 $this->entsubstitute = []; // Array of substitution decimal unicode for the Hi entities
1220 $this->lastoptionaltag = '';
1221 $this->charset_in = '';
1222 $this->blk = [];
1223 $this->blklvl = 0;
1224 $this->tts = false;
1225 $this->ttz = false;
1226 $this->tta = false;
1227 $this->ispre = false;
1229 $this->checkSIP = false;
1230 $this->checkSMP = false;
1231 $this->checkCJK = false;
1233 $this->page_break_after_avoid = false;
1234 $this->margin_bottom_collapse = false;
1235 $this->tablethead = 0;
1236 $this->tabletfoot = 0;
1237 $this->table_border_attr_set = 0;
1238 $this->table_border_css_set = 0;
1239 $this->shrin_k = 1.0;
1240 $this->shrink_this_table_to_fit = 0;
1241 $this->MarginCorrection = 0;
1243 $this->tabletheadjustfinished = false;
1244 $this->usingCoreFont = false;
1245 $this->charspacing = 0;
1247 $this->autoPageBreak = true;
1249 $this->_setPageSize($format, $orientation);
1250 $this->DefOrientation = $orientation;
1252 $this->margin_header = $mgh;
1253 $this->margin_footer = $mgf;
1255 $bmargin = $mgb;
1257 $this->DeflMargin = $mgl;
1258 $this->DefrMargin = $mgr;
1260 $this->orig_tMargin = $mgt;
1261 $this->orig_bMargin = $bmargin;
1262 $this->orig_lMargin = $this->DeflMargin;
1263 $this->orig_rMargin = $this->DefrMargin;
1264 $this->orig_hMargin = $this->margin_header;
1265 $this->orig_fMargin = $this->margin_footer;
1267 if ($this->setAutoTopMargin == 'pad') {
1268 $mgt += $this->margin_header;
1270 if ($this->setAutoBottomMargin == 'pad') {
1271 $mgb += $this->margin_footer;
1274 // sets l r t margin
1275 $this->SetMargins($this->DeflMargin, $this->DefrMargin, $mgt);
1277 // Automatic page break
1278 // sets $this->bMargin & PageBreakTrigger
1279 $this->SetAutoPageBreak($this->autoPageBreak, $bmargin);
1281 $this->pgwidth = $this->w - $this->lMargin - $this->rMargin;
1283 // Interior cell margin (1 mm) ? not used
1284 $this->cMarginL = 1;
1285 $this->cMarginR = 1;
1287 // Line width (0.2 mm)
1288 $this->LineWidth = .567 / Mpdf::SCALE;
1290 // Enable all tags as default
1291 $this->DisableTags();
1292 // Full width display mode
1293 $this->SetDisplayMode(100); // fullwidth? 'fullpage'
1295 // Compression
1296 $this->SetCompression(true);
1297 // Set default display preferences
1298 $this->SetDisplayPreferences('');
1300 $this->initFontConfig($originalConfig);
1302 // Available fonts
1303 $this->available_unifonts = [];
1304 foreach ($this->fontdata as $f => $fs) {
1305 if (isset($fs['R']) && $fs['R']) {
1306 $this->available_unifonts[] = $f;
1308 if (isset($fs['B']) && $fs['B']) {
1309 $this->available_unifonts[] = $f . 'B';
1311 if (isset($fs['I']) && $fs['I']) {
1312 $this->available_unifonts[] = $f . 'I';
1314 if (isset($fs['BI']) && $fs['BI']) {
1315 $this->available_unifonts[] = $f . 'BI';
1319 $this->default_available_fonts = $this->available_unifonts;
1321 $optcore = false;
1322 $onlyCoreFonts = false;
1323 if (preg_match('/([\-+])aCJK/i', $mode, $m)) {
1324 $mode = preg_replace('/([\-+])aCJK/i', '', $mode); // mPDF 6
1325 if ($m[1] == '+') {
1326 $this->useAdobeCJK = true;
1327 } else {
1328 $this->useAdobeCJK = false;
1332 if (strlen($mode) == 1) {
1333 if ($mode == 's') {
1334 $this->percentSubset = 100;
1335 $mode = '';
1336 } elseif ($mode == 'c') {
1337 $onlyCoreFonts = true;
1338 $mode = '';
1340 } elseif (substr($mode, -2) == '-s') {
1341 $this->percentSubset = 100;
1342 $mode = substr($mode, 0, strlen($mode) - 2);
1343 } elseif (substr($mode, -2) == '-c') {
1344 $onlyCoreFonts = true;
1345 $mode = substr($mode, 0, strlen($mode) - 2);
1346 } elseif (substr($mode, -2) == '-x') {
1347 $optcore = true;
1348 $mode = substr($mode, 0, strlen($mode) - 2);
1351 // Autodetect if mode is a language_country string (en-GB or en_GB or en)
1352 if ($mode && $mode != 'UTF-8') { // mPDF 6
1353 list ($coreSuitable, $mpdf_pdf_unifont) = $this->languageToFont->getLanguageOptions($mode, $this->useAdobeCJK);
1354 if ($coreSuitable && $optcore) {
1355 $onlyCoreFonts = true;
1357 if ($mpdf_pdf_unifont) { // mPDF 6
1358 $default_font = $mpdf_pdf_unifont;
1360 $this->currentLang = $mode;
1361 $this->default_lang = $mode;
1364 $this->onlyCoreFonts = $onlyCoreFonts;
1366 if ($this->onlyCoreFonts) {
1367 $this->setMBencoding('windows-1252'); // sets $this->mb_enc
1368 } else {
1369 $this->setMBencoding('UTF-8'); // sets $this->mb_enc
1371 @mb_regex_encoding('UTF-8'); // required only for mb_ereg... and mb_split functions
1373 // Adobe CJK fonts
1374 $this->available_CJK_fonts = [
1375 'gb',
1376 'big5',
1377 'sjis',
1378 'uhc',
1379 'gbB',
1380 'big5B',
1381 'sjisB',
1382 'uhcB',
1383 'gbI',
1384 'big5I',
1385 'sjisI',
1386 'uhcI',
1387 'gbBI',
1388 'big5BI',
1389 'sjisBI',
1390 'uhcBI',
1393 // Standard fonts
1394 $this->CoreFonts = [
1395 'ccourier' => 'Courier',
1396 'ccourierB' => 'Courier-Bold',
1397 'ccourierI' => 'Courier-Oblique',
1398 'ccourierBI' => 'Courier-BoldOblique',
1399 'chelvetica' => 'Helvetica',
1400 'chelveticaB' => 'Helvetica-Bold',
1401 'chelveticaI' => 'Helvetica-Oblique',
1402 'chelveticaBI' => 'Helvetica-BoldOblique',
1403 'ctimes' => 'Times-Roman',
1404 'ctimesB' => 'Times-Bold',
1405 'ctimesI' => 'Times-Italic',
1406 'ctimesBI' => 'Times-BoldItalic',
1407 'csymbol' => 'Symbol',
1408 'czapfdingbats' => 'ZapfDingbats'
1411 $this->fontlist = [
1412 "ctimes",
1413 "ccourier",
1414 "chelvetica",
1415 "csymbol",
1416 "czapfdingbats"
1419 // Substitutions
1420 $this->setHiEntitySubstitutions();
1422 if ($this->onlyCoreFonts) {
1423 $this->useSubstitutions = true;
1424 $this->SetSubstitutions();
1425 } else {
1426 $this->useSubstitutions = $config['useSubstitutions'];
1429 if (file_exists($this->defaultCssFile)) {
1430 $css = file_get_contents($this->defaultCssFile);
1431 $this->cssManager->ReadCSS('<style> ' . $css . ' </style>');
1432 } else {
1433 throw new \Mpdf\MpdfException(sprintf('Unable to read default CSS file "%s"', $this->defaultCssFile));
1436 if ($default_font == '') {
1437 if ($this->onlyCoreFonts) {
1438 if (in_array(strtolower($this->defaultCSS['BODY']['FONT-FAMILY']), $this->mono_fonts)) {
1439 $default_font = 'ccourier';
1440 } elseif (in_array(strtolower($this->defaultCSS['BODY']['FONT-FAMILY']), $this->sans_fonts)) {
1441 $default_font = 'chelvetica';
1442 } else {
1443 $default_font = 'ctimes';
1445 } else {
1446 $default_font = $this->defaultCSS['BODY']['FONT-FAMILY'];
1449 if (!$default_font_size) {
1450 $mmsize = $this->sizeConverter->convert($this->defaultCSS['BODY']['FONT-SIZE']);
1451 $default_font_size = $mmsize * (Mpdf::SCALE);
1454 if ($default_font) {
1455 $this->SetDefaultFont($default_font);
1457 if ($default_font_size) {
1458 $this->SetDefaultFontSize($default_font_size);
1461 $this->SetLineHeight(); // lineheight is in mm
1463 $this->SetFColor($this->colorConverter->convert(255, $this->PDFAXwarnings));
1464 $this->HREF = '';
1465 $this->oldy = -1;
1466 $this->B = 0;
1467 $this->I = 0;
1469 // mPDF 6 Lists
1470 $this->listlvl = 0;
1471 $this->listtype = [];
1472 $this->listitem = [];
1473 $this->listcounter = [];
1475 $this->tdbegin = false;
1476 $this->table = [];
1477 $this->cell = [];
1478 $this->col = -1;
1479 $this->row = -1;
1480 $this->cellBorderBuffer = [];
1482 $this->divbegin = false;
1483 // mPDF 6
1484 $this->cellTextAlign = '';
1485 $this->cellLineHeight = '';
1486 $this->cellLineStackingStrategy = '';
1487 $this->cellLineStackingShift = '';
1489 $this->divwidth = 0;
1490 $this->divheight = 0;
1491 $this->spanbgcolor = false;
1492 $this->spanborder = false;
1493 $this->spanborddet = [];
1495 $this->blockjustfinished = false;
1496 $this->ignorefollowingspaces = true; // in order to eliminate exceeding left-side spaces
1497 $this->dash_on = false;
1498 $this->dotted_on = false;
1499 $this->textshadow = '';
1501 $this->currentfontfamily = '';
1502 $this->currentfontsize = '';
1503 $this->currentfontstyle = '';
1504 $this->colorarray = ''; // mPDF 6
1505 $this->spanbgcolorarray = ''; // mPDF 6
1506 $this->textbuffer = [];
1507 $this->internallink = [];
1508 $this->basepath = "";
1510 $this->SetBasePath('');
1512 $this->textparam = [];
1514 $this->specialcontent = '';
1515 $this->selectoption = [];
1517 /* -- IMPORTS -- */
1518 $this->parsers = [];
1519 $this->tpls = [];
1520 $this->tpl = 0;
1521 $this->tplprefix = "/TPL";
1522 /* -- END IMPORTS -- */
1525 public function cleanup()
1527 mb_internal_encoding($this->originalMbEnc);
1528 @mb_regex_encoding($this->originalMbRegexEnc);
1532 * @param \Psr\Log\LoggerInterface
1534 * @return \Mpdf\Mpdf
1536 public function setLogger(LoggerInterface $logger)
1538 $this->logger = $logger;
1540 foreach ($this->services as $name) {
1541 if ($this->$name && $this->$name instanceof \Psr\Log\LoggerAwareInterface) {
1542 $this->$name->setLogger($logger);
1546 return $this;
1549 private function initConfig(array $config)
1551 $configObject = new ConfigVariables();
1552 $defaults = $configObject->getDefaults();
1553 $config = array_intersect_key($config + $defaults, $defaults);
1555 foreach ($config as $var => $val) {
1556 $this->{$var} = $val;
1559 return $config;
1562 private function initConstructorParams(array $config)
1564 $constructor = [
1565 'mode' => '',
1566 'format' => 'A4',
1567 'default_font_size' => 0,
1568 'default_font' => '',
1569 'margin_left' => 15,
1570 'margin_right' => 15,
1571 'margin_top' => 16,
1572 'margin_bottom' => 16,
1573 'margin_header' => 9,
1574 'margin_footer' => 9,
1575 'orientation' => 'P',
1578 foreach ($constructor as $key => $val) {
1579 if (isset($config[$key])) {
1580 $constructor[$key] = $config[$key];
1584 return array_values($constructor);
1587 private function initFontConfig(array $config)
1589 $configObject = new FontVariables();
1590 $defaults = $configObject->getDefaults();
1591 $config = array_intersect_key($config + $defaults, $defaults);
1592 foreach ($config as $var => $val) {
1593 $this->{$var} = $val;
1596 return $config;
1599 function _setPageSize($format, &$orientation)
1601 if (is_string($format)) {
1603 if (empty($format)) {
1604 $format = 'A4';
1607 // e.g. A4-L = A4 landscape, A4-P = A4 portrait
1608 if (preg_match('/([0-9a-zA-Z]*)-([P,L])/i', $format, $m)) {
1609 $format = $m[1];
1610 $orientation = $m[2];
1611 } elseif (empty($orientation)) {
1612 $orientation = 'P';
1615 $format = PageFormat::getSizeFromName($format);
1617 $this->fwPt = $format[0];
1618 $this->fhPt = $format[1];
1620 } else {
1622 if (!$format[0] || !$format[1]) {
1623 throw new \Mpdf\MpdfException('Invalid page format: ' . $format[0] . ' ' . $format[1]);
1626 $this->fwPt = $format[0] * Mpdf::SCALE;
1627 $this->fhPt = $format[1] * Mpdf::SCALE;
1630 $this->fw = $this->fwPt / Mpdf::SCALE;
1631 $this->fh = $this->fhPt / Mpdf::SCALE;
1633 // Page orientation
1634 $orientation = strtolower($orientation);
1635 if ($orientation === 'p' || $orientation == 'portrait') {
1636 $orientation = 'P';
1637 $this->wPt = $this->fwPt;
1638 $this->hPt = $this->fhPt;
1639 } elseif ($orientation === 'l' || $orientation == 'landscape') {
1640 $orientation = 'L';
1641 $this->wPt = $this->fhPt;
1642 $this->hPt = $this->fwPt;
1643 } else {
1644 throw new \Mpdf\MpdfException('Incorrect orientation: ' . $orientation);
1647 $this->CurOrientation = $orientation;
1649 $this->w = $this->wPt / Mpdf::SCALE;
1650 $this->h = $this->hPt / Mpdf::SCALE;
1653 function RestrictUnicodeFonts($res)
1655 // $res = array of (Unicode) fonts to restrict to: e.g. norasi|norasiB - language specific
1656 if (count($res)) { // Leave full list of available fonts if passed blank array
1657 $this->available_unifonts = $res;
1658 } else {
1659 $this->available_unifonts = $this->default_available_fonts;
1661 if (count($this->available_unifonts) == 0) {
1662 $this->available_unifonts[] = $this->default_available_fonts[0];
1664 $this->available_unifonts = array_values($this->available_unifonts);
1667 function setMBencoding($enc)
1669 if ($this->mb_enc != $enc) {
1670 $this->mb_enc = $enc;
1671 mb_internal_encoding($this->mb_enc);
1675 function SetMargins($left, $right, $top)
1677 // Set left, top and right margins
1678 $this->lMargin = $left;
1679 $this->rMargin = $right;
1680 $this->tMargin = $top;
1683 function ResetMargins()
1685 // ReSet left, top margins
1686 if (($this->forcePortraitHeaders || $this->forcePortraitMargins) && $this->DefOrientation == 'P' && $this->CurOrientation == 'L') {
1687 if (($this->mirrorMargins) && (($this->page) % 2 == 0)) { // EVEN
1688 $this->tMargin = $this->orig_rMargin;
1689 $this->bMargin = $this->orig_lMargin;
1690 } else { // ODD // OR NOT MIRRORING MARGINS/FOOTERS
1691 $this->tMargin = $this->orig_lMargin;
1692 $this->bMargin = $this->orig_rMargin;
1694 $this->lMargin = $this->DeflMargin;
1695 $this->rMargin = $this->DefrMargin;
1696 $this->MarginCorrection = 0;
1697 $this->PageBreakTrigger = $this->h - $this->bMargin;
1698 } elseif (($this->mirrorMargins) && (($this->page) % 2 == 0)) { // EVEN
1699 $this->lMargin = $this->DefrMargin;
1700 $this->rMargin = $this->DeflMargin;
1701 $this->MarginCorrection = $this->DefrMargin - $this->DeflMargin;
1702 } else { // ODD // OR NOT MIRRORING MARGINS/FOOTERS
1703 $this->lMargin = $this->DeflMargin;
1704 $this->rMargin = $this->DefrMargin;
1705 if ($this->mirrorMargins) {
1706 $this->MarginCorrection = $this->DeflMargin - $this->DefrMargin;
1709 $this->x = $this->lMargin;
1712 function SetLeftMargin($margin)
1714 // Set left margin
1715 $this->lMargin = $margin;
1716 if ($this->page > 0 and $this->x < $margin) {
1717 $this->x = $margin;
1721 function SetTopMargin($margin)
1723 // Set top margin
1724 $this->tMargin = $margin;
1727 function SetRightMargin($margin)
1729 // Set right margin
1730 $this->rMargin = $margin;
1733 function SetAutoPageBreak($auto, $margin = 0)
1735 // Set auto page break mode and triggering margin
1736 $this->autoPageBreak = $auto;
1737 $this->bMargin = $margin;
1738 $this->PageBreakTrigger = $this->h - $margin;
1741 function SetDisplayMode($zoom, $layout = 'continuous')
1743 // Set display mode in viewer
1744 if ($zoom == 'fullpage' or $zoom == 'fullwidth' or $zoom == 'real' or $zoom == 'default' or ! is_string($zoom)) {
1745 $this->ZoomMode = $zoom;
1746 } else {
1747 throw new \Mpdf\MpdfException('Incorrect zoom display mode: ' . $zoom);
1749 if ($layout == 'single' or $layout == 'continuous' or $layout == 'two' or $layout == 'twoleft' or $layout == 'tworight' or $layout == 'default') {
1750 $this->LayoutMode = $layout;
1751 } else {
1752 throw new \Mpdf\MpdfException('Incorrect layout display mode: ' . $layout);
1756 function SetCompression($compress)
1758 // Set page compression
1759 if (function_exists('gzcompress')) {
1760 $this->compress = $compress;
1761 } else {
1762 $this->compress = false;
1766 function SetTitle($title)
1768 // Title of document // Arrives as UTF-8
1769 $this->title = $title;
1772 function SetSubject($subject)
1774 // Subject of document
1775 $this->subject = $subject;
1778 function SetAuthor($author)
1780 // Author of document
1781 $this->author = $author;
1784 function SetKeywords($keywords)
1786 // Keywords of document
1787 $this->keywords = $keywords;
1790 function SetCreator($creator)
1792 // Creator of document
1793 $this->creator = $creator;
1796 function AddCustomProperty($key, $value)
1798 $this->customProperties[$key] = $value;
1802 * Set one or multiple associated file ("/AF" as required by PDF/A-3)
1804 * param $files is an array of hash containing:
1805 * path: file path on FS
1806 * content: file content
1807 * name: file name (not necessarily the same as the file on FS)
1808 * mime (optional): file mime type (will show up as /Subtype in the PDF)
1809 * description (optional): file description
1810 * AFRelationship (optional): PDF/A-3 AFRelationship (e.g. "Alternative")
1812 * e.g. to associate 1 file:
1813 * [[
1814 * 'path' => 'tmp/1234.xml',
1815 * 'content' => 'file content',
1816 * 'name' => 'public_name.xml',
1817 * 'mime' => 'text/xml',
1818 * 'description' => 'foo',
1819 * 'AFRelationship' => 'Alternative',
1820 * ]]
1822 * @param mixed[] $files Array of arrays of associated files. See above
1824 function SetAssociatedFiles(array $files)
1826 $this->associatedFiles = $files;
1829 function SetAdditionalXmpRdf($s)
1831 $this->additionalXmpRdf = $s;
1834 function SetAnchor2Bookmark($x)
1836 $this->anchor2Bookmark = $x;
1839 public function AliasNbPages($alias = '{nb}')
1841 // Define an alias for total number of pages
1842 $this->aliasNbPg = $alias;
1845 public function AliasNbPageGroups($alias = '{nbpg}')
1847 // Define an alias for total number of pages in a group
1848 $this->aliasNbPgGp = $alias;
1851 function SetAlpha($alpha, $bm = 'Normal', $return = false, $mode = 'B')
1853 // alpha: real value from 0 (transparent) to 1 (opaque)
1854 // bm: blend mode, one of the following:
1855 // Normal, Multiply, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn,
1856 // HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color, Luminosity
1857 // set alpha for stroking (CA) and non-stroking (ca) operations
1858 // mode determines F (fill) S (stroke) B (both)
1859 if (($this->PDFA || $this->PDFX) && $alpha != 1) {
1860 if (($this->PDFA && !$this->PDFAauto) || ($this->PDFX && !$this->PDFXauto)) {
1861 $this->PDFAXwarnings[] = "Image opacity must be 100% (Opacity changed to 100%)";
1863 $alpha = 1;
1865 $a = ['BM' => '/' . $bm];
1866 if ($mode == 'F' || $mode == 'B') {
1867 $a['ca'] = $alpha; // mPDF 5.7.2
1869 if ($mode == 'S' || $mode == 'B') {
1870 $a['CA'] = $alpha; // mPDF 5.7.2
1872 $gs = $this->AddExtGState($a);
1873 if ($return) {
1874 return sprintf('/GS%d gs', $gs);
1875 } else {
1876 $this->_out(sprintf('/GS%d gs', $gs));
1880 function AddExtGState($parms)
1882 $n = count($this->extgstates);
1883 // check if graphics state already exists
1884 for ($i = 1; $i <= $n; $i++) {
1885 if (count($this->extgstates[$i]['parms']) == count($parms)) {
1886 $same = true;
1887 foreach ($this->extgstates[$i]['parms'] as $k => $v) {
1888 if (!isset($parms[$k]) || $parms[$k] != $v) {
1889 $same = false;
1890 break;
1893 if ($same) {
1894 return $i;
1898 $n++;
1899 $this->extgstates[$n]['parms'] = $parms;
1900 return $n;
1903 function SetVisibility($v)
1905 if (($this->PDFA || $this->PDFX) && $this->visibility != 'visible') {
1906 $this->PDFAXwarnings[] = "Cannot set visibility to anything other than full when using PDFA or PDFX";
1907 return '';
1908 } elseif (!$this->PDFA && !$this->PDFX) {
1909 $this->pdf_version = '1.5';
1911 if ($this->visibility != 'visible') {
1912 $this->_out('EMC');
1913 $this->hasOC = intval($this->hasOC);
1915 if ($v == 'printonly') {
1916 $this->_out('/OC /OC1 BDC');
1917 $this->hasOC = ($this->hasOC | 1);
1918 } elseif ($v == 'screenonly') {
1919 $this->_out('/OC /OC2 BDC');
1920 $this->hasOC = ($this->hasOC | 2);
1921 } elseif ($v == 'hidden') {
1922 $this->_out('/OC /OC3 BDC');
1923 $this->hasOC = ($this->hasOC | 4);
1924 } elseif ($v != 'visible') {
1925 throw new \Mpdf\MpdfException('Incorrect visibility: ' . $v);
1927 $this->visibility = $v;
1930 function Open()
1932 // Begin document
1933 if ($this->state == 0) {
1934 // Was is function _begindoc()
1935 // Start document
1936 $this->state = 1;
1937 $this->_out('%PDF-' . $this->pdf_version);
1938 $this->_out('%' . chr(226) . chr(227) . chr(207) . chr(211)); // 4 chars > 128 to show binary file
1942 function Close()
1944 // @log Closing last page
1946 // Terminate document
1947 if ($this->state == 3) {
1948 return;
1950 if ($this->page == 0) {
1951 $this->AddPage($this->CurOrientation);
1953 if (count($this->cellBorderBuffer)) {
1954 $this->printcellbuffer();
1955 } // *TABLES*
1956 if ($this->tablebuffer) {
1957 $this->printtablebuffer();
1958 } // *TABLES*
1959 /* -- COLUMNS -- */
1961 if ($this->ColActive) {
1962 $this->SetColumns(0);
1963 $this->ColActive = 0;
1964 if (count($this->columnbuffer)) {
1965 $this->printcolumnbuffer();
1968 /* -- END COLUMNS -- */
1970 // BODY Backgrounds
1971 $s = '';
1973 $s .= $this->PrintBodyBackgrounds();
1974 $s .= $this->PrintPageBackgrounds();
1976 $this->pages[$this->page] = preg_replace(
1977 '/(___BACKGROUND___PATTERNS' . $this->uniqstr . ')/',
1978 "\n" . $s . "\n" . '\\1',
1979 $this->pages[$this->page]
1982 $this->pageBackgrounds = [];
1984 if ($this->visibility != 'visible') {
1985 $this->SetVisibility('visible');
1988 $this->EndLayer();
1990 if (!$this->tableOfContents->TOCmark) { // Page footer
1991 $this->InFooter = true;
1992 $this->Footer();
1993 $this->InFooter = false;
1996 if ($this->tableOfContents->TOCmark || count($this->tableOfContents->m_TOC)) {
1997 $this->tableOfContents->insertTOC();
2000 // *TOC*
2001 // Close page
2002 $this->_endpage();
2004 // Close document
2005 $this->_enddoc();
2008 /* -- BACKGROUNDS -- */
2010 function _resizeBackgroundImage($imw, $imh, $cw, $ch, $resize, $repx, $repy, $pba = [], $size = [])
2012 // pba is background positioning area (from CSS background-origin) may not always be set [x,y,w,h]
2013 // size is from CSS3 background-size - takes precendence over old resize
2014 // $w - absolute length or % or auto or cover | contain
2015 // $h - absolute length or % or auto or cover | contain
2016 if (isset($pba['w'])) {
2017 $cw = $pba['w'];
2019 if (isset($pba['h'])) {
2020 $ch = $pba['h'];
2023 $cw = $cw * Mpdf::SCALE;
2024 $ch = $ch * Mpdf::SCALE;
2025 if (empty($size) && !$resize) {
2026 return [$imw, $imh, $repx, $repy];
2029 if (isset($size['w']) && $size['w']) {
2030 if ($size['w'] == 'contain') {
2031 // Scale the image, while preserving its intrinsic aspect ratio (if any),
2032 // to the largest size such that both its width and its height can fit inside the background positioning area.
2033 // Same as resize==3
2034 $h = $imh * $cw / $imw;
2035 $w = $cw;
2036 if ($h > $ch) {
2037 $w = $w * $ch / $h;
2038 $h = $ch;
2040 } elseif ($size['w'] == 'cover') {
2041 // Scale the image, while preserving its intrinsic aspect ratio (if any),
2042 // to the smallest size such that both its width and its height can completely cover the background positioning area.
2043 $h = $imh * $cw / $imw;
2044 $w = $cw;
2045 if ($h < $ch) {
2046 $w = $w * $h / $ch;
2047 $h = $ch;
2049 } else {
2050 if (stristr($size['w'], '%')) {
2051 $size['w'] = (float) $size['w'];
2052 $size['w'] /= 100;
2053 $size['w'] = ($cw * $size['w']);
2055 if (stristr($size['h'], '%')) {
2056 $size['h'] = (float) $size['h'];
2057 $size['h'] /= 100;
2058 $size['h'] = ($ch * $size['h']);
2060 if ($size['w'] == 'auto' && $size['h'] == 'auto') {
2061 $w = $imw;
2062 $h = $imh;
2063 } elseif ($size['w'] == 'auto' && $size['h'] != 'auto') {
2064 $w = $imw * $size['h'] / $imh;
2065 $h = $size['h'];
2066 } elseif ($size['w'] != 'auto' && $size['h'] == 'auto') {
2067 $h = $imh * $size['w'] / $imw;
2068 $w = $size['w'];
2069 } else {
2070 $w = $size['w'];
2071 $h = $size['h'];
2074 return [$w, $h, $repx, $repy];
2075 } elseif ($resize == 1 && $imw > $cw) {
2076 $h = $imh * $cw / $imw;
2077 return [$cw, $h, $repx, $repy];
2078 } elseif ($resize == 2 && $imh > $ch) {
2079 $w = $imw * $ch / $imh;
2080 return [$w, $ch, $repx, $repy];
2081 } elseif ($resize == 3) {
2082 $w = $imw;
2083 $h = $imh;
2084 if ($w > $cw) {
2085 $h = $h * $cw / $w;
2086 $w = $cw;
2088 if ($h > $ch) {
2089 $w = $w * $ch / $h;
2090 $h = $ch;
2092 return [$w, $h, $repx, $repy];
2093 } elseif ($resize == 4) {
2094 $h = $imh * $cw / $imw;
2095 return [$cw, $h, $repx, $repy];
2096 } elseif ($resize == 5) {
2097 $w = $imw * $ch / $imh;
2098 return [$w, $ch, $repx, $repy];
2099 } elseif ($resize == 6) {
2100 return [$cw, $ch, $repx, $repy];
2102 return [$imw, $imh, $repx, $repy];
2105 function SetBackground(&$properties, &$maxwidth)
2107 if (isset($properties['BACKGROUND-ORIGIN']) && ($properties['BACKGROUND-ORIGIN'] == 'border-box' || $properties['BACKGROUND-ORIGIN'] == 'content-box')) {
2108 $origin = $properties['BACKGROUND-ORIGIN'];
2109 } else {
2110 $origin = 'padding-box';
2113 if (isset($properties['BACKGROUND-SIZE'])) {
2114 if (stristr($properties['BACKGROUND-SIZE'], 'contain')) {
2115 $bsw = $bsh = 'contain';
2116 } elseif (stristr($properties['BACKGROUND-SIZE'], 'cover')) {
2117 $bsw = $bsh = 'cover';
2118 } else {
2119 $bsw = $bsh = 'auto';
2120 $sz = preg_split('/\s+/', trim($properties['BACKGROUND-SIZE']));
2121 if (count($sz) == 2) {
2122 $bsw = $sz[0];
2123 $bsh = $sz[1];
2124 } else {
2125 $bsw = $sz[0];
2127 if (!stristr($bsw, '%') && !stristr($bsw, 'auto')) {
2128 $bsw = $this->sizeConverter->convert($bsw, $maxwidth, $this->FontSize);
2130 if (!stristr($bsh, '%') && !stristr($bsh, 'auto')) {
2131 $bsh = $this->sizeConverter->convert($bsh, $maxwidth, $this->FontSize);
2134 $size = ['w' => $bsw, 'h' => $bsh];
2135 } else {
2136 $size = false;
2137 } // mPDF 6
2138 if (preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $properties['BACKGROUND-IMAGE'])) {
2139 return ['gradient' => $properties['BACKGROUND-IMAGE'], 'origin' => $origin, 'size' => $size];
2140 } else {
2141 $file = $properties['BACKGROUND-IMAGE'];
2142 $sizesarray = $this->Image($file, 0, 0, 0, 0, '', '', false, false, false, false, true);
2143 if (isset($sizesarray['IMAGE_ID'])) {
2144 $image_id = $sizesarray['IMAGE_ID'];
2145 $orig_w = $sizesarray['WIDTH'] * Mpdf::SCALE; // in user units i.e. mm
2146 $orig_h = $sizesarray['HEIGHT'] * Mpdf::SCALE; // (using $this->img_dpi)
2147 if (isset($properties['BACKGROUND-IMAGE-RESOLUTION'])) {
2148 if (preg_match('/from-image/i', $properties['BACKGROUND-IMAGE-RESOLUTION']) && isset($sizesarray['set-dpi']) && $sizesarray['set-dpi'] > 0) {
2149 $orig_w *= $this->img_dpi / $sizesarray['set-dpi'];
2150 $orig_h *= $this->img_dpi / $sizesarray['set-dpi'];
2151 } elseif (preg_match('/(\d+)dpi/i', $properties['BACKGROUND-IMAGE-RESOLUTION'], $m)) {
2152 $dpi = $m[1];
2153 if ($dpi > 0) {
2154 $orig_w *= $this->img_dpi / $dpi;
2155 $orig_h *= $this->img_dpi / $dpi;
2159 $x_repeat = true;
2160 $y_repeat = true;
2161 if (isset($properties['BACKGROUND-REPEAT'])) {
2162 if ($properties['BACKGROUND-REPEAT'] == 'no-repeat' || $properties['BACKGROUND-REPEAT'] == 'repeat-x') {
2163 $y_repeat = false;
2165 if ($properties['BACKGROUND-REPEAT'] == 'no-repeat' || $properties['BACKGROUND-REPEAT'] == 'repeat-y') {
2166 $x_repeat = false;
2169 $x_pos = 0;
2170 $y_pos = 0;
2171 if (isset($properties['BACKGROUND-POSITION'])) {
2172 $ppos = preg_split('/\s+/', $properties['BACKGROUND-POSITION']);
2173 $x_pos = $ppos[0];
2174 $y_pos = $ppos[1];
2175 if (!stristr($x_pos, '%')) {
2176 $x_pos = $this->sizeConverter->convert($x_pos, $maxwidth, $this->FontSize);
2178 if (!stristr($y_pos, '%')) {
2179 $y_pos = $this->sizeConverter->convert($y_pos, $maxwidth, $this->FontSize);
2182 if (isset($properties['BACKGROUND-IMAGE-RESIZE'])) {
2183 $resize = $properties['BACKGROUND-IMAGE-RESIZE'];
2184 } else {
2185 $resize = 0;
2187 if (isset($properties['BACKGROUND-IMAGE-OPACITY'])) {
2188 $opacity = $properties['BACKGROUND-IMAGE-OPACITY'];
2189 } else {
2190 $opacity = 1;
2192 return ['image_id' => $image_id, 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $x_pos, 'y_pos' => $y_pos, 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'resize' => $resize, 'opacity' => $opacity, 'itype' => $sizesarray['itype'], 'origin' => $origin, 'size' => $size];
2195 return false;
2198 /* -- END BACKGROUNDS -- */
2200 function PrintBodyBackgrounds()
2202 $s = '';
2203 $clx = 0;
2204 $cly = 0;
2205 $clw = $this->w;
2206 $clh = $this->h;
2207 // If using bleed and trim margins in paged media
2208 if ($this->pageDim[$this->page]['outer_width_LR'] || $this->pageDim[$this->page]['outer_width_TB']) {
2209 $clx = $this->pageDim[$this->page]['outer_width_LR'] - $this->pageDim[$this->page]['bleedMargin'];
2210 $cly = $this->pageDim[$this->page]['outer_width_TB'] - $this->pageDim[$this->page]['bleedMargin'];
2211 $clw = $this->w - 2 * $clx;
2212 $clh = $this->h - 2 * $cly;
2215 if ($this->bodyBackgroundColor) {
2216 $s .= 'q ' . $this->SetFColor($this->bodyBackgroundColor, true) . "\n";
2217 if ($this->bodyBackgroundColor{0} == 5) { // RGBa
2218 $s .= $this->SetAlpha(ord($this->bodyBackgroundColor{4}) / 100, 'Normal', true, 'F') . "\n";
2219 } elseif ($this->bodyBackgroundColor{0} == 6) { // CMYKa
2220 $s .= $this->SetAlpha(ord($this->bodyBackgroundColor{5}) / 100, 'Normal', true, 'F') . "\n";
2222 $s .= sprintf('%.3F %.3F %.3F %.3F re f Q', ($clx * Mpdf::SCALE), ($cly * Mpdf::SCALE), $clw * Mpdf::SCALE, $clh * Mpdf::SCALE) . "\n";
2225 /* -- BACKGROUNDS -- */
2226 if ($this->bodyBackgroundGradient) {
2227 $g = $this->gradient->parseBackgroundGradient($this->bodyBackgroundGradient);
2228 if ($g) {
2229 $s .= $this->gradient->Gradient($clx, $cly, $clw, $clh, (isset($g['gradtype']) ? $g['gradtype'] : null), $g['stops'], $g['colorspace'], $g['coords'], $g['extend'], true);
2232 if ($this->bodyBackgroundImage) {
2233 if (isset($this->bodyBackgroundImage['gradient']) && $this->bodyBackgroundImage['gradient'] && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $this->bodyBackgroundImage['gradient'])) {
2234 $g = $this->gradient->parseMozGradient($this->bodyBackgroundImage['gradient']);
2235 if ($g) {
2236 $s .= $this->gradient->Gradient($clx, $cly, $clw, $clh, $g['type'], $g['stops'], $g['colorspace'], $g['coords'], $g['extend'], true);
2238 } elseif ($this->bodyBackgroundImage['image_id']) { // Background pattern
2239 $n = count($this->patterns) + 1;
2240 // If using resize, uses TrimBox (not including the bleed)
2241 list($orig_w, $orig_h, $x_repeat, $y_repeat) = $this->_resizeBackgroundImage($this->bodyBackgroundImage['orig_w'], $this->bodyBackgroundImage['orig_h'], $clw, $clh, $this->bodyBackgroundImage['resize'], $this->bodyBackgroundImage['x_repeat'], $this->bodyBackgroundImage['y_repeat']);
2243 $this->patterns[$n] = ['x' => $clx, 'y' => $cly, 'w' => $clw, 'h' => $clh, 'pgh' => $this->h, 'image_id' => $this->bodyBackgroundImage['image_id'], 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $this->bodyBackgroundImage['x_pos'], 'y_pos' => $this->bodyBackgroundImage['y_pos'], 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'itype' => $this->bodyBackgroundImage['itype']];
2244 if (($this->bodyBackgroundImage['opacity'] > 0 || $this->bodyBackgroundImage['opacity'] === '0') && $this->bodyBackgroundImage['opacity'] < 1) {
2245 $opac = $this->SetAlpha($this->bodyBackgroundImage['opacity'], 'Normal', true);
2246 } else {
2247 $opac = '';
2249 $s .= sprintf('q /Pattern cs /P%d scn %s %.3F %.3F %.3F %.3F re f Q', $n, $opac, ($clx * Mpdf::SCALE), ($cly * Mpdf::SCALE), $clw * Mpdf::SCALE, $clh * Mpdf::SCALE) . "\n";
2252 /* -- END BACKGROUNDS -- */
2253 return $s;
2256 function _setClippingPath($clx, $cly, $clw, $clh)
2258 $s = ' q 0 w '; // Line width=0
2259 $s .= sprintf('%.3F %.3F m ', ($clx) * Mpdf::SCALE, ($this->h - ($cly)) * Mpdf::SCALE); // start point TL before the arc
2260 $s .= sprintf('%.3F %.3F l ', ($clx) * Mpdf::SCALE, ($this->h - ($cly + $clh)) * Mpdf::SCALE); // line to BL
2261 $s .= sprintf('%.3F %.3F l ', ($clx + $clw) * Mpdf::SCALE, ($this->h - ($cly + $clh)) * Mpdf::SCALE); // line to BR
2262 $s .= sprintf('%.3F %.3F l ', ($clx + $clw) * Mpdf::SCALE, ($this->h - ($cly)) * Mpdf::SCALE); // line to TR
2263 $s .= sprintf('%.3F %.3F l ', ($clx) * Mpdf::SCALE, ($this->h - ($cly)) * Mpdf::SCALE); // line to TL
2264 $s .= ' W n '; // Ends path no-op & Sets the clipping path
2265 return $s;
2268 function PrintPageBackgrounds($adjustmenty = 0)
2270 $s = '';
2272 ksort($this->pageBackgrounds);
2273 foreach ($this->pageBackgrounds as $bl => $pbs) {
2274 foreach ($pbs as $pb) {
2275 if ((!isset($pb['image_id']) && !isset($pb['gradient'])) || isset($pb['shadowonly'])) { // Background colour or boxshadow
2276 if ($pb['z-index'] > 0) {
2277 $this->current_layer = $pb['z-index'];
2278 $s .= "\n" . '/OCBZ-index /ZI' . $pb['z-index'] . ' BDC' . "\n";
2281 if ($pb['visibility'] != 'visible') {
2282 if ($pb['visibility'] == 'printonly') {
2283 $s .= '/OC /OC1 BDC' . "\n";
2284 } elseif ($pb['visibility'] == 'screenonly') {
2285 $s .= '/OC /OC2 BDC' . "\n";
2286 } elseif ($pb['visibility'] == 'hidden') {
2287 $s .= '/OC /OC3 BDC' . "\n";
2290 // Box shadow
2291 if (isset($pb['shadow']) && $pb['shadow']) {
2292 $s .= $pb['shadow'] . "\n";
2294 if (isset($pb['clippath']) && $pb['clippath']) {
2295 $s .= $pb['clippath'] . "\n";
2297 $s .= 'q ' . $this->SetFColor($pb['col'], true) . "\n";
2298 if ($pb['col']{0} == 5) { // RGBa
2299 $s .= $this->SetAlpha(ord($pb['col']{4}) / 100, 'Normal', true, 'F') . "\n";
2300 } elseif ($pb['col']{0} == 6) { // CMYKa
2301 $s .= $this->SetAlpha(ord($pb['col']{5}) / 100, 'Normal', true, 'F') . "\n";
2303 $s .= sprintf('%.3F %.3F %.3F %.3F re f Q', $pb['x'] * Mpdf::SCALE, ($this->h - $pb['y']) * Mpdf::SCALE, $pb['w'] * Mpdf::SCALE, -$pb['h'] * Mpdf::SCALE) . "\n";
2304 if (isset($pb['clippath']) && $pb['clippath']) {
2305 $s .= 'Q' . "\n";
2307 if ($pb['visibility'] != 'visible') {
2308 $s .= 'EMC' . "\n";
2311 if ($pb['z-index'] > 0) {
2312 $s .= "\n" . 'EMCBZ-index' . "\n";
2313 $this->current_layer = 0;
2317 /* -- BACKGROUNDS -- */
2318 foreach ($pbs as $pb) {
2319 if ((isset($pb['gradient']) && $pb['gradient']) || (isset($pb['image_id']) && $pb['image_id'])) {
2320 if ($pb['z-index'] > 0) {
2321 $this->current_layer = $pb['z-index'];
2322 $s .= "\n" . '/OCGZ-index /ZI' . $pb['z-index'] . ' BDC' . "\n";
2324 if ($pb['visibility'] != 'visible') {
2325 if ($pb['visibility'] == 'printonly') {
2326 $s .= '/OC /OC1 BDC' . "\n";
2327 } elseif ($pb['visibility'] == 'screenonly') {
2328 $s .= '/OC /OC2 BDC' . "\n";
2329 } elseif ($pb['visibility'] == 'hidden') {
2330 $s .= '/OC /OC3 BDC' . "\n";
2334 if (isset($pb['gradient']) && $pb['gradient']) {
2335 if (isset($pb['clippath']) && $pb['clippath']) {
2336 $s .= $pb['clippath'] . "\n";
2338 $s .= $this->gradient->Gradient($pb['x'], $pb['y'], $pb['w'], $pb['h'], $pb['gradtype'], $pb['stops'], $pb['colorspace'], $pb['coords'], $pb['extend'], true);
2339 if (isset($pb['clippath']) && $pb['clippath']) {
2340 $s .= 'Q' . "\n";
2342 } elseif (isset($pb['image_id']) && $pb['image_id']) { // Background Image
2343 $pb['y'] -= $adjustmenty;
2344 $pb['h'] += $adjustmenty;
2345 $n = count($this->patterns) + 1;
2346 list($orig_w, $orig_h, $x_repeat, $y_repeat) = $this->_resizeBackgroundImage($pb['orig_w'], $pb['orig_h'], $pb['w'], $pb['h'], $pb['resize'], $pb['x_repeat'], $pb['y_repeat'], $pb['bpa'], $pb['size']);
2347 $this->patterns[$n] = ['x' => $pb['x'], 'y' => $pb['y'], 'w' => $pb['w'], 'h' => $pb['h'], 'pgh' => $this->h, 'image_id' => $pb['image_id'], 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $pb['x_pos'], 'y_pos' => $pb['y_pos'], 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'itype' => $pb['itype'], 'bpa' => $pb['bpa']];
2348 $x = $pb['x'] * Mpdf::SCALE;
2349 $y = ($this->h - $pb['y']) * Mpdf::SCALE;
2350 $w = $pb['w'] * Mpdf::SCALE;
2351 $h = -$pb['h'] * Mpdf::SCALE;
2352 if (isset($pb['clippath']) && $pb['clippath']) {
2353 $s .= $pb['clippath'] . "\n";
2355 if ($this->writingHTMLfooter || $this->writingHTMLheader) { // Write each (tiles) image rather than use as a pattern
2356 $iw = $pb['orig_w'] / Mpdf::SCALE;
2357 $ih = $pb['orig_h'] / Mpdf::SCALE;
2359 $w = $pb['w'];
2360 $h = $pb['h'];
2361 $x0 = $pb['x'];
2362 $y0 = $pb['y'];
2364 if (isset($pb['bpa']) && $pb['bpa']) {
2365 $w = $pb['bpa']['w'];
2366 $h = $pb['bpa']['h'];
2367 $x0 = $pb['bpa']['x'];
2368 $y0 = $pb['bpa']['y'];
2371 if (isset($pb['size']['w']) && $pb['size']['w']) {
2372 $size = $pb['size'];
2374 if ($size['w'] == 'contain') {
2375 // Scale the image, while preserving its intrinsic aspect ratio (if any), to the largest
2376 // size such that both its width and its height can fit inside the background positioning area.
2377 // Same as resize==3
2378 $ih = $ih * $pb['bpa']['w'] / $iw;
2379 $iw = $pb['bpa']['w'];
2380 if ($ih > $pb['bpa']['h']) {
2381 $iw = $iw * $pb['bpa']['h'] / $ih;
2382 $ih = $pb['bpa']['h'];
2384 } elseif ($size['w'] == 'cover') {
2385 // Scale the image, while preserving its intrinsic aspect ratio (if any), to the smallest
2386 // size such that both its width and its height can completely cover the background positioning area.
2387 $ih = $ih * $pb['bpa']['w'] / $iw;
2388 $iw = $pb['bpa']['w'];
2389 if ($ih < $pb['bpa']['h']) {
2390 $iw = $iw * $ih / $pb['bpa']['h'];
2391 $ih = $pb['bpa']['h'];
2393 } else {
2394 if (NumericString::containsPercentChar($size['w'])) {
2395 $size['w'] = NumericString::removePercentChar($size['w']);
2396 $size['w'] /= 100;
2397 $size['w'] = ($pb['bpa']['w'] * $size['w']);
2399 if (NumericString::containsPercentChar($size['h'])) {
2400 $size['h'] = NumericString::removePercentChar($size['h']);
2401 $size['h'] /= 100;
2402 $size['h'] = ($pb['bpa']['h'] * $size['h']);
2404 if ($size['w'] == 'auto' && $size['h'] == 'auto') {
2405 $iw = $iw;
2406 $ih = $ih;
2407 } elseif ($size['w'] == 'auto' && $size['h'] != 'auto') {
2408 $iw = $iw * $size['h'] / $ih;
2409 $ih = $size['h'];
2410 } elseif ($size['w'] != 'auto' && $size['h'] == 'auto') {
2411 $ih = $ih * $size['w'] / $iw;
2412 $iw = $size['w'];
2413 } else {
2414 $iw = $size['w'];
2415 $ih = $size['h'];
2420 // Number to repeat
2421 if ($pb['x_repeat']) {
2422 $nx = ceil($pb['w'] / $iw) + 1;
2423 } else {
2424 $nx = 1;
2427 if ($pb['y_repeat']) {
2428 $ny = ceil($pb['h'] / $ih) + 1;
2429 } else {
2430 $ny = 1;
2433 $x_pos = $pb['x_pos'];
2434 if (stristr($x_pos, '%')) {
2435 $x_pos = (float) $x_pos;
2436 $x_pos /= 100;
2437 $x_pos = ($pb['bpa']['w'] * $x_pos) - ($iw * $x_pos);
2440 $y_pos = $pb['y_pos'];
2441 if (stristr($y_pos, '%')) {
2442 $y_pos = (float) $y_pos;
2443 $y_pos /= 100;
2444 $y_pos = ($pb['bpa']['h'] * $y_pos) - ($ih * $y_pos);
2446 if ($nx > 1) {
2447 while ($x_pos > ($pb['x'] - $pb['bpa']['x'])) {
2448 $x_pos -= $iw;
2452 if ($ny > 1) {
2453 while ($y_pos > ($pb['y'] - $pb['bpa']['y'])) {
2454 $y_pos -= $ih;
2458 for ($xi = 0; $xi < $nx; $xi++) {
2459 for ($yi = 0; $yi < $ny; $yi++) {
2460 $x = $x0 + $x_pos + ($iw * $xi);
2461 $y = $y0 + $y_pos + ($ih * $yi);
2462 if ($pb['opacity'] > 0 && $pb['opacity'] < 1) {
2463 $opac = $this->SetAlpha($pb['opacity'], 'Normal', true);
2464 } else {
2465 $opac = '';
2467 $s .= sprintf("q %s %.3F 0 0 %.3F %.3F %.3F cm /I%d Do Q", $opac, $iw * Mpdf::SCALE, $ih * Mpdf::SCALE, $x * Mpdf::SCALE, ($this->h - ($y + $ih)) * Mpdf::SCALE, $pb['image_id']) . "\n";
2470 } else {
2471 if (($pb['opacity'] > 0 || $pb['opacity'] === '0') && $pb['opacity'] < 1) {
2472 $opac = $this->SetAlpha($pb['opacity'], 'Normal', true);
2473 } else {
2474 $opac = '';
2476 $s .= sprintf('q /Pattern cs /P%d scn %s %.3F %.3F %.3F %.3F re f Q', $n, $opac, $x, $y, $w, $h) . "\n";
2479 if (isset($pb['clippath']) && $pb['clippath']) {
2480 $s .= 'Q' . "\n";
2484 if ((isset($pb['gradient']) && $pb['gradient']) || (isset($pb['image_id']) && $pb['image_id'])) {
2485 if ($pb['visibility'] != 'visible') {
2486 $s .= 'EMC' . "\n";
2489 if ($pb['z-index'] > 0) {
2490 $s .= "\n" . 'EMCGZ-index' . "\n";
2491 $this->current_layer = 0;
2495 /* -- END BACKGROUNDS -- */
2497 return $s;
2500 function PrintTableBackgrounds($adjustmenty = 0)
2502 $s = '';
2503 /* -- BACKGROUNDS -- */
2504 ksort($this->tableBackgrounds);
2505 foreach ($this->tableBackgrounds as $bl => $pbs) {
2506 foreach ($pbs as $pb) {
2507 if ((!isset($pb['gradient']) || !$pb['gradient']) && (!isset($pb['image_id']) || !$pb['image_id'])) {
2508 $s .= 'q ' . $this->SetFColor($pb['col'], true) . "\n";
2509 if ($pb['col']{0} == 5) { // RGBa
2510 $s .= $this->SetAlpha(ord($pb['col']{4}) / 100, 'Normal', true, 'F') . "\n";
2511 } elseif ($pb['col']{0} == 6) { // CMYKa
2512 $s .= $this->SetAlpha(ord($pb['col']{5}) / 100, 'Normal', true, 'F') . "\n";
2514 $s .= sprintf('%.3F %.3F %.3F %.3F re %s Q', $pb['x'] * Mpdf::SCALE, ($this->h - $pb['y']) * Mpdf::SCALE, $pb['w'] * Mpdf::SCALE, -$pb['h'] * Mpdf::SCALE, 'f') . "\n";
2516 if (isset($pb['gradient']) && $pb['gradient']) {
2517 if (isset($pb['clippath']) && $pb['clippath']) {
2518 $s .= $pb['clippath'] . "\n";
2520 $s .= $this->gradient->Gradient($pb['x'], $pb['y'], $pb['w'], $pb['h'], $pb['gradtype'], $pb['stops'], $pb['colorspace'], $pb['coords'], $pb['extend'], true);
2521 if (isset($pb['clippath']) && $pb['clippath']) {
2522 $s .= 'Q' . "\n";
2525 if (isset($pb['image_id']) && $pb['image_id']) { // Background pattern
2526 $pb['y'] -= $adjustmenty;
2527 $pb['h'] += $adjustmenty;
2528 $n = count($this->patterns) + 1;
2529 list($orig_w, $orig_h, $x_repeat, $y_repeat) = $this->_resizeBackgroundImage($pb['orig_w'], $pb['orig_h'], $pb['w'], $pb['h'], $pb['resize'], $pb['x_repeat'], $pb['y_repeat']);
2530 $this->patterns[$n] = ['x' => $pb['x'], 'y' => $pb['y'], 'w' => $pb['w'], 'h' => $pb['h'], 'pgh' => $this->h, 'image_id' => $pb['image_id'], 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $pb['x_pos'], 'y_pos' => $pb['y_pos'], 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'itype' => $pb['itype']];
2531 $x = $pb['x'] * Mpdf::SCALE;
2532 $y = ($this->h - $pb['y']) * Mpdf::SCALE;
2533 $w = $pb['w'] * Mpdf::SCALE;
2534 $h = -$pb['h'] * Mpdf::SCALE;
2536 // mPDF 5.7.3
2537 if (($this->writingHTMLfooter || $this->writingHTMLheader) && (!isset($pb['clippath']) || $pb['clippath'] == '')) {
2538 // Set clipping path
2539 $pb['clippath'] = sprintf(' q 0 w %.3F %.3F m %.3F %.3F l %.3F %.3F l %.3F %.3F l %.3F %.3F l W n ', $x, $y, $x, $y + $h, $x + $w, $y + $h, $x + $w, $y, $x, $y);
2542 if (isset($pb['clippath']) && $pb['clippath']) {
2543 $s .= $pb['clippath'] . "\n";
2546 // mPDF 5.7.3
2547 if ($this->writingHTMLfooter || $this->writingHTMLheader) { // Write each (tiles) image rather than use as a pattern
2548 $iw = $pb['orig_w'] / Mpdf::SCALE;
2549 $ih = $pb['orig_h'] / Mpdf::SCALE;
2551 $w = $pb['w'];
2552 $h = $pb['h'];
2553 $x0 = $pb['x'];
2554 $y0 = $pb['y'];
2556 if (isset($pb['bpa']) && $pb['bpa']) {
2557 $w = $pb['bpa']['w'];
2558 $h = $pb['bpa']['h'];
2559 $x0 = $pb['bpa']['x'];
2560 $y0 = $pb['bpa']['y'];
2561 } // At present 'bpa' (background page area) is not set for tablebackgrounds - only pagebackgrounds
2562 // For now, just set it as:
2563 else {
2564 $pb['bpa'] = ['x' => $x0, 'y' => $y0, 'w' => $w, 'h' => $h];
2567 if (isset($pb['size']['w']) && $pb['size']['w']) {
2568 $size = $pb['size'];
2570 if ($size['w'] == 'contain') {
2571 // Scale the image, while preserving its intrinsic aspect ratio (if any), to the largest size such that both its width and its height can fit inside the background positioning area.
2572 // Same as resize==3
2573 $ih = $ih * $pb['bpa']['w'] / $iw;
2574 $iw = $pb['bpa']['w'];
2575 if ($ih > $pb['bpa']['h']) {
2576 $iw = $iw * $pb['bpa']['h'] / $ih;
2577 $ih = $pb['bpa']['h'];
2579 } elseif ($size['w'] == 'cover') {
2580 // Scale the image, while preserving its intrinsic aspect ratio (if any), to the smallest size such that both its width and its height can completely cover the background positioning area.
2581 $ih = $ih * $pb['bpa']['w'] / $iw;
2582 $iw = $pb['bpa']['w'];
2583 if ($ih < $pb['bpa']['h']) {
2584 $iw = $iw * $ih / $pb['bpa']['h'];
2585 $ih = $pb['bpa']['h'];
2587 } else {
2588 if (NumericString::containsPercentChar($size['w'])) {
2589 $size['w'] = NumericString::removePercentChar($size['w']);
2590 $size['w'] /= 100;
2591 $size['w'] = ($pb['bpa']['w'] * $size['w']);
2593 if (NumericString::containsPercentChar($size['h'])) {
2594 $size['h'] = NumericString::removePercentChar($size['h']);
2595 $size['h'] /= 100;
2596 $size['h'] = ($pb['bpa']['h'] * $size['h']);
2598 if ($size['w'] == 'auto' && $size['h'] == 'auto') {
2599 $iw = $iw;
2600 $ih = $ih;
2601 } elseif ($size['w'] == 'auto' && $size['h'] != 'auto') {
2602 $iw = $iw * $size['h'] / $ih;
2603 $ih = $size['h'];
2604 } elseif ($size['w'] != 'auto' && $size['h'] == 'auto') {
2605 $ih = $ih * $size['w'] / $iw;
2606 $iw = $size['w'];
2607 } else {
2608 $iw = $size['w'];
2609 $ih = $size['h'];
2614 // Number to repeat
2615 if (isset($pb['x_repeat']) && $pb['x_repeat']) {
2616 $nx = ceil($pb['w'] / $iw) + 1;
2617 } else {
2618 $nx = 1;
2620 if (isset($pb['y_repeat']) && $pb['y_repeat']) {
2621 $ny = ceil($pb['h'] / $ih) + 1;
2622 } else {
2623 $ny = 1;
2626 $x_pos = $pb['x_pos'];
2627 if (NumericString::containsPercentChar($x_pos)) {
2628 $x_pos = NumericString::removePercentChar($x_pos);
2629 $x_pos /= 100;
2630 $x_pos = ($pb['bpa']['w'] * $x_pos) - ($iw * $x_pos);
2632 $y_pos = $pb['y_pos'];
2633 if (NumericString::containsPercentChar($y_pos)) {
2634 $y_pos = NumericString::removePercentChar($y_pos);
2635 $y_pos /= 100;
2636 $y_pos = ($pb['bpa']['h'] * $y_pos) - ($ih * $y_pos);
2638 if ($nx > 1) {
2639 while ($x_pos > ($pb['x'] - $pb['bpa']['x'])) {
2640 $x_pos -= $iw;
2643 if ($ny > 1) {
2644 while ($y_pos > ($pb['y'] - $pb['bpa']['y'])) {
2645 $y_pos -= $ih;
2648 for ($xi = 0; $xi < $nx; $xi++) {
2649 for ($yi = 0; $yi < $ny; $yi++) {
2650 $x = $x0 + $x_pos + ($iw * $xi);
2651 $y = $y0 + $y_pos + ($ih * $yi);
2652 if ($pb['opacity'] > 0 && $pb['opacity'] < 1) {
2653 $opac = $this->SetAlpha($pb['opacity'], 'Normal', true);
2654 } else {
2655 $opac = '';
2657 $s .= sprintf("q %s %.3F 0 0 %.3F %.3F %.3F cm /I%d Do Q", $opac, $iw * Mpdf::SCALE, $ih * Mpdf::SCALE, $x * Mpdf::SCALE, ($this->h - ($y + $ih)) * Mpdf::SCALE, $pb['image_id']) . "\n";
2660 } else {
2661 if (($pb['opacity'] > 0 || $pb['opacity'] === '0') && $pb['opacity'] < 1) {
2662 $opac = $this->SetAlpha($pb['opacity'], 'Normal', true);
2663 } else {
2664 $opac = '';
2666 $s .= sprintf('q /Pattern cs /P%d scn %s %.3F %.3F %.3F %.3F re f Q', $n, $opac, $x, $y, $w, $h) . "\n";
2669 if (isset($pb['clippath']) && $pb['clippath']) {
2670 $s .= 'Q' . "\n";
2675 /* -- END BACKGROUNDS -- */
2676 return $s;
2679 function BeginLayer($id)
2681 if ($this->current_layer > 0) {
2682 $this->EndLayer();
2684 if ($id < 1) {
2685 return false;
2687 if (!isset($this->layers[$id])) {
2688 $this->layers[$id] = ['name' => 'Layer ' . ($id)];
2689 if (($this->PDFA || $this->PDFX)) {
2690 $this->PDFAXwarnings[] = "Cannot use layers when using PDFA or PDFX";
2691 return '';
2692 } elseif (!$this->PDFA && !$this->PDFX) {
2693 $this->pdf_version = '1.5';
2696 $this->current_layer = $id;
2697 $this->_out('/OCZ-index /ZI' . $id . ' BDC');
2699 $this->pageoutput[$this->page] = [];
2702 function EndLayer()
2704 if ($this->current_layer > 0) {
2705 $this->_out('EMCZ-index');
2706 $this->current_layer = 0;
2710 function AddPageByArray($a)
2712 if (!is_array($a)) {
2713 $a = [];
2716 $orientation = (isset($a['orientation']) ? $a['orientation'] : '');
2717 $condition = (isset($a['condition']) ? $a['condition'] : (isset($a['type']) ? $a['type'] : ''));
2718 $resetpagenum = (isset($a['resetpagenum']) ? $a['resetpagenum'] : '');
2719 $pagenumstyle = (isset($a['pagenumstyle']) ? $a['pagenumstyle'] : '');
2720 $suppress = (isset($a['suppress']) ? $a['suppress'] : '');
2721 $mgl = (isset($a['mgl']) ? $a['mgl'] : (isset($a['margin-left']) ? $a['margin-left'] : ''));
2722 $mgr = (isset($a['mgr']) ? $a['mgr'] : (isset($a['margin-right']) ? $a['margin-right'] : ''));
2723 $mgt = (isset($a['mgt']) ? $a['mgt'] : (isset($a['margin-top']) ? $a['margin-top'] : ''));
2724 $mgb = (isset($a['mgb']) ? $a['mgb'] : (isset($a['margin-bottom']) ? $a['margin-bottom'] : ''));
2725 $mgh = (isset($a['mgh']) ? $a['mgh'] : (isset($a['margin-header']) ? $a['margin-header'] : ''));
2726 $mgf = (isset($a['mgf']) ? $a['mgf'] : (isset($a['margin-footer']) ? $a['margin-footer'] : ''));
2727 $ohname = (isset($a['ohname']) ? $a['ohname'] : (isset($a['odd-header-name']) ? $a['odd-header-name'] : ''));
2728 $ehname = (isset($a['ehname']) ? $a['ehname'] : (isset($a['even-header-name']) ? $a['even-header-name'] : ''));
2729 $ofname = (isset($a['ofname']) ? $a['ofname'] : (isset($a['odd-footer-name']) ? $a['odd-footer-name'] : ''));
2730 $efname = (isset($a['efname']) ? $a['efname'] : (isset($a['even-footer-name']) ? $a['even-footer-name'] : ''));
2731 $ohvalue = (isset($a['ohvalue']) ? $a['ohvalue'] : (isset($a['odd-header-value']) ? $a['odd-header-value'] : 0));
2732 $ehvalue = (isset($a['ehvalue']) ? $a['ehvalue'] : (isset($a['even-header-value']) ? $a['even-header-value'] : 0));
2733 $ofvalue = (isset($a['ofvalue']) ? $a['ofvalue'] : (isset($a['odd-footer-value']) ? $a['odd-footer-value'] : 0));
2734 $efvalue = (isset($a['efvalue']) ? $a['efvalue'] : (isset($a['even-footer-value']) ? $a['even-footer-value'] : 0));
2735 $pagesel = (isset($a['pagesel']) ? $a['pagesel'] : (isset($a['pageselector']) ? $a['pageselector'] : ''));
2736 $newformat = (isset($a['newformat']) ? $a['newformat'] : (isset($a['sheet-size']) ? $a['sheet-size'] : ''));
2738 $this->AddPage($orientation, $condition, $resetpagenum, $pagenumstyle, $suppress, $mgl, $mgr, $mgt, $mgb, $mgh, $mgf, $ohname, $ehname, $ofname, $efname, $ohvalue, $ehvalue, $ofvalue, $efvalue, $pagesel, $newformat);
2741 // mPDF 6 pagebreaktype
2742 function _preForcedPagebreak($pagebreaktype)
2744 if ($pagebreaktype == 'cloneall') {
2745 // Close any open block tags
2746 $arr = [];
2747 $ai = 0;
2748 for ($b = $this->blklvl; $b > 0; $b--) {
2749 $this->tag->CloseTag($this->blk[$b]['tag'], $arr, $ai);
2751 if ($this->blklvl == 0 && !empty($this->textbuffer)) { // Output previously buffered content
2752 $this->printbuffer($this->textbuffer, 1);
2753 $this->textbuffer = [];
2755 } elseif ($pagebreaktype == 'clonebycss') {
2756 // Close open block tags whilst box-decoration-break==clone
2757 $arr = [];
2758 $ai = 0;
2759 for ($b = $this->blklvl; $b > 0; $b--) {
2760 if (isset($this->blk[$b]['box_decoration_break']) && $this->blk[$b]['box_decoration_break'] == 'clone') {
2761 $this->tag->CloseTag($this->blk[$b]['tag'], $arr, $ai);
2762 } else {
2763 if ($b == $this->blklvl && !empty($this->textbuffer)) { // Output previously buffered content
2764 $this->printbuffer($this->textbuffer, 1);
2765 $this->textbuffer = [];
2767 break;
2770 } elseif (!empty($this->textbuffer)) { // Output previously buffered content
2771 $this->printbuffer($this->textbuffer, 1);
2772 $this->textbuffer = [];
2776 // mPDF 6 pagebreaktype
2777 function _postForcedPagebreak($pagebreaktype, $startpage, $save_blk, $save_blklvl)
2779 if ($pagebreaktype == 'cloneall') {
2780 $this->blk = [];
2781 $this->blk[0] = $save_blk[0];
2782 // Re-open block tags
2783 $this->blklvl = 0;
2784 $arr = [];
2785 $i = 0;
2786 for ($b = 1; $b <= $save_blklvl; $b++) {
2787 $this->tag->OpenTag($save_blk[$b]['tag'], $save_blk[$b]['attr'], $arr, $i);
2789 } elseif ($pagebreaktype == 'clonebycss') {
2790 $this->blk = [];
2791 $this->blk[0] = $save_blk[0];
2792 // Don't re-open tags for lowest level elements - so need to do some adjustments
2793 for ($b = 1; $b <= $this->blklvl; $b++) {
2794 $this->blk[$b] = $save_blk[$b];
2795 $this->blk[$b]['startpage'] = 0;
2796 $this->blk[$b]['y0'] = $this->y; // ?? $this->tMargin
2797 if (($this->page - $startpage) % 2) {
2798 if (isset($this->blk[$b]['x0'])) {
2799 $this->blk[$b]['x0'] += $this->MarginCorrection;
2800 } else {
2801 $this->blk[$b]['x0'] = $this->MarginCorrection;
2804 // for Float DIV
2805 $this->blk[$b]['marginCorrected'][$this->page] = true;
2808 // Re-open block tags for any that have box_decoration_break==clone
2809 $arr = [];
2810 $i = 0;
2811 for ($b = $this->blklvl + 1; $b <= $save_blklvl; $b++) {
2812 if ($b < $this->blklvl) {
2813 $this->lastblocklevelchange = -1;
2815 $this->tag->OpenTag($save_blk[$b]['tag'], $save_blk[$b]['attr'], $arr, $i);
2817 if ($this->blk[$this->blklvl]['box_decoration_break'] != 'clone') {
2818 $this->lastblocklevelchange = -1;
2820 } else {
2821 $this->lastblocklevelchange = -1;
2825 function AddPage(
2826 $orientation = '',
2827 $condition = '',
2828 $resetpagenum = '',
2829 $pagenumstyle = '',
2830 $suppress = '',
2831 $mgl = '',
2832 $mgr = '',
2833 $mgt = '',
2834 $mgb = '',
2835 $mgh = '',
2836 $mgf = '',
2837 $ohname = '',
2838 $ehname = '',
2839 $ofname = '',
2840 $efname = '',
2841 $ohvalue = 0,
2842 $ehvalue = 0,
2843 $ofvalue = 0,
2844 $efvalue = 0,
2845 $pagesel = '',
2846 $newformat = ''
2848 /* -- CSS-FLOAT -- */
2849 // Float DIV
2850 // Cannot do with columns on, or if any change in page orientation/margins etc.
2851 // If next page already exists - i.e background /headers and footers already written
2852 if ($this->state > 0 && $this->page < count($this->pages)) {
2853 $bak_cml = $this->cMarginL;
2854 $bak_cmr = $this->cMarginR;
2855 $bak_dw = $this->divwidth;
2856 // Paint Div Border if necessary
2857 if ($this->blklvl > 0) {
2858 $save_tr = $this->table_rotate; // *TABLES*
2859 $this->table_rotate = 0; // *TABLES*
2860 if (isset($this->blk[$this->blklvl]['y0']) && $this->y == $this->blk[$this->blklvl]['y0']) {
2861 $this->blk[$this->blklvl]['startpage'] ++;
2863 if ((isset($this->blk[$this->blklvl]['y0']) && $this->y > $this->blk[$this->blklvl]['y0']) || $this->flowingBlockAttr['is_table']) {
2864 $toplvl = $this->blklvl;
2865 } else {
2866 $toplvl = $this->blklvl - 1;
2868 $sy = $this->y;
2869 for ($bl = 1; $bl <= $toplvl; $bl++) {
2870 $this->PaintDivBB('pagebottom', 0, $bl);
2872 $this->y = $sy;
2873 $this->table_rotate = $save_tr; // *TABLES*
2875 $s = $this->PrintPageBackgrounds();
2877 // Writes after the marker so not overwritten later by page background etc.
2878 $this->pages[$this->page] = preg_replace(
2879 '/(___BACKGROUND___PATTERNS' . $this->uniqstr . ')/',
2880 '\\1' . "\n" . $s . "\n",
2881 $this->pages[$this->page]
2884 $this->pageBackgrounds = [];
2885 $family = $this->FontFamily;
2886 $style = $this->FontStyle;
2887 $size = $this->FontSizePt;
2888 $lw = $this->LineWidth;
2889 $dc = $this->DrawColor;
2890 $fc = $this->FillColor;
2891 $tc = $this->TextColor;
2892 $cf = $this->ColorFlag;
2894 $this->printfloatbuffer();
2896 // Move to next page
2897 $this->page++;
2899 $this->ResetMargins();
2900 $this->SetAutoPageBreak($this->autoPageBreak, $this->bMargin);
2901 $this->x = $this->lMargin;
2902 $this->y = $this->tMargin;
2903 $this->FontFamily = '';
2904 $this->_out('2 J');
2905 $this->LineWidth = $lw;
2906 $this->_out(sprintf('%.3F w', $lw * Mpdf::SCALE));
2908 if ($family) {
2909 $this->SetFont($family, $style, $size, true, true);
2912 $this->DrawColor = $dc;
2914 if ($dc != $this->defDrawColor) {
2915 $this->_out($dc);
2918 $this->FillColor = $fc;
2920 if ($fc != $this->defFillColor) {
2921 $this->_out($fc);
2924 $this->TextColor = $tc;
2925 $this->ColorFlag = $cf;
2927 for ($bl = 1; $bl <= $this->blklvl; $bl++) {
2928 $this->blk[$bl]['y0'] = $this->y;
2929 // Don't correct more than once for background DIV containing a Float
2930 if (!isset($this->blk[$bl]['marginCorrected'][$this->page])) {
2931 if (isset($this->blk[$bl]['x0'])) {
2932 $this->blk[$bl]['x0'] += $this->MarginCorrection;
2933 } else {
2934 $this->blk[$bl]['x0'] = $this->MarginCorrection;
2937 $this->blk[$bl]['marginCorrected'][$this->page] = true;
2940 $this->cMarginL = $bak_cml;
2941 $this->cMarginR = $bak_cmr;
2942 $this->divwidth = $bak_dw;
2944 return '';
2946 /* -- END CSS-FLOAT -- */
2948 // Start a new page
2949 if ($this->state == 0) {
2950 $this->Open();
2953 $bak_cml = $this->cMarginL;
2954 $bak_cmr = $this->cMarginR;
2955 $bak_dw = $this->divwidth;
2957 $bak_lh = $this->lineheight;
2959 $orientation = substr(strtoupper($orientation), 0, 1);
2960 $condition = strtoupper($condition);
2963 if ($condition == 'E') { // only adds new page if needed to create an Even page
2964 if (!$this->mirrorMargins || ($this->page) % 2 == 0) {
2965 return false;
2967 } elseif ($condition == 'O') { // only adds new page if needed to create an Odd page
2968 if (!$this->mirrorMargins || ($this->page) % 2 == 1) {
2969 return false;
2971 } elseif ($condition == 'NEXT-EVEN') { // always adds at least one new page to create an Even page
2972 if (!$this->mirrorMargins) {
2973 $condition = '';
2974 } else {
2975 if ($pagesel) {
2976 $pbch = $pagesel;
2977 $pagesel = '';
2978 } // *CSS-PAGE*
2979 else {
2980 $pbch = false;
2981 } // *CSS-PAGE*
2982 $this->AddPage($this->CurOrientation, 'O');
2983 $this->extrapagebreak = true; // mPDF 6 pagebreaktype
2984 if ($pbch) {
2985 $pagesel = $pbch;
2986 } // *CSS-PAGE*
2987 $condition = '';
2989 } elseif ($condition == 'NEXT-ODD') { // always adds at least one new page to create an Odd page
2990 if (!$this->mirrorMargins) {
2991 $condition = '';
2992 } else {
2993 if ($pagesel) {
2994 $pbch = $pagesel;
2995 $pagesel = '';
2996 } // *CSS-PAGE*
2997 else {
2998 $pbch = false;
2999 } // *CSS-PAGE*
3000 $this->AddPage($this->CurOrientation, 'E');
3001 $this->extrapagebreak = true; // mPDF 6 pagebreaktype
3002 if ($pbch) {
3003 $pagesel = $pbch;
3004 } // *CSS-PAGE*
3005 $condition = '';
3009 if ($resetpagenum || $pagenumstyle || $suppress) {
3010 $this->PageNumSubstitutions[] = ['from' => ($this->page + 1), 'reset' => $resetpagenum, 'type' => $pagenumstyle, 'suppress' => $suppress];
3013 $save_tr = $this->table_rotate; // *TABLES*
3014 $this->table_rotate = 0; // *TABLES*
3015 $save_kwt = $this->kwt;
3016 $this->kwt = 0;
3017 $save_layer = $this->current_layer;
3018 $save_vis = $this->visibility;
3020 if ($this->visibility != 'visible') {
3021 $this->SetVisibility('visible');
3024 $this->EndLayer();
3026 // Paint Div Border if necessary
3027 // PAINTS BACKGROUND COLOUR OR BORDERS for DIV - DISABLED FOR COLUMNS (cf. AcceptPageBreak) AT PRESENT in ->PaintDivBB
3028 if (!$this->ColActive && $this->blklvl > 0) {
3029 if (isset($this->blk[$this->blklvl]['y0']) && $this->y == $this->blk[$this->blklvl]['y0'] && !$this->extrapagebreak) { // mPDF 6 pagebreaktype
3030 if (isset($this->blk[$this->blklvl]['startpage'])) {
3031 $this->blk[$this->blklvl]['startpage'] ++;
3032 } else {
3033 $this->blk[$this->blklvl]['startpage'] = 1;
3036 if ((isset($this->blk[$this->blklvl]['y0']) && $this->y > $this->blk[$this->blklvl]['y0']) || $this->flowingBlockAttr['is_table'] || $this->extrapagebreak) {
3037 $toplvl = $this->blklvl;
3038 } // mPDF 6 pagebreaktype
3039 else {
3040 $toplvl = $this->blklvl - 1;
3042 $sy = $this->y;
3043 for ($bl = 1; $bl <= $toplvl; $bl++) {
3044 if (isset($this->blk[$bl]['z-index']) && $this->blk[$bl]['z-index'] > 0) {
3045 $this->BeginLayer($this->blk[$bl]['z-index']);
3047 if (isset($this->blk[$bl]['visibility']) && $this->blk[$bl]['visibility'] && $this->blk[$bl]['visibility'] != 'visible') {
3048 $this->SetVisibility($this->blk[$bl]['visibility']);
3050 $this->PaintDivBB('pagebottom', 0, $bl);
3052 $this->y = $sy;
3053 // RESET block y0 and x0 - see below
3055 $this->extrapagebreak = false; // mPDF 6 pagebreaktype
3057 if ($this->visibility != 'visible') {
3058 $this->SetVisibility('visible');
3061 $this->EndLayer();
3063 // BODY Backgrounds
3064 if ($this->page > 0) {
3065 $s = '';
3066 $s .= $this->PrintBodyBackgrounds();
3068 $s .= $this->PrintPageBackgrounds();
3069 $this->pages[$this->page] = preg_replace('/(___BACKGROUND___PATTERNS' . $this->uniqstr . ')/', "\n" . $s . "\n" . '\\1', $this->pages[$this->page]);
3070 $this->pageBackgrounds = [];
3073 $save_kt = $this->keep_block_together;
3074 $this->keep_block_together = 0;
3076 $save_cols = false;
3078 /* -- COLUMNS -- */
3079 if ($this->ColActive) {
3080 $save_cols = true;
3081 $save_nbcol = $this->NbCol; // other values of gap and vAlign will not change by setting Columns off
3082 $this->SetColumns(0);
3084 /* -- END COLUMNS -- */
3086 $family = $this->FontFamily;
3087 $style = $this->FontStyle;
3088 $size = $this->FontSizePt;
3089 $this->ColumnAdjust = true; // enables column height adjustment for the page
3090 $lw = $this->LineWidth;
3091 $dc = $this->DrawColor;
3092 $fc = $this->FillColor;
3093 $tc = $this->TextColor;
3094 $cf = $this->ColorFlag;
3095 if ($this->page > 0) {
3096 // Page footer
3097 $this->InFooter = true;
3099 $this->Reset();
3100 $this->pageoutput[$this->page] = [];
3102 $this->Footer();
3103 // Close page
3104 $this->_endpage();
3107 // Start new page
3108 $this->_beginpage($orientation, $mgl, $mgr, $mgt, $mgb, $mgh, $mgf, $ohname, $ehname, $ofname, $efname, $ohvalue, $ehvalue, $ofvalue, $efvalue, $pagesel, $newformat);
3110 if ($this->docTemplate) {
3111 $pagecount = $this->SetSourceFile($this->docTemplate);
3112 if (($this->page - $this->docTemplateStart) > $pagecount) {
3113 if ($this->docTemplateContinue) {
3114 $tplIdx = $this->ImportPage($pagecount);
3115 $this->UseTemplate($tplIdx);
3117 } else {
3118 $tplIdx = $this->ImportPage(($this->page - $this->docTemplateStart));
3119 $this->UseTemplate($tplIdx);
3123 if ($this->pageTemplate) {
3124 $this->UseTemplate($this->pageTemplate);
3127 // Tiling Patterns
3128 $this->_out('___PAGE___START' . $this->uniqstr);
3129 $this->_out('___BACKGROUND___PATTERNS' . $this->uniqstr);
3130 $this->_out('___HEADER___MARKER' . $this->uniqstr);
3131 $this->pageBackgrounds = [];
3133 // Set line cap style to square
3134 $this->SetLineCap(2);
3135 // Set line width
3136 $this->LineWidth = $lw;
3137 $this->_out(sprintf('%.3F w', $lw * Mpdf::SCALE));
3138 // Set font
3139 if ($family) {
3140 $this->SetFont($family, $style, $size, true, true); // forces write
3143 // Set colors
3144 $this->DrawColor = $dc;
3145 if ($dc != $this->defDrawColor) {
3146 $this->_out($dc);
3148 $this->FillColor = $fc;
3149 if ($fc != $this->defFillColor) {
3150 $this->_out($fc);
3152 $this->TextColor = $tc;
3153 $this->ColorFlag = $cf;
3155 // Page header
3156 $this->Header();
3158 // Restore line width
3159 if ($this->LineWidth != $lw) {
3160 $this->LineWidth = $lw;
3161 $this->_out(sprintf('%.3F w', $lw * Mpdf::SCALE));
3163 // Restore font
3164 if ($family) {
3165 $this->SetFont($family, $style, $size, true, true); // forces write
3168 // Restore colors
3169 if ($this->DrawColor != $dc) {
3170 $this->DrawColor = $dc;
3171 $this->_out($dc);
3173 if ($this->FillColor != $fc) {
3174 $this->FillColor = $fc;
3175 $this->_out($fc);
3177 $this->TextColor = $tc;
3178 $this->ColorFlag = $cf;
3179 $this->InFooter = false;
3181 if ($save_layer > 0) {
3182 $this->BeginLayer($save_layer);
3185 if ($save_vis != 'visible') {
3186 $this->SetVisibility($save_vis);
3189 /* -- COLUMNS -- */
3190 if ($save_cols) {
3191 // Restore columns
3192 $this->SetColumns($save_nbcol, $this->colvAlign, $this->ColGap);
3194 if ($this->ColActive) {
3195 $this->SetCol(0);
3197 /* -- END COLUMNS -- */
3200 // RESET BLOCK BORDER TOP
3201 if (!$this->ColActive) {
3202 for ($bl = 1; $bl <= $this->blklvl; $bl++) {
3203 $this->blk[$bl]['y0'] = $this->y;
3204 if (isset($this->blk[$bl]['x0'])) {
3205 $this->blk[$bl]['x0'] += $this->MarginCorrection;
3206 } else {
3207 $this->blk[$bl]['x0'] = $this->MarginCorrection;
3209 // Added mPDF 3.0 Float DIV
3210 $this->blk[$bl]['marginCorrected'][$this->page] = true;
3215 $this->table_rotate = $save_tr; // *TABLES*
3216 $this->kwt = $save_kwt;
3218 $this->keep_block_together = $save_kt;
3220 $this->cMarginL = $bak_cml;
3221 $this->cMarginR = $bak_cmr;
3222 $this->divwidth = $bak_dw;
3224 $this->lineheight = $bak_lh;
3228 * Get current page number
3230 * @return int
3232 function PageNo()
3234 return $this->page;
3237 function AddSpotColorsFromFile($file)
3239 $colors = @file($file);
3240 if (!$colors) {
3241 throw new \Mpdf\MpdfException("Cannot load spot colors file - " . $file);
3243 foreach ($colors as $sc) {
3244 list($name, $c, $m, $y, $k) = preg_split("/\t/", $sc);
3245 $c = intval($c);
3246 $m = intval($m);
3247 $y = intval($y);
3248 $k = intval($k);
3249 $this->AddSpotColor($name, $c, $m, $y, $k);
3253 function AddSpotColor($name, $c, $m, $y, $k)
3255 $name = strtoupper(trim($name));
3256 if (!isset($this->spotColors[$name])) {
3257 $i = count($this->spotColors) + 1;
3258 $this->spotColors[$name] = ['i' => $i, 'c' => $c, 'm' => $m, 'y' => $y, 'k' => $k];
3259 $this->spotColorIDs[$i] = $name;
3263 function SetColor($col, $type = '')
3265 $out = '';
3266 if (!$col) {
3267 return '';
3268 } // mPDF 6
3269 if ($col{0} == 3 || $col{0} == 5) { // RGB / RGBa
3270 $out = sprintf('%.3F %.3F %.3F rg', ord($col{1}) / 255, ord($col{2}) / 255, ord($col{3}) / 255);
3271 } elseif ($col{0} == 1) { // GRAYSCALE
3272 $out = sprintf('%.3F g', ord($col{1}) / 255);
3273 } elseif ($col{0} == 2) { // SPOT COLOR
3274 $out = sprintf('/CS%d cs %.3F scn', ord($col{1}), ord($col{2}) / 100);
3275 } elseif ($col{0} == 4 || $col{0} == 6) { // CMYK / CMYKa
3276 $out = sprintf('%.3F %.3F %.3F %.3F k', ord($col{1}) / 100, ord($col{2}) / 100, ord($col{3}) / 100, ord($col{4}) / 100);
3278 if ($type == 'Draw') {
3279 $out = strtoupper($out);
3280 } // e.g. rg => RG
3281 elseif ($type == 'CodeOnly') {
3282 $out = preg_replace('/\s(rg|g|k)/', '', $out);
3284 return $out;
3287 function SetDColor($col, $return = false)
3289 $out = $this->SetColor($col, 'Draw');
3290 if ($return) {
3291 return $out;
3293 if ($out == '') {
3294 return '';
3296 $this->DrawColor = $out;
3297 if ($this->page > 0 && ((isset($this->pageoutput[$this->page]['DrawColor']) && $this->pageoutput[$this->page]['DrawColor'] != $this->DrawColor) || !isset($this->pageoutput[$this->page]['DrawColor']))) {
3298 $this->_out($this->DrawColor);
3300 $this->pageoutput[$this->page]['DrawColor'] = $this->DrawColor;
3303 function SetFColor($col, $return = false)
3305 $out = $this->SetColor($col, 'Fill');
3306 if ($return) {
3307 return $out;
3309 if ($out == '') {
3310 return '';
3312 $this->FillColor = $out;
3313 $this->ColorFlag = ($out != $this->TextColor);
3314 if ($this->page > 0 && ((isset($this->pageoutput[$this->page]['FillColor']) && $this->pageoutput[$this->page]['FillColor'] != $this->FillColor) || !isset($this->pageoutput[$this->page]['FillColor']))) {
3315 $this->_out($this->FillColor);
3317 $this->pageoutput[$this->page]['FillColor'] = $this->FillColor;
3320 function SetTColor($col, $return = false)
3322 $out = $this->SetColor($col, 'Text');
3323 if ($return) {
3324 return $out;
3326 if ($out == '') {
3327 return '';
3329 $this->TextColor = $out;
3330 $this->ColorFlag = ($this->FillColor != $out);
3333 function SetDrawColor($r, $g = -1, $b = -1, $col4 = -1, $return = false)
3335 // Set color for all stroking operations
3336 $col = [];
3337 if (($r == 0 and $g == 0 and $b == 0 && $col4 == -1) or $g == -1) {
3338 $col = $this->colorConverter->convert($r, $this->PDFAXwarnings);
3339 } elseif ($col4 == -1) {
3340 $col = $this->colorConverter->convert('rgb(' . $r . ',' . $g . ',' . $b . ')', $this->PDFAXwarnings);
3341 } else {
3342 $col = $this->colorConverter->convert('cmyk(' . $r . ',' . $g . ',' . $b . ',' . $col4 . ')', $this->PDFAXwarnings);
3344 $out = $this->SetDColor($col, $return);
3345 return $out;
3348 function SetFillColor($r, $g = -1, $b = -1, $col4 = -1, $return = false)
3350 // Set color for all filling operations
3351 $col = [];
3352 if (($r == 0 and $g == 0 and $b == 0 && $col4 == -1) or $g == -1) {
3353 $col = $this->colorConverter->convert($r, $this->PDFAXwarnings);
3354 } elseif ($col4 == -1) {
3355 $col = $this->colorConverter->convert('rgb(' . $r . ',' . $g . ',' . $b . ')', $this->PDFAXwarnings);
3356 } else {
3357 $col = $this->colorConverter->convert('cmyk(' . $r . ',' . $g . ',' . $b . ',' . $col4 . ')', $this->PDFAXwarnings);
3359 $out = $this->SetFColor($col, $return);
3360 return $out;
3363 function SetTextColor($r, $g = -1, $b = -1, $col4 = -1, $return = false)
3365 // Set color for text
3366 $col = [];
3367 if (($r == 0 and $g == 0 and $b == 0 && $col4 == -1) or $g == -1) {
3368 $col = $this->colorConverter->convert($r, $this->PDFAXwarnings);
3369 } elseif ($col4 == -1) {
3370 $col = $this->colorConverter->convert('rgb(' . $r . ',' . $g . ',' . $b . ')', $this->PDFAXwarnings);
3371 } else {
3372 $col = $this->colorConverter->convert('cmyk(' . $r . ',' . $g . ',' . $b . ',' . $col4 . ')', $this->PDFAXwarnings);
3374 $out = $this->SetTColor($col, $return);
3375 return $out;
3378 function _getCharWidth(&$cw, $u, $isdef = true)
3380 $w = 0;
3382 if ($u == 0) {
3383 $w = false;
3384 } elseif (isset($cw[$u * 2 + 1])) {
3385 $w = (ord($cw[$u * 2]) << 8) + ord($cw[$u * 2 + 1]);
3388 if ($w == 65535) {
3389 return 0;
3390 } elseif ($w) {
3391 return $w;
3392 } elseif ($isdef) {
3393 return false;
3394 } else {
3395 return 0;
3399 function _charDefined(&$cw, $u)
3401 $w = 0;
3402 if ($u == 0) {
3403 return false;
3405 if (isset($cw[$u * 2 + 1])) {
3406 $w = (ord($cw[$u * 2]) << 8) + ord($cw[$u * 2 + 1]);
3408 if ($w) {
3409 return true;
3410 } else {
3411 return false;
3415 function GetCharWidthCore($c)
3417 // Get width of a single character in the current Core font
3418 $c = (string) $c;
3419 $w = 0;
3420 // Soft Hyphens chr(173)
3421 if ($c == chr(173) && $this->FontFamily != 'csymbol' && $this->FontFamily != 'czapfdingbats') {
3422 return 0;
3423 } elseif (($this->textvar & TextVars::FC_SMALLCAPS) && isset($this->upperCase[ord($c)])) { // mPDF 5.7.1
3424 $charw = $this->CurrentFont['cw'][chr($this->upperCase[ord($c)])];
3425 if ($charw !== false) {
3426 $charw = $charw * $this->smCapsScale * $this->smCapsStretch / 100;
3427 $w+=$charw;
3429 } elseif (isset($this->CurrentFont['cw'][$c])) {
3430 $w += $this->CurrentFont['cw'][$c];
3431 } elseif (isset($this->CurrentFont['cw'][ord($c)])) {
3432 $w += $this->CurrentFont['cw'][ord($c)];
3434 $w *= ($this->FontSize / 1000);
3435 if ($this->minwSpacing || $this->fixedlSpacing) {
3436 if ($c == ' ') {
3437 $nb_spaces = 1;
3438 } else {
3439 $nb_spaces = 0;
3441 $w += $this->fixedlSpacing + ($nb_spaces * $this->minwSpacing);
3443 return ($w);
3446 function GetCharWidthNonCore($c, $addSubset = true)
3448 // Get width of a single character in the current Non-Core font
3449 $c = (string) $c;
3450 $w = 0;
3451 $unicode = $this->UTF8StringToArray($c, $addSubset);
3452 $char = $unicode[0];
3453 /* -- CJK-FONTS -- */
3454 if ($this->CurrentFont['type'] == 'Type0') { // CJK Adobe fonts
3455 if ($char == 173) {
3456 return 0;
3457 } // Soft Hyphens
3458 elseif (isset($this->CurrentFont['cw'][$char])) {
3459 $w+=$this->CurrentFont['cw'][$char];
3460 } elseif (isset($this->CurrentFont['MissingWidth'])) {
3461 $w += $this->CurrentFont['MissingWidth'];
3462 } else {
3463 $w += 500;
3465 } else {
3466 /* -- END CJK-FONTS -- */
3467 if ($char == 173) {
3468 return 0;
3469 } // Soft Hyphens
3470 elseif (($this->textvar & TextVars::FC_SMALLCAPS) && isset($this->upperCase[$char])) { // mPDF 5.7.1
3471 $charw = $this->_getCharWidth($this->CurrentFont['cw'], $this->upperCase[$char]);
3472 if ($charw !== false) {
3473 $charw = $charw * $this->smCapsScale * $this->smCapsStretch / 100;
3474 $w+=$charw;
3475 } elseif (isset($this->CurrentFont['desc']['MissingWidth'])) {
3476 $w += $this->CurrentFont['desc']['MissingWidth'];
3477 } elseif (isset($this->CurrentFont['MissingWidth'])) {
3478 $w += $this->CurrentFont['MissingWidth'];
3479 } else {
3480 $w += 500;
3482 } else {
3483 $charw = $this->_getCharWidth($this->CurrentFont['cw'], $char);
3484 if ($charw !== false) {
3485 $w+=$charw;
3486 } elseif (isset($this->CurrentFont['desc']['MissingWidth'])) {
3487 $w += $this->CurrentFont['desc']['MissingWidth'];
3488 } elseif (isset($this->CurrentFont['MissingWidth'])) {
3489 $w += $this->CurrentFont['MissingWidth'];
3490 } else {
3491 $w += 500;
3494 } // *CJK-FONTS*
3495 $w *= ($this->FontSize / 1000);
3496 if ($this->minwSpacing || $this->fixedlSpacing) {
3497 if ($c == ' ') {
3498 $nb_spaces = 1;
3499 } else {
3500 $nb_spaces = 0;
3502 $w += $this->fixedlSpacing + ($nb_spaces * $this->minwSpacing);
3504 return ($w);
3507 function GetCharWidth($c, $addSubset = true)
3509 if (!$this->usingCoreFont) {
3510 return $this->GetCharWidthNonCore($c, $addSubset);
3511 } else {
3512 return $this->GetCharWidthCore($c);
3516 function GetStringWidth($s, $addSubset = true, $OTLdata = false, $textvar = 0, $includeKashida = false)
3518 // mPDF 5.7.1
3519 // Get width of a string in the current font
3520 $s = (string) $s;
3521 $cw = &$this->CurrentFont['cw'];
3522 $w = 0;
3523 $kerning = 0;
3524 $lastchar = 0;
3525 $nb_carac = 0;
3526 $nb_spaces = 0;
3527 $kashida = 0;
3528 // mPDF ITERATION
3529 if ($this->iterationCounter) {
3530 $s = preg_replace('/{iteration ([a-zA-Z0-9_]+)}/', '\\1', $s);
3532 if (!$this->usingCoreFont) {
3533 $discards = substr_count($s, "\xc2\xad"); // mPDF 6 soft hyphens [U+00AD]
3534 $unicode = $this->UTF8StringToArray($s, $addSubset);
3535 if ($this->minwSpacing || $this->fixedlSpacing) {
3536 $nb_spaces = mb_substr_count($s, ' ', $this->mb_enc);
3537 $nb_carac = count($unicode) - $discards; // mPDF 6
3538 // mPDF 5.7.1
3539 // Use GPOS OTL
3540 if (isset($this->CurrentFont['useOTL']) && $this->CurrentFont['useOTL']) {
3541 if (isset($OTLdata['group']) && $OTLdata['group']) {
3542 $nb_carac -= substr_count($OTLdata['group'], 'M');
3546 /* -- CJK-FONTS -- */
3547 if ($this->CurrentFont['type'] == 'Type0') { // CJK Adobe fonts
3548 foreach ($unicode as $char) {
3549 if ($char == 0x00AD) {
3550 continue;
3551 } // mPDF 6 soft hyphens [U+00AD]
3552 if (isset($cw[$char])) {
3553 $w+=$cw[$char];
3554 } elseif (isset($this->CurrentFont['MissingWidth'])) {
3555 $w += $this->CurrentFont['MissingWidth'];
3556 } else {
3557 $w += 500;
3560 } else {
3561 /* -- END CJK-FONTS -- */
3562 foreach ($unicode as $i => $char) {
3563 if ($char == 0x00AD) {
3564 continue;
3565 } // mPDF 6 soft hyphens [U+00AD]
3566 if (($textvar & TextVars::FC_SMALLCAPS) && isset($this->upperCase[$char])) {
3567 $charw = $this->_getCharWidth($cw, $this->upperCase[$char]);
3568 if ($charw !== false) {
3569 $charw = $charw * $this->smCapsScale * $this->smCapsStretch / 100;
3570 $w+=$charw;
3571 } elseif (isset($this->CurrentFont['desc']['MissingWidth'])) {
3572 $w += $this->CurrentFont['desc']['MissingWidth'];
3573 } elseif (isset($this->CurrentFont['MissingWidth'])) {
3574 $w += $this->CurrentFont['MissingWidth'];
3575 } else {
3576 $w += 500;
3578 } else {
3579 $charw = $this->_getCharWidth($cw, $char);
3580 if ($charw !== false) {
3581 $w+=$charw;
3582 } elseif (isset($this->CurrentFont['desc']['MissingWidth'])) {
3583 $w += $this->CurrentFont['desc']['MissingWidth'];
3584 } elseif (isset($this->CurrentFont['MissingWidth'])) {
3585 $w += $this->CurrentFont['MissingWidth'];
3586 } else {
3587 $w += 500;
3589 // mPDF 5.7.1
3590 // Use GPOS OTL
3591 // ...GetStringWidth...
3592 if (isset($this->CurrentFont['useOTL']) && ($this->CurrentFont['useOTL'] & 0xFF) && !empty($OTLdata)) {
3593 if (isset($OTLdata['GPOSinfo'][$i]['wDir']) && $OTLdata['GPOSinfo'][$i]['wDir'] == 'RTL') {
3594 if (isset($OTLdata['GPOSinfo'][$i]['XAdvanceR']) && $OTLdata['GPOSinfo'][$i]['XAdvanceR']) {
3595 $w += $OTLdata['GPOSinfo'][$i]['XAdvanceR'] * 1000 / $this->CurrentFont['unitsPerEm'];
3597 } else {
3598 if (isset($OTLdata['GPOSinfo'][$i]['XAdvanceL']) && $OTLdata['GPOSinfo'][$i]['XAdvanceL']) {
3599 $w += $OTLdata['GPOSinfo'][$i]['XAdvanceL'] * 1000 / $this->CurrentFont['unitsPerEm'];
3602 // Kashida from GPOS
3603 // Kashida is set as an absolute length value (already set as a proportion based on useKashida %)
3604 if ($includeKashida && isset($OTLdata['GPOSinfo'][$i]['kashida_space']) && $OTLdata['GPOSinfo'][$i]['kashida_space']) {
3605 $kashida += $OTLdata['GPOSinfo'][$i]['kashida_space'];
3608 if (($textvar & TextVars::FC_KERNING) && $lastchar) {
3609 if (isset($this->CurrentFont['kerninfo'][$lastchar][$char])) {
3610 $kerning += $this->CurrentFont['kerninfo'][$lastchar][$char];
3613 $lastchar = $char;
3616 } // *CJK-FONTS*
3617 } else {
3618 if ($this->FontFamily != 'csymbol' && $this->FontFamily != 'czapfdingbats') {
3619 $s = str_replace(chr(173), '', $s);
3621 $nb_carac = $l = strlen($s);
3622 if ($this->minwSpacing || $this->fixedlSpacing) {
3623 $nb_spaces = substr_count($s, ' ');
3625 for ($i = 0; $i < $l; $i++) {
3626 if (($textvar & TextVars::FC_SMALLCAPS) && isset($this->upperCase[ord($s[$i])])) { // mPDF 5.7.1
3627 $charw = $cw[chr($this->upperCase[ord($s[$i])])];
3628 if ($charw !== false) {
3629 $charw = $charw * $this->smCapsScale * $this->smCapsStretch / 100;
3630 $w+=$charw;
3632 } elseif (isset($cw[$s[$i]])) {
3633 $w += $cw[$s[$i]];
3634 } elseif (isset($cw[ord($s[$i])])) {
3635 $w += $cw[ord($s[$i])];
3637 if (($textvar & TextVars::FC_KERNING) && $i > 0) { // mPDF 5.7.1
3638 if (isset($this->CurrentFont['kerninfo'][$s[($i - 1)]][$s[$i]])) {
3639 $kerning += $this->CurrentFont['kerninfo'][$s[($i - 1)]][$s[$i]];
3644 unset($cw);
3645 if ($textvar & TextVars::FC_KERNING) {
3646 $w += $kerning;
3647 } // mPDF 5.7.1
3648 $w *= ($this->FontSize / 1000);
3649 $w += (($nb_carac + $nb_spaces) * $this->fixedlSpacing) + ($nb_spaces * $this->minwSpacing);
3650 $w += $kashida / Mpdf::SCALE;
3652 return ($w);
3655 function SetLineWidth($width)
3657 // Set line width
3658 $this->LineWidth = $width;
3659 $lwout = (sprintf('%.3F w', $width * Mpdf::SCALE));
3660 if ($this->page > 0 && ((isset($this->pageoutput[$this->page]['LineWidth']) && $this->pageoutput[$this->page]['LineWidth'] != $lwout) || !isset($this->pageoutput[$this->page]['LineWidth']))) {
3661 $this->_out($lwout);
3663 $this->pageoutput[$this->page]['LineWidth'] = $lwout;
3666 function Line($x1, $y1, $x2, $y2)
3668 // Draw a line
3669 $this->_out(sprintf('%.3F %.3F m %.3F %.3F l S', $x1 * Mpdf::SCALE, ($this->h - $y1) * Mpdf::SCALE, $x2 * Mpdf::SCALE, ($this->h - $y2) * Mpdf::SCALE));
3672 function Arrow($x1, $y1, $x2, $y2, $headsize = 3, $fill = 'B', $angle = 25)
3674 // F == fill // S == stroke // B == stroke and fill
3675 // angle = splay of arrowhead - 1 - 89 degrees
3676 if ($fill == 'F') {
3677 $fill = 'f';
3678 } elseif ($fill == 'FD' or $fill == 'DF' or $fill == 'B') {
3679 $fill = 'B';
3680 } else {
3681 $fill = 'S';
3683 $a = atan2(($y2 - $y1), ($x2 - $x1));
3684 $b = $a + deg2rad($angle);
3685 $c = $a - deg2rad($angle);
3686 $x3 = $x2 - ($headsize * cos($b));
3687 $y3 = $this->h - ($y2 - ($headsize * sin($b)));
3688 $x4 = $x2 - ($headsize * cos($c));
3689 $y4 = $this->h - ($y2 - ($headsize * sin($c)));
3691 $x5 = $x3 - ($x3 - $x4) / 2; // mid point of base of arrowhead - to join arrow line to
3692 $y5 = $y3 - ($y3 - $y4) / 2;
3694 $s = '';
3695 $s.=sprintf('%.3F %.3F m %.3F %.3F l S', $x1 * Mpdf::SCALE, ($this->h - $y1) * Mpdf::SCALE, $x5 * Mpdf::SCALE, $y5 * Mpdf::SCALE);
3696 $this->_out($s);
3698 $s = '';
3699 $s.=sprintf('%.3F %.3F m %.3F %.3F l %.3F %.3F l %.3F %.3F l %.3F %.3F l ', $x5 * Mpdf::SCALE, $y5 * Mpdf::SCALE, $x3 * Mpdf::SCALE, $y3 * Mpdf::SCALE, $x2 * Mpdf::SCALE, ($this->h - $y2) * Mpdf::SCALE, $x4 * Mpdf::SCALE, $y4 * Mpdf::SCALE, $x5 * Mpdf::SCALE, $y5 * Mpdf::SCALE);
3700 $s.=$fill;
3701 $this->_out($s);
3704 function Rect($x, $y, $w, $h, $style = '')
3706 // Draw a rectangle
3707 if ($style == 'F') {
3708 $op = 'f';
3709 } elseif ($style == 'FD' or $style == 'DF') {
3710 $op = 'B';
3711 } else {
3712 $op = 'S';
3714 $this->_out(sprintf('%.3F %.3F %.3F %.3F re %s', $x * Mpdf::SCALE, ($this->h - $y) * Mpdf::SCALE, $w * Mpdf::SCALE, -$h * Mpdf::SCALE, $op));
3717 function AddFontDirectory($directory)
3719 $this->fontDir[] = $directory;
3720 $this->fontFileFinder->setDirectories($this->fontDir);
3723 function AddFont($family, $style = '')
3725 if (empty($family)) {
3726 return;
3729 $family = strtolower($family);
3730 $style = strtoupper($style);
3731 $style = str_replace('U', '', $style);
3733 if ($style == 'IB') {
3734 $style = 'BI';
3737 $fontkey = $family . $style;
3739 // check if the font has been already added
3740 if (isset($this->fonts[$fontkey])) {
3741 return;
3744 /* -- CJK-FONTS -- */
3745 if (in_array($family, $this->available_CJK_fonts)) {
3746 if (empty($this->Big5_widths)) {
3747 require __DIR__ . '/../data/CJKdata.php';
3749 $this->AddCJKFont($family); // don't need to add style
3750 return;
3752 /* -- END CJK-FONTS -- */
3754 if ($this->usingCoreFont) {
3755 throw new \Mpdf\MpdfException("mPDF Error - problem with Font management");
3758 $stylekey = $style;
3759 if (!$style) {
3760 $stylekey = 'R';
3763 if (!isset($this->fontdata[$family][$stylekey]) || !$this->fontdata[$family][$stylekey]) {
3764 throw new \Mpdf\MpdfException(sprintf('Font "%s%s%s" is not supported', $family, $style ? ' - ' : '', $style));
3767 $name = '';
3768 $cw = '';
3769 $glyphIDtoUni = '';
3770 $originalsize = 0;
3771 $sip = false;
3772 $smp = false;
3773 $useOTL = 0; // mPDF 5.7.1
3774 $fontmetrics = ''; // mPDF 6
3775 $haskerninfo = false;
3776 $haskernGPOS = false;
3777 $hassmallcapsGSUB = false;
3778 $BMPselected = false;
3779 $GSUBScriptLang = [];
3780 $GSUBFeatures = [];
3781 $GSUBLookups = [];
3782 $GPOSScriptLang = [];
3783 $GPOSFeatures = [];
3784 $GPOSLookups = [];
3786 if ($this->fontCache->has($fontkey . '.mtx.php')) {
3787 require $this->fontCache->tempFilename($fontkey . '.mtx.php');
3790 $ttffile = $this->fontFileFinder->findFontFile($this->fontdata[$family][$stylekey]);
3791 $ttfstat = stat($ttffile);
3793 if (isset($this->fontdata[$family]['TTCfontID'][$stylekey])) {
3794 $TTCfontID = $this->fontdata[$family]['TTCfontID'][$stylekey];
3795 } else {
3796 $TTCfontID = 0;
3799 $fontUseOTL = isset($this->fontdata[$family]['useOTL']) ? $this->fontdata[$family]['useOTL'] : false;
3801 $BMPonly = false;
3802 if (in_array($family, $this->BMPonly)) {
3803 $BMPonly = true;
3806 $regenerate = false;
3807 if ($BMPonly && !$BMPselected) {
3808 $regenerate = true;
3809 } elseif (!$BMPonly && $BMPselected) {
3810 $regenerate = true;
3813 // mPDF 5.7.1
3814 if ($fontUseOTL && $useOTL != $fontUseOTL) {
3815 $regenerate = true;
3816 $useOTL = $fontUseOTL;
3817 } elseif (!$fontUseOTL && $useOTL) {
3818 $regenerate = true;
3819 $useOTL = 0;
3822 if ($this->fontDescriptor != $fontmetrics) {
3823 $regenerate = true;
3824 } // mPDF 6
3826 if (empty($name) || $originalsize != $ttfstat['size'] || $regenerate) {
3827 $generator = new MetricsGenerator($this->fontCache, $this->fontDescriptor);
3829 $generator->generateMetrics(
3830 $ttffile,
3831 $ttfstat,
3832 $fontkey,
3833 $TTCfontID,
3834 $this->debugfonts,
3835 $BMPonly,
3836 $useOTL,
3837 $fontUseOTL
3840 require $this->fontCache->tempFilename($fontkey . '.mtx.php');
3841 $cw = $this->fontCache->load($fontkey . '.cw.dat');
3842 $glyphIDtoUni = $this->fontCache->load($fontkey . '.gid.dat');
3843 } else {
3844 if ($this->fontCache->has($fontkey . '.cw.dat')) {
3845 $cw = $this->fontCache->load($fontkey . '.cw.dat');
3848 if ($this->fontCache->has($fontkey . '.gid.dat')) {
3849 $glyphIDtoUni = $this->fontCache->load($fontkey . '.gid.dat');
3853 if (isset($this->fontdata[$family]['sip-ext']) && $this->fontdata[$family]['sip-ext']) {
3854 $sipext = $this->fontdata[$family]['sip-ext'];
3855 } else {
3856 $sipext = '';
3859 // Override with values from config_font.php
3860 if (isset($this->fontdata[$family]['Ascent']) && $this->fontdata[$family]['Ascent']) {
3861 $desc['Ascent'] = $this->fontdata[$family]['Ascent'];
3863 if (isset($this->fontdata[$family]['Descent']) && $this->fontdata[$family]['Descent']) {
3864 $desc['Descent'] = $this->fontdata[$family]['Descent'];
3866 if (isset($this->fontdata[$family]['Leading']) && $this->fontdata[$family]['Leading']) {
3867 $desc['Leading'] = $this->fontdata[$family]['Leading'];
3870 $i = count($this->fonts) + $this->extraFontSubsets + 1;
3871 if ($sip || $smp) {
3872 $this->fonts[$fontkey] = [
3873 'i' => $i,
3874 'type' => $type,
3875 'name' => $name,
3876 'desc' => $desc,
3877 'panose' => $panose,
3878 'unitsPerEm' => $unitsPerEm,
3879 'up' => $up,
3880 'ut' => $ut,
3881 'strs' => $strs,
3882 'strp' => $strp,
3883 'cw' => $cw,
3884 'ttffile' => $ttffile,
3885 'fontkey' => $fontkey,
3886 'subsets' => [0 => range(0, 127)],
3887 'subsetfontids' => [$i],
3888 'used' => false,
3889 'sip' => $sip,
3890 'sipext' => $sipext,
3891 'smp' => $smp,
3892 'TTCfontID' => $TTCfontID,
3893 'useOTL' => $fontUseOTL,
3894 'useKashida' => (isset($this->fontdata[$family]['useKashida']) ? $this->fontdata[$family]['useKashida'] : false),
3895 'GSUBScriptLang' => $GSUBScriptLang,
3896 'GSUBFeatures' => $GSUBFeatures,
3897 'GSUBLookups' => $GSUBLookups,
3898 'GPOSScriptLang' => $GPOSScriptLang,
3899 'GPOSFeatures' => $GPOSFeatures,
3900 'GPOSLookups' => $GPOSLookups,
3901 'rtlPUAstr' => $rtlPUAstr,
3902 'glyphIDtoUni' => $glyphIDtoUni,
3903 'haskerninfo' => $haskerninfo,
3904 'haskernGPOS' => $haskernGPOS,
3905 'hassmallcapsGSUB' => $hassmallcapsGSUB]; // mPDF 5.7.1 // mPDF 6
3906 } else {
3907 $ss = [];
3908 for ($s = 32; $s < 128; $s++) {
3909 $ss[$s] = $s;
3911 $this->fonts[$fontkey] = [
3912 'i' => $i,
3913 'type' => $type,
3914 'name' => $name,
3915 'desc' => $desc,
3916 'panose' => $panose,
3917 'unitsPerEm' => $unitsPerEm,
3918 'up' => $up,
3919 'ut' => $ut,
3920 'strs' => $strs,
3921 'strp' => $strp,
3922 'cw' => $cw,
3923 'ttffile' => $ttffile,
3924 'fontkey' => $fontkey,
3925 'subset' => $ss,
3926 'used' => false,
3927 'sip' => $sip,
3928 'sipext' => $sipext,
3929 'smp' => $smp,
3930 'TTCfontID' => $TTCfontID,
3931 'useOTL' => $fontUseOTL,
3932 'useKashida' => (isset($this->fontdata[$family]['useKashida']) ? $this->fontdata[$family]['useKashida'] : false),
3933 'GSUBScriptLang' => $GSUBScriptLang,
3934 'GSUBFeatures' => $GSUBFeatures,
3935 'GSUBLookups' => $GSUBLookups,
3936 'GPOSScriptLang' => $GPOSScriptLang,
3937 'GPOSFeatures' => $GPOSFeatures,
3938 'GPOSLookups' => $GPOSLookups,
3939 'rtlPUAstr' => $rtlPUAstr,
3940 'glyphIDtoUni' => $glyphIDtoUni,
3941 'haskerninfo' => $haskerninfo,
3942 'haskernGPOS' => $haskernGPOS,
3943 'hassmallcapsGSUB' => $hassmallcapsGSUB
3947 if ($haskerninfo) {
3948 $this->fonts[$fontkey]['kerninfo'] = $kerninfo;
3951 $this->FontFiles[$fontkey] = [
3952 'length1' => $originalsize,
3953 'type' => 'TTF',
3954 'ttffile' => $ttffile,
3955 'sip' => $sip,
3956 'smp' => $smp
3959 unset($cw);
3962 function SetFont($family, $style = '', $size = 0, $write = true, $forcewrite = false)
3964 $family = strtolower($family);
3966 if (!$this->onlyCoreFonts) {
3967 if ($family == 'sans' || $family == 'sans-serif') {
3968 $family = $this->sans_fonts[0];
3970 if ($family == 'serif') {
3971 $family = $this->serif_fonts[0];
3973 if ($family == 'mono' || $family == 'monospace') {
3974 $family = $this->mono_fonts[0];
3978 if (isset($this->fonttrans[$family]) && $this->fonttrans[$family]) {
3979 $family = $this->fonttrans[$family];
3982 if ($family == '') {
3983 if ($this->FontFamily) {
3984 $family = $this->FontFamily;
3985 } elseif ($this->default_font) {
3986 $family = $this->default_font;
3987 } else {
3988 throw new \Mpdf\MpdfException("No font or default font set!");
3992 $this->ReqFontStyle = $style; // required or requested style - used later for artificial bold/italic
3994 if (($family == 'csymbol') || ($family == 'czapfdingbats') || ($family == 'ctimes') || ($family == 'ccourier') || ($family == 'chelvetica')) {
3995 if ($this->PDFA || $this->PDFX) {
3996 if ($family == 'csymbol' || $family == 'czapfdingbats') {
3997 throw new \Mpdf\MpdfException("Symbol and Zapfdingbats cannot be embedded in mPDF (required for PDFA1-b or PDFX/1-a).");
3999 if ($family == 'ctimes' || $family == 'ccourier' || $family == 'chelvetica') {
4000 if (($this->PDFA && !$this->PDFAauto) || ($this->PDFX && !$this->PDFXauto)) {
4001 $this->PDFAXwarnings[] = "Core Adobe font " . ucfirst($family) . " cannot be embedded in mPDF, which is required for PDFA1-b or PDFX/1-a. (Embedded font will be substituted.)";
4003 if ($family == 'chelvetica') {
4004 $family = 'sans';
4006 if ($family == 'ctimes') {
4007 $family = 'serif';
4009 if ($family == 'ccourier') {
4010 $family = 'mono';
4013 $this->usingCoreFont = false;
4014 } else {
4015 $this->usingCoreFont = true;
4017 if ($family == 'csymbol' || $family == 'czapfdingbats') {
4018 $style = '';
4020 } else {
4021 $this->usingCoreFont = false;
4024 // mPDF 5.7.1
4025 if ($style) {
4026 $style = strtoupper($style);
4027 if ($style == 'IB') {
4028 $style = 'BI';
4031 if ($size == 0) {
4032 $size = $this->FontSizePt;
4035 $fontkey = $family . $style;
4037 $stylekey = $style;
4038 if (!$stylekey) {
4039 $stylekey = "R";
4042 if (!$this->onlyCoreFonts && !$this->usingCoreFont) {
4043 if (!isset($this->fonts[$fontkey]) || count($this->default_available_fonts) != count($this->available_unifonts)) { // not already added
4045 /* -- CJK-FONTS -- */
4046 if (in_array($fontkey, $this->available_CJK_fonts)) {
4047 if (!isset($this->fonts[$fontkey])) { // already added
4048 if (empty($this->Big5_widths)) {
4049 require __DIR__ . '/../data/CJKdata.php';
4051 $this->AddCJKFont($family); // don't need to add style
4053 } else { // Test to see if requested font/style is available - or substitute /* -- END CJK-FONTS -- */
4054 if (!in_array($fontkey, $this->available_unifonts)) {
4055 // If font[nostyle] exists - set it
4056 if (in_array($family, $this->available_unifonts)) {
4057 $style = '';
4058 } // elseif only one font available - set it (assumes if only one font available it will not have a style)
4059 elseif (count($this->available_unifonts) == 1) {
4060 $family = $this->available_unifonts[0];
4061 $style = '';
4062 } else {
4063 $found = 0;
4064 // else substitute font of similar type
4065 if (in_array($family, $this->sans_fonts)) {
4066 $i = array_intersect($this->sans_fonts, $this->available_unifonts);
4067 if (count($i)) {
4068 $i = array_values($i);
4069 // with requested style if possible
4070 if (!in_array(($i[0] . $style), $this->available_unifonts)) {
4071 $style = '';
4073 $family = $i[0];
4074 $found = 1;
4076 } elseif (in_array($family, $this->serif_fonts)) {
4077 $i = array_intersect($this->serif_fonts, $this->available_unifonts);
4078 if (count($i)) {
4079 $i = array_values($i);
4080 // with requested style if possible
4081 if (!in_array(($i[0] . $style), $this->available_unifonts)) {
4082 $style = '';
4084 $family = $i[0];
4085 $found = 1;
4087 } elseif (in_array($family, $this->mono_fonts)) {
4088 $i = array_intersect($this->mono_fonts, $this->available_unifonts);
4089 if (count($i)) {
4090 $i = array_values($i);
4091 // with requested style if possible
4092 if (!in_array(($i[0] . $style), $this->available_unifonts)) {
4093 $style = '';
4095 $family = $i[0];
4096 $found = 1;
4100 if (!$found) {
4101 // set first available font
4102 $fs = $this->available_unifonts[0];
4103 preg_match('/^([a-z_0-9\-]+)([BI]{0,2})$/', $fs, $fas); // Allow "-"
4104 // with requested style if possible
4105 $ws = $fas[1] . $style;
4106 if (in_array($ws, $this->available_unifonts)) {
4107 $family = $fas[1]; // leave $style as is
4108 } elseif (in_array($fas[1], $this->available_unifonts)) {
4109 // or without style
4110 $family = $fas[1];
4111 $style = '';
4112 } else {
4113 // or with the style specified
4114 $family = $fas[1];
4115 $style = $fas[2];
4119 $fontkey = $family . $style;
4124 // try to add font (if not already added)
4125 $this->AddFont($family, $style);
4127 // Test if font is already selected
4128 if ($this->FontFamily == $family && $this->FontFamily == $this->currentfontfamily && $this->FontStyle == $style && $this->FontStyle == $this->currentfontstyle && $this->FontSizePt == $size && $this->FontSizePt == $this->currentfontsize && !$forcewrite) {
4129 return $family;
4132 $fontkey = $family . $style;
4134 // Select it
4135 $this->FontFamily = $family;
4136 $this->FontStyle = $style;
4137 $this->FontSizePt = $size;
4138 $this->FontSize = $size / Mpdf::SCALE;
4139 $this->CurrentFont = &$this->fonts[$fontkey];
4140 if ($write) {
4141 $fontout = (sprintf('BT /F%d %.3F Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
4142 if ($this->page > 0 && ((isset($this->pageoutput[$this->page]['Font']) && $this->pageoutput[$this->page]['Font'] != $fontout) || !isset($this->pageoutput[$this->page]['Font']))) {
4143 $this->_out($fontout);
4145 $this->pageoutput[$this->page]['Font'] = $fontout;
4148 // Added - currentfont (lowercase) used in HTML2PDF
4149 $this->currentfontfamily = $family;
4150 $this->currentfontsize = $size;
4151 $this->currentfontstyle = $style;
4152 $this->setMBencoding('UTF-8');
4153 } else { // if using core fonts
4154 if ($this->PDFA || $this->PDFX) {
4155 throw new \Mpdf\MpdfException('Core Adobe fonts cannot be embedded in mPDF (required for PDFA1-b or PDFX/1-a) - cannot use option to use core fonts.');
4157 $this->setMBencoding('windows-1252');
4159 // Test if font is already selected
4160 if (($this->FontFamily == $family) and ( $this->FontStyle == $style) and ( $this->FontSizePt == $size) && !$forcewrite) {
4161 return $family;
4164 if (!isset($this->CoreFonts[$fontkey])) {
4165 if (in_array($family, $this->serif_fonts)) {
4166 $family = 'ctimes';
4167 } elseif (in_array($family, $this->mono_fonts)) {
4168 $family = 'ccourier';
4169 } else {
4170 $family = 'chelvetica';
4172 $this->usingCoreFont = true;
4173 $fontkey = $family . $style;
4176 if (!isset($this->fonts[$fontkey])) {
4177 // STANDARD CORE FONTS
4178 if (isset($this->CoreFonts[$fontkey])) {
4179 // Load metric file
4180 $file = $family;
4181 if ($family == 'ctimes' || $family == 'chelvetica' || $family == 'ccourier') {
4182 $file .= strtolower($style);
4184 require __DIR__ . '/../data/font/' . $file . '.php';
4185 if (!isset($cw)) {
4186 throw new \Mpdf\MpdfException(sprintf('Could not include font metric file "%s"', $file));
4188 $i = count($this->fonts) + $this->extraFontSubsets + 1;
4189 $this->fonts[$fontkey] = ['i' => $i, 'type' => 'core', 'name' => $this->CoreFonts[$fontkey], 'desc' => $desc, 'up' => $up, 'ut' => $ut, 'cw' => $cw];
4190 if ($this->useKerning && isset($kerninfo)) {
4191 $this->fonts[$fontkey]['kerninfo'] = $kerninfo;
4193 } else {
4194 throw new \Mpdf\MpdfException(sprintf('Font %s not defined', $fontkey));
4198 // Test if font is already selected
4199 if (($this->FontFamily == $family) and ( $this->FontStyle == $style) and ( $this->FontSizePt == $size) && !$forcewrite) {
4200 return $family;
4202 // Select it
4203 $this->FontFamily = $family;
4204 $this->FontStyle = $style;
4205 $this->FontSizePt = $size;
4206 $this->FontSize = $size / Mpdf::SCALE;
4207 $this->CurrentFont = &$this->fonts[$fontkey];
4208 if ($write) {
4209 $fontout = (sprintf('BT /F%d %.3F Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
4210 if ($this->page > 0 && ((isset($this->pageoutput[$this->page]['Font']) && $this->pageoutput[$this->page]['Font'] != $fontout) || !isset($this->pageoutput[$this->page]['Font']))) {
4211 $this->_out($fontout);
4213 $this->pageoutput[$this->page]['Font'] = $fontout;
4215 // Added - currentfont (lowercase) used in HTML2PDF
4216 $this->currentfontfamily = $family;
4217 $this->currentfontsize = $size;
4218 $this->currentfontstyle = $style;
4221 return $family;
4224 function SetFontSize($size, $write = true)
4226 // Set font size in points
4227 if ($this->FontSizePt == $size) {
4228 return;
4230 $this->FontSizePt = $size;
4231 $this->FontSize = $size / Mpdf::SCALE;
4232 $this->currentfontsize = $size;
4233 if ($write) {
4234 $fontout = (sprintf('BT /F%d %.3F Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
4235 // Edited mPDF 3.0
4236 if ($this->page > 0 && ((isset($this->pageoutput[$this->page]['Font']) && $this->pageoutput[$this->page]['Font'] != $fontout) || !isset($this->pageoutput[$this->page]['Font']))) {
4237 $this->_out($fontout);
4239 $this->pageoutput[$this->page]['Font'] = $fontout;
4243 function AddLink()
4245 // Create a new internal link
4246 $n = count($this->links) + 1;
4247 $this->links[$n] = [0, 0];
4248 return $n;
4251 function SetLink($link, $y = 0, $page = -1)
4253 // Set destination of internal link
4254 if ($y == -1) {
4255 $y = $this->y;
4257 if ($page == -1) {
4258 $page = $this->page;
4260 $this->links[$link] = [$page, $y];
4263 function Link($x, $y, $w, $h, $link)
4265 $l = [$x * Mpdf::SCALE, $this->hPt - $y * Mpdf::SCALE, $w * Mpdf::SCALE, $h * Mpdf::SCALE, $link];
4266 if ($this->keep_block_together) { // don't write yet
4267 return;
4268 } elseif ($this->table_rotate) { // *TABLES*
4269 $this->tbrot_Links[$this->page][] = $l; // *TABLES*
4270 return; // *TABLES*
4271 } // *TABLES*
4272 elseif ($this->kwt) {
4273 $this->kwt_Links[$this->page][] = $l;
4274 return;
4277 if ($this->writingHTMLheader || $this->writingHTMLfooter) {
4278 $this->HTMLheaderPageLinks[] = $l;
4279 return;
4281 // Put a link on the page
4282 $this->PageLinks[$this->page][] = $l;
4283 // Save cross-reference to Column buffer
4284 $ref = count($this->PageLinks[$this->page]) - 1; // *COLUMNS*
4285 $this->columnLinks[$this->CurrCol][(int) $this->x][(int) $this->y] = $ref; // *COLUMNS*
4288 function Text($x, $y, $txt, $OTLdata = [], $textvar = 0, $aixextra = '', $coordsys = '', $return = false)
4290 // Output (or return) a string
4291 // Called (internally) by Watermark() & _tableWrite() [rotated cells] & TableHeaderFooter() & WriteText()
4292 // Called also from classes/svg.php
4293 // Expects Font to be set
4294 // Expects input to be mb_encoded if necessary and RTL reversed & OTL processed
4295 // ARTIFICIAL BOLD AND ITALIC
4296 $s = 'q ';
4297 if ($this->falseBoldWeight && strpos($this->ReqFontStyle, "B") !== false && strpos($this->FontStyle, "B") === false) {
4298 $s .= '2 Tr 1 J 1 j ';
4299 $s .= sprintf('%.3F w ', ($this->FontSize / 130) * Mpdf::SCALE * $this->falseBoldWeight);
4300 $tc = strtoupper($this->TextColor); // change 0 0 0 rg to 0 0 0 RG
4301 if ($this->FillColor != $tc) {
4302 $s .= $tc . ' ';
4303 } // stroke (outline) = same colour as text(fill)
4305 if (strpos($this->ReqFontStyle, "I") !== false && strpos($this->FontStyle, "I") === false) {
4306 $aix = '1 0 0.261799 1 %.3F %.3F Tm';
4307 } else {
4308 $aix = '%.3F %.3F Td';
4311 $aix = $aixextra . $aix;
4313 if ($this->ColorFlag) {
4314 $s.=$this->TextColor . ' ';
4317 $this->CurrentFont['used'] = true;
4319 if ($this->usingCoreFont) {
4320 $txt2 = str_replace(chr(160), chr(32), $txt);
4321 } else {
4322 $txt2 = str_replace(chr(194) . chr(160), chr(32), $txt);
4325 $px = $x;
4326 $py = $y;
4327 if ($coordsys != 'SVG') {
4328 $px = $x * Mpdf::SCALE;
4329 $py = ($this->h - $y) * Mpdf::SCALE;
4333 /** ************** SIMILAR TO Cell() ************************ */
4335 // IF corefonts AND NOT SmCaps AND NOT Kerning
4336 // Just output text
4337 if ($this->usingCoreFont && !($textvar & TextVars::FC_SMALLCAPS) && !($textvar & TextVars::FC_KERNING)) {
4338 $txt2 = $this->_escape($txt2);
4339 $s .=sprintf('BT ' . $aix . ' (%s) Tj ET', $px, $py, $txt2);
4340 } // IF NOT corefonts [AND NO wordspacing] AND NOT SIP/SMP AND NOT SmCaps AND NOT Kerning AND NOT OTL
4341 // Just output text
4342 elseif (!$this->usingCoreFont && !($textvar & TextVars::FC_SMALLCAPS) && !($textvar & TextVars::FC_KERNING) && !(isset($this->CurrentFont['useOTL']) && ($this->CurrentFont['useOTL'] & 0xFF) && !empty($OTLdata['GPOSinfo']))) {
4343 // IF SIP/SMP
4344 if ($this->CurrentFont['sip'] || $this->CurrentFont['smp']) {
4345 $txt2 = $this->UTF8toSubset($txt2);
4346 $s .=sprintf('BT ' . $aix . ' %s Tj ET', $px, $py, $txt2);
4347 } // NOT SIP/SMP
4348 else {
4349 $txt2 = $this->UTF8ToUTF16BE($txt2, false);
4350 $txt2 = $this->_escape($txt2);
4351 $s .=sprintf('BT ' . $aix . ' (%s) Tj ET', $px, $py, $txt2);
4353 } // IF NOT corefonts [AND IS wordspacing] AND NOT SIP AND NOT SmCaps AND NOT Kerning AND NOT OTL
4354 // Not required here (cf. Cell() )
4355 // ELSE (IF SmCaps || Kerning || OTL) [corefonts or not corefonts; SIP or SMP or BMP]
4356 else {
4357 $s .= $this->applyGPOSpdf($txt2, $aix, $px, $py, $OTLdata, $textvar);
4359 /* * ************** END ************************ */
4361 $s .= ' ';
4363 if (($textvar & TextVars::FD_UNDERLINE) && $txt != '') { // mPDF 5.7.1
4364 $c = strtoupper($this->TextColor); // change 0 0 0 rg to 0 0 0 RG
4365 if ($this->FillColor != $c) {
4366 $s.= ' ' . $c . ' ';
4368 if (isset($this->CurrentFont['up']) && $this->CurrentFont['up']) {
4369 $up = $this->CurrentFont['up'];
4370 } else {
4371 $up = -100;
4373 $adjusty = (-$up / 1000 * $this->FontSize);
4374 if (isset($this->CurrentFont['ut']) && $this->CurrentFont['ut']) {
4375 $ut = $this->CurrentFont['ut'] / 1000 * $this->FontSize;
4376 } else {
4377 $ut = 60 / 1000 * $this->FontSize;
4379 $olw = $this->LineWidth;
4380 $s.=' ' . (sprintf(' %.3F w', $ut * Mpdf::SCALE));
4381 $s.=' ' . $this->_dounderline($x, $y + $adjusty, $txt, $OTLdata, $textvar);
4382 $s.=' ' . (sprintf(' %.3F w', $olw * Mpdf::SCALE));
4383 if ($this->FillColor != $c) {
4384 $s.= ' ' . $this->FillColor . ' ';
4387 // STRIKETHROUGH
4388 if (($textvar & TextVars::FD_LINETHROUGH) && $txt != '') { // mPDF 5.7.1
4389 $c = strtoupper($this->TextColor); // change 0 0 0 rg to 0 0 0 RG
4390 if ($this->FillColor != $c) {
4391 $s.= ' ' . $c . ' ';
4393 // Superscript and Subscript Y coordinate adjustment (now for striked-through texts)
4394 if (isset($this->CurrentFont['desc']['CapHeight']) && $this->CurrentFont['desc']['CapHeight']) {
4395 $ch = $this->CurrentFont['desc']['CapHeight'];
4396 } else {
4397 $ch = 700;
4399 $adjusty = (-$ch / 1000 * $this->FontSize) * 0.35;
4400 if (isset($this->CurrentFont['ut']) && $this->CurrentFont['ut']) {
4401 $ut = $this->CurrentFont['ut'] / 1000 * $this->FontSize;
4402 } else {
4403 $ut = 60 / 1000 * $this->FontSize;
4405 $olw = $this->LineWidth;
4406 $s.=' ' . (sprintf(' %.3F w', $ut * Mpdf::SCALE));
4407 $s.=' ' . $this->_dounderline($x, $y + $adjusty, $txt, $OTLdata, $textvar);
4408 $s.=' ' . (sprintf(' %.3F w', $olw * Mpdf::SCALE));
4409 if ($this->FillColor != $c) {
4410 $s.= ' ' . $this->FillColor . ' ';
4413 $s .= 'Q';
4415 if ($return) {
4416 return $s . " \n";
4418 $this->_out($s);
4421 /* -- DIRECTW -- */
4423 function WriteText($x, $y, $txt)
4425 // Output a string using Text() but does encoding and text reversing of RTL
4426 $txt = $this->purify_utf8_text($txt);
4427 if ($this->text_input_as_HTML) {
4428 $txt = $this->all_entities_to_utf8($txt);
4430 if ($this->usingCoreFont) {
4431 $txt = mb_convert_encoding($txt, $this->mb_enc, 'UTF-8');
4434 // DIRECTIONALITY
4435 if (preg_match("/([" . $this->pregRTLchars . "])/u", $txt)) {
4436 $this->biDirectional = true;
4437 } // *OTL*
4439 $textvar = 0;
4440 $save_OTLtags = $this->OTLtags;
4441 $this->OTLtags = [];
4442 if ($this->useKerning) {
4443 if ($this->CurrentFont['haskernGPOS']) {
4444 $this->OTLtags['Plus'] .= ' kern';
4445 } else {
4446 $textvar = ($textvar | TextVars::FC_KERNING);
4450 /* -- OTL -- */
4451 // Use OTL OpenType Table Layout - GSUB & GPOS
4452 if (isset($this->CurrentFont['useOTL']) && $this->CurrentFont['useOTL']) {
4453 $txt = $this->otl->applyOTL($txt, $this->CurrentFont['useOTL']);
4454 $OTLdata = $this->otl->OTLdata;
4456 /* -- END OTL -- */
4457 $this->OTLtags = $save_OTLtags;
4459 $this->magic_reverse_dir($txt, $this->directionality, $OTLdata);
4461 $this->Text($x, $y, $txt, $OTLdata, $textvar);
4464 function WriteCell($w, $h = 0, $txt = '', $border = 0, $ln = 0, $align = '', $fill = 0, $link = '', $currentx = 0)
4466 // Output a cell using Cell() but does encoding and text reversing of RTL
4467 $txt = $this->purify_utf8_text($txt);
4468 if ($this->text_input_as_HTML) {
4469 $txt = $this->all_entities_to_utf8($txt);
4471 if ($this->usingCoreFont) {
4472 $txt = mb_convert_encoding($txt, $this->mb_enc, 'UTF-8');
4474 // DIRECTIONALITY
4475 if (preg_match("/([" . $this->pregRTLchars . "])/u", $txt)) {
4476 $this->biDirectional = true;
4477 } // *OTL*
4479 $textvar = 0;
4480 $save_OTLtags = $this->OTLtags;
4481 $this->OTLtags = [];
4482 if ($this->useKerning) {
4483 if ($this->CurrentFont['haskernGPOS']) {
4484 $this->OTLtags['Plus'] .= ' kern';
4485 } else {
4486 $textvar = ($textvar | TextVars::FC_KERNING);
4490 /* -- OTL -- */
4491 // Use OTL OpenType Table Layout - GSUB & GPOS
4492 if (isset($this->CurrentFont['useOTL']) && $this->CurrentFont['useOTL']) {
4493 $txt = $this->otl->applyOTL($txt, $this->CurrentFont['useOTL']);
4494 $OTLdata = $this->otl->OTLdata;
4496 /* -- END OTL -- */
4497 $this->OTLtags = $save_OTLtags;
4499 $this->magic_reverse_dir($txt, $this->directionality, $OTLdata);
4501 $this->Cell($w, $h, $txt, $border, $ln, $align, $fill, $link, $currentx, 0, 0, 'M', 0, false, $OTLdata, $textvar);
4504 /* -- END DIRECTW -- */
4506 function ResetSpacing()
4508 if ($this->ws != 0) {
4509 $this->_out('BT 0 Tw ET');
4511 $this->ws = 0;
4512 if ($this->charspacing != 0) {
4513 $this->_out('BT 0 Tc ET');
4515 $this->charspacing = 0;
4518 function SetSpacing($cs, $ws)
4520 if (intval($cs * 1000) == 0) {
4521 $cs = 0;
4523 if ($cs) {
4524 $this->_out(sprintf('BT %.3F Tc ET', $cs));
4525 } elseif ($this->charspacing != 0) {
4526 $this->_out('BT 0 Tc ET');
4528 $this->charspacing = $cs;
4529 if (intval($ws * 1000) == 0) {
4530 $ws = 0;
4532 if ($ws) {
4533 $this->_out(sprintf('BT %.3F Tw ET', $ws));
4534 } elseif ($this->ws != 0) {
4535 $this->_out('BT 0 Tw ET');
4537 $this->ws = $ws;
4540 // WORD SPACING
4541 function GetJspacing($nc, $ns, $w, $inclCursive, &$cOTLdata)
4543 $kashida_present = false;
4544 $kashida_space = 0;
4545 if ($w > 0 && $inclCursive && isset($this->CurrentFont['useKashida']) && $this->CurrentFont['useKashida'] && !empty($cOTLdata)) {
4546 for ($c = 0; $c < count($cOTLdata); $c++) {
4547 for ($i = 0; $i < strlen($cOTLdata[$c]['group']); $i++) {
4548 if (isset($cOTLdata[$c]['GPOSinfo'][$i]['kashida']) && $cOTLdata[$c]['GPOSinfo'][$i]['kashida'] > 0) {
4549 $kashida_present = true;
4550 break 2;
4556 if ($kashida_present) {
4557 $k_ctr = 0; // Number of kashida points
4558 $k_total = 0; // Total of kashida values (priority)
4559 // Reset word
4560 $max_kashida_in_word = 0;
4561 $last_kashida_in_word = -1;
4563 for ($c = 0; $c < count($cOTLdata); $c++) {
4564 for ($i = 0; $i < strlen($cOTLdata[$c]['group']); $i++) {
4565 if ($cOTLdata[$c]['group']{$i} == 'S') {
4566 // Save from last word
4567 if ($max_kashida_in_word) {
4568 $k_ctr++;
4569 $k_total = $max_kashida_in_word;
4571 // Reset word
4572 $max_kashida_in_word = 0;
4573 $last_kashida_in_word = -1;
4576 if (isset($cOTLdata[$c]['GPOSinfo'][$i]['kashida']) && $cOTLdata[$c]['GPOSinfo'][$i]['kashida'] > 0) {
4577 if ($max_kashida_in_word) {
4578 if ($cOTLdata[$c]['GPOSinfo'][$i]['kashida'] > $max_kashida_in_word) {
4579 $max_kashida_in_word = $cOTLdata[$c]['GPOSinfo'][$i]['kashida'];
4580 $cOTLdata[$c]['GPOSinfo'][$last_kashida_in_word]['kashida'] = 0;
4581 $last_kashida_in_word = $i;
4582 } else {
4583 $cOTLdata[$c]['GPOSinfo'][$i]['kashida'] = 0;
4585 } else {
4586 $max_kashida_in_word = $cOTLdata[$c]['GPOSinfo'][$i]['kashida'];
4587 $last_kashida_in_word = $i;
4592 // Save from last word
4593 if ($max_kashida_in_word) {
4594 $k_ctr++;
4595 $k_total = $max_kashida_in_word;
4598 // Number of kashida points = $k_ctr
4599 // $useKashida is a % value from CurrentFont/config_fonts.php
4600 // % ratio divided between word-spacing and kashida-spacing
4601 $kashida_space_ratio = intval($this->CurrentFont['useKashida']) / 100;
4604 $kashida_space = $w * $kashida_space_ratio;
4606 $tatw = $this->_getCharWidth($this->CurrentFont['cw'], 0x0640);
4607 // Only use kashida if each allocated kashida width is > 0.01 x width of a tatweel
4608 // Otherwise fontstretch is too small and errors
4609 // If not just leave to adjust word-spacing
4610 if ($tatw && (($kashida_space / $k_ctr) / $tatw) > 0.01) {
4611 for ($c = 0; $c < count($cOTLdata); $c++) {
4612 for ($i = 0; $i < strlen($cOTLdata[$c]['group']); $i++) {
4613 if (isset($cOTLdata[$c]['GPOSinfo'][$i]['kashida']) && $cOTLdata[$c]['GPOSinfo'][$i]['kashida'] > 0) {
4614 // At this point kashida is a number representing priority (higher number - higher priority)
4615 // We are now going to set it as an actual length
4616 // This shares it equally amongst words:
4617 $cOTLdata[$c]['GPOSinfo'][$i]['kashida_space'] = (1 / $k_ctr) * $kashida_space;
4621 $w -= $kashida_space;
4625 $ws = 0;
4626 $charspacing = 0;
4627 $ww = $this->jSWord;
4628 $ncx = $nc - 1;
4629 if ($nc == 0) {
4630 return [0, 0, 0];
4631 } // Only word spacing allowed / possible
4632 elseif ($this->fixedlSpacing !== false || $inclCursive) {
4633 if ($ns) {
4634 $ws = $w / $ns;
4636 } elseif ($nc == 1) {
4637 $charspacing = $w;
4638 } elseif (!$ns) {
4639 $charspacing = $w / ($ncx );
4640 if (($this->jSmaxChar > 0) && ($charspacing > $this->jSmaxChar)) {
4641 $charspacing = $this->jSmaxChar;
4643 } elseif ($ns == ($ncx )) {
4644 $charspacing = $w / $ns;
4645 } else {
4646 if ($this->usingCoreFont) {
4647 $cs = ($w * (1 - $this->jSWord)) / ($ncx );
4648 if (($this->jSmaxChar > 0) && ($cs > $this->jSmaxChar)) {
4649 $cs = $this->jSmaxChar;
4650 $ww = 1 - (($cs * ($ncx )) / $w);
4652 $charspacing = $cs;
4653 $ws = ($w * ($ww) ) / $ns;
4654 } else {
4655 $cs = ($w * (1 - $this->jSWord)) / ($ncx - $ns);
4656 if (($this->jSmaxChar > 0) && ($cs > $this->jSmaxChar)) {
4657 $cs = $this->jSmaxChar;
4658 $ww = 1 - (($cs * ($ncx - $ns)) / $w);
4660 $charspacing = $cs;
4661 $ws = (($w * ($ww) ) / $ns) - $charspacing;
4664 return [$charspacing, $ws, $kashida_space];
4667 function Cell($w, $h = 0, $txt = '', $border = 0, $ln = 0, $align = '', $fill = 0, $link = '', $currentx = 0, $lcpaddingL = 0, $lcpaddingR = 0, $valign = 'M', $spanfill = 0, $exactWidth = false, $OTLdata = false, $textvar = 0, $lineBox = false)
4669 // mPDF 5.7.1
4670 // Output a cell
4671 // Expects input to be mb_encoded if necessary and RTL reversed
4672 // NON_BREAKING SPACE
4673 if ($this->usingCoreFont) {
4674 $txt = str_replace(chr(160), chr(32), $txt);
4675 } else {
4676 $txt = str_replace(chr(194) . chr(160), chr(32), $txt);
4679 $oldcolumn = $this->CurrCol;
4680 // Automatic page break
4681 // Allows PAGE-BREAK-AFTER = avoid to work
4682 if (isset($this->blk[$this->blklvl])) {
4683 $bottom = $this->blk[$this->blklvl]['padding_bottom'] + $this->blk[$this->blklvl]['margin_bottom'];
4684 } else {
4685 $bottom = 0;
4687 if (!$this->tableLevel && (($this->y + $this->divheight > $this->PageBreakTrigger) || ($this->y + $h > $this->PageBreakTrigger) ||
4688 ($this->y + ($h * 2) + $bottom > $this->PageBreakTrigger && $this->blk[$this->blklvl]['page_break_after_avoid'])) and ! $this->InFooter and $this->AcceptPageBreak()) { // mPDF 5.7.2
4689 $x = $this->x; // Current X position
4690 // WORD SPACING
4691 $ws = $this->ws; // Word Spacing
4692 $charspacing = $this->charspacing; // Character Spacing
4693 $this->ResetSpacing();
4695 $this->AddPage($this->CurOrientation);
4696 // Added to correct for OddEven Margins
4697 $x += $this->MarginCorrection;
4698 if ($currentx) {
4699 $currentx += $this->MarginCorrection;
4701 $this->x = $x;
4702 // WORD SPACING
4703 $this->SetSpacing($charspacing, $ws);
4706 // Test: to put line through centre of cell: $this->Line($this->x,$this->y+($h/2),$this->x+50,$this->y+($h/2));
4707 // Test: to put border around cell as it is specified: $border='LRTB';
4710 /* -- COLUMNS -- */
4711 // COLS
4712 // COLUMN CHANGE
4713 if ($this->CurrCol != $oldcolumn) {
4714 if ($currentx) {
4715 $currentx += $this->ChangeColumn * ($this->ColWidth + $this->ColGap);
4717 $this->x += $this->ChangeColumn * ($this->ColWidth + $this->ColGap);
4720 // COLUMNS Update/overwrite the lowest bottom of printing y value for a column
4721 if ($this->ColActive) {
4722 if ($h) {
4723 $this->ColDetails[$this->CurrCol]['bottom_margin'] = $this->y + $h;
4724 } else {
4725 $this->ColDetails[$this->CurrCol]['bottom_margin'] = $this->y + $this->divheight;
4728 /* -- END COLUMNS -- */
4731 if ($w == 0) {
4732 $w = $this->w - $this->rMargin - $this->x;
4734 $s = '';
4735 if ($fill == 1 && $this->FillColor) {
4736 if ((isset($this->pageoutput[$this->page]['FillColor']) && $this->pageoutput[$this->page]['FillColor'] != $this->FillColor) || !isset($this->pageoutput[$this->page]['FillColor'])) {
4737 $s .= $this->FillColor . ' ';
4739 $this->pageoutput[$this->page]['FillColor'] = $this->FillColor;
4743 if ($lineBox && isset($lineBox['boxtop']) && $txt) { // i.e. always from WriteFlowingBlock/finishFlowingBlock (but not objects -
4744 // which only have $lineBox['top'] set)
4745 $boxtop = $this->y + $lineBox['boxtop'];
4746 $boxbottom = $this->y + $lineBox['boxbottom'];
4747 $glyphYorigin = $lineBox['glyphYorigin'];
4748 $baseline_shift = $lineBox['baseline-shift'];
4749 $bord_boxtop = $bg_boxtop = $boxtop = $boxtop - $baseline_shift;
4750 $bord_boxbottom = $bg_boxbottom = $boxbottom = $boxbottom - $baseline_shift;
4751 $bord_boxheight = $bg_boxheight = $boxheight = $boxbottom - $boxtop;
4753 // If inline element BACKGROUND has bounding box set by parent element:
4754 if (isset($lineBox['background-boxtop'])) {
4755 $bg_boxtop = $this->y + $lineBox['background-boxtop'] - $lineBox['background-baseline-shift'];
4756 $bg_boxbottom = $this->y + $lineBox['background-boxbottom'] - $lineBox['background-baseline-shift'];
4757 $bg_boxheight = $bg_boxbottom - $bg_boxtop;
4759 // If inline element BORDER has bounding box set by parent element:
4760 if (isset($lineBox['border-boxtop'])) {
4761 $bord_boxtop = $this->y + $lineBox['border-boxtop'] - $lineBox['border-baseline-shift'];
4762 $bord_boxbottom = $this->y + $lineBox['border-boxbottom'] - $lineBox['border-baseline-shift'];
4763 $bord_boxheight = $bord_boxbottom - $bord_boxtop;
4765 } else {
4766 $boxtop = $this->y;
4767 $boxheight = $h;
4768 $boxbottom = $this->y + $h;
4769 $baseline_shift = 0;
4770 if ($txt != '') {
4771 // FONT SIZE - this determines the baseline caculation
4772 $bfs = $this->FontSize;
4773 // Calculate baseline Superscript and Subscript Y coordinate adjustment
4774 $bfx = $this->baselineC;
4775 $baseline = $bfx * $bfs;
4777 if ($textvar & TextVars::FA_SUPERSCRIPT) {
4778 $baseline_shift = $this->textparam['text-baseline'];
4779 } // mPDF 5.7.1 // mPDF 6
4780 elseif ($textvar & TextVars::FA_SUBSCRIPT) {
4781 $baseline_shift = $this->textparam['text-baseline'];
4782 } // mPDF 5.7.1 // mPDF 6
4783 elseif ($this->bullet) {
4784 $baseline += ($bfx - 0.7) * $this->FontSize;
4787 // Vertical align (for Images)
4788 if ($valign == 'T') {
4789 $va = (0.5 * $bfs * $this->normalLineheight);
4790 } elseif ($valign == 'B') {
4791 $va = $h - (0.5 * $bfs * $this->normalLineheight);
4792 } else {
4793 $va = 0.5 * $h;
4794 } // Middle
4795 // ONLY SET THESE IF WANT TO CONFINE BORDER +/- FILL TO FIT FONTSIZE - NOT FULL CELL AS IS ORIGINAL FUNCTION
4796 // spanfill or spanborder are set in FlowingBlock functions
4797 if ($spanfill || !empty($this->spanborddet) || $link != '') {
4798 $exth = 0.2; // Add to fontsize to increase height of background / link / border
4799 $boxtop = $this->y + $baseline + $va - ($this->FontSize * (1 + $exth / 2) * (0.5 + $bfx));
4800 $boxheight = $this->FontSize * (1 + $exth);
4801 $boxbottom = $boxtop + $boxheight;
4803 $glyphYorigin = $baseline + $va;
4805 $boxtop -= $baseline_shift;
4806 $boxbottom -= $baseline_shift;
4807 $bord_boxtop = $bg_boxtop = $boxtop;
4808 $bord_boxbottom = $bg_boxbottom = $boxbottom;
4809 $bord_boxheight = $bg_boxheight = $boxheight = $boxbottom - $boxtop;
4813 $bbw = $tbw = $lbw = $rbw = 0; // Border widths
4814 if (!empty($this->spanborddet)) {
4815 if (!isset($this->spanborddet['B'])) {
4816 $this->spanborddet['B'] = ['s' => 0, 'style' => '', 'w' => 0];
4818 if (!isset($this->spanborddet['T'])) {
4819 $this->spanborddet['T'] = ['s' => 0, 'style' => '', 'w' => 0];
4821 if (!isset($this->spanborddet['L'])) {
4822 $this->spanborddet['L'] = ['s' => 0, 'style' => '', 'w' => 0];
4824 if (!isset($this->spanborddet['R'])) {
4825 $this->spanborddet['R'] = ['s' => 0, 'style' => '', 'w' => 0];
4827 $bbw = $this->spanborddet['B']['w'];
4828 $tbw = $this->spanborddet['T']['w'];
4829 $lbw = $this->spanborddet['L']['w'];
4830 $rbw = $this->spanborddet['R']['w'];
4832 if ($fill == 1 || $border == 1 || !empty($this->spanborddet)) {
4833 if (!empty($this->spanborddet)) {
4834 if ($fill == 1) {
4835 $s.=sprintf('%.3F %.3F %.3F %.3F re f ', ($this->x - $lbw) * Mpdf::SCALE, ($this->h - $bg_boxtop + $tbw) * Mpdf::SCALE, ($w + $lbw + $rbw) * Mpdf::SCALE, (-$bg_boxheight - $tbw - $bbw) * Mpdf::SCALE);
4837 $s.= ' q ';
4838 $dashon = 3;
4839 $dashoff = 3.5;
4840 $dot = 2.5;
4841 if ($tbw) {
4842 $short = 0;
4843 if ($this->spanborddet['T']['style'] == 'dashed') {
4844 $s.=sprintf(' 0 j 0 J [%.3F %.3F] 0 d ', $tbw * $dashon * Mpdf::SCALE, $tbw * $dashoff * Mpdf::SCALE);
4845 } elseif ($this->spanborddet['T']['style'] == 'dotted') {
4846 $s.=sprintf(' 1 j 1 J [%.3F %.3F] %.3F d ', 0.001, $tbw * $dot * Mpdf::SCALE, -$tbw / 2 * Mpdf::SCALE);
4847 $short = $tbw / 2;
4848 } else {
4849 $s.=' 0 j 0 J [] 0 d ';
4851 if ($this->spanborddet['T']['style'] != 'dotted') {
4852 $s .= 'q ';
4853 $s .= sprintf('%.3F %.3F m ', ($this->x - $lbw) * Mpdf::SCALE, ($this->h - $bord_boxtop + $tbw) * Mpdf::SCALE);
4854 $s .= sprintf('%.3F %.3F l ', ($this->x + $w + $rbw) * Mpdf::SCALE, ($this->h - $bord_boxtop + $tbw) * Mpdf::SCALE);
4855 $s .= sprintf('%.3F %.3F l ', ($this->x + $w) * Mpdf::SCALE, ($this->h - $bord_boxtop) * Mpdf::SCALE);
4856 $s .= sprintf('%.3F %.3F l ', ($this->x) * Mpdf::SCALE, ($this->h - $bord_boxtop) * Mpdf::SCALE);
4857 $s .= ' h W n '; // Ends path no-op & Sets the clipping path
4859 $c = $this->SetDColor($this->spanborddet['T']['c'], true);
4860 if ($this->spanborddet['T']['style'] == 'double') {
4861 $s.=sprintf(' %s %.3F w ', $c, $tbw / 3 * Mpdf::SCALE);
4862 $s.=sprintf('%.3F %.3F m %.3F %.3F l S ', ($this->x - $lbw) * Mpdf::SCALE, ($this->h - $bord_boxtop + $tbw * 5 / 6) * Mpdf::SCALE, ($this->x + $w + $rbw) * Mpdf::SCALE, ($this->h - $bord_boxtop + $tbw * 5 / 6) * Mpdf::SCALE);
4863 $s.=sprintf('%.3F %.3F m %.3F %.3F l S ', ($this->x - $lbw) * Mpdf::SCALE, ($this->h - $bord_boxtop + $tbw / 6) * Mpdf::SCALE, ($this->x + $w + $rbw) * Mpdf::SCALE, ($this->h - $bord_boxtop + $tbw / 6) * Mpdf::SCALE);
4864 } elseif ($this->spanborddet['T']['style'] == 'dotted') {
4865 $s.=sprintf(' %s %.3F w ', $c, $tbw * Mpdf::SCALE);
4866 $s.=sprintf('%.3F %.3F m %.3F %.3F l S ', ($this->x - $lbw) * Mpdf::SCALE, ($this->h - $bord_boxtop + $tbw / 2) * Mpdf::SCALE, ($this->x + $w + $rbw - $short) * Mpdf::SCALE, ($this->h - $bord_boxtop + $tbw / 2) * Mpdf::SCALE);
4867 } else {
4868 $s.=sprintf(' %s %.3F w ', $c, $tbw * Mpdf::SCALE);
4869 $s.=sprintf('%.3F %.3F m %.3F %.3F l S ', ($this->x - $lbw) * Mpdf::SCALE, ($this->h - $bord_boxtop + $tbw / 2) * Mpdf::SCALE, ($this->x + $w + $rbw - $short) * Mpdf::SCALE, ($this->h - $bord_boxtop + $tbw / 2) * Mpdf::SCALE);
4871 if ($this->spanborddet['T']['style'] != 'dotted') {
4872 $s .= ' Q ';
4875 if ($bbw) {
4876 $short = 0;
4877 if ($this->spanborddet['B']['style'] == 'dashed') {
4878 $s.=sprintf(' 0 j 0 J [%.3F %.3F] 0 d ', $bbw * $dashon * Mpdf::SCALE, $bbw * $dashoff * Mpdf::SCALE);
4879 } elseif ($this->spanborddet['B']['style'] == 'dotted') {
4880 $s.=sprintf(' 1 j 1 J [%.3F %.3F] %.3F d ', 0.001, $bbw * $dot * Mpdf::SCALE, -$bbw / 2 * Mpdf::SCALE);
4881 $short = $bbw / 2;
4882 } else {
4883 $s.=' 0 j 0 J [] 0 d ';
4885 if ($this->spanborddet['B']['style'] != 'dotted') {
4886 $s .= 'q ';
4887 $s .= sprintf('%.3F %.3F m ', ($this->x - $lbw) * Mpdf::SCALE, ($this->h - $bord_boxbottom - $bbw) * Mpdf::SCALE);
4888 $s .= sprintf('%.3F %.3F l ', ($this->x + $w + $rbw) * Mpdf::SCALE, ($this->h - $bord_boxbottom - $bbw) * Mpdf::SCALE);
4889 $s .= sprintf('%.3F %.3F l ', ($this->x + $w) * Mpdf::SCALE, ($this->h - $bord_boxbottom) * Mpdf::SCALE);
4890 $s .= sprintf('%.3F %.3F l ', ($this->x) * Mpdf::SCALE, ($this->h - $bord_boxbottom) * Mpdf::SCALE);
4891 $s .= ' h W n '; // Ends path no-op & Sets the clipping path
4893 $c = $this->SetDColor($this->spanborddet['B']['c'], true);
4894 if ($this->spanborddet['B']['style'] == 'double') {
4895 $s.=sprintf(' %s %.3F w ', $c, $bbw / 3 * Mpdf::SCALE);
4896 $s.=sprintf('%.3F %.3F m %.3F %.3F l S ', ($this->x - $lbw) * Mpdf::SCALE, ($this->h - $bord_boxbottom - $bbw / 6) * Mpdf::SCALE, ($this->x + $w + $rbw - $short) * Mpdf::SCALE, ($this->h - $bord_boxbottom - $bbw / 6) * Mpdf::SCALE);
4897 $s.=sprintf('%.3F %.3F m %.3F %.3F l S ', ($this->x - $lbw) * Mpdf::SCALE, ($this->h - $bord_boxbottom - $bbw * 5 / 6) * Mpdf::SCALE, ($this->x + $w + $rbw - $short) * Mpdf::SCALE, ($this->h - $bord_boxbottom - $bbw * 5 / 6) * Mpdf::SCALE);
4898 } elseif ($this->spanborddet['B']['style'] == 'dotted') {
4899 $s.=sprintf(' %s %.3F w ', $c, $bbw * Mpdf::SCALE);
4900 $s.=sprintf('%.3F %.3F m %.3F %.3F l S ', ($this->x - $lbw) * Mpdf::SCALE, ($this->h - $bord_boxbottom - $bbw / 2) * Mpdf::SCALE, ($this->x + $w + $rbw - $short) * Mpdf::SCALE, ($this->h - $bord_boxbottom - $bbw / 2) * Mpdf::SCALE);
4901 } else {
4902 $s.=sprintf(' %s %.3F w ', $c, $bbw * Mpdf::SCALE);
4903 $s.=sprintf('%.3F %.3F m %.3F %.3F l S ', ($this->x - $lbw) * Mpdf::SCALE, ($this->h - $bord_boxbottom - $bbw / 2) * Mpdf::SCALE, ($this->x + $w + $rbw - $short) * Mpdf::SCALE, ($this->h - $bord_boxbottom - $bbw / 2) * Mpdf::SCALE);
4905 if ($this->spanborddet['B']['style'] != 'dotted') {
4906 $s .= ' Q ';
4909 if ($lbw) {
4910 $short = 0;
4911 if ($this->spanborddet['L']['style'] == 'dashed') {
4912 $s.=sprintf(' 0 j 0 J [%.3F %.3F] 0 d ', $lbw * $dashon * Mpdf::SCALE, $lbw * $dashoff * Mpdf::SCALE);
4913 } elseif ($this->spanborddet['L']['style'] == 'dotted') {
4914 $s.=sprintf(' 1 j 1 J [%.3F %.3F] %.3F d ', 0.001, $lbw * $dot * Mpdf::SCALE, -$lbw / 2 * Mpdf::SCALE);
4915 $short = $lbw / 2;
4916 } else {
4917 $s.=' 0 j 0 J [] 0 d ';
4919 if ($this->spanborddet['L']['style'] != 'dotted') {
4920 $s .= 'q ';
4921 $s .= sprintf('%.3F %.3F m ', ($this->x - $lbw) * Mpdf::SCALE, ($this->h - $bord_boxbottom - $bbw) * Mpdf::SCALE);
4922 $s .= sprintf('%.3F %.3F l ', ($this->x) * Mpdf::SCALE, ($this->h - $bord_boxbottom) * Mpdf::SCALE);
4923 $s .= sprintf('%.3F %.3F l ', ($this->x) * Mpdf::SCALE, ($this->h - $bord_boxtop) * Mpdf::SCALE);
4924 $s .= sprintf('%.3F %.3F l ', ($this->x - $lbw) * Mpdf::SCALE, ($this->h - $bord_boxtop + $tbw) * Mpdf::SCALE);
4925 $s .= ' h W n '; // Ends path no-op & Sets the clipping path
4927 $c = $this->SetDColor($this->spanborddet['L']['c'], true);
4928 if ($this->spanborddet['L']['style'] == 'double') {
4929 $s.=sprintf(' %s %.3F w ', $c, $lbw / 3 * Mpdf::SCALE);
4930 $s.=sprintf('%.3F %.3F m %.3F %.3F l S ', ($this->x - $lbw / 6) * Mpdf::SCALE, ($this->h - $bord_boxtop + $tbw) * Mpdf::SCALE, ($this->x - $lbw / 6) * Mpdf::SCALE, ($this->h - $bord_boxbottom - $bbw + $short) * Mpdf::SCALE);
4931 $s.=sprintf('%.3F %.3F m %.3F %.3F l S ', ($this->x - $lbw * 5 / 6) * Mpdf::SCALE, ($this->h - $bord_boxtop + $tbw) * Mpdf::SCALE, ($this->x - $lbw * 5 / 6) * Mpdf::SCALE, ($this->h - $bord_boxbottom - $bbw + $short) * Mpdf::SCALE);
4932 } elseif ($this->spanborddet['L']['style'] == 'dotted') {
4933 $s.=sprintf(' %s %.3F w ', $c, $lbw * Mpdf::SCALE);
4934 $s.=sprintf('%.3F %.3F m %.3F %.3F l S ', ($this->x - $lbw / 2) * Mpdf::SCALE, ($this->h - $bord_boxtop + $tbw) * Mpdf::SCALE, ($this->x - $lbw / 2) * Mpdf::SCALE, ($this->h - $bord_boxbottom - $bbw + $short) * Mpdf::SCALE);
4935 } else {
4936 $s.=sprintf(' %s %.3F w ', $c, $lbw * Mpdf::SCALE);
4937 $s.=sprintf('%.3F %.3F m %.3F %.3F l S ', ($this->x - $lbw / 2) * Mpdf::SCALE, ($this->h - $bord_boxtop + $tbw) * Mpdf::SCALE, ($this->x - $lbw / 2) * Mpdf::SCALE, ($this->h - $bord_boxbottom - $bbw + $short) * Mpdf::SCALE);
4939 if ($this->spanborddet['L']['style'] != 'dotted') {
4940 $s .= ' Q ';
4943 if ($rbw) {
4944 $short = 0;
4945 if ($this->spanborddet['R']['style'] == 'dashed') {
4946 $s.=sprintf(' 0 j 0 J [%.3F %.3F] 0 d ', $rbw * $dashon * Mpdf::SCALE, $rbw * $dashoff * Mpdf::SCALE);
4947 } elseif ($this->spanborddet['R']['style'] == 'dotted') {
4948 $s.=sprintf(' 1 j 1 J [%.3F %.3F] %.3F d ', 0.001, $rbw * $dot * Mpdf::SCALE, -$rbw / 2 * Mpdf::SCALE);
4949 $short = $rbw / 2;
4950 } else {
4951 $s.=' 0 j 0 J [] 0 d ';
4953 if ($this->spanborddet['R']['style'] != 'dotted') {
4954 $s .= 'q ';
4955 $s .= sprintf('%.3F %.3F m ', ($this->x + $w + $rbw) * Mpdf::SCALE, ($this->h - $bord_boxbottom - $bbw) * Mpdf::SCALE);
4956 $s .= sprintf('%.3F %.3F l ', ($this->x + $w) * Mpdf::SCALE, ($this->h - $bord_boxbottom) * Mpdf::SCALE);
4957 $s .= sprintf('%.3F %.3F l ', ($this->x + $w) * Mpdf::SCALE, ($this->h - $bord_boxtop) * Mpdf::SCALE);
4958 $s .= sprintf('%.3F %.3F l ', ($this->x + $w + $rbw) * Mpdf::SCALE, ($this->h - $bord_boxtop + $tbw) * Mpdf::SCALE);
4959 $s .= ' h W n '; // Ends path no-op & Sets the clipping path
4961 $c = $this->SetDColor($this->spanborddet['R']['c'], true);
4962 if ($this->spanborddet['R']['style'] == 'double') {
4963 $s.=sprintf(' %s %.3F w ', $c, $rbw / 3 * Mpdf::SCALE);
4964 $s.=sprintf('%.3F %.3F m %.3F %.3F l S ', ($this->x + $w + $rbw / 6) * Mpdf::SCALE, ($this->h - $bord_boxtop + $tbw) * Mpdf::SCALE, ($this->x + $w + $rbw / 6) * Mpdf::SCALE, ($this->h - $bord_boxbottom - $bbw + $short) * Mpdf::SCALE);
4965 $s.=sprintf('%.3F %.3F m %.3F %.3F l S ', ($this->x + $w + $rbw * 5 / 6) * Mpdf::SCALE, ($this->h - $bord_boxtop + $tbw) * Mpdf::SCALE, ($this->x + $w + $rbw * 5 / 6) * Mpdf::SCALE, ($this->h - $bord_boxbottom - $bbw + $short) * Mpdf::SCALE);
4966 } elseif ($this->spanborddet['R']['style'] == 'dotted') {
4967 $s.=sprintf(' %s %.3F w ', $c, $rbw * Mpdf::SCALE);
4968 $s.=sprintf('%.3F %.3F m %.3F %.3F l S ', ($this->x + $w + $rbw / 2) * Mpdf::SCALE, ($this->h - $bord_boxtop + $tbw) * Mpdf::SCALE, ($this->x + $w + $rbw / 2) * Mpdf::SCALE, ($this->h - $bord_boxbottom - $bbw + $short) * Mpdf::SCALE);
4969 } else {
4970 $s.=sprintf(' %s %.3F w ', $c, $rbw * Mpdf::SCALE);
4971 $s.=sprintf('%.3F %.3F m %.3F %.3F l S ', ($this->x + $w + $rbw / 2) * Mpdf::SCALE, ($this->h - $bord_boxtop + $tbw) * Mpdf::SCALE, ($this->x + $w + $rbw / 2) * Mpdf::SCALE, ($this->h - $bord_boxbottom - $bbw + $short) * Mpdf::SCALE);
4973 if ($this->spanborddet['R']['style'] != 'dotted') {
4974 $s .= ' Q ';
4977 $s.= ' Q ';
4978 } else { // If "border", does not come from WriteFlowingBlock or FinishFlowingBlock
4979 if ($fill == 1) {
4980 $op = ($border == 1) ? 'B' : 'f';
4981 } else {
4982 $op = 'S';
4984 $s.=sprintf('%.3F %.3F %.3F %.3F re %s ', $this->x * Mpdf::SCALE, ($this->h - $bg_boxtop) * Mpdf::SCALE, $w * Mpdf::SCALE, -$bg_boxheight * Mpdf::SCALE, $op);
4988 if (is_string($border)) { // If "border", does not come from WriteFlowingBlock or FinishFlowingBlock
4989 $x = $this->x;
4990 $y = $this->y;
4991 if (is_int(strpos($border, 'L'))) {
4992 $s.=sprintf('%.3F %.3F m %.3F %.3F l S ', $x * Mpdf::SCALE, ($this->h - $bord_boxtop) * Mpdf::SCALE, $x * Mpdf::SCALE, ($this->h - ($bord_boxbottom)) * Mpdf::SCALE);
4994 if (is_int(strpos($border, 'T'))) {
4995 $s.=sprintf('%.3F %.3F m %.3F %.3F l S ', $x * Mpdf::SCALE, ($this->h - $bord_boxtop) * Mpdf::SCALE, ($x + $w) * Mpdf::SCALE, ($this->h - $bord_boxtop) * Mpdf::SCALE);
4997 if (is_int(strpos($border, 'R'))) {
4998 $s.=sprintf('%.3F %.3F m %.3F %.3F l S ', ($x + $w) * Mpdf::SCALE, ($this->h - $bord_boxtop) * Mpdf::SCALE, ($x + $w) * Mpdf::SCALE, ($this->h - ($bord_boxbottom)) * Mpdf::SCALE);
5000 if (is_int(strpos($border, 'B'))) {
5001 $s.=sprintf('%.3F %.3F m %.3F %.3F l S ', $x * Mpdf::SCALE, ($this->h - ($bord_boxbottom)) * Mpdf::SCALE, ($x + $w) * Mpdf::SCALE, ($this->h - ($bord_boxbottom)) * Mpdf::SCALE);
5005 if ($txt != '') {
5006 if ($exactWidth) {
5007 $stringWidth = $w;
5008 } else {
5009 $stringWidth = $this->GetStringWidth($txt, true, $OTLdata, $textvar) + ( $this->charspacing * mb_strlen($txt, $this->mb_enc) / Mpdf::SCALE ) + ( $this->ws * mb_substr_count($txt, ' ', $this->mb_enc) / Mpdf::SCALE );
5012 // Set x OFFSET FOR PRINTING
5013 if ($align == 'R') {
5014 $dx = $w - $this->cMarginR - $stringWidth - $lcpaddingR;
5015 } elseif ($align == 'C') {
5016 $dx = (($w - $stringWidth ) / 2);
5017 } elseif ($align == 'L' or $align == 'J') {
5018 $dx = $this->cMarginL + $lcpaddingL;
5019 } else {
5020 $dx = 0;
5023 if ($this->ColorFlag) {
5024 $s .='q ' . $this->TextColor . ' ';
5027 // OUTLINE
5028 if (isset($this->textparam['outline-s']) && $this->textparam['outline-s'] && !($textvar & TextVars::FC_SMALLCAPS)) { // mPDF 5.7.1
5029 $s .=' ' . sprintf('%.3F w', $this->LineWidth * Mpdf::SCALE) . ' ';
5030 $s .=" $this->DrawColor ";
5031 $s .=" 2 Tr ";
5032 } elseif ($this->falseBoldWeight && strpos($this->ReqFontStyle, "B") !== false && strpos($this->FontStyle, "B") === false && !($textvar & TextVars::FC_SMALLCAPS)) { // can't use together with OUTLINE or Small Caps // mPDF 5.7.1 ??? why not with SmallCaps ???
5033 $s .= ' 2 Tr 1 J 1 j ';
5034 $s .= ' ' . sprintf('%.3F w', ($this->FontSize / 130) * Mpdf::SCALE * $this->falseBoldWeight) . ' ';
5035 $tc = strtoupper($this->TextColor); // change 0 0 0 rg to 0 0 0 RG
5036 if ($this->FillColor != $tc) {
5037 $s .= ' ' . $tc . ' ';
5038 } // stroke (outline) = same colour as text(fill)
5039 } else {
5040 $s .=" 0 Tr ";
5043 if (strpos($this->ReqFontStyle, "I") !== false && strpos($this->FontStyle, "I") === false) { // Artificial italic
5044 $aix = '1 0 0.261799 1 %.3F %.3F Tm ';
5045 } else {
5046 $aix = '%.3F %.3F Td ';
5049 $px = ($this->x + $dx) * Mpdf::SCALE;
5050 $py = ($this->h - ($this->y + $glyphYorigin - $baseline_shift)) * Mpdf::SCALE;
5052 // THE TEXT
5053 $txt2 = $txt;
5054 $sub = '';
5055 $this->CurrentFont['used'] = true;
5057 /* * ************** SIMILAR TO Text() ************************ */
5059 // IF corefonts AND NOT SmCaps AND NOT Kerning
5060 // Just output text; charspacing and wordspacing already set by charspacing (Tc) and ws (Tw)
5061 if ($this->usingCoreFont && !($textvar & TextVars::FC_SMALLCAPS) && !($textvar & TextVars::FC_KERNING)) {
5062 $txt2 = $this->_escape($txt2);
5063 $sub .=sprintf('BT ' . $aix . ' (%s) Tj ET', $px, $py, $txt2);
5064 } // IF NOT corefonts AND NO wordspacing AND NOT SIP/SMP AND NOT SmCaps AND NOT Kerning AND NOT OTL
5065 // Just output text
5066 elseif (!$this->usingCoreFont && !$this->ws && !($textvar & TextVars::FC_SMALLCAPS) && !($textvar & TextVars::FC_KERNING) && !(isset($this->CurrentFont['useOTL']) && ($this->CurrentFont['useOTL'] & 0xFF) && !empty($OTLdata['GPOSinfo']))) {
5067 // IF SIP/SMP
5068 if ((isset($this->CurrentFont['sip']) && $this->CurrentFont['sip']) || (isset($this->CurrentFont['smp']) && $this->CurrentFont['smp'])) {
5069 $txt2 = $this->UTF8toSubset($txt2);
5070 $sub .=sprintf('BT ' . $aix . ' %s Tj ET', $px, $py, $txt2);
5071 } // NOT SIP/SMP
5072 else {
5073 $txt2 = $this->UTF8ToUTF16BE($txt2, false);
5074 $txt2 = $this->_escape($txt2);
5075 $sub .=sprintf('BT ' . $aix . ' (%s) Tj ET', $px, $py, $txt2);
5077 } // IF NOT corefonts AND IS wordspacing AND NOT SIP AND NOT SmCaps AND NOT Kerning AND NOT OTL
5078 // Output text word by word with an adjustment to the intercharacter spacing for SPACEs to form word spacing
5079 // IF multibyte - Tw has no effect - need to do word spacing using an adjustment before each space
5080 elseif (!$this->usingCoreFont && $this->ws && !((isset($this->CurrentFont['sip']) && $this->CurrentFont['sip']) || (isset($this->CurrentFont['smp']) && $this->CurrentFont['smp'])) && !($textvar & TextVars::FC_SMALLCAPS) && !($textvar & TextVars::FC_KERNING) && !(isset($this->CurrentFont['useOTL']) && ($this->CurrentFont['useOTL'] & 0xFF) && (!empty($OTLdata['GPOSinfo']) || (strpos($OTLdata['group'], 'M') !== false && $this->charspacing)) )) {
5081 $space = " ";
5082 $space = $this->UTF8ToUTF16BE($space, false);
5083 $space = $this->_escape($space);
5084 $sub .=sprintf('BT ' . $aix . ' %.3F Tc [', $px, $py, $this->charspacing);
5085 $t = explode(' ', $txt2);
5086 $numt = count($t);
5087 for ($i = 0; $i < $numt; $i++) {
5088 $tx = $t[$i];
5089 $tx = $this->UTF8ToUTF16BE($tx, false);
5090 $tx = $this->_escape($tx);
5091 $sub .=sprintf('(%s) ', $tx);
5092 if (($i + 1) < $numt) {
5093 $adj = -($this->ws) * 1000 / $this->FontSizePt;
5094 $sub .=sprintf('%d(%s) ', $adj, $space);
5097 $sub .='] TJ ';
5098 $sub .=' ET';
5099 } // ELSE (IF SmCaps || Kerning || OTL) [corefonts or not corefonts; SIP or SMP or BMP]
5100 else {
5101 $sub = $this->applyGPOSpdf($txt, $aix, $px, $py, $OTLdata, $textvar);
5104 /** ************** END SIMILAR TO Text() ************************ */
5106 if ($this->shrin_k > 1) {
5107 $shrin_k = $this->shrin_k;
5108 } else {
5109 $shrin_k = 1;
5112 // UNDERLINE
5113 if ($textvar & TextVars::FD_UNDERLINE) { // mPDF 5.7.1 // mPDF 6
5115 // mPDF 5.7.3 inline text-decoration parameters
5117 $c = isset($this->textparam['u-decoration']['color']) ? $this->textparam['u-decoration']['color'] : '';
5118 if ($this->FillColor != $c) {
5119 $sub .= ' ' . $c . ' ';
5122 // mPDF 5.7.3 inline text-decoration parameters
5123 $decorationfontkey = isset($this->textparam['u-decoration']['fontkey']) ? $this->textparam['u-decoration']['fontkey'] : '';
5124 $decorationfontsize = isset($this->textparam['u-decoration']['fontsize']) ? $this->textparam['u-decoration']['fontsize'] / $shrin_k : 0;
5126 if (isset($this->fonts[$decorationfontkey]['ut']) && $this->fonts[$decorationfontkey]['ut']) {
5127 $ut = $this->fonts[$decorationfontkey]['ut'] / 1000 * $decorationfontsize;
5128 } else {
5129 $ut = 60 / 1000 * $decorationfontsize;
5132 if (isset($this->fonts[$decorationfontkey]['up']) && $this->fonts[$decorationfontkey]['up']) {
5133 $up = $this->fonts[$decorationfontkey]['up'];
5134 } else {
5135 $up = -100;
5138 $adjusty = (-$up / 1000 * $decorationfontsize) + $ut / 2;
5139 $ubaseline = isset($this->textparam['u-decoration']['baseline'])
5140 ? $glyphYorigin - $this->textparam['u-decoration']['baseline'] / $shrin_k
5141 : $glyphYorigin;
5143 $olw = $this->LineWidth;
5145 $sub .= ' ' . (sprintf(' %.3F w 0 j 0 J ', $ut * Mpdf::SCALE));
5146 $sub .= ' ' . $this->_dounderline($this->x + $dx, $this->y + $ubaseline + $adjusty, $txt, $OTLdata, $textvar);
5147 $sub .= ' ' . (sprintf(' %.3F w 2 j 2 J ', $olw * Mpdf::SCALE));
5149 if ($this->FillColor != $c) {
5150 $sub .= ' ' . $this->FillColor . ' ';
5154 // STRIKETHROUGH
5155 if ($textvar & TextVars::FD_LINETHROUGH) { // mPDF 5.7.1 // mPDF 6
5157 // mPDF 5.7.3 inline text-decoration parameters
5158 $c = $this->textparam['s-decoration']['color'];
5160 if ($this->FillColor != $c) {
5161 $sub .= ' ' . $c . ' ';
5164 // mPDF 5.7.3 inline text-decoration parameters
5165 $decorationfontkey = $this->textparam['s-decoration']['fontkey'];
5166 $decorationfontsize = $this->textparam['s-decoration']['fontsize'] / $shrin_k;
5168 // Use yStrikeoutSize from OS/2 if available
5169 if (isset($this->fonts[$decorationfontkey]['strs']) && $this->fonts[$decorationfontkey]['strs']) {
5170 $ut = $this->fonts[$decorationfontkey]['strs'] / 1000 * $decorationfontsize;
5171 } // else use underlineThickness from post if available
5172 elseif (isset($this->fonts[$decorationfontkey]['ut']) && $this->fonts[$decorationfontkey]['ut']) {
5173 $ut = $this->fonts[$decorationfontkey]['ut'] / 1000 * $decorationfontsize;
5174 } else {
5175 $ut = 50 / 1000 * $decorationfontsize;
5178 // Use yStrikeoutPosition from OS/2 if available
5179 if (isset($this->fonts[$decorationfontkey]['strp']) && $this->fonts[$decorationfontkey]['strp']) {
5180 $up = $this->fonts[$decorationfontkey]['strp'];
5181 $adjusty = (-$up / 1000 * $decorationfontsize);
5182 } // else use a fraction ($this->baselineS) of CapHeight
5183 else {
5184 if (isset($this->fonts[$decorationfontkey]['desc']['CapHeight']) && $this->fonts[$decorationfontkey]['desc']['CapHeight']) {
5185 $ch = $this->fonts[$decorationfontkey]['desc']['CapHeight'];
5186 } else {
5187 $ch = 700;
5189 $adjusty = (-$ch / 1000 * $decorationfontsize) * $this->baselineS;
5192 $sbaseline = $glyphYorigin - $this->textparam['s-decoration']['baseline'] / $shrin_k;
5194 $olw = $this->LineWidth;
5196 $sub .=' ' . (sprintf(' %.3F w 0 j 0 J ', $ut * Mpdf::SCALE));
5197 $sub .=' ' . $this->_dounderline($this->x + $dx, $this->y + $sbaseline + $adjusty, $txt, $OTLdata, $textvar);
5198 $sub .=' ' . (sprintf(' %.3F w 2 j 2 J ', $olw * Mpdf::SCALE));
5200 if ($this->FillColor != $c) {
5201 $sub .= ' ' . $this->FillColor . ' ';
5205 // mPDF 5.7.3 inline text-decoration parameters
5206 // OVERLINE
5207 if ($textvar & TextVars::FD_OVERLINE) { // mPDF 5.7.1 // mPDF 6
5208 // mPDF 5.7.3 inline text-decoration parameters
5209 $c = $this->textparam['o-decoration']['color'];
5210 if ($this->FillColor != $c) {
5211 $sub .= ' ' . $c . ' ';
5214 // mPDF 5.7.3 inline text-decoration parameters
5215 $decorationfontkey = (int) (((float) $this->textparam['o-decoration']['fontkey']) / $shrin_k);
5216 $decorationfontsize = $this->textparam['o-decoration']['fontsize'];
5218 if (isset($this->fonts[$decorationfontkey]['ut']) && $this->fonts[$decorationfontkey]['ut']) {
5219 $ut = $this->fonts[$decorationfontkey]['ut'] / 1000 * $decorationfontsize;
5220 } else {
5221 $ut = 60 / 1000 * $decorationfontsize;
5223 if (isset($this->fonts[$decorationfontkey]['desc']['CapHeight']) && $this->fonts[$decorationfontkey]['desc']['CapHeight']) {
5224 $ch = $this->fonts[$decorationfontkey]['desc']['CapHeight'];
5225 } else {
5226 $ch = 700;
5228 $adjusty = (-$ch / 1000 * $decorationfontsize) * $this->baselineO;
5229 $obaseline = $glyphYorigin - $this->textparam['o-decoration']['baseline'] / $shrin_k;
5230 $olw = $this->LineWidth;
5231 $sub .=' ' . (sprintf(' %.3F w 0 j 0 J ', $ut * Mpdf::SCALE));
5232 $sub .=' ' . $this->_dounderline($this->x + $dx, $this->y + $obaseline + $adjusty, $txt, $OTLdata, $textvar);
5233 $sub .=' ' . (sprintf(' %.3F w 2 j 2 J ', $olw * Mpdf::SCALE));
5234 if ($this->FillColor != $c) {
5235 $sub .= ' ' . $this->FillColor . ' ';
5239 // TEXT SHADOW
5240 if ($this->textshadow) { // First to process is last in CSS comma separated shadows
5241 foreach ($this->textshadow as $ts) {
5242 $s .= ' q ';
5243 $s .= $this->SetTColor($ts['col'], true) . "\n";
5244 if ($ts['col']{0} == 5 && ord($ts['col']{4}) < 100) { // RGBa
5245 $s .= $this->SetAlpha(ord($ts['col']{4}) / 100, 'Normal', true, 'F') . "\n";
5246 } elseif ($ts['col']{0} == 6 && ord($ts['col']{5}) < 100) { // CMYKa
5247 $s .= $this->SetAlpha(ord($ts['col']{5}) / 100, 'Normal', true, 'F') . "\n";
5248 } elseif ($ts['col']{0} == 1 && $ts['col']{2} == 1 && ord($ts['col']{3}) < 100) { // Gray
5249 $s .= $this->SetAlpha(ord($ts['col']{3}) / 100, 'Normal', true, 'F') . "\n";
5251 $s .= sprintf(' 1 0 0 1 %.4F %.4F cm', $ts['x'] * Mpdf::SCALE, -$ts['y'] * Mpdf::SCALE) . "\n";
5252 $s .= $sub;
5253 $s .= ' Q ';
5257 $s .= $sub;
5259 // COLOR
5260 if ($this->ColorFlag) {
5261 $s .=' Q';
5264 // LINK
5265 if ($link != '') {
5266 $this->Link($this->x, $boxtop, $w, $boxheight, $link);
5269 if ($s) {
5270 $this->_out($s);
5273 // WORD SPACING
5274 if ($this->ws && !$this->usingCoreFont) {
5275 $this->_out(sprintf('BT %.3F Tc ET', $this->charspacing));
5277 $this->lasth = $h;
5278 if (strpos($txt, "\n") !== false) {
5279 $ln = 1; // cell recognizes \n from <BR> tag
5281 if ($ln > 0) {
5282 // Go to next line
5283 $this->y += $h;
5284 if ($ln == 1) {
5285 // Move to next line
5286 if ($currentx != 0) {
5287 $this->x = $currentx;
5288 } else {
5289 $this->x = $this->lMargin;
5292 } else {
5293 $this->x+=$w;
5297 function applyGPOSpdf($txt, $aix, $x, $y, $OTLdata, $textvar = 0)
5299 // Generate PDF string
5300 // ==============================
5301 if ((isset($this->CurrentFont['sip']) && $this->CurrentFont['sip']) || (isset($this->CurrentFont['smp']) && $this->CurrentFont['smp'])) {
5302 $sipset = true;
5303 } else {
5304 $sipset = false;
5307 if ($textvar & TextVars::FC_SMALLCAPS) {
5308 $smcaps = true;
5309 } // IF SmallCaps using transformation, NOT OTL
5310 else {
5311 $smcaps = false;
5314 if ($sipset) {
5315 $fontid = $last_fontid = $original_fontid = $this->CurrentFont['subsetfontids'][0];
5316 } else {
5317 $fontid = $last_fontid = $original_fontid = $this->CurrentFont['i'];
5319 $SmallCapsON = false; // state: uppercase/not
5320 $lastSmallCapsON = false; // state: uppercase/not
5321 $last_fontsize = $fontsize = $this->FontSizePt;
5322 $last_fontstretch = $fontstretch = 100;
5323 $groupBreak = false;
5325 $unicode = $this->UTF8StringToArray($txt);
5327 $GPOSinfo = (isset($OTLdata['GPOSinfo']) ? $OTLdata['GPOSinfo'] : []);
5328 $charspacing = ($this->charspacing * 1000 / $this->FontSizePt);
5329 $wordspacing = ($this->ws * 1000 / $this->FontSizePt);
5331 $XshiftBefore = 0;
5332 $XshiftAfter = 0;
5333 $lastYPlacement = 0;
5335 if ($sipset) {
5336 // mPDF 6 DELETED ********
5337 // $txt= preg_replace('/'.preg_quote($this->aliasNbPg,'/').'/', chr(7), $txt); // ? Need to adjust OTL info
5338 // $txt= preg_replace('/'.preg_quote($this->aliasNbPgGp,'/').'/', chr(8), $txt); // ? Need to adjust OTL info
5339 $tj = '<';
5340 } else {
5341 $tj = '(';
5344 for ($i = 0; $i < count($unicode); $i++) {
5345 $c = $unicode[$i];
5346 $tx = '';
5347 $XshiftBefore = $XshiftAfter;
5348 $XshiftAfter = 0;
5349 $YPlacement = 0;
5350 $groupBreak = false;
5351 $kashida = 0;
5352 if (!empty($OTLdata)) {
5353 // YPlacement from GPOS
5354 if (isset($GPOSinfo[$i]['YPlacement']) && $GPOSinfo[$i]['YPlacement']) {
5355 $YPlacement = $GPOSinfo[$i]['YPlacement'] * $this->FontSizePt / $this->CurrentFont['unitsPerEm'];
5356 $groupBreak = true;
5358 // XPlacement from GPOS
5359 if (isset($GPOSinfo[$i]['XPlacement']) && $GPOSinfo[$i]['XPlacement']) {
5360 if (!isset($GPOSinfo[$i]['wDir']) || $GPOSinfo[$i]['wDir'] != 'RTL') {
5361 if (isset($GPOSinfo[$i]['BaseWidth'])) {
5362 $GPOSinfo[$i]['XPlacement'] -= $GPOSinfo[$i]['BaseWidth'];
5366 // Convert to PDF Text space (thousandths of a unit );
5367 $XshiftBefore += $GPOSinfo[$i]['XPlacement'] * 1000 / $this->CurrentFont['unitsPerEm'];
5368 $XshiftAfter += -$GPOSinfo[$i]['XPlacement'] * 1000 / $this->CurrentFont['unitsPerEm'];
5371 // Kashida from GPOS
5372 // Kashida is set as an absolute length value, but to adjust text needs to be converted to
5373 // font-related size
5374 if (isset($GPOSinfo[$i]['kashida_space']) && $GPOSinfo[$i]['kashida_space']) {
5375 $kashida = $GPOSinfo[$i]['kashida_space'];
5378 if ($c == 32) { // word spacing
5379 $XshiftAfter += $wordspacing;
5382 if (substr($OTLdata['group'], ($i + 1), 1) != 'M') { // Don't add inter-character spacing before Marks
5383 $XshiftAfter += $charspacing;
5386 // ...applyGPOSpdf...
5387 // XAdvance from GPOS - Convert to PDF Text space (thousandths of a unit );
5388 if (((isset($GPOSinfo[$i]['wDir']) && $GPOSinfo[$i]['wDir'] != 'RTL') || !isset($GPOSinfo[$i]['wDir'])) && isset($GPOSinfo[$i]['XAdvanceL']) && $GPOSinfo[$i]['XAdvanceL']) {
5389 $XshiftAfter += $GPOSinfo[$i]['XAdvanceL'] * 1000 / $this->CurrentFont['unitsPerEm'];
5390 } elseif (isset($GPOSinfo[$i]['wDir']) && $GPOSinfo[$i]['wDir'] == 'RTL' && isset($GPOSinfo[$i]['XAdvanceR']) && $GPOSinfo[$i]['XAdvanceR']) {
5391 $XshiftAfter += $GPOSinfo[$i]['XAdvanceR'] * 1000 / $this->CurrentFont['unitsPerEm'];
5393 } // Character & Word spacing - if NOT OTL
5394 else {
5395 $XshiftAfter += $charspacing;
5396 if ($c == 32) {
5397 $XshiftAfter += $wordspacing;
5401 // IF Kerning done using pairs rather than OTL
5402 if ($textvar & TextVars::FC_KERNING) {
5403 if ($i > 0 && isset($this->CurrentFont['kerninfo'][$unicode[($i - 1)]][$unicode[$i]])) {
5404 $XshiftBefore += $this->CurrentFont['kerninfo'][$unicode[($i - 1)]][$unicode[$i]];
5408 if ($YPlacement != $lastYPlacement) {
5409 $groupBreak = true;
5412 if ($XshiftBefore) { // +ve value in PDF moves to the left
5413 // If Fontstretch is ongoing, need to adjust X adjustments because these will be stretched out.
5414 $XshiftBefore *= 100 / $last_fontstretch;
5415 if ($sipset) {
5416 $tj .= sprintf('>%d<', (-$XshiftBefore));
5417 } else {
5418 $tj .= sprintf(')%d(', (-$XshiftBefore));
5422 // Small-Caps
5423 if ($smcaps) {
5424 if (isset($this->upperCase[$c])) {
5425 $c = $this->upperCase[$c];
5426 // $this->CurrentFont['subset'][$this->upperCase[$c]] = $this->upperCase[$c]; // add the CAP to subset
5427 $SmallCapsON = true;
5428 // For $sipset
5429 if (!$lastSmallCapsON) { // Turn ON SmallCaps
5430 $groupBreak = true;
5431 $fontstretch = $this->smCapsStretch;
5432 $fontsize = $this->FontSizePt * $this->smCapsScale;
5434 } else {
5435 $SmallCapsON = false;
5436 if ($lastSmallCapsON) { // Turn OFF SmallCaps
5437 $groupBreak = true;
5438 $fontstretch = 100;
5439 $fontsize = $this->FontSizePt;
5444 // Prepare Text and Select Font ID
5445 if ($sipset) {
5446 // mPDF 6 DELETED ********
5447 // if ($c == 7 || $c == 8) {
5448 // if ($original_fontid != $last_fontid) {
5449 // $groupBreak = true;
5450 // $fontid = $original_fontid;
5451 // }
5452 // if ($c == 7) { $tj .= $this->aliasNbPgHex; }
5453 // else { $tj .= $this->aliasNbPgGpHex; }
5454 // continue;
5455 // }
5456 for ($j = 0; $j < 99; $j++) {
5457 $init = array_search($c, $this->CurrentFont['subsets'][$j]);
5458 if ($init !== false) {
5459 if ($this->CurrentFont['subsetfontids'][$j] != $last_fontid) {
5460 $groupBreak = true;
5461 $fontid = $this->CurrentFont['subsetfontids'][$j];
5463 $tx = sprintf("%02s", strtoupper(dechex($init)));
5464 break;
5465 } elseif (count($this->CurrentFont['subsets'][$j]) < 255) {
5466 $n = count($this->CurrentFont['subsets'][$j]);
5467 $this->CurrentFont['subsets'][$j][$n] = $c;
5468 if ($this->CurrentFont['subsetfontids'][$j] != $last_fontid) {
5469 $groupBreak = true;
5470 $fontid = $this->CurrentFont['subsetfontids'][$j];
5472 $tx = sprintf("%02s", strtoupper(dechex($n)));
5473 break;
5474 } elseif (!isset($this->CurrentFont['subsets'][($j + 1)])) {
5475 $this->CurrentFont['subsets'][($j + 1)] = [0 => 0];
5476 $this->CurrentFont['subsetfontids'][($j + 1)] = count($this->fonts) + $this->extraFontSubsets + 1;
5477 $this->extraFontSubsets++;
5480 } else {
5481 $tx = UtfString::code2utf($c);
5482 if ($this->usingCoreFont) {
5483 $tx = utf8_decode($tx);
5484 } else {
5485 $tx = $this->UTF8ToUTF16BE($tx, false);
5487 $tx = $this->_escape($tx);
5490 // If any settings require a new Text Group
5491 if ($groupBreak || $fontstretch != $last_fontstretch) {
5492 if ($sipset) {
5493 $tj .= '>] TJ ';
5494 } else {
5495 $tj .= ')] TJ ';
5497 if ($fontid != $last_fontid || $fontsize != $last_fontsize) {
5498 $tj .= sprintf(' /F%d %.3F Tf ', $fontid, $fontsize);
5500 if ($fontstretch != $last_fontstretch) {
5501 $tj .= sprintf('%d Tz ', $fontstretch);
5503 if ($YPlacement != $lastYPlacement) {
5504 $tj .= sprintf('%.3F Ts ', $YPlacement);
5506 if ($sipset) {
5507 $tj .= '[<';
5508 } else {
5509 $tj .= '[(';
5513 // Output the code for the txt character
5514 $tj .= $tx;
5515 $lastSmallCapsON = $SmallCapsON;
5516 $last_fontid = $fontid;
5517 $last_fontsize = $fontsize;
5518 $last_fontstretch = $fontstretch;
5520 // Kashida
5521 if ($kashida) {
5522 $c = 0x0640; // add the Tatweel U+0640
5523 if (isset($this->CurrentFont['subset'])) {
5524 $this->CurrentFont['subset'][$c] = $c;
5526 $kashida *= 1000 / $this->FontSizePt;
5527 $tatw = $this->_getCharWidth($this->CurrentFont['cw'], 0x0640);
5529 // Get YPlacement from next Base character
5530 $nextbase = $i + 1;
5531 while ($OTLdata['group']{$nextbase} != 'C') {
5532 $nextbase++;
5534 if (isset($GPOSinfo[$nextbase]) && isset($GPOSinfo[$nextbase]['YPlacement']) && $GPOSinfo[$nextbase]['YPlacement']) {
5535 $YPlacement = $GPOSinfo[$nextbase]['YPlacement'] * $this->FontSizePt / $this->CurrentFont['unitsPerEm'];
5538 // Prepare Text and Select Font ID
5539 if ($sipset) {
5540 for ($j = 0; $j < 99; $j++) {
5541 $init = array_search($c, $this->CurrentFont['subsets'][$j]);
5542 if ($init !== false) {
5543 if ($this->CurrentFont['subsetfontids'][$j] != $last_fontid) {
5544 $fontid = $this->CurrentFont['subsetfontids'][$j];
5546 $tx = sprintf("%02s", strtoupper(dechex($init)));
5547 break;
5548 } elseif (count($this->CurrentFont['subsets'][$j]) < 255) {
5549 $n = count($this->CurrentFont['subsets'][$j]);
5550 $this->CurrentFont['subsets'][$j][$n] = $c;
5551 if ($this->CurrentFont['subsetfontids'][$j] != $last_fontid) {
5552 $fontid = $this->CurrentFont['subsetfontids'][$j];
5554 $tx = sprintf("%02s", strtoupper(dechex($n)));
5555 break;
5556 } elseif (!isset($this->CurrentFont['subsets'][($j + 1)])) {
5557 $this->CurrentFont['subsets'][($j + 1)] = [0 => 0];
5558 $this->CurrentFont['subsetfontids'][($j + 1)] = count($this->fonts) + $this->extraFontSubsets + 1;
5559 $this->extraFontSubsets++;
5562 } else {
5563 $tx = UtfString::code2utf($c);
5564 $tx = $this->UTF8ToUTF16BE($tx, false);
5565 $tx = $this->_escape($tx);
5568 if ($kashida > $tatw) {
5569 // Insert multiple tatweel characters, repositioning the last one to give correct total length
5570 $fontstretch = 100;
5571 $nt = intval($kashida / $tatw);
5572 $nudgeback = (($nt + 1) * $tatw) - $kashida;
5573 $optx = str_repeat($tx, $nt);
5574 if ($sipset) {
5575 $optx .= sprintf('>%d<', ($nudgeback));
5576 } else {
5577 $optx .= sprintf(')%d(', ($nudgeback));
5579 $optx .= $tx; // #last
5580 } else {
5581 // Insert single tatweel character and use fontstretch to get correct length
5582 $fontstretch = ($kashida / $tatw) * 100;
5583 $optx = $tx;
5586 if ($sipset) {
5587 $tj .= '>] TJ ';
5588 } else {
5589 $tj .= ')] TJ ';
5591 if ($fontid != $last_fontid || $fontsize != $last_fontsize) {
5592 $tj .= sprintf(' /F%d %.3F Tf ', $fontid, $fontsize);
5594 if ($fontstretch != $last_fontstretch) {
5595 $tj .= sprintf('%d Tz ', $fontstretch);
5597 $tj .= sprintf('%.3F Ts ', $YPlacement);
5598 if ($sipset) {
5599 $tj .= '[<';
5600 } else {
5601 $tj .= '[(';
5604 // Output the code for the txt character(s)
5605 $tj .= $optx;
5606 $last_fontid = $fontid;
5607 $last_fontstretch = $fontstretch;
5608 $fontstretch = 100;
5611 $lastYPlacement = $YPlacement;
5615 // Finish up
5616 if ($sipset) {
5617 $tj .= '>';
5618 if ($XshiftAfter) {
5619 $tj .= sprintf('%d', (-$XshiftAfter));
5621 if ($last_fontid != $original_fontid) {
5622 $tj .= '] TJ ';
5623 $tj .= sprintf(' /F%d %.3F Tf ', $original_fontid, $fontsize);
5624 $tj .= '[';
5626 $tj = preg_replace('/([^\\\])<>/', '\\1 ', $tj);
5627 } else {
5628 $tj .= ')';
5629 if ($XshiftAfter) {
5630 $tj .= sprintf('%d', (-$XshiftAfter));
5632 if ($last_fontid != $original_fontid) {
5633 $tj .= '] TJ ';
5634 $tj .= sprintf(' /F%d %.3F Tf ', $original_fontid, $fontsize);
5635 $tj .= '[';
5637 $tj = preg_replace('/([^\\\])\(\)/', '\\1 ', $tj);
5640 $s = sprintf(' BT ' . $aix . ' 0 Tc 0 Tw [%s] TJ ET ', $x, $y, $tj);
5642 // echo $s."\n\n"; // exit;
5644 return $s;
5647 function _kern($txt, $mode, $aix, $x, $y)
5649 if ($mode == 'MBTw') { // Multibyte requiring word spacing
5650 $space = ' ';
5651 // Convert string to UTF-16BE without BOM
5652 $space = $this->UTF8ToUTF16BE($space, false);
5653 $space = $this->_escape($space);
5654 $s = sprintf(' BT ' . $aix, $x * Mpdf::SCALE, ($this->h - $y) * Mpdf::SCALE);
5655 $t = explode(' ', $txt);
5656 for ($i = 0; $i < count($t); $i++) {
5657 $tx = $t[$i];
5659 $tj = '(';
5660 $unicode = $this->UTF8StringToArray($tx);
5661 for ($ti = 0; $ti < count($unicode); $ti++) {
5662 if ($ti > 0 && isset($this->CurrentFont['kerninfo'][$unicode[($ti - 1)]][$unicode[$ti]])) {
5663 $kern = -$this->CurrentFont['kerninfo'][$unicode[($ti - 1)]][$unicode[$ti]];
5664 $tj .= sprintf(')%d(', $kern);
5666 $tc = UtfString::code2utf($unicode[$ti]);
5667 $tc = $this->UTF8ToUTF16BE($tc, false);
5668 $tj .= $this->_escape($tc);
5670 $tj .= ')';
5671 $s.=sprintf(' %.3F Tc [%s] TJ', $this->charspacing, $tj);
5674 if (($i + 1) < count($t)) {
5675 $s.=sprintf(' %.3F Tc (%s) Tj', $this->ws + $this->charspacing, $space);
5678 $s.=' ET ';
5679 } elseif (!$this->usingCoreFont) {
5680 $s = '';
5681 $tj = '(';
5682 $unicode = $this->UTF8StringToArray($txt);
5683 for ($i = 0; $i < count($unicode); $i++) {
5684 if ($i > 0 && isset($this->CurrentFont['kerninfo'][$unicode[($i - 1)]][$unicode[$i]])) {
5685 $kern = -$this->CurrentFont['kerninfo'][$unicode[($i - 1)]][$unicode[$i]];
5686 $tj .= sprintf(')%d(', $kern);
5688 $tx = UtfString::code2utf($unicode[$i]);
5689 $tx = $this->UTF8ToUTF16BE($tx, false);
5690 $tj .= $this->_escape($tx);
5692 $tj .= ')';
5693 $s.=sprintf(' BT ' . $aix . ' [%s] TJ ET ', $x * Mpdf::SCALE, ($this->h - $y) * Mpdf::SCALE, $tj);
5694 } else { // CORE Font
5695 $s = '';
5696 $tj = '(';
5697 $l = strlen($txt);
5698 for ($i = 0; $i < $l; $i++) {
5699 if ($i > 0 && isset($this->CurrentFont['kerninfo'][$txt[($i - 1)]][$txt[$i]])) {
5700 $kern = -$this->CurrentFont['kerninfo'][$txt[($i - 1)]][$txt[$i]];
5701 $tj .= sprintf(')%d(', $kern);
5703 $tj .= $this->_escape($txt[$i]);
5705 $tj .= ')';
5706 $s.=sprintf(' BT ' . $aix . ' [%s] TJ ET ', $x * Mpdf::SCALE, ($this->h - $y) * Mpdf::SCALE, $tj);
5709 return $s;
5712 function MultiCell($w, $h, $txt, $border = 0, $align = '', $fill = 0, $link = '', $directionality = 'ltr', $encoded = false, $OTLdata = false, $maxrows = false)
5714 // maxrows is called from mpdfform->TEXTAREA
5715 // Parameter (pre-)encoded - When called internally from form::textarea - mb_encoding already done and OTL - but not reverse RTL
5716 if (!$encoded) {
5717 $txt = $this->purify_utf8_text($txt);
5718 if ($this->text_input_as_HTML) {
5719 $txt = $this->all_entities_to_utf8($txt);
5721 if ($this->usingCoreFont) {
5722 $txt = mb_convert_encoding($txt, $this->mb_enc, 'UTF-8');
5724 if (preg_match("/([" . $this->pregRTLchars . "])/u", $txt)) {
5725 $this->biDirectional = true;
5726 } // *OTL*
5727 /* -- OTL -- */
5728 $OTLdata = [];
5729 // Use OTL OpenType Table Layout - GSUB & GPOS
5730 if (isset($this->CurrentFont['useOTL']) && $this->CurrentFont['useOTL']) {
5731 $txt = $this->otl->applyOTL($txt, $this->CurrentFont['useOTL']);
5732 $OTLdata = $this->otl->OTLdata;
5734 if ($directionality == 'rtl' || $this->biDirectional) {
5735 if (!isset($OTLdata)) {
5736 $unicode = $this->UTF8StringToArray($txt, false);
5737 $is_strong = false;
5738 $this->getBasicOTLdata($OTLdata, $unicode, $is_strong);
5741 /* -- END OTL -- */
5743 if (!$align) {
5744 $align = $this->defaultAlign;
5747 // Output text with automatic or explicit line breaks
5748 $cw = &$this->CurrentFont['cw'];
5749 if ($w == 0) {
5750 $w = $this->w - $this->rMargin - $this->x;
5753 $wmax = ($w - ($this->cMarginL + $this->cMarginR));
5754 if ($this->usingCoreFont) {
5755 $s = str_replace("\r", '', $txt);
5756 $nb = strlen($s);
5757 while ($nb > 0 and $s[$nb - 1] == "\n") {
5758 $nb--;
5760 } else {
5761 $s = str_replace("\r", '', $txt);
5762 $nb = mb_strlen($s, $this->mb_enc);
5763 while ($nb > 0 and mb_substr($s, $nb - 1, 1, $this->mb_enc) == "\n") {
5764 $nb--;
5767 $b = 0;
5768 if ($border) {
5769 if ($border == 1) {
5770 $border = 'LTRB';
5771 $b = 'LRT';
5772 $b2 = 'LR';
5773 } else {
5774 $b2 = '';
5775 if (is_int(strpos($border, 'L'))) {
5776 $b2.='L';
5778 if (is_int(strpos($border, 'R'))) {
5779 $b2.='R';
5781 $b = is_int(strpos($border, 'T')) ? $b2 . 'T' : $b2;
5784 $sep = -1;
5785 $i = 0;
5786 $j = 0;
5787 $l = 0;
5788 $ns = 0;
5789 $nl = 1;
5791 $rows = 0;
5792 $start_y = $this->y;
5794 if (!$this->usingCoreFont) {
5795 $inclCursive = false;
5796 if (preg_match("/([" . $this->pregCURSchars . "])/u", $s)) {
5797 $inclCursive = true;
5799 while ($i < $nb) {
5800 // Get next character
5801 $c = mb_substr($s, $i, 1, $this->mb_enc);
5802 if ($c == "\n") {
5803 // Explicit line break
5804 // WORD SPACING
5805 $this->ResetSpacing();
5806 $tmp = rtrim(mb_substr($s, $j, $i - $j, $this->mb_enc));
5807 $tmpOTLdata = false;
5808 /* -- OTL -- */
5809 if (isset($OTLdata)) {
5810 $tmpOTLdata = $this->otl->sliceOTLdata($OTLdata, $j, $i - $j);
5811 $this->otl->trimOTLdata($tmpOTLdata, false, true);
5812 $this->magic_reverse_dir($tmp, $directionality, $tmpOTLdata);
5814 /* -- END OTL -- */
5815 $this->Cell($w, $h, $tmp, $b, 2, $align, $fill, $link, 0, 0, 0, 'M', 0, false, $tmpOTLdata);
5816 if ($maxrows != false && isset($this->form) && ($this->y - $start_y) / $h > $maxrows) {
5817 return false;
5819 $i++;
5820 $sep = -1;
5821 $j = $i;
5822 $l = 0;
5823 $ns = 0;
5824 $nl++;
5825 if ($border and $nl == 2) {
5826 $b = $b2;
5828 continue;
5830 if ($c == " ") {
5831 $sep = $i;
5832 $ls = $l;
5833 $ns++;
5836 $l += $this->GetCharWidthNonCore($c);
5838 if ($l > $wmax) {
5839 // Automatic line break
5840 if ($sep == -1) { // Only one word
5841 if ($i == $j) {
5842 $i++;
5844 // WORD SPACING
5845 $this->ResetSpacing();
5846 $tmp = rtrim(mb_substr($s, $j, $i - $j, $this->mb_enc));
5847 $tmpOTLdata = false;
5848 /* -- OTL -- */
5849 if (isset($OTLdata)) {
5850 $tmpOTLdata = $this->otl->sliceOTLdata($OTLdata, $j, $i - $j);
5851 $this->otl->trimOTLdata($tmpOTLdata, false, true);
5852 $this->magic_reverse_dir($tmp, $directionality, $tmpOTLdata);
5854 /* -- END OTL -- */
5855 $this->Cell($w, $h, $tmp, $b, 2, $align, $fill, $link, 0, 0, 0, 'M', 0, false, $tmpOTLdata);
5856 } else {
5857 $tmp = rtrim(mb_substr($s, $j, $sep - $j, $this->mb_enc));
5858 $tmpOTLdata = false;
5859 /* -- OTL -- */
5860 if (isset($OTLdata)) {
5861 $tmpOTLdata = $this->otl->sliceOTLdata($OTLdata, $j, $sep - $j);
5862 $this->otl->trimOTLdata($tmpOTLdata, false, true);
5864 /* -- END OTL -- */
5865 if ($align == 'J') {
5866 //////////////////////////////////////////
5867 // JUSTIFY J using Unicode fonts (Word spacing doesn't work)
5868 // WORD SPACING UNICODE
5869 // Change NON_BREAKING SPACE to spaces so they are 'spaced' properly
5870 $tmp = str_replace(chr(194) . chr(160), chr(32), $tmp);
5871 $len_ligne = $this->GetStringWidth($tmp, false, $tmpOTLdata);
5872 $nb_carac = mb_strlen($tmp, $this->mb_enc);
5873 $nb_spaces = mb_substr_count($tmp, ' ', $this->mb_enc);
5874 // Take off number of Marks
5875 // Use GPOS OTL
5876 if (isset($this->CurrentFont['useOTL']) && ($this->CurrentFont['useOTL'])) {
5877 if (isset($tmpOTLdata['group']) && $tmpOTLdata['group']) {
5878 $nb_carac -= substr_count($tmpOTLdata['group'], 'M');
5882 list($charspacing, $ws, $kashida) = $this->GetJspacing($nb_carac, $nb_spaces, ((($wmax) - $len_ligne) * Mpdf::SCALE), $inclCursive, $tmpOTLdata);
5883 $this->SetSpacing($charspacing, $ws);
5884 //////////////////////////////////////////
5886 if (isset($OTLdata)) {
5887 $this->magic_reverse_dir($tmp, $directionality, $tmpOTLdata);
5889 $this->Cell($w, $h, $tmp, $b, 2, $align, $fill, $link, 0, 0, 0, 'M', 0, false, $tmpOTLdata);
5890 $i = $sep + 1;
5892 if ($maxrows != false && isset($this->form) && ($this->y - $start_y) / $h > $maxrows) {
5893 return false;
5895 $sep = -1;
5896 $j = $i;
5897 $l = 0;
5898 $ns = 0;
5899 $nl++;
5900 if ($border and $nl == 2) {
5901 $b = $b2;
5903 } else {
5904 $i++;
5907 // Last chunk
5908 // WORD SPACING
5910 $this->ResetSpacing();
5911 } else {
5912 while ($i < $nb) {
5913 // Get next character
5914 $c = $s[$i];
5915 if ($c == "\n") {
5916 // Explicit line break
5917 // WORD SPACING
5918 $this->ResetSpacing();
5919 $this->Cell($w, $h, substr($s, $j, $i - $j), $b, 2, $align, $fill, $link);
5920 if ($maxrows != false && isset($this->form) && ($this->y - $start_y) / $h > $maxrows) {
5921 return false;
5923 $i++;
5924 $sep = -1;
5925 $j = $i;
5926 $l = 0;
5927 $ns = 0;
5928 $nl++;
5929 if ($border and $nl == 2) {
5930 $b = $b2;
5932 continue;
5934 if ($c == " ") {
5935 $sep = $i;
5936 $ls = $l;
5937 $ns++;
5940 $l += $this->GetCharWidthCore($c);
5941 if ($l > $wmax) {
5942 // Automatic line break
5943 if ($sep == -1) {
5944 if ($i == $j) {
5945 $i++;
5947 // WORD SPACING
5948 $this->ResetSpacing();
5949 $this->Cell($w, $h, substr($s, $j, $i - $j), $b, 2, $align, $fill, $link);
5950 } else {
5951 if ($align == 'J') {
5952 $tmp = rtrim(substr($s, $j, $sep - $j));
5953 //////////////////////////////////////////
5954 // JUSTIFY J using Unicode fonts (Word spacing doesn't work)
5955 // WORD SPACING NON_UNICODE/CJK
5956 // Change NON_BREAKING SPACE to spaces so they are 'spaced' properly
5957 $tmp = str_replace(chr(160), chr(32), $tmp);
5958 $len_ligne = $this->GetStringWidth($tmp);
5959 $nb_carac = strlen($tmp);
5960 $nb_spaces = substr_count($tmp, ' ');
5961 $tmpOTLdata = [];
5962 list($charspacing, $ws, $kashida) = $this->GetJspacing($nb_carac, $nb_spaces, ((($wmax) - $len_ligne) * Mpdf::SCALE), false, $tmpOTLdata);
5963 $this->SetSpacing($charspacing, $ws);
5964 //////////////////////////////////////////
5966 $this->Cell($w, $h, substr($s, $j, $sep - $j), $b, 2, $align, $fill, $link);
5967 $i = $sep + 1;
5969 if ($maxrows != false && isset($this->form) && ($this->y - $start_y) / $h > $maxrows) {
5970 return false;
5972 $sep = -1;
5973 $j = $i;
5974 $l = 0;
5975 $ns = 0;
5976 $nl++;
5977 if ($border and $nl == 2) {
5978 $b = $b2;
5980 } else {
5981 $i++;
5984 // Last chunk
5985 // WORD SPACING
5987 $this->ResetSpacing();
5989 // Last chunk
5990 if ($border and is_int(strpos($border, 'B'))) {
5991 $b.='B';
5993 if (!$this->usingCoreFont) {
5994 $tmp = rtrim(mb_substr($s, $j, $i - $j, $this->mb_enc));
5995 $tmpOTLdata = false;
5996 /* -- OTL -- */
5997 if (isset($OTLdata)) {
5998 $tmpOTLdata = $this->otl->sliceOTLdata($OTLdata, $j, $i - $j);
5999 $this->otl->trimOTLdata($tmpOTLdata, false, true);
6000 $this->magic_reverse_dir($tmp, $directionality, $tmpOTLdata);
6002 /* -- END OTL -- */
6003 $this->Cell($w, $h, $tmp, $b, 2, $align, $fill, $link, 0, 0, 0, 'M', 0, false, $tmpOTLdata);
6004 } else {
6005 $this->Cell($w, $h, substr($s, $j, $i - $j), $b, 2, $align, $fill, $link);
6007 $this->x = $this->lMargin;
6010 /* -- DIRECTW -- */
6012 function Write($h, $txt, $currentx = 0, $link = '', $directionality = 'ltr', $align = '', $fill = 0)
6014 if (empty($this->directWrite)) {
6015 $this->directWrite = new DirectWrite($this, $this->otl, $this->sizeConverter, $this->colorConverter);
6018 $this->directWrite->Write($h, $txt, $currentx, $link, $directionality, $align, $fill);
6021 /* -- END DIRECTW -- */
6024 /* -- HTML-CSS -- */
6026 function saveInlineProperties()
6028 $saved = [];
6029 $saved['family'] = $this->FontFamily;
6030 $saved['style'] = $this->FontStyle;
6031 $saved['sizePt'] = $this->FontSizePt;
6032 $saved['size'] = $this->FontSize;
6033 $saved['HREF'] = $this->HREF;
6034 $saved['textvar'] = $this->textvar; // mPDF 5.7.1
6035 $saved['OTLtags'] = $this->OTLtags; // mPDF 5.7.1
6036 $saved['textshadow'] = $this->textshadow;
6037 $saved['linewidth'] = $this->LineWidth;
6038 $saved['drawcolor'] = $this->DrawColor;
6039 $saved['textparam'] = $this->textparam;
6040 $saved['lSpacingCSS'] = $this->lSpacingCSS;
6041 $saved['wSpacingCSS'] = $this->wSpacingCSS;
6042 $saved['I'] = $this->I;
6043 $saved['B'] = $this->B;
6044 $saved['colorarray'] = $this->colorarray;
6045 $saved['bgcolorarray'] = $this->spanbgcolorarray;
6046 $saved['border'] = $this->spanborddet;
6047 $saved['color'] = $this->TextColor;
6048 $saved['bgcolor'] = $this->FillColor;
6049 $saved['lang'] = $this->currentLang;
6050 $saved['fontLanguageOverride'] = $this->fontLanguageOverride; // mPDF 5.7.1
6051 $saved['display_off'] = $this->inlineDisplayOff;
6053 return $saved;
6056 function restoreInlineProperties(&$saved)
6058 $FontFamily = $saved['family'];
6059 $this->FontStyle = $saved['style'];
6060 $this->FontSizePt = $saved['sizePt'];
6061 $this->FontSize = $saved['size'];
6063 $this->currentLang = $saved['lang'];
6064 $this->fontLanguageOverride = $saved['fontLanguageOverride']; // mPDF 5.7.1
6066 $this->ColorFlag = ($this->FillColor != $this->TextColor); // Restore ColorFlag as well
6068 $this->HREF = $saved['HREF'];
6069 $this->textvar = $saved['textvar']; // mPDF 5.7.1
6070 $this->OTLtags = $saved['OTLtags']; // mPDF 5.7.1
6071 $this->textshadow = $saved['textshadow'];
6072 $this->LineWidth = $saved['linewidth'];
6073 $this->DrawColor = $saved['drawcolor'];
6074 $this->textparam = $saved['textparam'];
6075 $this->inlineDisplayOff = $saved['display_off'];
6077 $this->lSpacingCSS = $saved['lSpacingCSS'];
6078 if (($this->lSpacingCSS || $this->lSpacingCSS === '0') && strtoupper($this->lSpacingCSS) != 'NORMAL') {
6079 $this->fixedlSpacing = $this->sizeConverter->convert($this->lSpacingCSS, $this->FontSize);
6080 } else {
6081 $this->fixedlSpacing = false;
6083 $this->wSpacingCSS = $saved['wSpacingCSS'];
6084 if ($this->wSpacingCSS && strtoupper($this->wSpacingCSS) != 'NORMAL') {
6085 $this->minwSpacing = $this->sizeConverter->convert($this->wSpacingCSS, $this->FontSize);
6086 } else {
6087 $this->minwSpacing = 0;
6090 $this->SetFont($FontFamily, $saved['style'], $saved['sizePt'], false);
6092 $this->currentfontstyle = $saved['style'];
6093 $this->currentfontsize = $saved['sizePt'];
6094 $this->SetStylesArray(['B' => $saved['B'], 'I' => $saved['I']]); // mPDF 5.7.1
6096 $this->TextColor = $saved['color'];
6097 $this->FillColor = $saved['bgcolor'];
6098 $this->colorarray = $saved['colorarray'];
6099 $cor = $saved['colorarray'];
6100 if ($cor) {
6101 $this->SetTColor($cor);
6103 $this->spanbgcolorarray = $saved['bgcolorarray'];
6104 $cor = $saved['bgcolorarray'];
6105 if ($cor) {
6106 $this->SetFColor($cor);
6108 $this->spanborddet = $saved['border'];
6111 // Used when ColActive for tables - updated to return first block with background fill OR borders
6112 function GetFirstBlockFill()
6114 // Returns the first blocklevel that uses a bgcolor fill
6115 $startfill = 0;
6116 for ($i = 1; $i <= $this->blklvl; $i++) {
6117 if ($this->blk[$i]['bgcolor'] || $this->blk[$i]['border_left']['w'] || $this->blk[$i]['border_right']['w'] || $this->blk[$i]['border_top']['w'] || $this->blk[$i]['border_bottom']['w']) {
6118 $startfill = $i;
6119 break;
6122 return $startfill;
6125 // -------------------------FLOWING BLOCK------------------------------------//
6126 // The following functions were originally written by Damon Kohler //
6127 // --------------------------------------------------------------------------//
6129 function saveFont()
6131 $saved = [];
6132 $saved['family'] = $this->FontFamily;
6133 $saved['style'] = $this->FontStyle;
6134 $saved['sizePt'] = $this->FontSizePt;
6135 $saved['size'] = $this->FontSize;
6136 $saved['curr'] = &$this->CurrentFont;
6137 $saved['lang'] = $this->currentLang; // mPDF 6
6138 $saved['color'] = $this->TextColor;
6139 $saved['spanbgcolor'] = $this->spanbgcolor;
6140 $saved['spanbgcolorarray'] = $this->spanbgcolorarray;
6141 $saved['bord'] = $this->spanborder;
6142 $saved['border'] = $this->spanborddet;
6143 $saved['HREF'] = $this->HREF;
6144 $saved['textvar'] = $this->textvar; // mPDF 5.7.1
6145 $saved['textshadow'] = $this->textshadow;
6146 $saved['linewidth'] = $this->LineWidth;
6147 $saved['drawcolor'] = $this->DrawColor;
6148 $saved['textparam'] = $this->textparam;
6149 $saved['ReqFontStyle'] = $this->ReqFontStyle;
6150 $saved['fixedlSpacing'] = $this->fixedlSpacing;
6151 $saved['minwSpacing'] = $this->minwSpacing;
6152 return $saved;
6155 function restoreFont(&$saved, $write = true)
6157 if (!isset($saved) || empty($saved)) {
6158 return;
6161 $this->FontFamily = $saved['family'];
6162 $this->FontStyle = $saved['style'];
6163 $this->FontSizePt = $saved['sizePt'];
6164 $this->FontSize = $saved['size'];
6165 $this->CurrentFont = &$saved['curr'];
6166 $this->currentLang = $saved['lang']; // mPDF 6
6167 $this->TextColor = $saved['color'];
6168 $this->spanbgcolor = $saved['spanbgcolor'];
6169 $this->spanbgcolorarray = $saved['spanbgcolorarray'];
6170 $this->spanborder = $saved['bord'];
6171 $this->spanborddet = $saved['border'];
6172 $this->ColorFlag = ($this->FillColor != $this->TextColor); // Restore ColorFlag as well
6173 $this->HREF = $saved['HREF'];
6174 $this->fixedlSpacing = $saved['fixedlSpacing'];
6175 $this->minwSpacing = $saved['minwSpacing'];
6176 $this->textvar = $saved['textvar']; // mPDF 5.7.1
6177 $this->textshadow = $saved['textshadow'];
6178 $this->LineWidth = $saved['linewidth'];
6179 $this->DrawColor = $saved['drawcolor'];
6180 $this->textparam = $saved['textparam'];
6181 if ($write) {
6182 $this->SetFont($saved['family'], $saved['style'], $saved['sizePt'], true, true); // force output
6183 $fontout = (sprintf('BT /F%d %.3F Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
6184 if ($this->page > 0 && ((isset($this->pageoutput[$this->page]['Font']) && $this->pageoutput[$this->page]['Font'] != $fontout) || !isset($this->pageoutput[$this->page]['Font']))) {
6185 $this->_out($fontout);
6187 $this->pageoutput[$this->page]['Font'] = $fontout;
6188 } else {
6189 $this->SetFont($saved['family'], $saved['style'], $saved['sizePt'], false);
6191 $this->ReqFontStyle = $saved['ReqFontStyle'];
6194 function newFlowingBlock($w, $h, $a = '', $is_table = false, $blockstate = 0, $newblock = true, $blockdir = 'ltr', $table_draft = false)
6196 if (!$a) {
6197 if ($blockdir == 'rtl') {
6198 $a = 'R';
6199 } else {
6200 $a = 'L';
6203 $this->flowingBlockAttr['width'] = ($w * Mpdf::SCALE);
6204 // line height in user units
6205 $this->flowingBlockAttr['is_table'] = $is_table;
6206 $this->flowingBlockAttr['table_draft'] = $table_draft;
6207 $this->flowingBlockAttr['height'] = $h;
6208 $this->flowingBlockAttr['lineCount'] = 0;
6209 $this->flowingBlockAttr['align'] = $a;
6210 $this->flowingBlockAttr['font'] = [];
6211 $this->flowingBlockAttr['content'] = [];
6212 $this->flowingBlockAttr['contentB'] = [];
6213 $this->flowingBlockAttr['contentWidth'] = 0;
6214 $this->flowingBlockAttr['blockstate'] = $blockstate;
6216 $this->flowingBlockAttr['newblock'] = $newblock;
6217 $this->flowingBlockAttr['valign'] = 'M';
6218 $this->flowingBlockAttr['blockdir'] = $blockdir;
6219 $this->flowingBlockAttr['cOTLdata'] = []; // mPDF 5.7.1
6220 $this->flowingBlockAttr['lastBidiText'] = ''; // mPDF 5.7.1
6221 if (!empty($this->otl)) {
6222 $this->otl->lastBidiStrongType = '';
6223 } // *OTL*
6226 function finishFlowingBlock($endofblock = false, $next = '')
6228 $currentx = $this->x;
6229 // prints out the last chunk
6230 $is_table = $this->flowingBlockAttr['is_table'];
6231 $table_draft = $this->flowingBlockAttr['table_draft'];
6232 $maxWidth = & $this->flowingBlockAttr['width'];
6233 $stackHeight = & $this->flowingBlockAttr['height'];
6234 $align = & $this->flowingBlockAttr['align'];
6235 $content = & $this->flowingBlockAttr['content'];
6236 $contentB = & $this->flowingBlockAttr['contentB'];
6237 $font = & $this->flowingBlockAttr['font'];
6238 $contentWidth = & $this->flowingBlockAttr['contentWidth'];
6239 $lineCount = & $this->flowingBlockAttr['lineCount'];
6240 $valign = & $this->flowingBlockAttr['valign'];
6241 $blockstate = $this->flowingBlockAttr['blockstate'];
6243 $cOTLdata = & $this->flowingBlockAttr['cOTLdata']; // mPDF 5.7.1
6244 $newblock = $this->flowingBlockAttr['newblock'];
6245 $blockdir = $this->flowingBlockAttr['blockdir'];
6247 // *********** BLOCK BACKGROUND COLOR *****************//
6248 if ($this->blk[$this->blklvl]['bgcolor'] && !$is_table) {
6249 $fill = 0;
6250 } else {
6251 $this->SetFColor($this->colorConverter->convert(255, $this->PDFAXwarnings));
6252 $fill = 0;
6255 $hanger = '';
6256 // Always right trim!
6257 // Right trim last content and adjust width if needed to justify (later)
6258 if (isset($content[count($content) - 1]) && preg_match('/[ ]+$/', $content[count($content) - 1], $m)) {
6259 $strip = strlen($m[0]);
6260 $content[count($content) - 1] = substr($content[count($content) - 1], 0, (strlen($content[count($content) - 1]) - $strip));
6261 /* -- OTL -- */
6262 if (isset($this->CurrentFont['useOTL']) && $this->CurrentFont['useOTL']) {
6263 $this->otl->trimOTLdata($cOTLdata[count($cOTLdata) - 1], false, true);
6265 /* -- END OTL -- */
6268 // the amount of space taken up so far in user units
6269 $usedWidth = 0;
6271 // COLS
6272 $oldcolumn = $this->CurrCol;
6274 if ($this->ColActive && !$is_table) {
6275 $this->breakpoints[$this->CurrCol][] = $this->y;
6276 } // *COLUMNS*
6277 // Print out each chunk
6279 /* -- TABLES -- */
6280 if ($is_table) {
6281 $ipaddingL = 0;
6282 $ipaddingR = 0;
6283 $paddingL = 0;
6284 $paddingR = 0;
6285 } else {
6286 /* -- END TABLES -- */
6287 $ipaddingL = $this->blk[$this->blklvl]['padding_left'];
6288 $ipaddingR = $this->blk[$this->blklvl]['padding_right'];
6289 $paddingL = ($ipaddingL * Mpdf::SCALE);
6290 $paddingR = ($ipaddingR * Mpdf::SCALE);
6291 $this->cMarginL = $this->blk[$this->blklvl]['border_left']['w'];
6292 $this->cMarginR = $this->blk[$this->blklvl]['border_right']['w'];
6294 // Added mPDF 3.0 Float DIV
6295 $fpaddingR = 0;
6296 $fpaddingL = 0;
6297 /* -- CSS-FLOAT -- */
6298 if (count($this->floatDivs)) {
6299 list($l_exists, $r_exists, $l_max, $r_max, $l_width, $r_width) = $this->GetFloatDivInfo($this->blklvl);
6300 if ($r_exists) {
6301 $fpaddingR = $r_width;
6303 if ($l_exists) {
6304 $fpaddingL = $l_width;
6307 /* -- END CSS-FLOAT -- */
6309 $usey = $this->y + 0.002;
6310 if (($newblock) && ($blockstate == 1 || $blockstate == 3) && ($lineCount == 0)) {
6311 $usey += $this->blk[$this->blklvl]['margin_top'] + $this->blk[$this->blklvl]['padding_top'] + $this->blk[$this->blklvl]['border_top']['w'];
6313 /* -- CSS-IMAGE-FLOAT -- */
6314 // If float exists at this level
6315 if (isset($this->floatmargins['R']) && $usey <= $this->floatmargins['R']['y1'] && $usey >= $this->floatmargins['R']['y0'] && !$this->floatmargins['R']['skipline']) {
6316 $fpaddingR += $this->floatmargins['R']['w'];
6318 if (isset($this->floatmargins['L']) && $usey <= $this->floatmargins['L']['y1'] && $usey >= $this->floatmargins['L']['y0'] && !$this->floatmargins['L']['skipline']) {
6319 $fpaddingL += $this->floatmargins['L']['w'];
6321 /* -- END CSS-IMAGE-FLOAT -- */
6322 } // *TABLES*
6325 $lineBox = [];
6327 $this->_setInlineBlockHeights($lineBox, $stackHeight, $content, $font, $is_table);
6329 if ($is_table && count($content) == 0) {
6330 $stackHeight = 0;
6333 if ($table_draft) {
6334 $this->y += $stackHeight;
6335 $this->objectbuffer = [];
6336 return 0;
6339 // While we're at it, check if contains cursive text
6340 // Change NBSP to SPACE.
6341 // Re-calculate contentWidth
6342 $contentWidth = 0;
6344 foreach ($content as $k => $chunk) {
6345 $this->restoreFont($font[$k], false);
6346 if (!isset($this->objectbuffer[$k]) || (isset($this->objectbuffer[$k]) && !$this->objectbuffer[$k])) {
6347 // Soft Hyphens chr(173)
6348 if (!$this->usingCoreFont) {
6349 /* -- OTL -- */
6350 // mPDF 5.7.1
6351 if (isset($this->CurrentFont['useOTL']) && $this->CurrentFont['useOTL']) {
6352 $this->otl->removeChar($chunk, $cOTLdata[$k], "\xc2\xad");
6353 $this->otl->replaceSpace($chunk, $cOTLdata[$k]);
6354 $content[$k] = $chunk;
6355 } /* -- END OTL -- */ else { // *OTL*
6356 $content[$k] = $chunk = str_replace("\xc2\xad", '', $chunk);
6357 $content[$k] = $chunk = str_replace(chr(194) . chr(160), chr(32), $chunk);
6358 } // *OTL*
6359 } elseif ($this->FontFamily != 'csymbol' && $this->FontFamily != 'czapfdingbats') {
6360 $content[$k] = $chunk = str_replace(chr(173), '', $chunk);
6361 $content[$k] = $chunk = str_replace(chr(160), chr(32), $chunk);
6363 $contentWidth += $this->GetStringWidth($chunk, true, (isset($cOTLdata[$k]) ? $cOTLdata[$k] : false), $this->textvar) * Mpdf::SCALE;
6364 } elseif (isset($this->objectbuffer[$k]) && $this->objectbuffer[$k]) {
6365 // LIST MARKERS // mPDF 6 Lists
6366 if ($this->objectbuffer[$k]['type'] == 'image' && isset($this->objectbuffer[$k]['listmarker']) && $this->objectbuffer[$k]['listmarker'] && $this->objectbuffer[$k]['listmarkerposition'] == 'outside') {
6367 // do nothing
6368 } else {
6369 $contentWidth += $this->objectbuffer[$k]['OUTER-WIDTH'] * Mpdf::SCALE;
6374 if (isset($font[count($font) - 1])) {
6375 $lastfontreqstyle = (isset($font[count($font) - 1]['ReqFontStyle']) ? $font[count($font) - 1]['ReqFontStyle'] : '');
6376 $lastfontstyle = (isset($font[count($font) - 1]['style']) ? $font[count($font) - 1]['style'] : '');
6377 } else {
6378 $lastfontreqstyle = null;
6379 $lastfontstyle = null;
6381 if ($blockdir == 'ltr' && strpos($lastfontreqstyle, "I") !== false && strpos($lastfontstyle, "I") === false) { // Artificial italic
6382 $lastitalic = $this->FontSize * 0.15 * Mpdf::SCALE;
6383 } else {
6384 $lastitalic = 0;
6387 // Get PAGEBREAK TO TEST for height including the bottom border/padding
6388 $check_h = max($this->divheight, $stackHeight);
6390 // This fixes a proven bug...
6391 if ($endofblock && $newblock && $blockstate == 0 && !$content) {
6392 $check_h = 0;
6394 // but ? needs to fix potentially more widespread...
6395 // if (!$content) { $check_h = 0; }
6397 if ($this->blklvl > 0 && !$is_table) {
6398 if ($endofblock && $blockstate > 1) {
6399 if ($this->blk[$this->blklvl]['page_break_after_avoid']) {
6400 $check_h += $stackHeight;
6402 $check_h += ($this->blk[$this->blklvl]['padding_bottom'] + $this->blk[$this->blklvl]['border_bottom']['w']);
6404 if (($newblock && ($blockstate == 1 || $blockstate == 3) && $lineCount == 0) || ($endofblock && $blockstate == 3 && $lineCount == 0)) {
6405 $check_h += ($this->blk[$this->blklvl]['padding_top'] + $this->blk[$this->blklvl]['margin_top'] + $this->blk[$this->blklvl]['border_top']['w']);
6409 // Force PAGE break if column height cannot take check-height
6410 if ($this->ColActive && $check_h > ($this->PageBreakTrigger - $this->y0)) {
6411 $this->SetCol($this->NbCol - 1);
6414 // Avoid just border/background-color moved on to next page
6415 if ($endofblock && $blockstate > 1 && !$content) {
6416 $buff = $this->margBuffer;
6417 } else {
6418 $buff = 0;
6422 // PAGEBREAK
6423 if (!$is_table && ($this->y + $check_h) > ($this->PageBreakTrigger + $buff) and ! $this->InFooter and $this->AcceptPageBreak()) {
6424 $bak_x = $this->x; // Current X position
6425 // WORD SPACING
6426 $ws = $this->ws; // Word Spacing
6427 $charspacing = $this->charspacing; // Character Spacing
6428 $this->ResetSpacing();
6430 $this->AddPage($this->CurOrientation);
6432 $this->x = $bak_x;
6433 // Added to correct for OddEven Margins
6434 $currentx += $this->MarginCorrection;
6435 $this->x += $this->MarginCorrection;
6437 // WORD SPACING
6438 $this->SetSpacing($charspacing, $ws);
6442 /* -- COLUMNS -- */
6443 // COLS
6444 // COLUMN CHANGE
6445 if ($this->CurrCol != $oldcolumn) {
6446 $currentx += $this->ChangeColumn * ($this->ColWidth + $this->ColGap);
6447 $this->x += $this->ChangeColumn * ($this->ColWidth + $this->ColGap);
6448 $oldcolumn = $this->CurrCol;
6452 if ($this->ColActive && !$is_table) {
6453 $this->breakpoints[$this->CurrCol][] = $this->y;
6455 /* -- END COLUMNS -- */
6457 // TOP MARGIN
6458 if ($newblock && ($blockstate == 1 || $blockstate == 3) && ($this->blk[$this->blklvl]['margin_top']) && $lineCount == 0 && !$is_table) {
6459 $this->DivLn($this->blk[$this->blklvl]['margin_top'], $this->blklvl - 1, true, $this->blk[$this->blklvl]['margin_collapse']);
6460 if ($this->ColActive) {
6461 $this->breakpoints[$this->CurrCol][] = $this->y;
6462 } // *COLUMNS*
6465 if ($newblock && ($blockstate == 1 || $blockstate == 3) && $lineCount == 0 && !$is_table) {
6466 $this->blk[$this->blklvl]['y0'] = $this->y;
6467 $this->blk[$this->blklvl]['startpage'] = $this->page;
6468 if ($this->blk[$this->blklvl]['float']) {
6469 $this->blk[$this->blklvl]['float_start_y'] = $this->y;
6471 if ($this->ColActive) {
6472 $this->breakpoints[$this->CurrCol][] = $this->y;
6473 } // *COLUMNS*
6476 // Paragraph INDENT
6477 $WidthCorrection = 0;
6478 if (($newblock) && ($blockstate == 1 || $blockstate == 3) && isset($this->blk[$this->blklvl]['text_indent']) && ($lineCount == 0) && (!$is_table) && ($align != 'C')) {
6479 $ti = $this->sizeConverter->convert($this->blk[$this->blklvl]['text_indent'], $this->blk[$this->blklvl]['inner_width'], $this->blk[$this->blklvl]['InlineProperties']['size'], false); // mPDF 5.7.4
6480 $WidthCorrection = ($ti * Mpdf::SCALE);
6484 // PADDING and BORDER spacing/fill
6485 if (($newblock) && ($blockstate == 1 || $blockstate == 3) && (($this->blk[$this->blklvl]['padding_top']) || ($this->blk[$this->blklvl]['border_top'])) && ($lineCount == 0) && (!$is_table)) {
6486 // $state = 0 normal; 1 top; 2 bottom; 3 top and bottom
6487 $this->DivLn($this->blk[$this->blklvl]['padding_top'] + $this->blk[$this->blklvl]['border_top']['w'], -3, true, false, 1);
6488 if ($this->ColActive) {
6489 $this->breakpoints[$this->CurrCol][] = $this->y;
6490 } // *COLUMNS*
6491 $this->x = $currentx;
6495 // Added mPDF 3.0 Float DIV
6496 $fpaddingR = 0;
6497 $fpaddingL = 0;
6498 /* -- CSS-FLOAT -- */
6499 if (count($this->floatDivs)) {
6500 list($l_exists, $r_exists, $l_max, $r_max, $l_width, $r_width) = $this->GetFloatDivInfo($this->blklvl);
6501 if ($r_exists) {
6502 $fpaddingR = $r_width;
6504 if ($l_exists) {
6505 $fpaddingL = $l_width;
6508 /* -- END CSS-FLOAT -- */
6510 $usey = $this->y + 0.002;
6511 if (($newblock) && ($blockstate == 1 || $blockstate == 3) && ($lineCount == 0)) {
6512 $usey += $this->blk[$this->blklvl]['margin_top'] + $this->blk[$this->blklvl]['padding_top'] + $this->blk[$this->blklvl]['border_top']['w'];
6514 /* -- CSS-IMAGE-FLOAT -- */
6515 // If float exists at this level
6516 if (isset($this->floatmargins['R']) && $usey <= $this->floatmargins['R']['y1'] && $usey >= $this->floatmargins['R']['y0'] && !$this->floatmargins['R']['skipline']) {
6517 $fpaddingR += $this->floatmargins['R']['w'];
6519 if (isset($this->floatmargins['L']) && $usey <= $this->floatmargins['L']['y1'] && $usey >= $this->floatmargins['L']['y0'] && !$this->floatmargins['L']['skipline']) {
6520 $fpaddingL += $this->floatmargins['L']['w'];
6522 /* -- END CSS-IMAGE-FLOAT -- */
6525 if ($content) {
6526 // In FinishFlowing Block no lines are justified as it is always last line
6527 // but if CJKorphan has allowed content width to go over max width, use J charspacing to compress line
6528 // JUSTIFICATION J - NOT!
6529 $nb_carac = 0;
6530 $nb_spaces = 0;
6531 $jcharspacing = 0;
6532 $jkashida = 0;
6533 $jws = 0;
6534 $inclCursive = false;
6535 $dottab = false;
6536 foreach ($content as $k => $chunk) {
6537 if (!isset($this->objectbuffer[$k]) || (isset($this->objectbuffer[$k]) && !$this->objectbuffer[$k])) {
6538 $nb_carac += mb_strlen($chunk, $this->mb_enc);
6539 $nb_spaces += mb_substr_count($chunk, ' ', $this->mb_enc);
6540 // mPDF 6
6541 // Use GPOS OTL
6542 $this->restoreFont($font[$k], false);
6543 if (isset($this->CurrentFont['useOTL']) && $this->CurrentFont['useOTL']) {
6544 if (isset($cOTLdata[$k]['group']) && $cOTLdata[$k]['group']) {
6545 $nb_marks = substr_count($cOTLdata[$k]['group'], 'M');
6546 $nb_carac -= $nb_marks;
6548 if (preg_match("/([" . $this->pregCURSchars . "])/u", $chunk)) {
6549 $inclCursive = true;
6552 } else {
6553 $nb_carac ++; // mPDF 6 allow spacing for inline object
6554 if ($this->objectbuffer[$k]['type'] == 'dottab') {
6555 $dottab = $this->objectbuffer[$k]['outdent'];
6560 // DIRECTIONALITY RTL
6561 $chunkorder = range(0, count($content) - 1); // mPDF 6
6562 /* -- OTL -- */
6563 // mPDF 6
6564 if ($blockdir == 'rtl' || $this->biDirectional) {
6565 $this->otl->bidiReorder($chunkorder, $content, $cOTLdata, $blockdir);
6566 // From this point on, $content and $cOTLdata may contain more elements (and re-ordered) compared to
6567 // $this->objectbuffer and $font ($chunkorder contains the mapping)
6569 /* -- END OTL -- */
6571 // Remove any XAdvance from OTL data at end of line
6572 // And correct for XPlacement on last character
6573 // BIDI is applied
6574 foreach ($chunkorder as $aord => $k) {
6575 if (count($cOTLdata)) {
6576 $this->restoreFont($font[$k], false);
6577 // ...FinishFlowingBlock...
6578 if ($aord == count($chunkorder) - 1 && isset($cOTLdata[$aord]['group'])) { // Last chunk on line
6579 $nGPOS = strlen($cOTLdata[$aord]['group']) - 1; // Last character
6580 if (isset($cOTLdata[$aord]['GPOSinfo'][$nGPOS]['XAdvanceL']) || isset($cOTLdata[$aord]['GPOSinfo'][$nGPOS]['XAdvanceR'])) {
6581 if (isset($cOTLdata[$aord]['GPOSinfo'][$nGPOS]['XAdvanceL'])) {
6582 $w = $cOTLdata[$aord]['GPOSinfo'][$nGPOS]['XAdvanceL'] * 1000 / $this->CurrentFont['unitsPerEm'];
6583 } else {
6584 $w = $cOTLdata[$aord]['GPOSinfo'][$nGPOS]['XAdvanceR'] * 1000 / $this->CurrentFont['unitsPerEm'];
6586 $w *= ($this->FontSize / 1000);
6587 $contentWidth -= $w * Mpdf::SCALE;
6588 $cOTLdata[$aord]['GPOSinfo'][$nGPOS]['XAdvanceL'] = 0;
6589 $cOTLdata[$aord]['GPOSinfo'][$nGPOS]['XAdvanceR'] = 0;
6592 // If last character has an XPlacement set, adjust width calculation, and add to XAdvance to account for it
6593 if (isset($cOTLdata[$aord]['GPOSinfo'][$nGPOS]['XPlacement'])) {
6594 $w = -$cOTLdata[$aord]['GPOSinfo'][$nGPOS]['XPlacement'] * 1000 / $this->CurrentFont['unitsPerEm'];
6595 $w *= ($this->FontSize / 1000);
6596 $contentWidth -= $w * Mpdf::SCALE;
6597 $cOTLdata[$aord]['GPOSinfo'][$nGPOS]['XAdvanceL'] = $cOTLdata[$aord]['GPOSinfo'][$nGPOS]['XPlacement'];
6598 $cOTLdata[$aord]['GPOSinfo'][$nGPOS]['XAdvanceR'] = $cOTLdata[$aord]['GPOSinfo'][$nGPOS]['XPlacement'];
6604 // if it's justified, we need to find the char/word spacing (or if orphans have allowed length of line to go over the maxwidth)
6605 // If "orphans" in fact is just a final space - ignore this
6606 $lastchar = mb_substr($content[(count($chunkorder) - 1)], mb_strlen($content[(count($chunkorder) - 1)], $this->mb_enc) - 1, 1, $this->mb_enc);
6607 if (preg_match("/[" . $this->CJKoverflow . "]/u", $lastchar)) {
6608 $CJKoverflow = true;
6609 } else {
6610 $CJKoverflow = false;
6612 if ((((($contentWidth + $lastitalic) > $maxWidth) && ($content[(count($chunkorder) - 1)] != ' ') ) ||
6613 (!$endofblock && $align == 'J' && ($next == 'image' || $next == 'select' || $next == 'input' || $next == 'textarea' || ($next == 'br' && $this->justifyB4br)))) && !($CJKoverflow && $this->allowCJKoverflow)) {
6614 // WORD SPACING
6615 list($jcharspacing, $jws, $jkashida) = $this->GetJspacing($nb_carac, $nb_spaces, ($maxWidth - $lastitalic - $contentWidth - $WidthCorrection - (($this->cMarginL + $this->cMarginR) * Mpdf::SCALE) - ($paddingL + $paddingR + (($fpaddingL + $fpaddingR) * Mpdf::SCALE) )), $inclCursive, $cOTLdata);
6616 } /* -- CJK-FONTS -- */ elseif ($this->checkCJK && $align == 'J' && $CJKoverflow && $this->allowCJKoverflow && $this->CJKforceend) {
6617 // force-end overhang
6618 $hanger = mb_substr($content[(count($chunkorder) - 1)], mb_strlen($content[(count($chunkorder) - 1)], $this->mb_enc) - 1, 1, $this->mb_enc);
6619 if (preg_match("/[" . $this->CJKoverflow . "]/u", $hanger)) {
6620 $content[(count($chunkorder) - 1)] = mb_substr($content[(count($chunkorder) - 1)], 0, mb_strlen($content[(count($chunkorder) - 1)], $this->mb_enc) - 1, $this->mb_enc);
6621 $this->restoreFont($font[$chunkorder[count($chunkorder) - 1]], false);
6622 $contentWidth -= $this->GetStringWidth($hanger) * Mpdf::SCALE;
6623 $nb_carac -= 1;
6624 list($jcharspacing, $jws, $jkashida) = $this->GetJspacing($nb_carac, $nb_spaces, ($maxWidth - $lastitalic - $contentWidth - $WidthCorrection - (($this->cMarginL + $this->cMarginR) * Mpdf::SCALE) - ($paddingL + $paddingR + (($fpaddingL + $fpaddingR) * Mpdf::SCALE) )), $inclCursive, $cOTLdata);
6626 } /* -- END CJK-FONTS -- */
6628 // Check if will fit at word/char spacing of previous line - if so continue it
6629 // but only allow a maximum of $this->jSmaxWordLast and $this->jSmaxCharLast
6630 elseif ($contentWidth < ($maxWidth - $lastitalic - $WidthCorrection - (($this->cMarginL + $this->cMarginR) * Mpdf::SCALE) - ($paddingL + $paddingR + (($fpaddingL + $fpaddingR) * Mpdf::SCALE))) && !$this->fixedlSpacing) {
6631 if ($this->ws > $this->jSmaxWordLast) {
6632 $jws = $this->jSmaxWordLast;
6634 if ($this->charspacing > $this->jSmaxCharLast) {
6635 $jcharspacing = $this->jSmaxCharLast;
6637 $check = $maxWidth - $lastitalic - $WidthCorrection - $contentWidth - (($this->cMarginL + $this->cMarginR) * Mpdf::SCALE) - ($paddingL + $paddingR + (($fpaddingL + $fpaddingR) * Mpdf::SCALE) ) - ( $jcharspacing * $nb_carac) - ( $jws * $nb_spaces);
6638 if ($check <= 0) {
6639 $jcharspacing = 0;
6640 $jws = 0;
6644 $empty = $maxWidth - $lastitalic - $WidthCorrection - $contentWidth - (($this->cMarginL + $this->cMarginR) * Mpdf::SCALE) - ($paddingL + $paddingR + (($fpaddingL + $fpaddingR) * Mpdf::SCALE) );
6647 $empty -= ($jcharspacing * ($nb_carac - 1)); // mPDF 6 nb_carac MINUS 1
6648 $empty -= ($jws * $nb_spaces);
6649 $empty -= ($jkashida);
6651 $empty /= Mpdf::SCALE;
6653 if (!$is_table) {
6654 $this->maxPosR = max($this->maxPosR, ($this->w - $this->rMargin - $this->blk[$this->blklvl]['outer_right_margin'] - $empty));
6655 $this->maxPosL = min($this->maxPosL, ($this->lMargin + $this->blk[$this->blklvl]['outer_left_margin'] + $empty));
6658 $arraysize = count($chunkorder);
6660 $margins = ($this->cMarginL + $this->cMarginR) + ($ipaddingL + $ipaddingR + $fpaddingR + $fpaddingR );
6662 if (!$is_table) {
6663 $this->DivLn($stackHeight, $this->blklvl, false);
6664 } // false -> don't advance y
6666 $this->x = $currentx + $this->cMarginL + $ipaddingL + $fpaddingL;
6667 if ($dottab !== false && $blockdir == 'rtl') {
6668 $this->x -= $dottab;
6669 } elseif ($align == 'R') {
6670 $this->x += $empty;
6671 } elseif ($align == 'J' && $blockdir == 'rtl') {
6672 $this->x += $empty;
6673 } elseif ($align == 'C') {
6674 $this->x += ($empty / 2);
6677 // Paragraph INDENT
6678 $WidthCorrection = 0;
6679 if (($newblock) && ($blockstate == 1 || $blockstate == 3) && isset($this->blk[$this->blklvl]['text_indent']) && ($lineCount == 0) && (!$is_table) && ($align != 'C')) {
6680 $ti = $this->sizeConverter->convert($this->blk[$this->blklvl]['text_indent'], $this->blk[$this->blklvl]['inner_width'], $this->blk[$this->blklvl]['InlineProperties']['size'], false); // mPDF 5.7.4
6681 if ($blockdir != 'rtl') {
6682 $this->x += $ti;
6683 } // mPDF 6
6686 foreach ($chunkorder as $aord => $k) { // mPDF 5.7
6687 $chunk = $content[$aord];
6688 if (isset($this->objectbuffer[$k]) && $this->objectbuffer[$k]) {
6689 $xadj = $this->x - $this->objectbuffer[$k]['OUTER-X'];
6690 $this->objectbuffer[$k]['OUTER-X'] += $xadj;
6691 $this->objectbuffer[$k]['BORDER-X'] += $xadj;
6692 $this->objectbuffer[$k]['INNER-X'] += $xadj;
6694 if ($this->objectbuffer[$k]['type'] == 'listmarker') {
6695 $this->objectbuffer[$k]['lineBox'] = $lineBox[-1]; // Block element details for glyph-origin
6697 $yadj = $this->y - $this->objectbuffer[$k]['OUTER-Y'];
6698 if ($this->objectbuffer[$k]['type'] == 'dottab') { // mPDF 6 DOTTAB
6699 $this->objectbuffer[$k]['lineBox'] = $lineBox[$k]; // element details for glyph-origin
6701 if ($this->objectbuffer[$k]['type'] != 'dottab') { // mPDF 6 DOTTAB
6702 $yadj += $lineBox[$k]['top'];
6704 $this->objectbuffer[$k]['OUTER-Y'] += $yadj;
6705 $this->objectbuffer[$k]['BORDER-Y'] += $yadj;
6706 $this->objectbuffer[$k]['INNER-Y'] += $yadj;
6709 $this->restoreFont($font[$k]); // mPDF 5.7
6711 if ($is_table && substr($align, 0, 1) == 'D' && $aord == 0) {
6712 $dp = $this->decimal_align[substr($align, 0, 2)];
6713 $s = preg_split('/' . preg_quote($dp, '/') . '/', $content[0], 2); // ? needs to be /u if not core
6714 $s0 = $this->GetStringWidth($s[0], false);
6715 $this->x += ($this->decimal_offset - $s0);
6718 $this->SetSpacing(($this->fixedlSpacing * Mpdf::SCALE) + $jcharspacing, ($this->fixedlSpacing + $this->minwSpacing) * Mpdf::SCALE + $jws);
6719 $this->fixedlSpacing = false;
6720 $this->minwSpacing = 0;
6722 $save_vis = $this->visibility;
6723 if (isset($this->textparam['visibility']) && $this->textparam['visibility'] && $this->textparam['visibility'] != $this->visibility) {
6724 $this->SetVisibility($this->textparam['visibility']);
6727 // *********** SPAN BACKGROUND COLOR ***************** //
6728 if (isset($this->spanbgcolor) && $this->spanbgcolor) {
6729 $cor = $this->spanbgcolorarray;
6730 $this->SetFColor($cor);
6731 $save_fill = $fill;
6732 $spanfill = 1;
6733 $fill = 1;
6735 if (!empty($this->spanborddet)) {
6736 if (strpos($contentB[$k], 'L') !== false && isset($this->spanborddet['L'])) {
6737 $this->x += $this->spanborddet['L']['w'];
6739 if (strpos($contentB[$k], 'L') === false) {
6740 $this->spanborddet['L']['s'] = $this->spanborddet['L']['w'] = 0;
6742 if (strpos($contentB[$k], 'R') === false) {
6743 $this->spanborddet['R']['s'] = $this->spanborddet['R']['w'] = 0;
6746 // WORD SPACING
6747 // mPDF 5.7.1
6748 $stringWidth = $this->GetStringWidth($chunk, true, (isset($cOTLdata[$aord]) ? $cOTLdata[$aord] : false), $this->textvar);
6749 $nch = mb_strlen($chunk, $this->mb_enc);
6750 // Use GPOS OTL
6751 if (isset($this->CurrentFont['useOTL']) && $this->CurrentFont['useOTL']) {
6752 if (isset($cOTLdata[$aord]['group']) && $cOTLdata[$aord]['group']) {
6753 $nch -= substr_count($cOTLdata[$aord]['group'], 'M');
6756 $stringWidth += ( $this->charspacing * $nch / Mpdf::SCALE );
6758 $stringWidth += ( $this->ws * mb_substr_count($chunk, ' ', $this->mb_enc) / Mpdf::SCALE );
6760 if (isset($this->objectbuffer[$k])) {
6761 if ($this->objectbuffer[$k]['type'] == 'dottab') {
6762 $this->objectbuffer[$k]['OUTER-WIDTH'] +=$empty;
6763 $this->objectbuffer[$k]['OUTER-WIDTH'] +=$this->objectbuffer[$k]['outdent'];
6765 // LIST MARKERS // mPDF 6 Lists
6766 if ($this->objectbuffer[$k]['type'] == 'image' && isset($this->objectbuffer[$k]['listmarker']) && $this->objectbuffer[$k]['listmarker'] && $this->objectbuffer[$k]['listmarkerposition'] == 'outside') {
6767 // do nothing
6768 } else {
6769 $stringWidth = $this->objectbuffer[$k]['OUTER-WIDTH'];
6773 if ($stringWidth == 0) {
6774 $stringWidth = 0.000001;
6776 if ($aord == $arraysize - 1) { // mPDF 5.7
6777 // mPDF 5.7.1
6778 if ($this->checkCJK && $CJKoverflow && $align == 'J' && $this->allowCJKoverflow && $hanger && $this->CJKforceend) {
6779 // force-end overhang
6780 $this->Cell($stringWidth, $stackHeight, $chunk, '', 0, '', $fill, $this->HREF, $currentx, 0, 0, 'M', $fill, true, (isset($cOTLdata[$aord]) ? $cOTLdata[$aord] : false), $this->textvar, (isset($lineBox[$k]) ? $lineBox[$k] : false)); // mPDF 5.7.1
6781 $this->Cell($this->GetStringWidth($hanger), $stackHeight, $hanger, '', 1, '', $fill, $this->HREF, $currentx, 0, 0, 'M', $fill, true, (isset($cOTLdata[$aord]) ? $cOTLdata[$aord] : false), $this->textvar, (isset($lineBox[$k]) ? $lineBox[$k] : false)); // mPDF 5.7.1
6782 } else {
6783 $this->Cell($stringWidth, $stackHeight, $chunk, '', 1, '', $fill, $this->HREF, $currentx, 0, 0, 'M', $fill, true, (isset($cOTLdata[$aord]) ? $cOTLdata[$aord] : false), $this->textvar, (isset($lineBox[$k]) ? $lineBox[$k] : false)); // mPDF 5.7.1
6785 } else {
6786 $this->Cell($stringWidth, $stackHeight, $chunk, '', 0, '', $fill, $this->HREF, 0, 0, 0, 'M', $fill, true, (isset($cOTLdata[$aord]) ? $cOTLdata[$aord] : false), $this->textvar, (isset($lineBox[$k]) ? $lineBox[$k] : false)); // first or middle part // mPDF 5.7.1
6790 if (!empty($this->spanborddet)) {
6791 if (strpos($contentB[$k], 'R') !== false && $aord != $arraysize - 1) {
6792 $this->x += $this->spanborddet['R']['w'];
6795 // *********** SPAN BACKGROUND COLOR OFF - RESET BLOCK BGCOLOR ***************** //
6796 if (isset($spanfill) && $spanfill) {
6797 $fill = $save_fill;
6798 $spanfill = 0;
6799 if ($fill) {
6800 $this->SetFColor($bcor);
6803 if (isset($this->textparam['visibility']) && $this->textparam['visibility'] && $this->visibility != $save_vis) {
6804 $this->SetVisibility($save_vis);
6808 $this->printobjectbuffer($is_table, $blockdir);
6809 $this->objectbuffer = [];
6810 $this->ResetSpacing();
6811 } // END IF CONTENT
6813 /* -- CSS-IMAGE-FLOAT -- */
6814 // Update values if set to skipline
6815 if ($this->floatmargins) {
6816 $this->_advanceFloatMargins();
6820 if ($endofblock && $blockstate > 1) {
6821 // If float exists at this level
6822 if (isset($this->floatmargins['R']['y1'])) {
6823 $fry1 = $this->floatmargins['R']['y1'];
6824 } else {
6825 $fry1 = 0;
6827 if (isset($this->floatmargins['L']['y1'])) {
6828 $fly1 = $this->floatmargins['L']['y1'];
6829 } else {
6830 $fly1 = 0;
6832 if ($this->y < $fry1 || $this->y < $fly1) {
6833 $drop = max($fry1, $fly1) - $this->y;
6834 $this->DivLn($drop);
6835 $this->x = $currentx;
6838 /* -- END CSS-IMAGE-FLOAT -- */
6841 // PADDING and BORDER spacing/fill
6842 if ($endofblock && ($blockstate > 1) && ($this->blk[$this->blklvl]['padding_bottom'] || $this->blk[$this->blklvl]['border_bottom'] || $this->blk[$this->blklvl]['css_set_height']) && (!$is_table)) {
6843 // If CSS height set, extend bottom - if on same page as block started, and CSS HEIGHT > actual height,
6844 // and does not force pagebreak
6845 $extra = 0;
6846 if (isset($this->blk[$this->blklvl]['css_set_height']) && $this->blk[$this->blklvl]['css_set_height'] && $this->blk[$this->blklvl]['startpage'] == $this->page) {
6847 // predicted height
6848 $h1 = ($this->y - $this->blk[$this->blklvl]['y0']) + $this->blk[$this->blklvl]['padding_bottom'] + $this->blk[$this->blklvl]['border_bottom']['w'];
6849 if ($h1 < ($this->blk[$this->blklvl]['css_set_height'] + $this->blk[$this->blklvl]['padding_bottom'] + $this->blk[$this->blklvl]['padding_top'])) {
6850 $extra = ($this->blk[$this->blklvl]['css_set_height'] + $this->blk[$this->blklvl]['padding_bottom'] + $this->blk[$this->blklvl]['padding_top']) - $h1;
6852 if ($this->y + $this->blk[$this->blklvl]['padding_bottom'] + $this->blk[$this->blklvl]['border_bottom']['w'] + $extra > $this->PageBreakTrigger) {
6853 $extra = $this->PageBreakTrigger - ($this->y + $this->blk[$this->blklvl]['padding_bottom'] + $this->blk[$this->blklvl]['border_bottom']['w']);
6857 // $state = 0 normal; 1 top; 2 bottom; 3 top and bottom
6858 $this->DivLn($this->blk[$this->blklvl]['padding_bottom'] + $this->blk[$this->blklvl]['border_bottom']['w'] + $extra, -3, true, false, 2);
6859 $this->x = $currentx;
6861 if ($this->ColActive) {
6862 $this->breakpoints[$this->CurrCol][] = $this->y;
6863 } // *COLUMNS*
6866 // SET Bottom y1 of block (used for painting borders)
6867 if (($endofblock) && ($blockstate > 1) && (!$is_table)) {
6868 $this->blk[$this->blklvl]['y1'] = $this->y;
6871 // BOTTOM MARGIN
6872 if (($endofblock) && ($blockstate > 1) && ($this->blk[$this->blklvl]['margin_bottom']) && (!$is_table)) {
6873 if ($this->y + $this->blk[$this->blklvl]['margin_bottom'] < $this->PageBreakTrigger and ! $this->InFooter) {
6874 $this->DivLn($this->blk[$this->blklvl]['margin_bottom'], $this->blklvl - 1, true, $this->blk[$this->blklvl]['margin_collapse']);
6875 if ($this->ColActive) {
6876 $this->breakpoints[$this->CurrCol][] = $this->y;
6877 } // *COLUMNS*
6881 // Reset lineheight
6882 $stackHeight = $this->divheight;
6885 function printobjectbuffer($is_table = false, $blockdir = false)
6887 if (!$blockdir) {
6888 $blockdir = $this->directionality;
6891 if ($is_table && $this->shrin_k > 1) {
6892 $k = $this->shrin_k;
6893 } else {
6894 $k = 1;
6897 $save_y = $this->y;
6898 $save_x = $this->x;
6900 $save_currentfontfamily = $this->FontFamily;
6901 $save_currentfontsize = $this->FontSizePt;
6902 $save_currentfontstyle = $this->FontStyle;
6904 if ($blockdir == 'rtl') {
6905 $rtlalign = 'R';
6906 } else {
6907 $rtlalign = 'L';
6910 foreach ($this->objectbuffer as $ib => $objattr) {
6912 if ($objattr['type'] == 'bookmark' || $objattr['type'] == 'indexentry' || $objattr['type'] == 'toc') {
6913 $x = $objattr['OUTER-X'];
6914 $y = $objattr['OUTER-Y'];
6915 $this->y = $y - $this->FontSize / 2;
6916 $this->x = $x;
6917 if ($objattr['type'] == 'bookmark') {
6918 $this->Bookmark($objattr['CONTENT'], $objattr['bklevel'], $y - $this->FontSize);
6919 } // *BOOKMARKS*
6920 if ($objattr['type'] == 'indexentry') {
6921 $this->IndexEntry($objattr['CONTENT']);
6922 } // *INDEX*
6923 if ($objattr['type'] == 'toc') {
6924 $this->TOC_Entry($objattr['CONTENT'], $objattr['toclevel'], (isset($objattr['toc_id']) ? $objattr['toc_id'] : ''));
6925 } // *TOC*
6926 } /* -- ANNOTATIONS -- */ elseif ($objattr['type'] == 'annot') {
6927 if ($objattr['POS-X']) {
6928 $x = $objattr['POS-X'];
6929 } elseif ($this->annotMargin <> 0) {
6930 $x = -$objattr['OUTER-X'];
6931 } else {
6932 $x = $objattr['OUTER-X'];
6934 if ($objattr['POS-Y']) {
6935 $y = $objattr['POS-Y'];
6936 } else {
6937 $y = $objattr['OUTER-Y'] - $this->FontSize / 2;
6939 // Create a dummy entry in the _out/columnBuffer with position sensitive data,
6940 // linking $y-1 in the Columnbuffer with entry in $this->columnAnnots
6941 // and when columns are split in length will not break annotation from current line
6942 $this->y = $y - 1;
6943 $this->x = $x - 1;
6944 $this->Line($x - 1, $y - 1, $x - 1, $y - 1);
6945 $this->Annotation($objattr['CONTENT'], $x, $y, $objattr['ICON'], $objattr['AUTHOR'], $objattr['SUBJECT'], $objattr['OPACITY'], $objattr['COLOR'], (isset($objattr['POPUP']) ? $objattr['POPUP'] : ''), (isset($objattr['FILE']) ? $objattr['FILE'] : ''));
6946 } /* -- END ANNOTATIONS -- */ else {
6947 $y = $objattr['OUTER-Y'];
6948 $x = $objattr['OUTER-X'];
6949 $w = $objattr['OUTER-WIDTH'];
6950 $h = $objattr['OUTER-HEIGHT'];
6951 if (isset($objattr['text'])) {
6952 $texto = $objattr['text'];
6954 $this->y = $y;
6955 $this->x = $x;
6956 if (isset($objattr['fontfamily'])) {
6957 $this->SetFont($objattr['fontfamily'], '', $objattr['fontsize']);
6961 // HR
6962 if ($objattr['type'] == 'hr') {
6963 $this->SetDColor($objattr['color']);
6964 switch ($objattr['align']) {
6965 case 'C':
6966 $empty = $objattr['OUTER-WIDTH'] - $objattr['INNER-WIDTH'];
6967 $empty /= 2;
6968 $x += $empty;
6969 break;
6970 case 'R':
6971 $empty = $objattr['OUTER-WIDTH'] - $objattr['INNER-WIDTH'];
6972 $x += $empty;
6973 break;
6975 $oldlinewidth = $this->LineWidth;
6976 $this->SetLineWidth($objattr['linewidth'] / $k);
6977 $this->y += ($objattr['linewidth'] / 2) + $objattr['margin_top'] / $k;
6978 $this->Line($x, $this->y, $x + $objattr['INNER-WIDTH'], $this->y);
6979 $this->SetLineWidth($oldlinewidth);
6980 $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
6982 // IMAGE
6983 if ($objattr['type'] == 'image') {
6984 // mPDF 5.7.3 TRANSFORMS
6985 if (isset($objattr['transform'])) {
6986 $this->_out("\n" . '% BTR'); // Begin Transform
6988 if (isset($objattr['z-index']) && $objattr['z-index'] > 0 && $this->current_layer == 0) {
6989 $this->BeginLayer($objattr['z-index']);
6991 if (isset($objattr['visibility']) && $objattr['visibility'] != 'visible' && $objattr['visibility']) {
6992 $this->SetVisibility($objattr['visibility']);
6994 if (isset($objattr['opacity'])) {
6995 $this->SetAlpha($objattr['opacity']);
6998 $obiw = $objattr['INNER-WIDTH'];
6999 $obih = $objattr['INNER-HEIGHT'];
7001 $sx = $objattr['orig_w'] ? ($objattr['INNER-WIDTH'] * Mpdf::SCALE / $objattr['orig_w']) : INF;
7002 $sy = $objattr['orig_h'] ? ($objattr['INNER-HEIGHT'] * Mpdf::SCALE / $objattr['orig_h']) : INF;
7004 $rotate = 0;
7005 if (isset($objattr['ROTATE'])) {
7006 $rotate = $objattr['ROTATE'];
7009 if ($rotate == 90) {
7010 // Clockwise
7011 $obiw = $objattr['INNER-HEIGHT'];
7012 $obih = $objattr['INNER-WIDTH'];
7013 $tr = $this->transformTranslate(0, -$objattr['INNER-WIDTH'], true);
7014 $tr .= ' ' . $this->transformRotate(90, $objattr['INNER-X'], ($objattr['INNER-Y'] + $objattr['INNER-WIDTH']), true);
7015 $sx = $obiw * Mpdf::SCALE / $objattr['orig_h'];
7016 $sy = $obih * Mpdf::SCALE / $objattr['orig_w'];
7017 } elseif ($rotate == -90 || $rotate == 270) {
7018 // AntiClockwise
7019 $obiw = $objattr['INNER-HEIGHT'];
7020 $obih = $objattr['INNER-WIDTH'];
7021 $tr = $this->transformTranslate($objattr['INNER-WIDTH'], ($objattr['INNER-HEIGHT'] - $objattr['INNER-WIDTH']), true);
7022 $tr .= ' ' . $this->transformRotate(-90, $objattr['INNER-X'], ($objattr['INNER-Y'] + $objattr['INNER-WIDTH']), true);
7023 $sx = $obiw * Mpdf::SCALE / $objattr['orig_h'];
7024 $sy = $obih * Mpdf::SCALE / $objattr['orig_w'];
7025 } elseif ($rotate == 180) {
7026 // Mirror
7027 $tr = $this->transformTranslate($objattr['INNER-WIDTH'], -$objattr['INNER-HEIGHT'], true);
7028 $tr .= ' ' . $this->transformRotate(180, $objattr['INNER-X'], ($objattr['INNER-Y'] + $objattr['INNER-HEIGHT']), true);
7029 } else {
7030 $tr = '';
7032 $tr = trim($tr);
7033 if ($tr) {
7034 $tr .= ' ';
7036 $gradmask = '';
7038 // mPDF 5.7.3 TRANSFORMS
7039 $tr2 = '';
7040 if (isset($objattr['transform'])) {
7041 $maxsize_x = $w;
7042 $maxsize_y = $h;
7043 $cx = $x + $w / 2;
7044 $cy = $y + $h / 2;
7045 preg_match_all('/(translatex|translatey|translate|scalex|scaley|scale|rotate|skewX|skewY|skew)\((.*?)\)/is', $objattr['transform'], $m);
7046 if (count($m[0])) {
7047 for ($i = 0; $i < count($m[0]); $i++) {
7048 $c = strtolower($m[1][$i]);
7049 $v = trim($m[2][$i]);
7050 $vv = preg_split('/[ ,]+/', $v);
7051 if ($c == 'translate' && count($vv)) {
7052 $translate_x = $this->sizeConverter->convert($vv[0], $maxsize_x, false, false);
7053 if (count($vv) == 2) {
7054 $translate_y = $this->sizeConverter->convert($vv[1], $maxsize_y, false, false);
7055 } else {
7056 $translate_y = 0;
7058 $tr2 .= $this->transformTranslate($translate_x, $translate_y, true) . ' ';
7059 } elseif ($c == 'translatex' && count($vv)) {
7060 $translate_x = $this->sizeConverter->convert($vv[0], $maxsize_x, false, false);
7061 $tr2 .= $this->transformTranslate($translate_x, 0, true) . ' ';
7062 } elseif ($c == 'translatey' && count($vv)) {
7063 $translate_y = $this->sizeConverter->convert($vv[1], $maxsize_y, false, false);
7064 $tr2 .= $this->transformTranslate(0, $translate_y, true) . ' ';
7065 } elseif ($c == 'scale' && count($vv)) {
7066 $scale_x = $vv[0] * 100;
7067 if (count($vv) == 2) {
7068 $scale_y = $vv[1] * 100;
7069 } else {
7070 $scale_y = $scale_x;
7072 $tr2 .= $this->transformScale($scale_x, $scale_y, $cx, $cy, true) . ' ';
7073 } elseif ($c == 'scalex' && count($vv)) {
7074 $scale_x = $vv[0] * 100;
7075 $tr2 .= $this->transformScale($scale_x, 0, $cx, $cy, true) . ' ';
7076 } elseif ($c == 'scaley' && count($vv)) {
7077 $scale_y = $vv[1] * 100;
7078 $tr2 .= $this->transformScale(0, $scale_y, $cx, $cy, true) . ' ';
7079 } elseif ($c == 'skew' && count($vv)) {
7080 $angle_x = $this->ConvertAngle($vv[0], false);
7081 if (count($vv) == 2) {
7082 $angle_y = $this->ConvertAngle($vv[1], false);
7083 } else {
7084 $angle_y = 0;
7086 $tr2 .= $this->transformSkew($angle_x, $angle_y, $cx, $cy, true) . ' ';
7087 } elseif ($c == 'skewx' && count($vv)) {
7088 $angle = $this->ConvertAngle($vv[0], false);
7089 $tr2 .= $this->transformSkew($angle, 0, $cx, $cy, true) . ' ';
7090 } elseif ($c == 'skewy' && count($vv)) {
7091 $angle = $this->ConvertAngle($vv[0], false);
7092 $tr2 .= $this->transformSkew(0, $angle, $cx, $cy, true) . ' ';
7093 } elseif ($c == 'rotate' && count($vv)) {
7094 $angle = $this->ConvertAngle($vv[0]);
7095 $tr2 .= $this->transformRotate($angle, $cx, $cy, true) . ' ';
7101 // LIST MARKERS (Images) // mPDF 6 Lists
7102 if (isset($objattr['listmarker']) && $objattr['listmarker'] && $objattr['listmarkerposition'] == 'outside') {
7103 $mw = $objattr['OUTER-WIDTH'];
7104 // NB If change marker-offset, also need to alter in function _getListMarkerWidth
7105 $adjx = $this->sizeConverter->convert($this->list_marker_offset, $this->FontSize);
7106 if ($objattr['dir'] == 'rtl') {
7107 $objattr['INNER-X'] += $adjx;
7108 } else {
7109 $objattr['INNER-X'] -= $adjx;
7110 $objattr['INNER-X'] -= $mw;
7113 // mPDF 5.7.3 TRANSFORMS / BACKGROUND COLOR
7114 // Transform also affects image background
7115 if ($tr2) {
7116 $this->_out('q ' . $tr2 . ' ');
7118 if (isset($objattr['bgcolor']) && $objattr['bgcolor']) {
7119 $bgcol = $objattr['bgcolor'];
7120 $this->SetFColor($bgcol);
7121 $this->Rect($x, $y, $w, $h, 'F');
7122 $this->SetFColor($this->colorConverter->convert(255, $this->PDFAXwarnings));
7124 if ($tr2) {
7125 $this->_out('Q');
7128 /* -- BACKGROUNDS -- */
7129 if (isset($objattr['GRADIENT-MASK'])) {
7130 $g = $this->gradient->parseMozGradient($objattr['GRADIENT-MASK']);
7131 if ($g) {
7132 $dummy = $this->gradient->Gradient($objattr['INNER-X'], $objattr['INNER-Y'], $obiw, $obih, $g['type'], $g['stops'], $g['colorspace'], $g['coords'], $g['extend'], true, true);
7133 $gradmask = '/TGS' . count($this->gradients) . ' gs ';
7136 /* -- END BACKGROUNDS -- */
7137 /* -- IMAGES-WMF -- */
7138 if (isset($objattr['itype']) && $objattr['itype'] == 'wmf') {
7139 $outstring = sprintf('q ' . $tr . $tr2 . '%.3F 0 0 %.3F %.3F %.3F cm /FO%d Do Q', $sx, -$sy, $objattr['INNER-X'] * Mpdf::SCALE - $sx * $objattr['wmf_x'], (($this->h - $objattr['INNER-Y']) * Mpdf::SCALE) + $sy * $objattr['wmf_y'], $objattr['ID']); // mPDF 5.7.3 TRANSFORMS
7140 } else { /* -- END IMAGES-WMF -- */
7141 if (isset($objattr['itype']) && $objattr['itype'] == 'svg') {
7142 $outstring = sprintf('q ' . $tr . $tr2 . '%.3F 0 0 %.3F %.3F %.3F cm /FO%d Do Q', $sx, -$sy, $objattr['INNER-X'] * Mpdf::SCALE - $sx * $objattr['wmf_x'], (($this->h - $objattr['INNER-Y']) * Mpdf::SCALE) + $sy * $objattr['wmf_y'], $objattr['ID']); // mPDF 5.7.3 TRANSFORMS
7143 } else {
7144 $outstring = sprintf("q " . $tr . $tr2 . "%.3F 0 0 %.3F %.3F %.3F cm " . $gradmask . "/I%d Do Q", $obiw * Mpdf::SCALE, $obih * Mpdf::SCALE, $objattr['INNER-X'] * Mpdf::SCALE, ($this->h - ($objattr['INNER-Y'] + $obih )) * Mpdf::SCALE, $objattr['ID']); // mPDF 5.7.3 TRANSFORMS
7147 $this->_out($outstring);
7148 // LINK
7149 if (isset($objattr['link'])) {
7150 $this->Link($objattr['INNER-X'], $objattr['INNER-Y'], $objattr['INNER-WIDTH'], $objattr['INNER-HEIGHT'], $objattr['link']);
7152 if (isset($objattr['opacity'])) {
7153 $this->SetAlpha(1);
7156 // mPDF 5.7.3 TRANSFORMS
7157 // Transform also affects image borders
7158 if ($tr2) {
7159 $this->_out('q ' . $tr2 . ' ');
7161 if ((isset($objattr['border_top']) && $objattr['border_top'] > 0) || (isset($objattr['border_left']) && $objattr['border_left'] > 0) || (isset($objattr['border_right']) && $objattr['border_right'] > 0) || (isset($objattr['border_bottom']) && $objattr['border_bottom'] > 0)) {
7162 $this->PaintImgBorder($objattr, $is_table);
7164 if ($tr2) {
7165 $this->_out('Q');
7168 if (isset($objattr['visibility']) && $objattr['visibility'] != 'visible' && $objattr['visibility']) {
7169 $this->SetVisibility('visible');
7171 if (isset($objattr['z-index']) && $objattr['z-index'] > 0 && $this->current_layer == 0) {
7172 $this->EndLayer();
7174 // mPDF 5.7.3 TRANSFORMS
7175 if (isset($objattr['transform'])) {
7176 $this->_out("\n" . '% ETR'); // End Transform
7180 if ($objattr['type'] === 'barcode') {
7182 $bgcol = $this->colorConverter->convert(255, $this->PDFAXwarnings);
7184 if (isset($objattr['bgcolor']) && $objattr['bgcolor']) {
7185 $bgcol = $objattr['bgcolor'];
7188 $col = $this->colorConverter->convert(0, $this->PDFAXwarnings);
7190 if (isset($objattr['color']) && $objattr['color']) {
7191 $col = $objattr['color'];
7194 $this->SetFColor($bgcol);
7195 $this->Rect($objattr['BORDER-X'], $objattr['BORDER-Y'], $objattr['BORDER-WIDTH'], $objattr['BORDER-HEIGHT'], 'F');
7196 $this->SetFColor($this->colorConverter->convert(255, $this->PDFAXwarnings));
7198 if (isset($objattr['BORDER-WIDTH'])) {
7199 $this->PaintImgBorder($objattr, $is_table);
7202 $barcodeTypes = ['EAN13', 'ISBN', 'ISSN', 'UPCA', 'UPCE', 'EAN8'];
7203 if (in_array($objattr['btype'], $barcodeTypes, true)) {
7205 $this->WriteBarcode(
7206 $objattr['code'],
7207 $objattr['showtext'],
7208 $objattr['INNER-X'],
7209 $objattr['INNER-Y'],
7210 $objattr['bsize'],
7216 $objattr['bheight'],
7217 $bgcol,
7218 $col,
7219 $objattr['btype'],
7220 $objattr['bsupp'],
7221 (isset($objattr['bsupp_code']) ? $objattr['bsupp_code'] : ''),
7225 } elseif ($objattr['btype'] === 'QR') {
7227 $barcodeContent = str_replace('\r\n', "\r\n", $objattr['code']);
7228 $barcodeContent = str_replace('\n', "\n", $barcodeContent);
7230 $this->qrcode = new QrCode\QrCode($barcodeContent, $objattr['errorlevel']);
7231 if ($objattr['disableborder']) {
7232 $this->qrcode->disableBorder();
7235 $bgColor = [255, 255, 255];
7236 if ($objattr['bgcolor']) {
7237 $bgColor = array_map(
7238 function ($col) {
7239 return intval(255 * floatval($col));
7241 explode(" ", $this->SetColor($objattr['bgcolor'], 'CodeOnly'))
7244 $color = [0, 0, 0];
7245 if ($objattr['color']) {
7246 $color = array_map(
7247 function ($col) {
7248 return intval(255 * floatval($col));
7250 explode(" ", $this->SetColor($objattr['color'], 'CodeOnly'))
7254 $this->qrcode->displayFPDF(
7255 $this,
7256 $objattr['INNER-X'],
7257 $objattr['INNER-Y'],
7258 $objattr['bsize'] * 25,
7259 $bgColor,
7260 $color
7263 } else {
7265 $this->WriteBarcode2(
7266 $objattr['code'],
7267 $objattr['INNER-X'],
7268 $objattr['INNER-Y'],
7269 $objattr['bsize'],
7270 $objattr['bheight'],
7271 $bgcol,
7272 $col,
7273 $objattr['btype'],
7274 $objattr['pr_ratio'],
7281 // TEXT CIRCLE
7282 if ($objattr['type'] == 'textcircle') {
7283 $bgcol = '';
7284 if (isset($objattr['bgcolor']) && $objattr['bgcolor']) {
7285 $bgcol = $objattr['bgcolor'];
7287 $col = $this->colorConverter->convert(0, $this->PDFAXwarnings);
7288 if (isset($objattr['color']) && $objattr['color']) {
7289 $col = $objattr['color'];
7291 $this->SetTColor($col);
7292 $this->SetFColor($bgcol);
7293 if ($bgcol) {
7294 $this->Rect($objattr['BORDER-X'], $objattr['BORDER-Y'], $objattr['BORDER-WIDTH'], $objattr['BORDER-HEIGHT'], 'F');
7296 $this->SetFColor($this->colorConverter->convert(255, $this->PDFAXwarnings));
7297 if (isset($objattr['BORDER-WIDTH'])) {
7298 $this->PaintImgBorder($objattr, $is_table);
7300 if (empty($this->directWrite)) {
7301 $this->directWrite = new DirectWrite($this, $this->otl, $this->sizeConverter, $this->colorConverter);
7303 if (isset($objattr['top-text'])) {
7304 $this->directWrite->CircularText($objattr['INNER-X'] + $objattr['INNER-WIDTH'] / 2, $objattr['INNER-Y'] + $objattr['INNER-HEIGHT'] / 2, $objattr['r'] / $k, $objattr['top-text'], 'top', $objattr['fontfamily'], $objattr['fontsize'] / $k, $objattr['fontstyle'], $objattr['space-width'], $objattr['char-width'], (isset($objattr['divider']) ? $objattr['divider'] : ''));
7306 if (isset($objattr['bottom-text'])) {
7307 $this->directWrite->CircularText($objattr['INNER-X'] + $objattr['INNER-WIDTH'] / 2, $objattr['INNER-Y'] + $objattr['INNER-HEIGHT'] / 2, $objattr['r'] / $k, $objattr['bottom-text'], 'bottom', $objattr['fontfamily'], $objattr['fontsize'] / $k, $objattr['fontstyle'], $objattr['space-width'], $objattr['char-width'], (isset($objattr['divider']) ? $objattr['divider'] : ''));
7311 $this->ResetSpacing();
7313 // LIST MARKERS (Text or bullets) // mPDF 6 Lists
7314 if ($objattr['type'] == 'listmarker') {
7315 if (isset($objattr['fontfamily'])) {
7316 $this->SetFont($objattr['fontfamily'], $objattr['fontstyle'], $objattr['fontsizept']);
7318 $col = $this->colorConverter->convert(0, $this->PDFAXwarnings);
7319 if (isset($objattr['colorarray']) && ($objattr['colorarray'])) {
7320 $col = $objattr['colorarray'];
7323 if (isset($objattr['bullet']) && $objattr['bullet']) { // Used for position "outside" only
7324 $type = $objattr['bullet'];
7325 $size = $objattr['size'];
7327 if ($objattr['listmarkerposition'] == 'inside') {
7328 $adjx = $size / 2;
7329 if ($objattr['dir'] == 'rtl') {
7330 $adjx += $objattr['offset'];
7332 $this->x += $adjx;
7333 } else {
7334 $adjx = $objattr['offset'];
7335 $adjx += $size / 2;
7336 if ($objattr['dir'] == 'rtl') {
7337 $this->x += $adjx;
7338 } else {
7339 $this->x -= $adjx;
7343 $yadj = $objattr['lineBox']['glyphYorigin'];
7344 if (isset($this->CurrentFont['desc']['XHeight']) && $this->CurrentFont['desc']['XHeight']) {
7345 $xh = $this->CurrentFont['desc']['XHeight'];
7346 } else {
7347 $xh = 500;
7349 $yadj -= ($this->FontSize * $xh / 1000) * 0.625; // Vertical height of bullet (centre) from baseline= XHeight * 0.625
7350 $this->y += $yadj;
7352 $this->_printListBullet($this->x, $this->y, $size, $type, $col);
7353 } else {
7354 $this->SetTColor($col);
7355 $w = $this->GetStringWidth($texto);
7356 // NB If change marker-offset, also need to alter in function _getListMarkerWidth
7357 $adjx = $this->sizeConverter->convert($this->list_marker_offset, $this->FontSize);
7358 if ($objattr['dir'] == 'rtl') {
7359 $align = 'L';
7360 $this->x += $adjx;
7361 } else {
7362 // Use these lines to set as marker-offset, right-aligned - default
7363 $align = 'R';
7364 $this->x -= $adjx;
7365 $this->x -= $w;
7367 $this->Cell($w, $this->FontSize, $texto, 0, 0, $align, 0, '', 0, 0, 0, 'T', 0, false, false, 0, $objattr['lineBox']);
7368 $this->SetTColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
7372 // DOT-TAB
7373 if ($objattr['type'] == 'dottab') {
7374 if (isset($objattr['fontfamily'])) {
7375 $this->SetFont($objattr['fontfamily'], '', $objattr['fontsize']);
7377 $sp = $this->GetStringWidth(' ');
7378 $nb = floor(($w - 2 * $sp) / $this->GetStringWidth('.'));
7379 if ($nb > 0) {
7380 $dots = ' ' . str_repeat('.', $nb) . ' ';
7381 } else {
7382 $dots = ' ';
7384 $col = $this->colorConverter->convert(0, $this->PDFAXwarnings);
7385 if (isset($objattr['colorarray']) && ($objattr['colorarray'])) {
7386 $col = $objattr['colorarray'];
7388 $this->SetTColor($col);
7389 $save_dh = $this->divheight;
7390 $save_sbd = $this->spanborddet;
7391 $save_textvar = $this->textvar; // mPDF 5.7.1
7392 $this->spanborddet = '';
7393 $this->divheight = 0;
7394 $this->textvar = 0x00; // mPDF 5.7.1
7396 $this->Cell($w, $h, $dots, 0, 0, 'C', 0, '', 0, 0, 0, 'T', 0, false, false, 0, $objattr['lineBox']); // mPDF 6 DOTTAB
7397 $this->spanborddet = $save_sbd;
7398 $this->textvar = $save_textvar; // mPDF 5.7.1
7399 $this->divheight = $save_dh;
7400 $this->SetTColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
7403 /* -- FORMS -- */
7404 // TEXT/PASSWORD INPUT
7405 if ($objattr['type'] == 'input' && ($objattr['subtype'] == 'TEXT' || $objattr['subtype'] == 'PASSWORD')) {
7406 $this->form->print_ob_text($objattr, $w, $h, $texto, $rtlalign, $k, $blockdir);
7409 // TEXTAREA
7410 if ($objattr['type'] == 'textarea') {
7411 $this->form->print_ob_textarea($objattr, $w, $h, $texto, $rtlalign, $k, $blockdir);
7414 // SELECT
7415 if ($objattr['type'] == 'select') {
7416 $this->form->print_ob_select($objattr, $w, $h, $texto, $rtlalign, $k, $blockdir);
7420 // INPUT/BUTTON as IMAGE
7421 if ($objattr['type'] == 'input' && $objattr['subtype'] == 'IMAGE') {
7422 $this->form->print_ob_imageinput($objattr, $w, $h, $texto, $rtlalign, $k, $blockdir, $is_table);
7425 // BUTTON
7426 if ($objattr['type'] == 'input' && ($objattr['subtype'] == 'SUBMIT' || $objattr['subtype'] == 'RESET' || $objattr['subtype'] == 'BUTTON')) {
7427 $this->form->print_ob_button($objattr, $w, $h, $texto, $rtlalign, $k, $blockdir);
7430 // CHECKBOX
7431 if ($objattr['type'] == 'input' && ($objattr['subtype'] == 'CHECKBOX')) {
7432 $this->form->print_ob_checkbox($objattr, $w, $h, $texto, $rtlalign, $k, $blockdir, $x, $y);
7434 // RADIO
7435 if ($objattr['type'] == 'input' && ($objattr['subtype'] == 'RADIO')) {
7436 $this->form->print_ob_radio($objattr, $w, $h, $texto, $rtlalign, $k, $blockdir, $x, $y);
7438 /* -- END FORMS -- */
7441 $this->SetFont($save_currentfontfamily, $save_currentfontstyle, $save_currentfontsize);
7443 $this->y = $save_y;
7444 $this->x = $save_x;
7446 unset($content);
7449 function _printListBullet($x, $y, $size, $type, $color)
7451 // x and y are the centre of the bullet; size is the width and/or height in mm
7452 $fcol = $this->SetTColor($color, true);
7453 $lcol = strtoupper($fcol); // change 0 0 0 rg to 0 0 0 RG
7454 $this->_out(sprintf('q %s %s', $lcol, $fcol));
7455 $this->_out('0 j 0 J [] 0 d');
7456 if ($type == 'square') {
7457 $size *= 0.85; // Smaller to appear the same size as circle/disc
7458 $this->_out(sprintf('%.3F %.3F %.3F %.3F re f', ($x - $size / 2) * Mpdf::SCALE, ($this->h - $y + $size / 2) * Mpdf::SCALE, ($size) * Mpdf::SCALE, (-$size) * Mpdf::SCALE));
7459 } elseif ($type == 'disc') {
7460 $this->Circle($x, $y, $size / 2, 'F'); // Fill
7461 } elseif ($type == 'circle') {
7462 $lw = $size / 12; // Line width
7463 $this->_out(sprintf('%.3F w ', $lw * Mpdf::SCALE));
7464 $this->Circle($x, $y, $size / 2 - $lw / 2, 'S'); // Stroke
7466 $this->_out('Q');
7469 // mPDF 6
7470 // Get previous character and move pointers
7471 function _moveToPrevChar(&$contentctr, &$charctr, $content)
7473 $lastchar = false;
7474 $charctr--;
7475 while ($charctr < 0) { // go back to previous $content[]
7476 $contentctr--;
7477 if ($contentctr < 0) {
7478 return false;
7480 if ($this->usingCoreFont) {
7481 $charctr = strlen($content[$contentctr]) - 1;
7482 } else {
7483 $charctr = mb_strlen($content[$contentctr], $this->mb_enc) - 1;
7486 if ($this->usingCoreFont) {
7487 $lastchar = $content[$contentctr][$charctr];
7488 } else {
7489 $lastchar = mb_substr($content[$contentctr], $charctr, 1, $this->mb_enc);
7491 return $lastchar;
7494 // Get previous character
7495 function _getPrevChar($contentctr, $charctr, $content)
7497 $lastchar = false;
7498 $charctr--;
7499 while ($charctr < 0) { // go back to previous $content[]
7500 $contentctr--;
7501 if ($contentctr < 0) {
7502 return false;
7504 if ($this->usingCoreFont) {
7505 $charctr = strlen($content[$contentctr]) - 1;
7506 } else {
7507 $charctr = mb_strlen($content[$contentctr], $this->mb_enc) - 1;
7510 if ($this->usingCoreFont) {
7511 $lastchar = $content[$contentctr][$charctr];
7512 } else {
7513 $lastchar = mb_substr($content[$contentctr], $charctr, 1, $this->mb_enc);
7515 return $lastchar;
7518 function WriteFlowingBlock($s, $sOTLdata)
7520 // mPDF 5.7.1
7521 $currentx = $this->x;
7522 $is_table = $this->flowingBlockAttr['is_table'];
7523 $table_draft = $this->flowingBlockAttr['table_draft'];
7524 // width of all the content so far in points
7525 $contentWidth = & $this->flowingBlockAttr['contentWidth'];
7526 // cell width in points
7527 $maxWidth = & $this->flowingBlockAttr['width'];
7528 $lineCount = & $this->flowingBlockAttr['lineCount'];
7529 // line height in user units
7530 $stackHeight = & $this->flowingBlockAttr['height'];
7531 $align = & $this->flowingBlockAttr['align'];
7532 $content = & $this->flowingBlockAttr['content'];
7533 $contentB = & $this->flowingBlockAttr['contentB'];
7534 $font = & $this->flowingBlockAttr['font'];
7535 $valign = & $this->flowingBlockAttr['valign'];
7536 $blockstate = $this->flowingBlockAttr['blockstate'];
7537 $cOTLdata = & $this->flowingBlockAttr['cOTLdata']; // mPDF 5.7.1
7539 $newblock = $this->flowingBlockAttr['newblock'];
7540 $blockdir = $this->flowingBlockAttr['blockdir'];
7542 // *********** BLOCK BACKGROUND COLOR ***************** //
7543 if ($this->blk[$this->blklvl]['bgcolor'] && !$is_table) {
7544 $fill = 0;
7545 } else {
7546 $this->SetFColor($this->colorConverter->convert(255, $this->PDFAXwarnings));
7547 $fill = 0;
7549 $font[] = $this->saveFont();
7550 $content[] = '';
7551 $contentB[] = '';
7552 $cOTLdata[] = $sOTLdata; // mPDF 5.7.1
7553 $currContent = & $content[count($content) - 1];
7555 $CJKoverflow = false;
7556 $Oikomi = false; // mPDF 6
7557 $hanger = '';
7559 // COLS
7560 $oldcolumn = $this->CurrCol;
7561 if ($this->ColActive && !$is_table) {
7562 $this->breakpoints[$this->CurrCol][] = $this->y;
7563 } // *COLUMNS*
7565 /* -- TABLES -- */
7566 if ($is_table) {
7567 $ipaddingL = 0;
7568 $ipaddingR = 0;
7569 $paddingL = 0;
7570 $paddingR = 0;
7571 $cpaddingadjustL = 0;
7572 $cpaddingadjustR = 0;
7573 // Added mPDF 3.0
7574 $fpaddingR = 0;
7575 $fpaddingL = 0;
7576 } else {
7577 /* -- END TABLES -- */
7578 $ipaddingL = $this->blk[$this->blklvl]['padding_left'];
7579 $ipaddingR = $this->blk[$this->blklvl]['padding_right'];
7580 $paddingL = ($ipaddingL * Mpdf::SCALE);
7581 $paddingR = ($ipaddingR * Mpdf::SCALE);
7582 $this->cMarginL = $this->blk[$this->blklvl]['border_left']['w'];
7583 $cpaddingadjustL = -$this->cMarginL;
7584 $this->cMarginR = $this->blk[$this->blklvl]['border_right']['w'];
7585 $cpaddingadjustR = -$this->cMarginR;
7586 // Added mPDF 3.0 Float DIV
7587 $fpaddingR = 0;
7588 $fpaddingL = 0;
7589 /* -- CSS-FLOAT -- */
7590 if (count($this->floatDivs)) {
7591 list($l_exists, $r_exists, $l_max, $r_max, $l_width, $r_width) = $this->GetFloatDivInfo($this->blklvl);
7592 if ($r_exists) {
7593 $fpaddingR = $r_width;
7595 if ($l_exists) {
7596 $fpaddingL = $l_width;
7599 /* -- END CSS-FLOAT -- */
7601 $usey = $this->y + 0.002;
7602 if (($newblock) && ($blockstate == 1 || $blockstate == 3) && ($lineCount == 0)) {
7603 $usey += $this->blk[$this->blklvl]['margin_top'] + $this->blk[$this->blklvl]['padding_top'] + $this->blk[$this->blklvl]['border_top']['w'];
7605 /* -- CSS-IMAGE-FLOAT -- */
7606 // If float exists at this level
7607 if (isset($this->floatmargins['R']) && $usey <= $this->floatmargins['R']['y1'] && $usey >= $this->floatmargins['R']['y0'] && !$this->floatmargins['R']['skipline']) {
7608 $fpaddingR += $this->floatmargins['R']['w'];
7610 if (isset($this->floatmargins['L']) && $usey <= $this->floatmargins['L']['y1'] && $usey >= $this->floatmargins['L']['y0'] && !$this->floatmargins['L']['skipline']) {
7611 $fpaddingL += $this->floatmargins['L']['w'];
7613 /* -- END CSS-IMAGE-FLOAT -- */
7614 } // *TABLES*
7615 // OBJECTS - IMAGES & FORM Elements (NB has already skipped line/page if required - in printbuffer)
7616 if (substr($s, 0, 3) == "\xbb\xa4\xac") { // identifier has been identified!
7617 $objattr = $this->_getObjAttr($s);
7618 $h_corr = 0;
7619 if ($is_table) { // *TABLES*
7620 $maximumW = ($maxWidth / Mpdf::SCALE) - ($this->cellPaddingL + $this->cMarginL + $this->cellPaddingR + $this->cMarginR); // *TABLES*
7621 } // *TABLES*
7622 else { // *TABLES*
7623 if (($newblock) && ($blockstate == 1 || $blockstate == 3) && ($lineCount == 0) && (!$is_table)) {
7624 $h_corr = $this->blk[$this->blklvl]['padding_top'] + $this->blk[$this->blklvl]['border_top']['w'];
7626 $maximumW = ($maxWidth / Mpdf::SCALE) - ($this->blk[$this->blklvl]['padding_left'] + $this->blk[$this->blklvl]['border_left']['w'] + $this->blk[$this->blklvl]['padding_right'] + $this->blk[$this->blklvl]['border_right']['w'] + $fpaddingL + $fpaddingR );
7627 } // *TABLES*
7628 $objattr = $this->inlineObject($objattr['type'], $this->lMargin + $fpaddingL + ($contentWidth / Mpdf::SCALE), ($this->y + $h_corr), $objattr, $this->lMargin, ($contentWidth / Mpdf::SCALE), $maximumW, $stackHeight, true, $is_table);
7630 // SET LINEHEIGHT for this line ================ RESET AT END
7631 $stackHeight = max($stackHeight, $objattr['OUTER-HEIGHT']);
7632 $this->objectbuffer[count($content) - 1] = $objattr;
7633 // if (isset($objattr['vertical-align'])) { $valign = $objattr['vertical-align']; }
7634 // else { $valign = ''; }
7635 // LIST MARKERS // mPDF 6 Lists
7636 if ($objattr['type'] == 'image' && isset($objattr['listmarker']) && $objattr['listmarker'] && $objattr['listmarkerposition'] == 'outside') {
7637 // do nothing
7638 } else {
7639 $contentWidth += ($objattr['OUTER-WIDTH'] * Mpdf::SCALE);
7641 return;
7644 $lbw = $rbw = 0; // Border widths
7645 if (!empty($this->spanborddet)) {
7646 if (isset($this->spanborddet['L'])) {
7647 $lbw = $this->spanborddet['L']['w'];
7649 if (isset($this->spanborddet['R'])) {
7650 $rbw = $this->spanborddet['R']['w'];
7654 if ($this->usingCoreFont) {
7655 $clen = strlen($s);
7656 } else {
7657 $clen = mb_strlen($s, $this->mb_enc);
7660 // for every character in the string
7661 for ($i = 0; $i < $clen; $i++) {
7662 // extract the current character
7663 // get the width of the character in points
7664 if ($this->usingCoreFont) {
7665 $c = $s[$i];
7666 // Soft Hyphens chr(173)
7667 $cw = ($this->GetCharWidthCore($c) * Mpdf::SCALE);
7668 if (($this->textvar & TextVars::FC_KERNING) && $i > 0) { // mPDF 5.7.1
7669 if (isset($this->CurrentFont['kerninfo'][$s[($i - 1)]][$c])) {
7670 $cw += ($this->CurrentFont['kerninfo'][$s[($i - 1)]][$c] * $this->FontSizePt / 1000 );
7673 } else {
7674 $c = mb_substr($s, $i, 1, $this->mb_enc);
7675 $cw = ($this->GetCharWidthNonCore($c, false) * Mpdf::SCALE);
7676 // mPDF 5.7.1
7677 // Use OTL GPOS
7678 if (isset($this->CurrentFont['useOTL']) && ($this->CurrentFont['useOTL'] & 0xFF)) {
7679 // ...WriteFlowingBlock...
7680 // Only add XAdvanceL (not sure at present whether RTL or LTR writing direction)
7681 // At this point, XAdvanceL and XAdvanceR will balance
7682 if (isset($sOTLdata['GPOSinfo'][$i]['XAdvanceL'])) {
7683 $cw += $sOTLdata['GPOSinfo'][$i]['XAdvanceL'] * (1000 / $this->CurrentFont['unitsPerEm']) * ($this->FontSize / 1000) * Mpdf::SCALE;
7686 if (($this->textvar & TextVars::FC_KERNING) && $i > 0) { // mPDF 5.7.1
7687 $lastc = mb_substr($s, ($i - 1), 1, $this->mb_enc);
7688 $ulastc = $this->UTF8StringToArray($lastc, false);
7689 $uc = $this->UTF8StringToArray($c, false);
7690 if (isset($this->CurrentFont['kerninfo'][$ulastc[0]][$uc[0]])) {
7691 $cw += ($this->CurrentFont['kerninfo'][$ulastc[0]][$uc[0]] * $this->FontSizePt / 1000 );
7696 if ($i == 0) {
7697 $cw += $lbw * Mpdf::SCALE;
7698 $contentB[(count($contentB) - 1)] .= 'L';
7700 if ($i == ($clen - 1)) {
7701 $cw += $rbw * Mpdf::SCALE;
7702 $contentB[(count($contentB) - 1)] .= 'R';
7704 if ($c == ' ') {
7705 $currContent .= $c;
7706 $contentWidth += $cw;
7707 continue;
7710 // Paragraph INDENT
7711 $WidthCorrection = 0;
7712 if (($newblock) && ($blockstate == 1 || $blockstate == 3) && isset($this->blk[$this->blklvl]['text_indent']) && ($lineCount == 0) && (!$is_table) && ($align != 'C')) {
7713 $ti = $this->sizeConverter->convert($this->blk[$this->blklvl]['text_indent'], $this->blk[$this->blklvl]['inner_width'], $this->blk[$this->blklvl]['InlineProperties']['size'], false); // mPDF 5.7.4
7714 $WidthCorrection = ($ti * Mpdf::SCALE);
7716 // OUTDENT
7717 foreach ($this->objectbuffer as $k => $objattr) { // mPDF 6 DOTTAB
7718 if ($objattr['type'] == 'dottab') {
7719 $WidthCorrection -= ($objattr['outdent'] * Mpdf::SCALE);
7720 break;
7725 // Added mPDF 3.0 Float DIV
7726 $fpaddingR = 0;
7727 $fpaddingL = 0;
7728 /* -- CSS-FLOAT -- */
7729 if (count($this->floatDivs)) {
7730 list($l_exists, $r_exists, $l_max, $r_max, $l_width, $r_width) = $this->GetFloatDivInfo($this->blklvl);
7731 if ($r_exists) {
7732 $fpaddingR = $r_width;
7734 if ($l_exists) {
7735 $fpaddingL = $l_width;
7738 /* -- END CSS-FLOAT -- */
7740 $usey = $this->y + 0.002;
7741 if (($newblock) && ($blockstate == 1 || $blockstate == 3) && ($lineCount == 0)) {
7742 $usey += $this->blk[$this->blklvl]['margin_top'] + $this->blk[$this->blklvl]['padding_top'] + $this->blk[$this->blklvl]['border_top']['w'];
7745 /* -- CSS-IMAGE-FLOAT -- */
7746 // If float exists at this level
7747 if (isset($this->floatmargins['R']) && $usey <= $this->floatmargins['R']['y1'] && $usey >= $this->floatmargins['R']['y0'] && !$this->floatmargins['R']['skipline']) {
7748 $fpaddingR += $this->floatmargins['R']['w'];
7750 if (isset($this->floatmargins['L']) && $usey <= $this->floatmargins['L']['y1'] && $usey >= $this->floatmargins['L']['y0'] && !$this->floatmargins['L']['skipline']) {
7751 $fpaddingL += $this->floatmargins['L']['w'];
7753 /* -- END CSS-IMAGE-FLOAT -- */
7756 // try adding another char
7757 if (( $contentWidth + $cw > $maxWidth - $WidthCorrection - (($this->cMarginL + $this->cMarginR) * Mpdf::SCALE) - ($paddingL + $paddingR + (($fpaddingL + $fpaddingR) * Mpdf::SCALE) ) + 0.001)) {// 0.001 is to correct for deviations converting mm=>pts
7758 // it won't fit, output what we already have
7759 $lineCount++;
7761 // contains any content that didn't make it into this print
7762 $savedContent = '';
7763 $savedContentB = '';
7764 $savedOTLdata = []; // mPDF 5.7.1
7765 $savedFont = [];
7766 $savedObj = [];
7767 $savedPreOTLdata = []; // mPDF 5.7.1
7768 $savedPreContent = [];
7769 $savedPreContentB = [];
7770 $savedPreFont = [];
7772 // mPDF 6
7773 // New line-breaking algorithm
7774 /////////////////////
7775 // LINE BREAKING
7776 /////////////////////
7777 $breakfound = false;
7778 $contentctr = count($content) - 1;
7779 if ($this->usingCoreFont) {
7780 $charctr = strlen($currContent);
7781 } else {
7782 $charctr = mb_strlen($currContent, $this->mb_enc);
7784 $checkchar = $c;
7785 $prevchar = $this->_getPrevChar($contentctr, $charctr, $content);
7787 /* -- CJK-FONTS -- */
7788 // 1) CJK Overflowing a) punctuation or b) Oikomi
7789 // Next character ($c) is suitable to add as overhanging or squeezed punctuation, or Oikomi
7790 if ($CJKoverflow || $Oikomi) { // If flag already set
7791 $CJKoverflow = false;
7792 $Oikomi = false;
7793 $breakfound = true;
7795 if (!$this->usingCoreFont && !$breakfound && $this->checkCJK) {
7797 // Get next/following character (in this chunk)
7798 $followingchar = '';
7799 if ($i < ($clen - 1)) {
7800 if ($this->usingCoreFont) {
7801 $followingchar = $s[$i + 1];
7802 } else {
7803 $followingchar = mb_substr($s, $i + 1, 1, $this->mb_enc);
7807 // 1a) Overflow punctuation
7808 if (preg_match("/[" . $this->pregCJKchars . "]/u", $prevchar) && preg_match("/[" . $this->CJKoverflow . "]/u", $checkchar) && $this->allowCJKorphans) {
7809 // add character onto this line
7810 $currContent .= $c;
7811 $contentWidth += $cw;
7812 $CJKoverflow = true; // Set flag
7813 continue;
7814 } elseif (preg_match("/[" . $this->pregCJKchars . "]/u", $checkchar) && $this->allowCJKorphans &&
7815 (preg_match("/[" . $this->CJKleading . "]/u", $followingchar) || preg_match("/[" . $this->CJKfollowing . "]/u", $checkchar)) &&
7816 !preg_match("/[" . $this->CJKleading . "]/u", $checkchar) && !preg_match("/[" . $this->CJKfollowing . "]/u", $followingchar) &&
7817 !(preg_match("/[0-9\x{ff10}-\x{ff19}]/u", $followingchar) && preg_match("/[0-9\x{ff10}-\x{ff19}]/u", $checkchar))) {
7818 // 1b) Try squeezing another character(s) onto this line = Oikomi, if character cannot end line
7819 // or next character cannot start line (and not splitting CJK numerals)
7820 // NB otherwise it move lastchar(s) to next line to keep $c company = Oidashi, which is done below in standard way
7821 // add character onto this line
7822 $currContent .= $c;
7823 $contentWidth += $cw;
7824 $Oikomi = true; // Set flag
7825 continue;
7828 /* -- END CJK-FONTS -- */
7829 /* -- HYPHENATION -- */
7831 // AUTOMATIC HYPHENATION
7832 // 2) Automatic hyphen in current word (does not cross tags)
7833 if (isset($this->textparam['hyphens']) && $this->textparam['hyphens'] == 1) {
7834 $currWord = '';
7835 // Look back and ahead to get current word
7836 for ($ac = $charctr - 1; $ac >= 0; $ac--) {
7837 if ($this->usingCoreFont) {
7838 $addc = substr($currContent, $ac, 1);
7839 } else {
7840 $addc = mb_substr($currContent, $ac, 1, $this->mb_enc);
7842 if ($addc == ' ') {
7843 break;
7845 $currWord = $addc . $currWord;
7847 $start = $ac + 1;
7848 for ($ac = $i; $ac < ($clen - 1); $ac++) {
7849 if ($this->usingCoreFont) {
7850 $addc = substr($s, $ac, 1);
7851 } else {
7852 $addc = mb_substr($s, $ac, 1, $this->mb_enc);
7854 if ($addc == ' ') {
7855 break;
7857 $currWord .= $addc;
7859 $ptr = $this->hyphenator->hyphenateWord($currWord, $charctr - $start);
7860 if ($ptr > -1) {
7861 $breakfound = [$contentctr, $start + $ptr, $contentctr, $start + $ptr, 'hyphen'];
7864 /* -- END HYPHENATION -- */
7866 // Search backwards to find first line-break opportunity
7867 while ($breakfound == false && $prevchar !== false) {
7868 $cutcontentctr = $contentctr;
7869 $cutcharctr = $charctr;
7870 $prevchar = $this->_moveToPrevChar($contentctr, $charctr, $content);
7871 /////////////////////
7872 // 3) Break at SPACE
7873 /////////////////////
7874 if ($prevchar == ' ') {
7875 $breakfound = [$contentctr, $charctr, $cutcontentctr, $cutcharctr, 'discard'];
7876 } /////////////////////
7877 // 4) Break at U+200B in current word (Khmer, Lao & Thai Invisible word boundary, and Tibetan)
7878 /////////////////////
7879 elseif ($prevchar == "\xe2\x80\x8b") { // U+200B Zero-width Word Break
7880 $breakfound = [$contentctr, $charctr, $cutcontentctr, $cutcharctr, 'discard'];
7881 } /////////////////////
7882 // 5) Break at Hard HYPHEN '-' or U+2010
7883 /////////////////////
7884 elseif (isset($this->textparam['hyphens']) && $this->textparam['hyphens'] != 2 && ($prevchar == '-' || $prevchar == "\xe2\x80\x90")) {
7885 // Don't break a URL
7886 // Look back to get first part of current word
7887 $checkw = '';
7888 for ($ac = $charctr - 1; $ac >= 0; $ac--) {
7889 if ($this->usingCoreFont) {
7890 $addc = substr($currContent, $ac, 1);
7891 } else {
7892 $addc = mb_substr($currContent, $ac, 1, $this->mb_enc);
7894 if ($addc == ' ') {
7895 break;
7897 $checkw = $addc . $checkw;
7899 // Don't break if HyphenMinus AND (a URL or before a numeral or before a >)
7900 if ((!preg_match('/(http:|ftp:|https:|www\.)/', $checkw) && $checkchar != '>' && !preg_match('/[0-9]/', $checkchar)) || $prevchar == "\xe2\x80\x90") {
7901 $breakfound = [$cutcontentctr, $cutcharctr, $cutcontentctr, $cutcharctr, 'cut'];
7903 } /////////////////////
7904 // 6) Break at Soft HYPHEN (replace with hard hyphen)
7905 /////////////////////
7906 elseif (isset($this->textparam['hyphens']) && $this->textparam['hyphens'] != 2 && !$this->usingCoreFont && $prevchar == "\xc2\xad") {
7907 $breakfound = [$cutcontentctr, $cutcharctr, $cutcontentctr, $cutcharctr, 'cut'];
7908 $content[$contentctr] = mb_substr($content[$contentctr], 0, $charctr, $this->mb_enc) . '-' . mb_substr($content[$contentctr], $charctr + 1, mb_strlen($content[$contentctr]), $this->mb_enc);
7909 if (!empty($cOTLdata[$contentctr])) {
7910 $cOTLdata[$contentctr]['char_data'][$charctr] = ['bidi_class' => 9, 'uni' => 45];
7911 $cOTLdata[$contentctr]['group'][$charctr] = 'C';
7913 } elseif (isset($this->textparam['hyphens']) && $this->textparam['hyphens'] != 2 && $this->FontFamily != 'csymbol' && $this->FontFamily != 'czapfdingbats' && $prevchar == chr(173)) {
7914 $breakfound = [$cutcontentctr, $cutcharctr, $cutcontentctr, $cutcharctr, 'cut'];
7915 $content[$contentctr] = substr($content[$contentctr], 0, $charctr) . '-' . substr($content[$contentctr], $charctr + 1);
7916 } /* -- CJK-FONTS -- */
7917 /////////////////////
7918 // 7) Break at CJK characters (unless forbidden characters to end or start line)
7919 // CJK Avoiding line break in the middle of numerals
7920 /////////////////////
7921 elseif (!$this->usingCoreFont && $this->checkCJK && preg_match("/[" . $this->pregCJKchars . "]/u", $checkchar) &&
7922 !preg_match("/[" . $this->CJKfollowing . "]/u", $checkchar) && !preg_match("/[" . $this->CJKleading . "]/u", $prevchar) &&
7923 !(preg_match("/[0-9\x{ff10}-\x{ff19}]/u", $prevchar) && preg_match("/[0-9\x{ff10}-\x{ff19}]/u", $checkchar))) {
7924 $breakfound = [$cutcontentctr, $cutcharctr, $cutcontentctr, $cutcharctr, 'cut'];
7926 /* -- END CJK-FONTS -- */
7927 /////////////////////
7928 // 8) Break at OBJECT (Break before all objects here - selected objects are moved forward to next line below e.g. dottab)
7929 /////////////////////
7930 if (isset($this->objectbuffer[$contentctr])) {
7931 $breakfound = [$cutcontentctr, $cutcharctr, $cutcontentctr, $cutcharctr, 'cut'];
7935 $checkchar = $prevchar;
7938 // If a line-break opportunity found:
7939 if (is_array($breakfound)) {
7940 $contentctr = $breakfound[0];
7941 $charctr = $breakfound[1];
7942 $cutcontentctr = $breakfound[2];
7943 $cutcharctr = $breakfound[3];
7944 $type = $breakfound[4];
7945 // Cache chunks which are already processed, but now need to be passed on to the new line
7946 for ($ix = count($content) - 1; $ix > $cutcontentctr; $ix--) {
7947 // save and crop off any subsequent chunks
7948 /* -- OTL -- */
7949 if (!empty($sOTLdata)) {
7950 $tmpOTL = array_pop($cOTLdata);
7951 $savedPreOTLdata[] = $tmpOTL;
7953 /* -- END OTL -- */
7954 $savedPreContent[] = array_pop($content);
7955 $savedPreContentB[] = array_pop($contentB);
7956 $savedPreFont[] = array_pop($font);
7959 // Next cache the part which will start the next line
7960 if ($this->usingCoreFont) {
7961 $savedPreContent[] = substr($content[$cutcontentctr], $cutcharctr);
7962 } else {
7963 $savedPreContent[] = mb_substr($content[$cutcontentctr], $cutcharctr, mb_strlen($content[$cutcontentctr]), $this->mb_enc);
7965 $savedPreContentB[] = preg_replace('/L/', '', $contentB[$cutcontentctr]);
7966 $savedPreFont[] = $font[$cutcontentctr];
7967 /* -- OTL -- */
7968 if (!empty($sOTLdata)) {
7969 $savedPreOTLdata[] = $this->otl->splitOTLdata($cOTLdata[$cutcontentctr], $cutcharctr, $cutcharctr);
7971 /* -- END OTL -- */
7974 // Finally adjust the Current content which ends this line
7975 if ($cutcharctr == 0 && $type == 'discard') {
7976 array_pop($content);
7977 array_pop($contentB);
7978 array_pop($font);
7979 array_pop($cOTLdata);
7982 $currContent = & $content[count($content) - 1];
7983 if ($this->usingCoreFont) {
7984 $currContent = substr($currContent, 0, $charctr);
7985 } else {
7986 $currContent = mb_substr($currContent, 0, $charctr, $this->mb_enc);
7989 if (!empty($sOTLdata)) {
7990 $savedPreOTLdata[] = $this->otl->splitOTLdata($cOTLdata[(count($cOTLdata) - 1)], mb_strlen($currContent, $this->mb_enc));
7993 if (strpos($contentB[(count($contentB) - 1)], 'R') !== false) { // ???
7994 $contentB[count($content) - 1] = preg_replace('/R/', '', $contentB[count($content) - 1]); // ???
7997 if ($type == 'hyphen') {
7998 $currContent .= '-';
7999 if (!empty($cOTLdata[(count($cOTLdata) - 1)])) {
8000 $cOTLdata[(count($cOTLdata) - 1)]['char_data'][] = ['bidi_class' => 9, 'uni' => 45];
8001 $cOTLdata[(count($cOTLdata) - 1)]['group'] .= 'C';
8005 $savedContent = '';
8006 $savedContentB = '';
8007 $savedFont = [];
8008 $savedOTLdata = [];
8010 // If no line-break opportunity found - split at current position
8011 // or - Next character ($c) is suitable to add as overhanging or squeezed punctuation, or Oikomi, as set above by:
8012 // 1) CJK Overflowing a) punctuation or b) Oikomi
8013 // in which case $breakfound==1 and NOT array
8015 if (!is_array($breakfound)) {
8016 $savedFont = $this->saveFont();
8017 if (!empty($sOTLdata)) {
8018 $savedOTLdata = $this->otl->splitOTLdata($cOTLdata[(count($cOTLdata) - 1)], mb_strlen($currContent, $this->mb_enc));
8022 if ($content[count($content) - 1] == '' && !isset($this->objectbuffer[count($content) - 1])) {
8023 array_pop($content);
8024 array_pop($contentB);
8025 array_pop($font);
8026 array_pop($cOTLdata);
8027 $currContent = & $content[count($content) - 1];
8030 // Right Trim current content - including CJK space, and for OTLdata
8031 // incl. CJK - strip CJK space at end of line &#x3000; = \xe3\x80\x80 = CJK space
8032 $currContent = rtrim($currContent);
8033 if ($this->checkCJK) {
8034 $currContent = preg_replace("/\xe3\x80\x80$/", '', $currContent);
8035 } // *CJK-FONTS*
8036 /* -- OTL -- */
8037 if (isset($this->CurrentFont['useOTL']) && $this->CurrentFont['useOTL']) {
8038 $this->otl->trimOTLdata($cOTLdata[count($cOTLdata) - 1], false, true); // NB also does U+3000
8040 /* -- END OTL -- */
8043 // Selected OBJECTS are moved forward to next line, unless they come before a space or U+200B (type='discard')
8044 if (isset($this->objectbuffer[(count($content) - 1)]) && (!isset($type) || $type != 'discard')) {
8045 $objtype = $this->objectbuffer[(count($content) - 1)]['type'];
8046 if ($objtype == 'dottab' || $objtype == 'bookmark' || $objtype == 'indexentry' || $objtype == 'toc' || $objtype == 'annot') {
8047 $savedObj = array_pop($this->objectbuffer);
8052 // Decimal alignment (cancel if wraps to > 1 line)
8053 if ($is_table && substr($align, 0, 1) == 'D') {
8054 $align = substr($align, 2, 1);
8057 $lineBox = [];
8059 $this->_setInlineBlockHeights($lineBox, $stackHeight, $content, $font, $is_table);
8061 // update $contentWidth since it has changed with cropping
8062 $contentWidth = 0;
8064 $inclCursive = false;
8065 foreach ($content as $k => $chunk) {
8066 if (isset($this->objectbuffer[$k]) && $this->objectbuffer[$k]) {
8067 // LIST MARKERS
8068 if ($this->objectbuffer[$k]['type'] == 'image' && isset($this->objectbuffer[$k]['listmarker']) && $this->objectbuffer[$k]['listmarker']) {
8069 if ($this->objectbuffer[$k]['listmarkerposition'] != 'outside') {
8070 $contentWidth += $this->objectbuffer[$k]['OUTER-WIDTH'] * Mpdf::SCALE;
8072 } else {
8073 $contentWidth += $this->objectbuffer[$k]['OUTER-WIDTH'] * Mpdf::SCALE;
8075 } elseif (!isset($this->objectbuffer[$k]) || (isset($this->objectbuffer[$k]) && !$this->objectbuffer[$k])) {
8076 $this->restoreFont($font[$k], false);
8077 if ($this->checkCJK && $k == count($content) - 1 && $CJKoverflow && $align == 'J' && $this->allowCJKoverflow && $this->CJKforceend) {
8078 // force-end overhang
8079 $hanger = mb_substr($chunk, mb_strlen($chunk, $this->mb_enc) - 1, 1, $this->mb_enc);
8080 // Probably ought to do something with char_data and GPOS in cOTLdata...
8081 $content[$k] = $chunk = mb_substr($chunk, 0, mb_strlen($chunk, $this->mb_enc) - 1, $this->mb_enc);
8084 // Soft Hyphens chr(173) + Replace NBSP with SPACE + Set inclcursive if includes CURSIVE TEXT
8085 if (!$this->usingCoreFont) {
8086 /* -- OTL -- */
8087 if ((isset($this->CurrentFont['useOTL']) && $this->CurrentFont['useOTL']) || !empty($sOTLdata)) {
8088 $this->otl->removeChar($chunk, $cOTLdata[$k], "\xc2\xad");
8089 $this->otl->replaceSpace($chunk, $cOTLdata[$k]); // NBSP -> space
8090 if (preg_match("/([" . $this->pregCURSchars . "])/u", $chunk)) {
8091 $inclCursive = true;
8093 $content[$k] = $chunk;
8094 } /* -- END OTL -- */ else { // *OTL*
8095 $content[$k] = $chunk = str_replace("\xc2\xad", '', $chunk);
8096 $content[$k] = $chunk = str_replace(chr(194) . chr(160), chr(32), $chunk);
8097 } // *OTL*
8098 } elseif ($this->FontFamily != 'csymbol' && $this->FontFamily != 'czapfdingbats') {
8099 $content[$k] = $chunk = str_replace(chr(173), '', $chunk);
8100 $content[$k] = $chunk = str_replace(chr(160), chr(32), $chunk);
8103 $contentWidth += $this->GetStringWidth($chunk, true, (isset($cOTLdata[$k]) ? $cOTLdata[$k] : false), $this->textvar) * Mpdf::SCALE; // mPDF 5.7.1
8104 if (!empty($this->spanborddet)) {
8105 if (isset($this->spanborddet['L']['w']) && strpos($contentB[$k], 'L') !== false) {
8106 $contentWidth += $this->spanborddet['L']['w'] * Mpdf::SCALE;
8108 if (isset($this->spanborddet['R']['w']) && strpos($contentB[$k], 'R') !== false) {
8109 $contentWidth += $this->spanborddet['R']['w'] * Mpdf::SCALE;
8115 $lastfontreqstyle = (isset($font[count($font) - 1]['ReqFontStyle']) ? $font[count($font) - 1]['ReqFontStyle'] : '');
8116 $lastfontstyle = (isset($font[count($font) - 1]['style']) ? $font[count($font) - 1]['style'] : '');
8117 if ($blockdir == 'ltr' && strpos($lastfontreqstyle, "I") !== false && strpos($lastfontstyle, "I") === false) { // Artificial italic
8118 $lastitalic = $this->FontSize * 0.15 * Mpdf::SCALE;
8119 } else {
8120 $lastitalic = 0;
8126 // NOW FORMAT THE LINE TO OUTPUT
8127 if (!$table_draft) {
8128 // DIRECTIONALITY RTL
8129 $chunkorder = range(0, count($content) - 1); // mPDF 5.7
8130 /* -- OTL -- */
8131 // mPDF 6
8132 if ($blockdir == 'rtl' || $this->biDirectional) {
8133 $this->otl->bidiReorder($chunkorder, $content, $cOTLdata, $blockdir);
8134 // From this point on, $content and $cOTLdata may contain more elements (and re-ordered) compared to
8135 // $this->objectbuffer and $font ($chunkorder contains the mapping)
8138 /* -- END OTL -- */
8139 // Remove any XAdvance from OTL data at end of line
8140 foreach ($chunkorder as $aord => $k) {
8141 if (count($cOTLdata)) {
8142 $this->restoreFont($font[$k], false);
8143 // ...WriteFlowingBlock...
8144 if ($aord == count($chunkorder) - 1 && isset($cOTLdata[$aord]['group'])) { // Last chunk on line
8145 $nGPOS = strlen($cOTLdata[$aord]['group']) - 1; // Last character
8146 if (isset($cOTLdata[$aord]['GPOSinfo'][$nGPOS]['XAdvanceL']) || isset($cOTLdata[$aord]['GPOSinfo'][$nGPOS]['XAdvanceR'])) {
8147 if (isset($cOTLdata[$aord]['GPOSinfo'][$nGPOS]['XAdvanceL'])) {
8148 $w = $cOTLdata[$aord]['GPOSinfo'][$nGPOS]['XAdvanceL'] * 1000 / $this->CurrentFont['unitsPerEm'];
8149 } else {
8150 $w = $cOTLdata[$aord]['GPOSinfo'][$nGPOS]['XAdvanceR'] * 1000 / $this->CurrentFont['unitsPerEm'];
8152 $w *= ($this->FontSize / 1000);
8153 $contentWidth -= $w * Mpdf::SCALE;
8154 $cOTLdata[$aord]['GPOSinfo'][$nGPOS]['XAdvanceL'] = 0;
8155 $cOTLdata[$aord]['GPOSinfo'][$nGPOS]['XAdvanceR'] = 0;
8158 // If last character has an XPlacement set, adjust width calculation, and add to XAdvance to account for it
8159 if (isset($cOTLdata[$aord]['GPOSinfo'][$nGPOS]['XPlacement'])) {
8160 $w = -$cOTLdata[$aord]['GPOSinfo'][$nGPOS]['XPlacement'] * 1000 / $this->CurrentFont['unitsPerEm'];
8161 $w *= ($this->FontSize / 1000);
8162 $contentWidth -= $w * Mpdf::SCALE;
8163 $cOTLdata[$aord]['GPOSinfo'][$nGPOS]['XAdvanceL'] = $cOTLdata[$aord]['GPOSinfo'][$nGPOS]['XPlacement'];
8164 $cOTLdata[$aord]['GPOSinfo'][$nGPOS]['XAdvanceR'] = $cOTLdata[$aord]['GPOSinfo'][$nGPOS]['XPlacement'];
8170 // JUSTIFICATION J
8171 $jcharspacing = 0;
8172 $jws = 0;
8173 $nb_carac = 0;
8174 $nb_spaces = 0;
8175 $jkashida = 0;
8176 // if it's justified, we need to find the char/word spacing (or if hanger $this->CJKforceend)
8177 if (($align == 'J' && !$CJKoverflow) || (($contentWidth + $lastitalic > $maxWidth - $WidthCorrection - (($this->cMarginL + $this->cMarginR) * Mpdf::SCALE) - ($paddingL + $paddingR + (($fpaddingL + $fpaddingR) * Mpdf::SCALE) ) + 0.001) && (!$CJKoverflow || ($CJKoverflow && !$this->allowCJKoverflow))) || $CJKoverflow && $align == 'J' && $this->allowCJKoverflow && $hanger && $this->CJKforceend) { // 0.001 is to correct for deviations converting mm=>pts
8178 // JUSTIFY J (Use character spacing)
8179 // WORD SPACING
8180 // mPDF 5.7
8181 foreach ($chunkorder as $aord => $k) {
8182 $chunk = isset($content[$aord]) ? $content[$aord] : '';
8183 if (!isset($this->objectbuffer[$k]) || (isset($this->objectbuffer[$k]) && !$this->objectbuffer[$k])) {
8184 $nb_carac += mb_strlen($chunk, $this->mb_enc);
8185 $nb_spaces += mb_substr_count($chunk, ' ', $this->mb_enc);
8186 // Use GPOS OTL
8187 if (isset($this->CurrentFont['useOTL']) && ($this->CurrentFont['useOTL'] & 0xFF)) {
8188 if (isset($cOTLdata[$aord]['group']) && $cOTLdata[$aord]['group']) {
8189 $nb_carac -= substr_count($cOTLdata[$aord]['group'], 'M');
8192 } else {
8193 $nb_carac ++;
8194 } // mPDF 6 allow spacing for inline object
8196 // GetJSpacing adds kashida spacing to GPOSinfo if appropriate for Font
8197 list($jcharspacing, $jws, $jkashida) = $this->GetJspacing($nb_carac, $nb_spaces, ($maxWidth - $lastitalic - $contentWidth - $WidthCorrection - (($this->cMarginL + $this->cMarginR) * Mpdf::SCALE) - ($paddingL + $paddingR + (($fpaddingL + $fpaddingR) * Mpdf::SCALE) )), $inclCursive, $cOTLdata);
8200 // WORD SPACING
8201 $empty = $maxWidth - $lastitalic - $WidthCorrection - $contentWidth - (($this->cMarginL + $this->cMarginR) * Mpdf::SCALE) - ($paddingL + $paddingR + (($fpaddingL + $fpaddingR) * Mpdf::SCALE) );
8203 $empty -= ($jcharspacing * ($nb_carac - 1)); // mPDF 6 nb_carac MINUS 1
8204 $empty -= ($jws * $nb_spaces);
8205 $empty -= ($jkashida);
8206 $empty /= Mpdf::SCALE;
8208 $b = ''; // do not use borders
8209 // Get PAGEBREAK TO TEST for height including the top border/padding
8210 $check_h = max($this->divheight, $stackHeight);
8211 if (($newblock) && ($blockstate == 1 || $blockstate == 3) && ($this->blklvl > 0) && ($lineCount == 1) && (!$is_table)) {
8212 $check_h += ($this->blk[$this->blklvl]['padding_top'] + $this->blk[$this->blklvl]['margin_top'] + $this->blk[$this->blklvl]['border_top']['w']);
8215 if ($this->ColActive && $check_h > ($this->PageBreakTrigger - $this->y0)) {
8216 $this->SetCol($this->NbCol - 1);
8219 // PAGEBREAK
8220 // 'If' below used in order to fix "first-line of other page with justify on" bug
8221 if (!$is_table && ($this->y + $check_h) > $this->PageBreakTrigger and ! $this->InFooter and $this->AcceptPageBreak()) {
8222 $bak_x = $this->x; // Current X position
8223 // WORD SPACING
8224 $ws = $this->ws; // Word Spacing
8225 $charspacing = $this->charspacing; // Character Spacing
8226 $this->ResetSpacing();
8228 $this->AddPage($this->CurOrientation);
8230 $this->x = $bak_x;
8231 // Added to correct for OddEven Margins
8232 $currentx += $this->MarginCorrection;
8233 $this->x += $this->MarginCorrection;
8235 // WORD SPACING
8236 $this->SetSpacing($charspacing, $ws);
8239 if ($this->kwt && !$is_table) { // mPDF 5.7+
8240 $this->printkwtbuffer();
8241 $this->kwt = false;
8245 /* -- COLUMNS -- */
8246 // COLS
8247 // COLUMN CHANGE
8248 if ($this->CurrCol != $oldcolumn) {
8249 $currentx += $this->ChangeColumn * ($this->ColWidth + $this->ColGap);
8250 $this->x += $this->ChangeColumn * ($this->ColWidth + $this->ColGap);
8251 $oldcolumn = $this->CurrCol;
8254 if ($this->ColActive && !$is_table) {
8255 $this->breakpoints[$this->CurrCol][] = $this->y;
8256 } // *COLUMNS*
8257 /* -- END COLUMNS -- */
8259 // TOP MARGIN
8260 if (($newblock) && ($blockstate == 1 || $blockstate == 3) && ($this->blk[$this->blklvl]['margin_top']) && ($lineCount == 1) && (!$is_table)) {
8261 $this->DivLn($this->blk[$this->blklvl]['margin_top'], $this->blklvl - 1, true, $this->blk[$this->blklvl]['margin_collapse']);
8262 if ($this->ColActive) {
8263 $this->breakpoints[$this->CurrCol][] = $this->y;
8264 } // *COLUMNS*
8268 // Update y0 for top of block (used to paint border)
8269 if (($newblock) && ($blockstate == 1 || $blockstate == 3) && ($lineCount == 1) && (!$is_table)) {
8270 $this->blk[$this->blklvl]['y0'] = $this->y;
8271 $this->blk[$this->blklvl]['startpage'] = $this->page;
8272 if ($this->blk[$this->blklvl]['float']) {
8273 $this->blk[$this->blklvl]['float_start_y'] = $this->y;
8277 // TOP PADDING and BORDER spacing/fill
8278 if (($newblock) && ($blockstate == 1 || $blockstate == 3) && (($this->blk[$this->blklvl]['padding_top']) || ($this->blk[$this->blklvl]['border_top'])) && ($lineCount == 1) && (!$is_table)) {
8279 // $state = 0 normal; 1 top; 2 bottom; 3 top and bottom
8280 $this->DivLn($this->blk[$this->blklvl]['padding_top'] + $this->blk[$this->blklvl]['border_top']['w'], -3, true, false, 1);
8281 if ($this->ColActive) {
8282 $this->breakpoints[$this->CurrCol][] = $this->y;
8283 } // *COLUMNS*
8286 $arraysize = count($chunkorder);
8288 $margins = ($this->cMarginL + $this->cMarginR) + ($ipaddingL + $ipaddingR + $fpaddingR + $fpaddingR );
8290 // PAINT BACKGROUND FOR THIS LINE
8291 if (!$is_table) {
8292 $this->DivLn($stackHeight, $this->blklvl, false);
8293 } // false -> don't advance y
8295 $this->x = $currentx + $this->cMarginL + $ipaddingL + $fpaddingL;
8296 if ($align == 'R') {
8297 $this->x += $empty;
8298 } elseif ($align == 'C') {
8299 $this->x += ($empty / 2);
8302 // Paragraph INDENT
8303 if (isset($this->blk[$this->blklvl]['text_indent']) && ($newblock) && ($blockstate == 1 || $blockstate == 3) && ($lineCount == 1) && (!$is_table) && ($blockdir != 'rtl') && ($align != 'C')) {
8304 $ti = $this->sizeConverter->convert($this->blk[$this->blklvl]['text_indent'], $this->blk[$this->blklvl]['inner_width'], $this->blk[$this->blklvl]['InlineProperties']['size'], false); // mPDF 5.7.4
8305 $this->x += $ti;
8308 // BIDI magic_reverse moved upwards from here
8309 foreach ($chunkorder as $aord => $k) { // mPDF 5.7
8311 $chunk = isset($content[$aord]) ? $content[$aord] : '';
8313 if (isset($this->objectbuffer[$k]) && $this->objectbuffer[$k]) {
8314 $xadj = $this->x - $this->objectbuffer[$k]['OUTER-X'];
8315 $this->objectbuffer[$k]['OUTER-X'] += $xadj;
8316 $this->objectbuffer[$k]['BORDER-X'] += $xadj;
8317 $this->objectbuffer[$k]['INNER-X'] += $xadj;
8319 if ($this->objectbuffer[$k]['type'] == 'listmarker') {
8320 $this->objectbuffer[$k]['lineBox'] = $lineBox[-1]; // Block element details for glyph-origin
8322 $yadj = $this->y - $this->objectbuffer[$k]['OUTER-Y'];
8323 if ($this->objectbuffer[$k]['type'] == 'dottab') { // mPDF 6 DOTTAB
8324 $this->objectbuffer[$k]['lineBox'] = $lineBox[$k]; // element details for glyph-origin
8326 if ($this->objectbuffer[$k]['type'] != 'dottab') { // mPDF 6 DOTTAB
8327 $yadj += $lineBox[$k]['top'];
8329 $this->objectbuffer[$k]['OUTER-Y'] += $yadj;
8330 $this->objectbuffer[$k]['BORDER-Y'] += $yadj;
8331 $this->objectbuffer[$k]['INNER-Y'] += $yadj;
8334 $this->restoreFont($font[$k]); // mPDF 5.7
8336 $this->SetSpacing(($this->fixedlSpacing * Mpdf::SCALE) + $jcharspacing, ($this->fixedlSpacing + $this->minwSpacing) * Mpdf::SCALE + $jws);
8337 // Now unset these values so they don't influence GetStringwidth below or in fn. Cell
8338 $this->fixedlSpacing = false;
8339 $this->minwSpacing = 0;
8341 $save_vis = $this->visibility;
8342 if (isset($this->textparam['visibility']) && $this->textparam['visibility'] && $this->textparam['visibility'] != $this->visibility) {
8343 $this->SetVisibility($this->textparam['visibility']);
8345 // *********** SPAN BACKGROUND COLOR ***************** //
8346 if ($this->spanbgcolor) {
8347 $cor = $this->spanbgcolorarray;
8348 $this->SetFColor($cor);
8349 $save_fill = $fill;
8350 $spanfill = 1;
8351 $fill = 1;
8353 if (!empty($this->spanborddet)) {
8354 if (strpos($contentB[$k], 'L') !== false) {
8355 $this->x += (isset($this->spanborddet['L']['w']) ? $this->spanborddet['L']['w'] : 0);
8357 if (strpos($contentB[$k], 'L') === false) {
8358 $this->spanborddet['L']['s'] = $this->spanborddet['L']['w'] = 0;
8360 if (strpos($contentB[$k], 'R') === false) {
8361 $this->spanborddet['R']['s'] = $this->spanborddet['R']['w'] = 0;
8365 // WORD SPACING
8366 // StringWidth this time includes any kashida spacing
8367 $stringWidth = $this->GetStringWidth($chunk, true, (isset($cOTLdata[$aord]) ? $cOTLdata[$aord] : false), $this->textvar, true);
8369 $nch = mb_strlen($chunk, $this->mb_enc);
8370 // Use GPOS OTL
8371 if (isset($this->CurrentFont['useOTL']) && ($this->CurrentFont['useOTL'] & 0xFF)) {
8372 if (isset($cOTLdata[$aord]['group']) && $cOTLdata[$aord]['group']) {
8373 $nch -= substr_count($cOTLdata[$aord]['group'], 'M');
8376 $stringWidth += ( $this->charspacing * $nch / Mpdf::SCALE );
8378 $stringWidth += ( $this->ws * mb_substr_count($chunk, ' ', $this->mb_enc) / Mpdf::SCALE );
8380 if (isset($this->objectbuffer[$k])) {
8381 // LIST MARKERS // mPDF 6 Lists
8382 if ($this->objectbuffer[$k]['type'] == 'image' && isset($this->objectbuffer[$k]['listmarker']) && $this->objectbuffer[$k]['listmarker'] && $this->objectbuffer[$k]['listmarkerposition'] == 'outside') {
8383 $stringWidth = 0;
8384 } else {
8385 $stringWidth = $this->objectbuffer[$k]['OUTER-WIDTH'];
8389 if ($stringWidth == 0) {
8390 $stringWidth = 0.000001;
8393 if ($aord == $arraysize - 1) {
8394 $stringWidth -= ( $this->charspacing / Mpdf::SCALE );
8395 if ($this->checkCJK && $CJKoverflow && $align == 'J' && $this->allowCJKoverflow && $hanger && $this->CJKforceend) {
8396 // force-end overhang
8397 $this->Cell($stringWidth, $stackHeight, $chunk, '', 0, '', $fill, $this->HREF, $currentx, 0, 0, 'M', $fill, true, (isset($cOTLdata[$aord]) ? $cOTLdata[$aord] : false), $this->textvar, (isset($lineBox[$k]) ? $lineBox[$k] : false));
8398 $this->Cell($this->GetStringWidth($hanger), $stackHeight, $hanger, '', 1, '', $fill, $this->HREF, $currentx, 0, 0, 'M', $fill, true, (isset($cOTLdata[$aord]) ? $cOTLdata[$aord] : false), $this->textvar, (isset($lineBox[$k]) ? $lineBox[$k] : false));
8399 } else {
8400 $this->Cell($stringWidth, $stackHeight, $chunk, '', 1, '', $fill, $this->HREF, $currentx, 0, 0, 'M', $fill, true, (isset($cOTLdata[$aord]) ? $cOTLdata[$aord] : false), $this->textvar, (isset($lineBox[$k]) ? $lineBox[$k] : false)); // mono-style line or last part (skips line)
8402 } else {
8403 $this->Cell($stringWidth, $stackHeight, $chunk, '', 0, '', $fill, $this->HREF, 0, 0, 0, 'M', $fill, true, (isset($cOTLdata[$aord]) ? $cOTLdata[$aord] : false), $this->textvar, (isset($lineBox[$k]) ? $lineBox[$k] : false)); // first or middle part
8406 if (!empty($this->spanborddet)) {
8407 if (strpos($contentB[$k], 'R') !== false && $aord != $arraysize - 1) {
8408 $this->x += $this->spanborddet['R']['w'];
8411 // *********** SPAN BACKGROUND COLOR OFF - RESET BLOCK BGCOLOR ***************** //
8412 if (isset($spanfill) && $spanfill) {
8413 $fill = $save_fill;
8414 $spanfill = 0;
8415 if ($fill) {
8416 $this->SetFColor($bcor);
8419 if (isset($this->textparam['visibility']) && $this->textparam['visibility'] && $this->visibility != $save_vis) {
8420 $this->SetVisibility($save_vis);
8423 } elseif ($table_draft) {
8424 $this->y += $stackHeight;
8427 if (!$is_table) {
8428 $this->maxPosR = max($this->maxPosR, ($this->w - $this->rMargin - $this->blk[$this->blklvl]['outer_right_margin']));
8429 $this->maxPosL = min($this->maxPosL, ($this->lMargin + $this->blk[$this->blklvl]['outer_left_margin']));
8432 // move on to the next line, reset variables, tack on saved content and current char
8434 if (!$table_draft) {
8435 $this->printobjectbuffer($is_table, $blockdir);
8437 $this->objectbuffer = [];
8440 /* -- CSS-IMAGE-FLOAT -- */
8441 // Update values if set to skipline
8442 if ($this->floatmargins) {
8443 $this->_advanceFloatMargins();
8445 /* -- END CSS-IMAGE-FLOAT -- */
8447 // Reset lineheight
8448 $stackHeight = $this->divheight;
8449 $valign = 'M';
8451 $font = [];
8452 $content = [];
8453 $contentB = [];
8454 $cOTLdata = []; // mPDF 5.7.1
8455 $contentWidth = 0;
8456 if (!empty($savedObj)) {
8457 $this->objectbuffer[] = $savedObj;
8458 $font[] = $savedFont;
8459 $content[] = '';
8460 $contentB[] = '';
8461 $cOTLdata[] = []; // mPDF 5.7.1
8462 $contentWidth += $savedObj['OUTER-WIDTH'] * Mpdf::SCALE;
8464 if (count($savedPreContent) > 0) {
8465 for ($ix = count($savedPreContent) - 1; $ix >= 0; $ix--) {
8466 $font[] = $savedPreFont[$ix];
8467 $content[] = $savedPreContent[$ix];
8468 $contentB[] = $savedPreContentB[$ix];
8469 if (!empty($sOTLdata)) {
8470 $cOTLdata[] = $savedPreOTLdata[$ix];
8472 $this->restoreFont($savedPreFont[$ix]);
8473 $lbw = $rbw = 0; // Border widths
8474 if (!empty($this->spanborddet)) {
8475 $lbw = (isset($this->spanborddet['L']['w']) ? $this->spanborddet['L']['w'] : 0);
8476 $rbw = (isset($this->spanborddet['R']['w']) ? $this->spanborddet['R']['w'] : 0);
8478 if ($ix > 0) {
8479 $contentWidth += $this->GetStringWidth($savedPreContent[$ix], true, (isset($savedPreOTLdata[$ix]) ? $savedPreOTLdata[$ix] : false), $this->textvar) * Mpdf::SCALE; // mPDF 5.7.1
8480 if (strpos($savedPreContentB[$ix], 'L') !== false) {
8481 $contentWidth += $lbw;
8483 if (strpos($savedPreContentB[$ix], 'R') !== false) {
8484 $contentWidth += $rbw;
8488 $savedPreContent = [];
8489 $savedPreContentB = [];
8490 $savedPreOTLdata = []; // mPDF 5.7.1
8491 $savedPreFont = [];
8492 $content[(count($content) - 1)] .= $c;
8493 } else {
8494 $font[] = $savedFont;
8495 $content[] = $savedContent . $c;
8496 $contentB[] = $savedContentB;
8497 $cOTLdata[] = $savedOTLdata; // mPDF 5.7.1
8500 $currContent = & $content[(count($content) - 1)];
8501 $this->restoreFont($font[(count($font) - 1)]); // mPDF 6.0
8503 /* -- CJK-FONTS -- */
8504 // CJK - strip CJK space at start of line
8505 // &#x3000; = \xe3\x80\x80 = CJK space
8506 if ($this->checkCJK && $currContent == "\xe3\x80\x80") {
8507 $currContent = '';
8508 if (isset($this->CurrentFont['useOTL']) && $this->CurrentFont['useOTL']) {
8509 $this->otl->trimOTLdata($cOTLdata[count($cOTLdata) - 1], true, false); // left trim U+3000
8512 /* -- END CJK-FONTS -- */
8514 $lbw = $rbw = 0; // Border widths
8515 if (!empty($this->spanborddet)) {
8516 $lbw = (isset($this->spanborddet['L']['w']) ? $this->spanborddet['L']['w'] : 0);
8517 $rbw = (isset($this->spanborddet['R']['w']) ? $this->spanborddet['R']['w'] : 0);
8520 $contentWidth += $this->GetStringWidth($currContent, false, (isset($cOTLdata[(count($cOTLdata) - 1)]) ? $cOTLdata[(count($cOTLdata) - 1)] : false), $this->textvar) * Mpdf::SCALE; // mPDF 5.7.1
8521 if (strpos($savedContentB, 'L') !== false) {
8522 $contentWidth += $lbw;
8524 $CJKoverflow = false;
8525 $hanger = '';
8526 } // another character will fit, so add it on
8527 else {
8528 $contentWidth += $cw;
8529 $currContent .= $c;
8533 unset($content);
8534 unset($contentB);
8537 // ----------------------END OF FLOWING BLOCK------------------------------------//
8540 /* -- CSS-IMAGE-FLOAT -- */
8541 // Update values if set to skipline
8542 function _advanceFloatMargins()
8544 // Update floatmargins - L
8545 if (isset($this->floatmargins['L']) && $this->floatmargins['L']['skipline'] && $this->floatmargins['L']['y0'] != $this->y) {
8546 $yadj = $this->y - $this->floatmargins['L']['y0'];
8547 $this->floatmargins['L']['y0'] = $this->y;
8548 $this->floatmargins['L']['y1'] += $yadj;
8550 // Update objattr in floatbuffer
8551 if ($this->floatbuffer[$this->floatmargins['L']['id']]['border_left']['w']) {
8552 $this->floatbuffer[$this->floatmargins['L']['id']]['BORDER-Y'] += $yadj;
8554 $this->floatbuffer[$this->floatmargins['L']['id']]['INNER-Y'] += $yadj;
8555 $this->floatbuffer[$this->floatmargins['L']['id']]['OUTER-Y'] += $yadj;
8557 // Unset values
8558 $this->floatbuffer[$this->floatmargins['L']['id']]['skipline'] = false;
8559 $this->floatmargins['L']['skipline'] = false;
8560 $this->floatmargins['L']['id'] = '';
8562 // Update floatmargins - R
8563 if (isset($this->floatmargins['R']) && $this->floatmargins['R']['skipline'] && $this->floatmargins['R']['y0'] != $this->y) {
8564 $yadj = $this->y - $this->floatmargins['R']['y0'];
8565 $this->floatmargins['R']['y0'] = $this->y;
8566 $this->floatmargins['R']['y1'] += $yadj;
8568 // Update objattr in floatbuffer
8569 if ($this->floatbuffer[$this->floatmargins['R']['id']]['border_left']['w']) {
8570 $this->floatbuffer[$this->floatmargins['R']['id']]['BORDER-Y'] += $yadj;
8572 $this->floatbuffer[$this->floatmargins['R']['id']]['INNER-Y'] += $yadj;
8573 $this->floatbuffer[$this->floatmargins['R']['id']]['OUTER-Y'] += $yadj;
8575 // Unset values
8576 $this->floatbuffer[$this->floatmargins['R']['id']]['skipline'] = false;
8577 $this->floatmargins['R']['skipline'] = false;
8578 $this->floatmargins['R']['id'] = '';
8582 /* -- END CSS-IMAGE-FLOAT -- */
8586 /* -- END HTML-CSS -- */
8588 function _SetTextRendering($mode)
8590 if (!(($mode == 0) || ($mode == 1) || ($mode == 2))) {
8591 throw new \Mpdf\MpdfException("Text rendering mode should be 0, 1 or 2 (value : $mode)");
8593 $tr = ($mode . ' Tr');
8594 if ($this->page > 0 && ((isset($this->pageoutput[$this->page]['TextRendering']) && $this->pageoutput[$this->page]['TextRendering'] != $tr) || !isset($this->pageoutput[$this->page]['TextRendering']))) {
8595 $this->_out($tr);
8597 $this->pageoutput[$this->page]['TextRendering'] = $tr;
8600 function SetTextOutline($params = [])
8602 if (isset($params['outline-s']) && $params['outline-s']) {
8603 $this->SetLineWidth($params['outline-WIDTH']);
8604 $this->SetDColor($params['outline-COLOR']);
8605 $tr = ('2 Tr');
8606 if ($this->page > 0 && ((isset($this->pageoutput[$this->page]['TextRendering']) && $this->pageoutput[$this->page]['TextRendering'] != $tr) || !isset($this->pageoutput[$this->page]['TextRendering']))) {
8607 $this->_out($tr);
8609 $this->pageoutput[$this->page]['TextRendering'] = $tr;
8610 } else { // Now resets all values
8611 $this->SetLineWidth(0.2);
8612 $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
8613 $this->_SetTextRendering(0);
8614 $tr = ('0 Tr');
8615 if ($this->page > 0 && ((isset($this->pageoutput[$this->page]['TextRendering']) && $this->pageoutput[$this->page]['TextRendering'] != $tr) || !isset($this->pageoutput[$this->page]['TextRendering']))) {
8616 $this->_out($tr);
8618 $this->pageoutput[$this->page]['TextRendering'] = $tr;
8622 function Image($file, $x, $y, $w = 0, $h = 0, $type = '', $link = '', $paint = true, $constrain = true, $watermark = false, $shownoimg = true, $allowvector = true)
8624 $orig_srcpath = $file;
8625 $this->GetFullPath($file);
8627 $info = $this->imageProcessor->getImage($file, true, $allowvector, $orig_srcpath);
8628 if (!$info && $paint) {
8629 $info = $this->imageProcessor->getImage($this->noImageFile);
8630 if ($info) {
8631 $file = $this->noImageFile;
8632 $w = ($info['w'] * (25.4 / $this->dpi)); // 14 x 16px
8633 $h = ($info['h'] * (25.4 / $this->dpi)); // 14 x 16px
8636 if (!$info) {
8637 return false;
8639 // Automatic width and height calculation if needed
8640 if ($w == 0 and $h == 0) {
8641 /* -- IMAGES-WMF -- */
8642 if ($info['type'] == 'wmf') {
8643 // WMF units are twips (1/20pt)
8644 // divide by 20 to get points
8645 // divide by k to get user units
8646 $w = abs($info['w']) / (20 * Mpdf::SCALE);
8647 $h = abs($info['h']) / (20 * Mpdf::SCALE);
8648 } else { /* -- END IMAGES-WMF -- */
8649 if ($info['type'] == 'svg') {
8650 // returned SVG units are pts
8651 // divide by k to get user units (mm)
8652 $w = abs($info['w']) / Mpdf::SCALE;
8653 $h = abs($info['h']) / Mpdf::SCALE;
8654 } else {
8655 // Put image at default image dpi
8656 $w = ($info['w'] / Mpdf::SCALE) * (72 / $this->img_dpi);
8657 $h = ($info['h'] / Mpdf::SCALE) * (72 / $this->img_dpi);
8661 if ($w == 0) {
8662 $w = abs($h * $info['w'] / $info['h']);
8664 if ($h == 0) {
8665 $h = abs($w * $info['h'] / $info['w']);
8668 /* -- WATERMARK -- */
8669 if ($watermark) {
8670 $maxw = $this->w;
8671 $maxh = $this->h;
8672 // Size = D PF or array
8673 if (is_array($this->watermark_size)) {
8674 $w = $this->watermark_size[0];
8675 $h = $this->watermark_size[1];
8676 } elseif (!is_string($this->watermark_size)) {
8677 $maxw -= $this->watermark_size * 2;
8678 $maxh -= $this->watermark_size * 2;
8679 $w = $maxw;
8680 $h = abs($w * $info['h'] / $info['w']);
8681 if ($h > $maxh) {
8682 $h = $maxh;
8683 $w = abs($h * $info['w'] / $info['h']);
8685 } elseif ($this->watermark_size == 'F') {
8686 if ($this->ColActive) {
8687 $maxw = $this->w - ($this->DeflMargin + $this->DefrMargin);
8688 } else {
8689 $maxw = $this->pgwidth;
8691 $maxh = $this->h - ($this->tMargin + $this->bMargin);
8692 $w = $maxw;
8693 $h = abs($w * $info['h'] / $info['w']);
8694 if ($h > $maxh) {
8695 $h = $maxh;
8696 $w = abs($h * $info['w'] / $info['h']);
8698 } elseif ($this->watermark_size == 'P') { // Default P
8699 $w = $maxw;
8700 $h = abs($w * $info['h'] / $info['w']);
8701 if ($h > $maxh) {
8702 $h = $maxh;
8703 $w = abs($h * $info['w'] / $info['h']);
8706 // Automatically resize to maximum dimensions of page if too large
8707 if ($w > $maxw) {
8708 $w = $maxw;
8709 $h = abs($w * $info['h'] / $info['w']);
8711 if ($h > $maxh) {
8712 $h = $maxh;
8713 $w = abs($h * $info['w'] / $info['h']);
8715 // Position
8716 if (is_array($this->watermark_pos)) {
8717 $x = $this->watermark_pos[0];
8718 $y = $this->watermark_pos[1];
8719 } elseif ($this->watermark_pos == 'F') { // centred on printable area
8720 if ($this->ColActive) { // *COLUMNS*
8721 if (($this->mirrorMargins) && (($this->page) % 2 == 0)) {
8722 $xadj = $this->DeflMargin - $this->DefrMargin;
8723 } // *COLUMNS*
8724 else {
8725 $xadj = 0;
8726 } // *COLUMNS*
8727 $x = ($this->DeflMargin - $xadj + ($this->w - ($this->DeflMargin + $this->DefrMargin)) / 2) - ($w / 2); // *COLUMNS*
8728 } // *COLUMNS*
8729 else { // *COLUMNS*
8730 $x = ($this->lMargin + ($this->pgwidth) / 2) - ($w / 2);
8731 } // *COLUMNS*
8732 $y = ($this->tMargin + ($this->h - ($this->tMargin + $this->bMargin)) / 2) - ($h / 2);
8733 } else { // default P - centred on whole page
8734 $x = ($this->w / 2) - ($w / 2);
8735 $y = ($this->h / 2) - ($h / 2);
8737 /* -- IMAGES-WMF -- */
8738 if ($info['type'] == 'wmf') {
8739 $sx = $w * Mpdf::SCALE / $info['w'];
8740 $sy = -$h * Mpdf::SCALE / $info['h'];
8741 $outstring = sprintf('q %.3F 0 0 %.3F %.3F %.3F cm /FO%d Do Q', $sx, $sy, $x * Mpdf::SCALE - $sx * $info['x'], (($this->h - $y) * Mpdf::SCALE) - $sy * $info['y'], $info['i']);
8742 } else { /* -- END IMAGES-WMF -- */
8743 if ($info['type'] == 'svg') {
8744 $sx = $w * Mpdf::SCALE / $info['w'];
8745 $sy = -$h * Mpdf::SCALE / $info['h'];
8746 $outstring = sprintf('q %.3F 0 0 %.3F %.3F %.3F cm /FO%d Do Q', $sx, $sy, $x * Mpdf::SCALE - $sx * $info['x'], (($this->h - $y) * Mpdf::SCALE) - $sy * $info['y'], $info['i']);
8747 } else {
8748 $outstring = sprintf("q %.3F 0 0 %.3F %.3F %.3F cm /I%d Do Q", $w * Mpdf::SCALE, $h * Mpdf::SCALE, $x * Mpdf::SCALE, ($this->h - ($y + $h)) * Mpdf::SCALE, $info['i']);
8752 if ($this->watermarkImgBehind) {
8753 $outstring = $this->watermarkImgAlpha . "\n" . $outstring . "\n" . $this->SetAlpha(1, 'Normal', true) . "\n";
8754 $this->pages[$this->page] = preg_replace('/(___BACKGROUND___PATTERNS' . $this->uniqstr . ')/', "\n" . $outstring . "\n" . '\\1', $this->pages[$this->page]);
8755 } else {
8756 $this->_out($outstring);
8759 return 0;
8760 } // end of IF watermark
8761 /* -- END WATERMARK -- */
8763 if ($constrain) {
8764 // Automatically resize to maximum dimensions of page if too large
8765 if (isset($this->blk[$this->blklvl]['inner_width']) && $this->blk[$this->blklvl]['inner_width']) {
8766 $maxw = $this->blk[$this->blklvl]['inner_width'];
8767 } else {
8768 $maxw = $this->pgwidth;
8770 if ($w > $maxw) {
8771 $w = $maxw;
8772 $h = abs($w * $info['h'] / $info['w']);
8774 if ($h > $this->h - ($this->tMargin + $this->bMargin + 1)) { // see below - +10 to avoid drawing too close to border of page
8775 $h = $this->h - ($this->tMargin + $this->bMargin + 1);
8776 if ($this->fullImageHeight) {
8777 $h = $this->fullImageHeight;
8779 $w = abs($h * $info['w'] / $info['h']);
8783 // Avoid drawing out of the paper(exceeding width limits).
8784 // if ( ($x + $w) > $this->fw ) {
8785 if (($x + $w) > $this->w) {
8786 $x = $this->lMargin;
8787 $y += 5;
8790 $changedpage = false;
8791 $oldcolumn = $this->CurrCol;
8792 // Avoid drawing out of the page.
8793 if ($y + $h > $this->PageBreakTrigger and ! $this->InFooter and $this->AcceptPageBreak()) {
8794 $this->AddPage($this->CurOrientation);
8795 // Added to correct for OddEven Margins
8796 $x = $x + $this->MarginCorrection;
8797 $y = $this->tMargin; // mPDF 5.7.3
8798 $changedpage = true;
8800 /* -- COLUMNS -- */
8801 // COLS
8802 // COLUMN CHANGE
8803 if ($this->CurrCol != $oldcolumn) {
8804 $y = $this->y0;
8805 $x += $this->ChangeColumn * ($this->ColWidth + $this->ColGap);
8806 $this->x += $this->ChangeColumn * ($this->ColWidth + $this->ColGap);
8808 /* -- END COLUMNS -- */
8809 } // end of IF constrain
8811 /* -- IMAGES-WMF -- */
8812 if ($info['type'] == 'wmf') {
8813 $sx = $w * Mpdf::SCALE / $info['w'];
8814 $sy = -$h * Mpdf::SCALE / $info['h'];
8815 $outstring = sprintf('q %.3F 0 0 %.3F %.3F %.3F cm /FO%d Do Q', $sx, $sy, $x * Mpdf::SCALE - $sx * $info['x'], (($this->h - $y) * Mpdf::SCALE) - $sy * $info['y'], $info['i']);
8816 } else { /* -- END IMAGES-WMF -- */
8817 if ($info['type'] == 'svg') {
8818 $sx = $w * Mpdf::SCALE / $info['w'];
8819 $sy = -$h * Mpdf::SCALE / $info['h'];
8820 $outstring = sprintf('q %.3F 0 0 %.3F %.3F %.3F cm /FO%d Do Q', $sx, $sy, $x * Mpdf::SCALE - $sx * $info['x'], (($this->h - $y) * Mpdf::SCALE) - $sy * $info['y'], $info['i']);
8821 } else {
8822 $outstring = sprintf("q %.3F 0 0 %.3F %.3F %.3F cm /I%d Do Q", $w * Mpdf::SCALE, $h * Mpdf::SCALE, $x * Mpdf::SCALE, ($this->h - ($y + $h)) * Mpdf::SCALE, $info['i']);
8826 if ($paint) {
8827 $this->_out($outstring);
8828 if ($link) {
8829 $this->Link($x, $y, $w, $h, $link);
8832 // Avoid writing text on top of the image. // THIS WAS OUTSIDE THE if ($paint) bit!!!!!!!!!!!!!!!!
8833 $this->y = $y + $h;
8836 // Return width-height array
8837 $sizesarray['WIDTH'] = $w;
8838 $sizesarray['HEIGHT'] = $h;
8839 $sizesarray['X'] = $x; // Position before painting image
8840 $sizesarray['Y'] = $y; // Position before painting image
8841 $sizesarray['OUTPUT'] = $outstring;
8843 $sizesarray['IMAGE_ID'] = $info['i'];
8844 $sizesarray['itype'] = $info['type'];
8845 $sizesarray['set-dpi'] = (isset($info['set-dpi']) ? $info['set-dpi'] : 0);
8846 return $sizesarray;
8849 // =============================================================
8850 // =============================================================
8851 // =============================================================
8852 // =============================================================
8853 // =============================================================
8854 /* -- HTML-CSS -- */
8856 function _getObjAttr($t)
8858 $c = explode("\xbb\xa4\xac", $t, 2);
8859 $c = explode(",", $c[1], 2);
8860 foreach ($c as $v) {
8861 $v = explode("=", $v, 2);
8862 $sp[$v[0]] = $v[1];
8864 return (unserialize($sp['objattr']));
8867 function inlineObject($type, $x, $y, $objattr, $Lmargin, $widthUsed, $maxWidth, $lineHeight, $paint = false, $is_table = false)
8869 if ($is_table) {
8870 $k = $this->shrin_k;
8871 } else {
8872 $k = 1;
8875 // NB $x is only used when paint=true
8876 // Lmargin not used
8877 $w = 0;
8878 if (isset($objattr['width'])) {
8879 $w = $objattr['width'] / $k;
8881 $h = 0;
8882 if (isset($objattr['height'])) {
8883 $h = abs($objattr['height'] / $k);
8885 $widthLeft = $maxWidth - $widthUsed;
8886 $maxHeight = $this->h - ($this->tMargin + $this->bMargin + 10);
8887 if ($this->fullImageHeight) {
8888 $maxHeight = $this->fullImageHeight;
8890 // For Images
8891 if (isset($objattr['border_left'])) {
8892 $extraWidth = ($objattr['border_left']['w'] + $objattr['border_right']['w'] + $objattr['margin_left'] + $objattr['margin_right']) / $k;
8893 $extraHeight = ($objattr['border_top']['w'] + $objattr['border_bottom']['w'] + $objattr['margin_top'] + $objattr['margin_bottom']) / $k;
8895 if ($type == 'image' || $type == 'barcode' || $type == 'textcircle') {
8896 $extraWidth += ($objattr['padding_left'] + $objattr['padding_right']) / $k;
8897 $extraHeight += ($objattr['padding_top'] + $objattr['padding_bottom']) / $k;
8901 if (!isset($objattr['vertical-align'])) {
8902 if ($objattr['type'] == 'select') {
8903 $objattr['vertical-align'] = 'M';
8904 } else {
8905 $objattr['vertical-align'] = 'BS';
8907 } // mPDF 6
8909 if ($type == 'image' || (isset($objattr['subtype']) && $objattr['subtype'] == 'IMAGE')) {
8910 if (isset($objattr['itype']) && ($objattr['itype'] == 'wmf' || $objattr['itype'] == 'svg')) {
8911 $file = $objattr['file'];
8912 $info = $this->formobjects[$file];
8913 } elseif (isset($objattr['file'])) {
8914 $file = $objattr['file'];
8915 $info = $this->images[$file];
8918 if ($type == 'annot' || $type == 'bookmark' || $type == 'indexentry' || $type == 'toc') {
8919 $w = 0.00001;
8920 $h = 0.00001;
8923 // TEST whether need to skipline
8924 if (!$paint) {
8925 if ($type == 'hr') { // always force new line
8926 if (($y + $h + $lineHeight > $this->PageBreakTrigger) && !$this->InFooter && !$is_table) {
8927 return [-2, $w, $h];
8928 } // New page + new line
8929 else {
8930 return [1, $w, $h];
8931 } // new line
8932 } else {
8933 // LIST MARKERS // mPDF 6 Lists
8934 $displayheight = $h;
8935 $displaywidth = $w;
8936 if ($objattr['type'] == 'image' && isset($objattr['listmarker']) && $objattr['listmarker']) {
8937 $displayheight = 0;
8938 if ($objattr['listmarkerposition'] == 'outside') {
8939 $displaywidth = 0;
8943 if ($widthUsed > 0 && $displaywidth > $widthLeft && (!$is_table || $type != 'image')) { // New line needed
8944 // mPDF 6 Lists
8945 if (($y + $displayheight + $lineHeight > $this->PageBreakTrigger) && !$this->InFooter) {
8946 return [-2, $w, $h];
8947 } // New page + new line
8948 return [1, $w, $h]; // new line
8949 } elseif ($widthUsed > 0 && $displaywidth > $widthLeft && $is_table) { // New line needed in TABLE
8950 return [1, $w, $h]; // new line
8951 } // Will fit on line but NEW PAGE REQUIRED
8952 elseif (($y + $displayheight > $this->PageBreakTrigger) && !$this->InFooter && !$is_table) {
8953 return [-1, $w, $h];
8954 } // mPDF 6 Lists
8955 else {
8956 return [0, $w, $h];
8961 if ($type == 'annot' || $type == 'bookmark' || $type == 'indexentry' || $type == 'toc') {
8962 $w = 0.00001;
8963 $h = 0.00001;
8964 $objattr['BORDER-WIDTH'] = 0;
8965 $objattr['BORDER-HEIGHT'] = 0;
8966 $objattr['BORDER-X'] = $x;
8967 $objattr['BORDER-Y'] = $y;
8968 $objattr['INNER-WIDTH'] = 0;
8969 $objattr['INNER-HEIGHT'] = 0;
8970 $objattr['INNER-X'] = $x;
8971 $objattr['INNER-Y'] = $y;
8974 if ($type == 'image') {
8975 // Automatically resize to width remaining
8976 if ($w > ($widthLeft + 0.0001) && !$is_table) { // mPDF 5.7.4 0.0001 to allow for rounding errors when w==maxWidth
8977 $w = $widthLeft;
8978 $h = abs($w * $info['h'] / $info['w']);
8980 $img_w = $w - $extraWidth;
8981 $img_h = $h - $extraHeight;
8983 $objattr['BORDER-WIDTH'] = $img_w + $objattr['padding_left'] / $k + $objattr['padding_right'] / $k + (($objattr['border_left']['w'] / $k + $objattr['border_right']['w'] / $k) / 2);
8984 $objattr['BORDER-HEIGHT'] = $img_h + $objattr['padding_top'] / $k + $objattr['padding_bottom'] / $k + (($objattr['border_top']['w'] / $k + $objattr['border_bottom']['w'] / $k) / 2);
8985 $objattr['BORDER-X'] = $x + $objattr['margin_left'] / $k + (($objattr['border_left']['w'] / $k) / 2);
8986 $objattr['BORDER-Y'] = $y + $objattr['margin_top'] / $k + (($objattr['border_top']['w'] / $k) / 2);
8987 $objattr['INNER-WIDTH'] = $img_w;
8988 $objattr['INNER-HEIGHT'] = $img_h;
8989 $objattr['INNER-X'] = $x + $objattr['padding_left'] / $k + $objattr['margin_left'] / $k + ($objattr['border_left']['w'] / $k);
8990 $objattr['INNER-Y'] = $y + $objattr['padding_top'] / $k + $objattr['margin_top'] / $k + ($objattr['border_top']['w'] / $k);
8991 $objattr['ID'] = $info['i'];
8994 if ($type == 'input' && $objattr['subtype'] == 'IMAGE') {
8995 $img_w = $w - $extraWidth;
8996 $img_h = $h - $extraHeight;
8997 $objattr['BORDER-WIDTH'] = $img_w + (($objattr['border_left']['w'] / $k + $objattr['border_right']['w'] / $k) / 2);
8998 $objattr['BORDER-HEIGHT'] = $img_h + (($objattr['border_top']['w'] / $k + $objattr['border_bottom']['w'] / $k) / 2);
8999 $objattr['BORDER-X'] = $x + $objattr['margin_left'] / $k + (($objattr['border_left']['w'] / $k) / 2);
9000 $objattr['BORDER-Y'] = $y + $objattr['margin_top'] / $k + (($objattr['border_top']['w'] / $k) / 2);
9001 $objattr['INNER-WIDTH'] = $img_w;
9002 $objattr['INNER-HEIGHT'] = $img_h;
9003 $objattr['INNER-X'] = $x + $objattr['margin_left'] / $k + ($objattr['border_left']['w'] / $k);
9004 $objattr['INNER-Y'] = $y + $objattr['margin_top'] / $k + ($objattr['border_top']['w'] / $k);
9005 $objattr['ID'] = $info['i'];
9008 if ($type == 'barcode' || $type == 'textcircle') {
9009 $b_w = $w - $extraWidth;
9010 $b_h = $h - $extraHeight;
9011 $objattr['BORDER-WIDTH'] = $b_w + $objattr['padding_left'] / $k + $objattr['padding_right'] / $k + (($objattr['border_left']['w'] / $k + $objattr['border_right']['w'] / $k) / 2);
9012 $objattr['BORDER-HEIGHT'] = $b_h + $objattr['padding_top'] / $k + $objattr['padding_bottom'] / $k + (($objattr['border_top']['w'] / $k + $objattr['border_bottom']['w'] / $k) / 2);
9013 $objattr['BORDER-X'] = $x + $objattr['margin_left'] / $k + (($objattr['border_left']['w'] / $k) / 2);
9014 $objattr['BORDER-Y'] = $y + $objattr['margin_top'] / $k + (($objattr['border_top']['w'] / $k) / 2);
9015 $objattr['INNER-X'] = $x + $objattr['padding_left'] / $k + $objattr['margin_left'] / $k + ($objattr['border_left']['w'] / $k);
9016 $objattr['INNER-Y'] = $y + $objattr['padding_top'] / $k + $objattr['margin_top'] / $k + ($objattr['border_top']['w'] / $k);
9017 $objattr['INNER-WIDTH'] = $b_w;
9018 $objattr['INNER-HEIGHT'] = $b_h;
9022 if ($type == 'textarea') {
9023 // Automatically resize to width remaining
9024 if ($w > $widthLeft && !$is_table) {
9025 $w = $widthLeft;
9027 // This used to resize height to maximum remaining on page ? why. Causes problems when in table and causing a new column
9028 // if (($y + $h > $this->PageBreakTrigger) && !$this->InFooter) {
9029 // $h=$this->h - $y - $this->bMargin;
9030 // }
9033 if ($type == 'hr') {
9034 if ($is_table) {
9035 $objattr['INNER-WIDTH'] = $maxWidth * $objattr['W-PERCENT'] / 100;
9036 $objattr['width'] = $objattr['INNER-WIDTH'];
9037 $w = $maxWidth;
9038 } else {
9039 if ($w > $maxWidth) {
9040 $w = $maxWidth;
9042 $objattr['INNER-WIDTH'] = $w;
9043 $w = $maxWidth;
9049 if (($type == 'select') || ($type == 'input' && ($objattr['subtype'] == 'TEXT' || $objattr['subtype'] == 'PASSWORD'))) {
9050 // Automatically resize to width remaining
9051 if ($w > $widthLeft && !$is_table) {
9052 $w = $widthLeft;
9056 if ($type == 'textarea' || $type == 'select' || $type == 'input') {
9057 if (isset($objattr['fontsize'])) {
9058 $objattr['fontsize'] /= $k;
9060 if (isset($objattr['linewidth'])) {
9061 $objattr['linewidth'] /= $k;
9065 if (!isset($objattr['BORDER-Y'])) {
9066 $objattr['BORDER-Y'] = 0;
9068 if (!isset($objattr['BORDER-X'])) {
9069 $objattr['BORDER-X'] = 0;
9071 if (!isset($objattr['INNER-Y'])) {
9072 $objattr['INNER-Y'] = 0;
9074 if (!isset($objattr['INNER-X'])) {
9075 $objattr['INNER-X'] = 0;
9078 // Return width-height array
9079 $objattr['OUTER-WIDTH'] = $w;
9080 $objattr['OUTER-HEIGHT'] = $h;
9081 $objattr['OUTER-X'] = $x;
9082 $objattr['OUTER-Y'] = $y;
9083 return $objattr;
9086 /* -- END HTML-CSS -- */
9088 // =============================================================
9089 // =============================================================
9090 // =============================================================
9091 // =============================================================
9092 // =============================================================
9094 function SetLineJoin($mode = 0)
9096 $s = sprintf('%d j', $mode);
9097 if ($this->page > 0 && ((isset($this->pageoutput[$this->page]['LineJoin']) && $this->pageoutput[$this->page]['LineJoin'] != $s) || !isset($this->pageoutput[$this->page]['LineJoin']))) {
9098 $this->_out($s);
9100 $this->pageoutput[$this->page]['LineJoin'] = $s;
9103 function SetLineCap($mode = 2)
9105 $s = sprintf('%d J', $mode);
9106 if ($this->page > 0 && ((isset($this->pageoutput[$this->page]['LineCap']) && $this->pageoutput[$this->page]['LineCap'] != $s) || !isset($this->pageoutput[$this->page]['LineCap']))) {
9107 $this->_out($s);
9109 $this->pageoutput[$this->page]['LineCap'] = $s;
9112 function SetDash($black = false, $white = false)
9114 if ($black and $white) {
9115 $s = sprintf('[%.3F %.3F] 0 d', $black * Mpdf::SCALE, $white * Mpdf::SCALE);
9116 } else {
9117 $s = '[] 0 d';
9119 if ($this->page > 0 && ((isset($this->pageoutput[$this->page]['Dash']) && $this->pageoutput[$this->page]['Dash'] != $s) || !isset($this->pageoutput[$this->page]['Dash']))) {
9120 $this->_out($s);
9122 $this->pageoutput[$this->page]['Dash'] = $s;
9125 function SetDisplayPreferences($preferences)
9127 // String containing any or none of /HideMenubar/HideToolbar/HideWindowUI/DisplayDocTitle/CenterWindow/FitWindow
9128 $this->DisplayPreferences .= $preferences;
9131 function Ln($h = '', $collapsible = 0)
9133 // Added collapsible to allow collapsible top-margin on new page
9134 // Line feed; default value is last cell height
9135 $this->x = $this->lMargin + $this->blk[$this->blklvl]['outer_left_margin'];
9136 if ($collapsible && ($this->y == $this->tMargin) && (!$this->ColActive)) {
9137 $h = 0;
9139 if (is_string($h)) {
9140 $this->y+=$this->lasth;
9141 } else {
9142 $this->y+=$h;
9146 /* -- HTML-CSS -- */
9148 function DivLn($h, $level = -3, $move_y = true, $collapsible = false, $state = 0)
9150 // $state = 0 normal; 1 top; 2 bottom; 3 top and bottom
9151 // Used in Columns and keep-with-table i.e. "kwt"
9152 // writes background block by block so it can be repositioned
9153 // and also used in writingFlowingBlock at top and bottom of blocks to move y (not to draw/paint anything)
9154 // adds lines (y) where DIV bgcolors are filled in
9155 // this->x is returned as it was
9156 // allows .00001 as nominal height used for bookmarks/annotations etc.
9157 if ($collapsible && (sprintf("%0.4f", $this->y) == sprintf("%0.4f", $this->tMargin)) && (!$this->ColActive)) {
9158 return;
9161 // mPDF 6 Columns
9162 // if ($collapsible && (sprintf("%0.4f", $this->y)==sprintf("%0.4f", $this->y0)) && ($this->ColActive) && $this->CurrCol == 0) { return; } // *COLUMNS*
9163 if ($collapsible && (sprintf("%0.4f", $this->y) == sprintf("%0.4f", $this->y0)) && ($this->ColActive)) {
9164 return;
9165 } // *COLUMNS*
9166 // Still use this method if columns or keep-with-table, as it allows repositioning later
9167 // otherwise, now uses PaintDivBB()
9168 if (!$this->ColActive && !$this->kwt) {
9169 if ($move_y && !$this->ColActive) {
9170 $this->y += $h;
9172 return;
9175 if ($level == -3) {
9176 $level = $this->blklvl;
9178 $firstblockfill = $this->GetFirstBlockFill();
9179 if ($firstblockfill && $this->blklvl > 0 && $this->blklvl >= $firstblockfill) {
9180 $last_x = 0;
9181 $last_w = 0;
9182 $last_fc = $this->FillColor;
9183 $bak_x = $this->x;
9184 $bak_h = $this->divheight;
9185 $this->divheight = 0; // Temporarily turn off divheight - as Cell() uses it to check for PageBreak
9186 for ($blvl = $firstblockfill; $blvl <= $level; $blvl++) {
9187 $this->x = $this->lMargin + $this->blk[$blvl]['outer_left_margin'];
9188 // mPDF 6
9189 if ($this->blk[$blvl]['bgcolor']) {
9190 $this->SetFColor($this->blk[$blvl]['bgcolorarray']);
9192 if ($last_x != ($this->lMargin + $this->blk[$blvl]['outer_left_margin']) || ($last_w != $this->blk[$blvl]['width']) || $last_fc != $this->FillColor || (isset($this->blk[$blvl]['border_top']['s']) && $this->blk[$blvl]['border_top']['s']) || (isset($this->blk[$blvl]['border_bottom']['s']) && $this->blk[$blvl]['border_bottom']['s']) || (isset($this->blk[$blvl]['border_left']['s']) && $this->blk[$blvl]['border_left']['s']) || (isset($this->blk[$blvl]['border_right']['s']) && $this->blk[$blvl]['border_right']['s'])) {
9193 $x = $this->x;
9194 $this->Cell(($this->blk[$blvl]['width']), $h, '', '', 0, '', 1);
9195 $this->x = $x;
9196 if (!$this->keep_block_together && !$this->writingHTMLheader && !$this->writingHTMLfooter) {
9197 // $state = 0 normal; 1 top; 2 bottom; 3 top and bottom
9198 if ($blvl == $this->blklvl) {
9199 $this->PaintDivLnBorder($state, $blvl, $h);
9200 } else {
9201 $this->PaintDivLnBorder(0, $blvl, $h);
9205 $last_x = $this->lMargin + $this->blk[$blvl]['outer_left_margin'];
9206 $last_w = $this->blk[$blvl]['width'];
9207 $last_fc = $this->FillColor;
9209 // Reset current block fill
9210 if (isset($this->blk[$this->blklvl]['bgcolorarray'])) {
9211 $bcor = $this->blk[$this->blklvl]['bgcolorarray'];
9212 $this->SetFColor($bcor);
9214 $this->x = $bak_x;
9215 $this->divheight = $bak_h;
9217 if ($move_y) {
9218 $this->y += $h;
9222 /* -- END HTML-CSS -- */
9224 function SetX($x)
9226 // Set x position
9227 if ($x >= 0) {
9228 $this->x = $x;
9229 } else {
9230 $this->x = $this->w + $x;
9234 function SetY($y)
9236 // Set y position and reset x
9237 $this->x = $this->lMargin;
9238 if ($y >= 0) {
9239 $this->y = $y;
9240 } else {
9241 $this->y = $this->h + $y;
9245 function SetXY($x, $y)
9247 // Set x and y positions
9248 $this->SetY($y);
9249 $this->SetX($x);
9252 function Output($name = '', $dest = '')
9254 $this->logger->debug(sprintf('PDF generated in %.6F seconds', microtime(true) - $this->time0), ['context' => LogContext::STATISTICS]);
9256 // Finish document if necessary
9257 if ($this->state < 3) {
9258 $this->Close();
9261 if ($this->debug && error_get_last()) {
9262 $e = error_get_last();
9263 if (($e['type'] < 2048 && $e['type'] != 8) || (intval($e['type']) & intval(ini_get("error_reporting")))) {
9264 throw new \Mpdf\MpdfException(
9265 sprintf('Error detected. PDF file generation aborted: %s', $e['message']),
9266 $e['type'],
9268 $e['file'],
9269 $e['line']
9274 if (($this->PDFA || $this->PDFX) && $this->encrypted) {
9275 throw new \Mpdf\MpdfException('PDF/A1-b or PDF/X1-a does not permit encryption of documents.');
9278 if (count($this->PDFAXwarnings) && (($this->PDFA && !$this->PDFAauto) || ($this->PDFX && !$this->PDFXauto))) {
9279 if ($this->PDFA) {
9280 $standard = 'PDFA/1-b';
9281 $option = '$mpdf->PDFAauto';
9282 } else {
9283 $standard = 'PDFX/1-a ';
9284 $option = '$mpdf->PDFXauto';
9287 $this->logger->warning(sprintf('PDF could not be generated as it stands as a %s compliant file.', $standard), ['context' => LogContext::PDFA_PDFX]);
9288 $this->logger->warning(sprintf('These issues can be automatically fixed by mPDF using %s = true;', $option), ['context' => LogContext::PDFA_PDFX]);
9289 $this->logger->warning(sprintf('Action that mPDF will take to automatically force %s compliance are shown further in the log.', $standard), ['context' => LogContext::PDFA_PDFX]);
9291 $this->PDFAXwarnings = array_unique($this->PDFAXwarnings);
9292 foreach ($this->PDFAXwarnings as $w) {
9293 $this->logger->warning($w, ['context' => LogContext::PDFA_PDFX]);
9296 throw new \Mpdf\MpdfException('PDFA/PDFX warnings generated. See log for further details');
9299 $this->logger->debug(sprintf('Compiled in %.6F seconds', microtime(true) - $this->time0), ['context' => LogContext::STATISTICS]);
9300 $this->logger->debug(sprintf('Peak Memory usage %s MB', number_format(memory_get_peak_usage(true) / (1024 * 1024), 2)), ['context' => LogContext::STATISTICS]);
9301 $this->logger->debug(sprintf('PDF file size %s kB', number_format(strlen($this->buffer) / 1024)), ['context' => LogContext::STATISTICS]);
9302 $this->logger->debug(sprintf('%d fonts used', count($this->fonts)), ['context' => LogContext::STATISTICS]);
9304 if (is_bool($dest)) {
9305 $dest = $dest ? Destination::DOWNLOAD : Destination::FILE;
9308 $dest = strtoupper($dest);
9309 if (empty($dest)) {
9310 if (empty($name)) {
9311 $name = 'mpdf.pdf';
9312 $dest = Destination::INLINE;
9313 } else {
9314 $dest = Destination::FILE;
9318 switch ($dest) {
9320 case Destination::INLINE:
9322 if (headers_sent($filename, $line)) {
9323 throw new \Mpdf\MpdfException(
9324 sprintf('Data has already been sent to output (%s at line %s), unable to output PDF file', $filename, $line)
9328 if ($this->debug && !$this->allow_output_buffering && ob_get_contents()) {
9329 throw new \Mpdf\MpdfException('Output has already been sent from the script - PDF file generation aborted.');
9332 // We send to a browser
9333 if (PHP_SAPI !== 'cli') {
9334 header('Content-Type: application/pdf');
9336 if (!isset($_SERVER['HTTP_ACCEPT_ENCODING']) || empty($_SERVER['HTTP_ACCEPT_ENCODING'])) {
9337 // don't use length if server using compression
9338 header('Content-Length: ' . strlen($this->buffer));
9341 header('Content-disposition: inline; filename="' . $name . '"');
9342 header('Cache-Control: public, must-revalidate, max-age=0');
9343 header('Pragma: public');
9344 header('X-Generator: mPDF ' . static::VERSION);
9345 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT');
9346 header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
9349 echo $this->buffer;
9351 break;
9353 case Destination::DOWNLOAD:
9355 if (headers_sent()) {
9356 throw new \Mpdf\MpdfException('Data has already been sent to output, unable to output PDF file');
9359 header('Content-Description: File Transfer');
9360 header('Content-Transfer-Encoding: binary');
9361 header('Cache-Control: public, must-revalidate, max-age=0');
9362 header('Pragma: public');
9363 header('X-Generator: mPDF ' . static::VERSION);
9364 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT');
9365 header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
9366 header('Content-Type: application/pdf');
9368 if (!isset($_SERVER['HTTP_ACCEPT_ENCODING']) || empty($_SERVER['HTTP_ACCEPT_ENCODING'])) {
9369 // don't use length if server using compression
9370 header('Content-Length: ' . strlen($this->buffer));
9373 header('Content-Disposition: attachment; filename="' . $name . '"');
9375 echo $this->buffer;
9377 break;
9379 case Destination::FILE:
9380 $f = fopen($name, 'wb');
9382 if (!$f) {
9383 throw new \Mpdf\MpdfException(sprintf('Unable to create output file %s', $name));
9386 fwrite($f, $this->buffer, strlen($this->buffer));
9387 fclose($f);
9389 break;
9391 case Destination::STRING_RETURN:
9392 $this->cache->clearOld();
9393 return $this->buffer;
9395 default:
9396 throw new \Mpdf\MpdfException(sprintf('Incorrect output destination %s', $dest));
9399 $this->cache->clearOld();
9402 // *****************************************************************************
9403 // *
9404 // Protected methods *
9405 // *
9406 // *****************************************************************************
9407 function _dochecks()
9409 // Check for locale-related bug
9410 if (1.1 == 1) {
9411 throw new \Mpdf\MpdfException('Do not alter the locale before including mPDF');
9414 // Check for decimal separator
9415 if (sprintf('%.1f', 1.0) != '1.0') {
9416 setlocale(LC_NUMERIC, 'C');
9419 if (ini_get('mbstring.func_overload')) {
9420 throw new \Mpdf\MpdfException('Mpdf cannot function properly with mbstring.func_overload enabled');
9423 if (!function_exists('mb_substr')) {
9424 throw new \Mpdf\MpdfException('mbstring extension must be loaded in order to run mPDF');
9428 function _puthtmlheaders()
9430 $this->state = 2;
9431 $nb = $this->page;
9432 for ($n = 1; $n <= $nb; $n++) {
9433 if ($this->mirrorMargins && $n % 2 == 0) {
9434 $OE = 'E';
9435 } // EVEN
9436 else {
9437 $OE = 'O';
9439 $this->page = $n;
9440 $pn = $this->docPageNum($n);
9441 if ($pn) {
9442 $pnstr = $this->pagenumPrefix . $pn . $this->pagenumSuffix;
9443 } else {
9444 $pnstr = '';
9447 $pnt = $this->docPageNumTotal($n);
9449 if ($pnt) {
9450 $pntstr = $this->nbpgPrefix . $pnt . $this->nbpgSuffix;
9451 } else {
9452 $pntstr = '';
9455 if (isset($this->saveHTMLHeader[$n][$OE])) {
9456 $html = isset($this->saveHTMLHeader[$n][$OE]['html']) ? $this->saveHTMLHeader[$n][$OE]['html'] : '';
9457 $this->lMargin = $this->saveHTMLHeader[$n][$OE]['ml'];
9458 $this->rMargin = $this->saveHTMLHeader[$n][$OE]['mr'];
9459 $this->tMargin = $this->saveHTMLHeader[$n][$OE]['mh'];
9460 $this->bMargin = $this->saveHTMLHeader[$n][$OE]['mf'];
9461 $this->margin_header = $this->saveHTMLHeader[$n][$OE]['mh'];
9462 $this->margin_footer = $this->saveHTMLHeader[$n][$OE]['mf'];
9463 $this->w = $this->saveHTMLHeader[$n][$OE]['pw'];
9464 $this->h = $this->saveHTMLHeader[$n][$OE]['ph'];
9465 $rotate = (isset($this->saveHTMLHeader[$n][$OE]['rotate']) ? $this->saveHTMLHeader[$n][$OE]['rotate'] : null);
9466 $this->Reset();
9467 $this->pageoutput[$n] = [];
9468 $this->pgwidth = $this->w - $this->lMargin - $this->rMargin;
9469 $this->x = $this->lMargin;
9470 $this->y = $this->margin_header;
9471 $html = str_replace('{PAGENO}', $pnstr, $html);
9472 $html = str_replace($this->aliasNbPgGp, $pntstr, $html); // {nbpg}
9473 $html = str_replace($this->aliasNbPg, $nb, $html); // {nb}
9474 $html = preg_replace_callback('/\{DATE\s+(.*?)\}/', [$this, 'date_callback'], $html); // mPDF 5.7
9476 $this->HTMLheaderPageLinks = [];
9477 $this->HTMLheaderPageAnnots = [];
9478 $this->HTMLheaderPageForms = [];
9479 $this->pageBackgrounds = [];
9481 $this->writingHTMLheader = true;
9482 $this->WriteHTML($html, 4); // parameter 4 saves output to $this->headerbuffer
9483 $this->writingHTMLheader = false;
9484 $this->Reset();
9485 $this->pageoutput[$n] = [];
9487 $s = $this->PrintPageBackgrounds();
9488 $this->headerbuffer = $s . $this->headerbuffer;
9489 $os = '';
9490 if ($rotate) {
9491 $os .= sprintf('q 0 -1 1 0 0 %.3F cm ', ($this->w * Mpdf::SCALE));
9492 // To rotate the other way i.e. Header to left of page:
9493 // $os .= sprintf('q 0 1 -1 0 %.3F %.3F cm ',($this->h*Mpdf::SCALE), (($this->rMargin - $this->lMargin )*Mpdf::SCALE));
9495 $os .= $this->headerbuffer;
9496 if ($rotate) {
9497 $os .= ' Q' . "\n";
9500 // Writes over the page background but behind any other output on page
9501 $os = preg_replace(['/\\\\/', '/\$/'], ['\\\\\\\\', '\\\\$'], $os);
9503 $this->pages[$n] = preg_replace('/(___HEADER___MARKER' . $this->uniqstr . ')/', "\n" . $os . "\n" . '\\1', $this->pages[$n]);
9505 $lks = $this->HTMLheaderPageLinks;
9506 foreach ($lks as $lk) {
9507 if ($rotate) {
9508 $lw = $lk[2];
9509 $lh = $lk[3];
9510 $lk[2] = $lh;
9511 $lk[3] = $lw; // swap width and height
9512 $ax = $lk[0] / Mpdf::SCALE;
9513 $ay = $lk[1] / Mpdf::SCALE;
9514 $bx = $ay - ($lh / Mpdf::SCALE);
9515 $by = $this->w - $ax;
9516 $lk[0] = $bx * Mpdf::SCALE;
9517 $lk[1] = ($this->h - $by) * Mpdf::SCALE - $lw;
9519 $this->PageLinks[$n][] = $lk;
9521 /* -- FORMS -- */
9522 foreach ($this->HTMLheaderPageForms as $f) {
9523 $this->form->forms[$f['n']] = $f;
9525 /* -- END FORMS -- */
9528 if (isset($this->saveHTMLFooter[$n][$OE])) {
9530 $html = $this->saveHTMLFooter[$this->page][$OE]['html'];
9532 $this->lMargin = $this->saveHTMLFooter[$n][$OE]['ml'];
9533 $this->rMargin = $this->saveHTMLFooter[$n][$OE]['mr'];
9534 $this->tMargin = $this->saveHTMLFooter[$n][$OE]['mh'];
9535 $this->bMargin = $this->saveHTMLFooter[$n][$OE]['mf'];
9536 $this->margin_header = $this->saveHTMLFooter[$n][$OE]['mh'];
9537 $this->margin_footer = $this->saveHTMLFooter[$n][$OE]['mf'];
9538 $this->w = $this->saveHTMLFooter[$n][$OE]['pw'];
9539 $this->h = $this->saveHTMLFooter[$n][$OE]['ph'];
9540 $rotate = (isset($this->saveHTMLFooter[$n][$OE]['rotate']) ? $this->saveHTMLFooter[$n][$OE]['rotate'] : null);
9541 $this->Reset();
9542 $this->pageoutput[$n] = [];
9543 $this->pgwidth = $this->w - $this->lMargin - $this->rMargin;
9544 $this->x = $this->lMargin;
9545 $top_y = $this->y = $this->h - $this->margin_footer;
9547 // if bottom-margin==0, corrects to avoid division by zero
9548 if ($this->y == $this->h) {
9549 $top_y = $this->y = ($this->h + 0.01);
9552 $html = str_replace('{PAGENO}', $pnstr, $html);
9553 $html = str_replace($this->aliasNbPgGp, $pntstr, $html); // {nbpg}
9554 $html = str_replace($this->aliasNbPg, $nb, $html); // {nb}
9555 $html = preg_replace_callback('/\{DATE\s+(.*?)\}/', [$this, 'date_callback'], $html); // mPDF 5.7
9558 $this->HTMLheaderPageLinks = [];
9559 $this->HTMLheaderPageAnnots = [];
9560 $this->HTMLheaderPageForms = [];
9561 $this->pageBackgrounds = [];
9563 $this->writingHTMLfooter = true;
9564 $this->InFooter = true;
9565 $this->WriteHTML($html, 4); // parameter 4 saves output to $this->headerbuffer
9566 $this->InFooter = false;
9567 $this->Reset();
9568 $this->pageoutput[$n] = [];
9570 $fheight = $this->y - $top_y;
9571 $adj = -$fheight;
9573 $s = $this->PrintPageBackgrounds(-$adj);
9574 $this->headerbuffer = $s . $this->headerbuffer;
9575 $this->writingHTMLfooter = false; // mPDF 5.7.3 (moved after PrintPageBackgrounds so can adjust position of images in footer)
9577 $os = '';
9578 $os .= $this->StartTransform(true) . "\n";
9580 if ($rotate) {
9581 $os .= sprintf('q 0 -1 1 0 0 %.3F cm ', ($this->w * Mpdf::SCALE));
9582 // To rotate the other way i.e. Header to left of page:
9583 // $os .= sprintf('q 0 1 -1 0 %.3F %.3F cm ',($this->h*Mpdf::SCALE), (($this->rMargin - $this->lMargin )*Mpdf::SCALE));
9586 $os .= $this->transformTranslate(0, $adj, true) . "\n";
9587 $os .= $this->headerbuffer;
9589 if ($rotate) {
9590 $os .= ' Q' . "\n";
9593 $os .= $this->StopTransform(true) . "\n";
9595 // Writes over the page background but behind any other output on page
9596 $os = preg_replace(['/\\\\/', '/\$/'], ['\\\\\\\\', '\\\\$'], $os);
9598 $this->pages[$n] = preg_replace('/(___HEADER___MARKER' . $this->uniqstr . ')/', "\n" . $os . "\n" . '\\1', $this->pages[$n]);
9600 $lks = $this->HTMLheaderPageLinks;
9602 foreach ($lks as $lk) {
9604 $lk[1] -= $adj * Mpdf::SCALE;
9606 if ($rotate) {
9607 $lw = $lk[2];
9608 $lh = $lk[3];
9609 $lk[2] = $lh;
9610 $lk[3] = $lw; // swap width and height
9612 $ax = $lk[0] / Mpdf::SCALE;
9613 $ay = $lk[1] / Mpdf::SCALE;
9614 $bx = $ay - ($lh / Mpdf::SCALE);
9615 $by = $this->w - $ax;
9616 $lk[0] = $bx * Mpdf::SCALE;
9617 $lk[1] = ($this->h - $by) * Mpdf::SCALE - $lw;
9620 $this->PageLinks[$n][] = $lk;
9623 /* -- FORMS -- */
9624 foreach ($this->HTMLheaderPageForms as $f) {
9625 $f['y'] += $adj;
9626 $this->form->forms[$f['n']] = $f;
9628 /* -- END FORMS -- */
9632 $this->page = $nb;
9633 $this->state = 1;
9636 function _putpages()
9638 $nb = $this->page;
9639 $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
9641 if ($this->DefOrientation == 'P') {
9642 $defwPt = $this->fwPt;
9643 $defhPt = $this->fhPt;
9644 } else {
9645 $defwPt = $this->fhPt;
9646 $defhPt = $this->fwPt;
9648 $annotid = (3 + 2 * $nb);
9650 // Active Forms
9651 $totaladdnum = 0;
9652 for ($n = 1; $n <= $nb; $n++) {
9653 if (isset($this->PageLinks[$n])) {
9654 $totaladdnum += count($this->PageLinks[$n]);
9656 /* -- ANNOTATIONS -- */
9657 if (isset($this->PageAnnots[$n])) {
9658 foreach ($this->PageAnnots[$n] as $k => $pl) {
9659 if (!empty($pl['opt']['popup']) || !empty($pl['opt']['file'])) {
9660 $totaladdnum += 2;
9661 } else {
9662 $totaladdnum++;
9666 /* -- END ANNOTATIONS -- */
9668 /* -- FORMS -- */
9669 if (count($this->form->forms) > 0) {
9670 $this->form->countPageForms($n, $totaladdnum);
9672 /* -- END FORMS -- */
9674 /* -- FORMS -- */
9675 // Make a note in the radio button group of the obj_id it will have
9676 $ctr = 0;
9677 if (count($this->form->form_radio_groups)) {
9678 foreach ($this->form->form_radio_groups as $name => $frg) {
9679 $this->form->form_radio_groups[$name]['obj_id'] = $annotid + $totaladdnum + $ctr;
9680 $ctr++;
9683 /* -- END FORMS -- */
9685 // Select unused fonts (usually default font)
9686 $unused = [];
9687 foreach ($this->fonts as $fk => $font) {
9688 if (isset($font['type']) && $font['type'] == 'TTF' && !$font['used']) {
9689 $unused[] = $fk;
9694 for ($n = 1; $n <= $nb; $n++) {
9695 $thispage = $this->pages[$n];
9696 if (isset($this->OrientationChanges[$n])) {
9697 $hPt = $this->pageDim[$n]['w'] * Mpdf::SCALE;
9698 $wPt = $this->pageDim[$n]['h'] * Mpdf::SCALE;
9699 $owidthPt_LR = $this->pageDim[$n]['outer_width_TB'] * Mpdf::SCALE;
9700 $owidthPt_TB = $this->pageDim[$n]['outer_width_LR'] * Mpdf::SCALE;
9701 } else {
9702 $wPt = $this->pageDim[$n]['w'] * Mpdf::SCALE;
9703 $hPt = $this->pageDim[$n]['h'] * Mpdf::SCALE;
9704 $owidthPt_LR = $this->pageDim[$n]['outer_width_LR'] * Mpdf::SCALE;
9705 $owidthPt_TB = $this->pageDim[$n]['outer_width_TB'] * Mpdf::SCALE;
9707 // Remove references to unused fonts (usually default font)
9708 foreach ($unused as $fk) {
9709 if ($this->fonts[$fk]['sip'] || $this->fonts[$fk]['smp']) {
9710 foreach ($this->fonts[$fk]['subsetfontids'] as $k => $fid) {
9711 $thispage = preg_replace('/\s\/F' . $fid . ' \d[\d.]* Tf\s/is', ' ', $thispage);
9713 } else {
9714 $thispage = preg_replace('/\s\/F' . $this->fonts[$fk]['i'] . ' \d[\d.]* Tf\s/is', ' ', $thispage);
9717 // Clean up repeated /GS1 gs statements
9718 // For some reason using + for repetition instead of {2,20} crashes PHP Script Interpreter ???
9719 $thispage = preg_replace('/(\/GS1 gs\n){2,20}/', "/GS1 gs\n", $thispage);
9721 $thispage = preg_replace('/(\s*___BACKGROUND___PATTERNS' . $this->uniqstr . '\s*)/', " ", $thispage);
9722 $thispage = preg_replace('/(\s*___HEADER___MARKER' . $this->uniqstr . '\s*)/', " ", $thispage);
9723 $thispage = preg_replace('/(\s*___PAGE___START' . $this->uniqstr . '\s*)/', " ", $thispage);
9724 $thispage = preg_replace('/(\s*___TABLE___BACKGROUNDS' . $this->uniqstr . '\s*)/', " ", $thispage);
9725 // mPDF 5.7.3 TRANSFORMS
9726 while (preg_match('/(\% BTR(.*?)\% ETR)/is', $thispage, $m)) {
9727 $thispage = preg_replace('/(\% BTR.*?\% ETR)/is', '', $thispage, 1) . "\n" . $m[2];
9730 // Page
9731 $this->_newobj();
9732 $this->_out('<</Type /Page');
9733 $this->_out('/Parent 1 0 R');
9734 if (isset($this->OrientationChanges[$n])) {
9735 $this->_out(sprintf('/MediaBox [0 0 %.3F %.3F]', $hPt, $wPt));
9736 // If BleedBox is defined, it must be larger than the TrimBox, but smaller than the MediaBox
9737 $bleedMargin = $this->pageDim[$n]['bleedMargin'] * Mpdf::SCALE;
9738 if ($bleedMargin && ($owidthPt_TB || $owidthPt_LR)) {
9739 $x0 = $owidthPt_TB - $bleedMargin;
9740 $y0 = $owidthPt_LR - $bleedMargin;
9741 $x1 = $hPt - $owidthPt_TB + $bleedMargin;
9742 $y1 = $wPt - $owidthPt_LR + $bleedMargin;
9743 $this->_out(sprintf('/BleedBox [%.3F %.3F %.3F %.3F]', $x0, $y0, $x1, $y1));
9745 $this->_out(sprintf('/TrimBox [%.3F %.3F %.3F %.3F]', $owidthPt_TB, $owidthPt_LR, ($hPt - $owidthPt_TB), ($wPt - $owidthPt_LR)));
9746 if (isset($this->OrientationChanges[$n]) && $this->displayDefaultOrientation) {
9747 if ($this->DefOrientation == 'P') {
9748 $this->_out('/Rotate 270');
9749 } else {
9750 $this->_out('/Rotate 90');
9753 } // elseif($wPt != $defwPt || $hPt != $defhPt) {
9754 else {
9755 $this->_out(sprintf('/MediaBox [0 0 %.3F %.3F]', $wPt, $hPt));
9756 $bleedMargin = $this->pageDim[$n]['bleedMargin'] * Mpdf::SCALE;
9757 if ($bleedMargin && ($owidthPt_TB || $owidthPt_LR)) {
9758 $x0 = $owidthPt_LR - $bleedMargin;
9759 $y0 = $owidthPt_TB - $bleedMargin;
9760 $x1 = $wPt - $owidthPt_LR + $bleedMargin;
9761 $y1 = $hPt - $owidthPt_TB + $bleedMargin;
9762 $this->_out(sprintf('/BleedBox [%.3F %.3F %.3F %.3F]', $x0, $y0, $x1, $y1));
9764 $this->_out(sprintf('/TrimBox [%.3F %.3F %.3F %.3F]', $owidthPt_LR, $owidthPt_TB, ($wPt - $owidthPt_LR), ($hPt - $owidthPt_TB)));
9766 $this->_out('/Resources 2 0 R');
9768 // Important to keep in RGB colorSpace when using transparency
9769 if (!$this->PDFA && !$this->PDFX) {
9770 if ($this->restrictColorSpace == 3) {
9771 $this->_out('/Group << /Type /Group /S /Transparency /CS /DeviceCMYK >> ');
9772 } elseif ($this->restrictColorSpace == 1) {
9773 $this->_out('/Group << /Type /Group /S /Transparency /CS /DeviceGray >> ');
9774 } else {
9775 $this->_out('/Group << /Type /Group /S /Transparency /CS /DeviceRGB >> ');
9779 $annotsnum = 0;
9780 $embeddedfiles = []; // mPDF 5.7.2 /EmbeddedFiles
9782 if (isset($this->PageLinks[$n])) {
9783 $annotsnum += count($this->PageLinks[$n]);
9785 /* -- ANNOTATIONS -- */
9786 if (isset($this->PageAnnots[$n])) {
9787 foreach ($this->PageAnnots[$n] as $k => $pl) {
9788 if (!empty($pl['opt']['file'])) {
9789 $embeddedfiles[$annotsnum + 1] = true;
9790 } // mPDF 5.7.2 /EmbeddedFiles
9791 if (!empty($pl['opt']['popup']) || !empty($pl['opt']['file'])) {
9792 $annotsnum += 2;
9793 } else {
9794 $annotsnum++;
9796 $this->PageAnnots[$n][$k]['pageobj'] = $this->n;
9799 /* -- END ANNOTATIONS -- */
9801 /* -- FORMS -- */
9802 // Active Forms
9803 $formsnum = 0;
9804 if (count($this->form->forms) > 0) {
9805 foreach ($this->form->forms as $val) {
9806 if ($val['page'] == $n) {
9807 $formsnum++;
9811 /* -- END FORMS -- */
9812 if ($annotsnum || $formsnum) {
9813 $s = '/Annots [ ';
9814 for ($i = 0; $i < $annotsnum; $i++) {
9815 if (!isset($embeddedfiles[$i])) {
9816 $s .= ($annotid + $i) . ' 0 R ';
9817 } // mPDF 5.7.2 /EmbeddedFiles
9819 $annotid += $annotsnum;
9820 /* -- FORMS -- */
9821 if (count($this->form->forms) > 0) {
9822 $this->form->addFormIds($n, $s, $annotid);
9824 /* -- END FORMS -- */
9825 $s .= '] ';
9826 $this->_out($s);
9829 $this->_out('/Contents ' . ($this->n + 1) . ' 0 R>>');
9830 $this->_out('endobj');
9832 // Page content
9833 $this->_newobj();
9834 $p = ($this->compress) ? gzcompress($thispage) : $thispage;
9835 $this->_out('<<' . $filter . '/Length ' . strlen($p) . '>>');
9836 $this->_putstream($p);
9837 $this->_out('endobj');
9839 $this->_putannots(); // mPDF 5.7.2
9840 // Pages root
9841 $this->offsets[1] = strlen($this->buffer);
9842 $this->_out('1 0 obj');
9843 $this->_out('<</Type /Pages');
9844 $kids = '/Kids [';
9845 for ($i = 0; $i < $nb; $i++) {
9846 $kids.=(3 + 2 * $i) . ' 0 R ';
9848 $this->_out($kids . ']');
9849 $this->_out('/Count ' . $nb);
9850 $this->_out(sprintf('/MediaBox [0 0 %.3F %.3F]', $defwPt, $defhPt));
9851 $this->_out('>>');
9852 $this->_out('endobj');
9856 * @since 5.7.2
9858 function _putannots()
9860 $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
9862 $nb = $this->page;
9864 for ($n = 1; $n <= $nb; $n++) {
9866 $annotobjs = [];
9868 if (isset($this->PageLinks[$n]) || isset($this->PageAnnots[$n]) || count($this->form->forms) > 0) {
9870 $wPt = $this->pageDim[$n]['w'] * Mpdf::SCALE;
9871 $hPt = $this->pageDim[$n]['h'] * Mpdf::SCALE;
9873 // Links
9874 if (isset($this->PageLinks[$n])) {
9876 foreach ($this->PageLinks[$n] as $key => $pl) {
9878 $this->_newobj();
9879 $annot = '';
9881 $rect = sprintf('%.3F %.3F %.3F %.3F', $pl[0], $pl[1], $pl[0] + $pl[2], $pl[1] - $pl[3]);
9883 $annot .= '<</Type /Annot /Subtype /Link /Rect [' . $rect . ']';
9884 // Removed as causing undesired effects in Chrome PDF viewer https://github.com/mpdf/mpdf/issues/283
9885 // $annot .= ' /Contents ' . $this->_UTF16BEtextstring($pl[4]);
9886 $annot .= ' /NM ' . $this->_textstring(sprintf('%04u-%04u', $n, $key));
9887 $annot .= ' /M ' . $this->_textstring('D:' . date('YmdHis'));
9889 $annot .= ' /Border [0 0 0]';
9891 // Use this (instead of /Border) to specify border around link
9893 // $annot .= ' /BS <</W 1'; // Width on points; 0 = no line
9894 // $annot .= ' /S /D'; // style - [S]olid, [D]ashed, [B]eveled, [I]nset, [U]nderline
9895 // $annot .= ' /D [3 2]'; // Dash array - if dashed
9896 // $annot .= ' >>';
9897 // $annot .= ' /C [1 0 0]'; // Color RGB
9899 if ($this->PDFA || $this->PDFX) {
9900 $annot .= ' /F 28';
9903 if (strpos($pl[4], '@') === 0) {
9905 $p = substr($pl[4], 1);
9906 // $h=isset($this->OrientationChanges[$p]) ? $wPt : $hPt;
9907 $htarg = $this->pageDim[$p]['h'] * Mpdf::SCALE;
9908 $annot .= sprintf(' /Dest [%d 0 R /XYZ 0 %.3F null]>>', 1 + 2 * $p, $htarg);
9910 } elseif (is_string($pl[4])) {
9912 $annot .= ' /A <</S /URI /URI ' . $this->_textstring($pl[4]) . '>> >>';
9914 } else {
9916 $l = $this->links[$pl[4]];
9917 // may not be set if #link points to non-existent target
9918 if (isset($this->pageDim[$l[0]]['h'])) {
9919 $htarg = $this->pageDim[$l[0]]['h'] * Mpdf::SCALE;
9920 } else {
9921 $htarg = $this->h * Mpdf::SCALE;
9922 } // doesn't really matter
9924 $annot .= sprintf(' /Dest [%d 0 R /XYZ 0 %.3F null]>>', 1 + 2 * $l[0], $htarg - $l[1] * Mpdf::SCALE);
9927 $this->_out($annot);
9928 $this->_out('endobj');
9933 /* -- ANNOTATIONS -- */
9934 if (isset($this->PageAnnots[$n])) {
9936 foreach ($this->PageAnnots[$n] as $key => $pl) {
9938 $fileAttachment = (bool) $pl['opt']['file'];
9940 if ($fileAttachment && !$this->allowAnnotationFiles) {
9941 $this->logger->warning('Embedded files for annotations have to be allowed explicitly with "allowAnnotationFiles" config key');
9942 $fileAttachment = false;
9945 $this->_newobj();
9946 $annot = '';
9947 $pl['opt'] = array_change_key_case($pl['opt'], CASE_LOWER);
9948 $x = $pl['x'];
9950 if ($this->annotMargin <> 0 || $x == 0 || $x < 0) { // Odd page
9951 $x = ($wPt / Mpdf::SCALE) - $this->annotMargin;
9954 $w = $h = 0;
9955 $a = $x * Mpdf::SCALE;
9956 $b = $hPt - ($pl['y'] * Mpdf::SCALE);
9958 $annot .= '<</Type /Annot ';
9960 if ($fileAttachment) {
9961 $annot .= '/Subtype /FileAttachment ';
9962 // Need to set a size for FileAttachment icons
9963 if ($pl['opt']['icon'] == 'Paperclip') {
9964 $w = 8.235;
9965 $h = 20;
9966 } elseif ($pl['opt']['icon'] == 'Tag') {
9967 $w = 20;
9968 $h = 16;
9969 } elseif ($pl['opt']['icon'] == 'Graph') {
9970 $w = 20;
9971 $h = 20;
9972 } else {
9973 $w = 14;
9974 $h = 20;
9977 // PushPin
9978 $f = $pl['opt']['file'];
9979 $f = preg_replace('/^.*\//', '', $f);
9980 $f = preg_replace('/[^a-zA-Z0-9._]/', '', $f);
9982 $annot .= '/FS <</Type /Filespec /F (' . $f . ')';
9983 $annot .= '/EF <</F ' . ($this->n + 1) . ' 0 R>>';
9984 $annot .= '>>';
9986 } else {
9987 $annot .= '/Subtype /Text';
9988 $w = 20;
9989 $h = 20; // mPDF 6
9992 $rect = sprintf('%.3F %.3F %.3F %.3F', $a, $b - $h, $a + $w, $b);
9993 $annot .= ' /Rect [' . $rect . ']';
9995 // contents = description of file in free text
9996 $annot .= ' /Contents ' . $this->_UTF16BEtextstring($pl['txt']);
9998 $annot .= ' /NM ' . $this->_textstring(sprintf('%04u-%04u', $n, (2000 + $key)));
9999 $annot .= ' /M ' . $this->_textstring('D:' . date('YmdHis'));
10000 $annot .= ' /CreationDate ' . $this->_textstring('D:' . date('YmdHis'));
10001 $annot .= ' /Border [0 0 0]';
10003 if ($this->PDFA || $this->PDFX) {
10004 $annot .= ' /F 28';
10005 $annot .= ' /CA 1';
10006 } elseif ($pl['opt']['ca'] > 0) {
10007 $annot .= ' /CA ' . $pl['opt']['ca'];
10010 $annotcolor = ' /C [';
10011 if (isset($pl['opt']['c']) and $pl['opt']['c']) {
10012 $col = $pl['opt']['c'];
10013 if ($col{0} == 3 || $col{0} == 5) {
10014 $annotcolor .= sprintf("%.3F %.3F %.3F", ord($col{1}) / 255, ord($col{2}) / 255, ord($col{3}) / 255);
10015 } elseif ($col{0} == 1) {
10016 $annotcolor .= sprintf("%.3F", ord($col{1}) / 255);
10017 } elseif ($col{0} == 4 || $col{0} == 6) {
10018 $annotcolor .= sprintf("%.3F %.3F %.3F %.3F", ord($col{1}) / 100, ord($col{2}) / 100, ord($col{3}) / 100, ord($col{4}) / 100);
10019 } else {
10020 $annotcolor .= '1 1 0';
10022 } else {
10023 $annotcolor .= '1 1 0';
10025 $annotcolor .= ']';
10026 $annot .= $annotcolor;
10028 // Usually Author
10029 // Use as Title for fileattachment
10030 if (isset($pl['opt']['t']) and is_string($pl['opt']['t'])) {
10031 $annot .= ' /T ' . $this->_UTF16BEtextstring($pl['opt']['t']);
10034 if ($fileAttachment) {
10035 $iconsapp = ['Paperclip', 'Graph', 'PushPin', 'Tag'];
10036 } else {
10037 $iconsapp = ['Comment', 'Help', 'Insert', 'Key', 'NewParagraph', 'Note', 'Paragraph'];
10040 if (isset($pl['opt']['icon']) and in_array($pl['opt']['icon'], $iconsapp)) {
10041 $annot .= ' /Name /' . $pl['opt']['icon'];
10042 } elseif ($fileAttachment) {
10043 $annot .= ' /Name /PushPin';
10044 } else {
10045 $annot .= ' /Name /Note';
10048 if (!$fileAttachment) {
10049 ///Subj is PDF 1.5 spec.
10050 if (isset($pl['opt']['subj']) && !$this->PDFA && !$this->PDFX) {
10051 $annot .= ' /Subj ' . $this->_UTF16BEtextstring($pl['opt']['subj']);
10053 if (!empty($pl['opt']['popup'])) {
10054 $annot .= ' /Open true';
10055 $annot .= ' /Popup ' . ($this->n + 1) . ' 0 R';
10056 } else {
10057 $annot .= ' /Open false';
10061 $annot .= ' /P ' . $pl['pageobj'] . ' 0 R';
10062 $annot .= '>>';
10063 $this->_out($annot);
10064 $this->_out('endobj');
10066 if ($fileAttachment) {
10067 $file = @file_get_contents($pl['opt']['file']);
10068 if (!$file) {
10069 throw new \Mpdf\MpdfException('mPDF Error: Cannot access file attachment - ' . $pl['opt']['file']);
10071 $filestream = gzcompress($file);
10072 $this->_newobj();
10073 $this->_out('<</Type /EmbeddedFile');
10074 $this->_out('/Length ' . strlen($filestream));
10075 $this->_out('/Filter /FlateDecode');
10076 $this->_out('>>');
10077 $this->_putstream($filestream);
10078 $this->_out('endobj');
10079 } elseif (!empty($pl['opt']['popup'])) {
10080 $this->_newobj();
10081 $annot = '';
10082 if (is_array($pl['opt']['popup']) && isset($pl['opt']['popup'][0])) {
10083 $x = $pl['opt']['popup'][0] * Mpdf::SCALE;
10084 } else {
10085 $x = $pl['x'] * Mpdf::SCALE;
10087 if (is_array($pl['opt']['popup']) && isset($pl['opt']['popup'][1])) {
10088 $y = $hPt - ($pl['opt']['popup'][1] * Mpdf::SCALE);
10089 } else {
10090 $y = $hPt - ($pl['y'] * Mpdf::SCALE);
10092 if (is_array($pl['opt']['popup']) && isset($pl['opt']['popup'][2])) {
10093 $w = $pl['opt']['popup'][2] * Mpdf::SCALE;
10094 } else {
10095 $w = 180;
10097 if (is_array($pl['opt']['popup']) && isset($pl['opt']['popup'][3])) {
10098 $h = $pl['opt']['popup'][3] * Mpdf::SCALE;
10099 } else {
10100 $h = 120;
10102 $rect = sprintf('%.3F %.3F %.3F %.3F', $x, $y - $h, $x + $w, $y);
10103 $annot .= '<</Type /Annot /Subtype /Popup /Rect [' . $rect . ']';
10104 $annot .= ' /M ' . $this->_textstring('D:' . date('YmdHis'));
10105 if ($this->PDFA || $this->PDFX) {
10106 $annot .= ' /F 28';
10108 $annot .= ' /Parent ' . ($this->n - 1) . ' 0 R';
10109 $annot .= '>>';
10110 $this->_out($annot);
10111 $this->_out('endobj');
10116 /* -- END ANNOTATIONS -- */
10118 /* -- FORMS -- */
10119 // Active Forms
10120 if (count($this->form->forms) > 0) {
10121 $this->form->_putFormItems($n, $hPt);
10123 /* -- END FORMS -- */
10127 /* -- FORMS -- */
10128 // Active Forms - Radio Button Group entries
10129 // Output Radio Button Group form entries (radio_on_obj_id already determined)
10130 if (count($this->form->form_radio_groups)) {
10131 $this->form->_putRadioItems($n);
10133 /* -- END FORMS -- */
10136 /* -- ANNOTATIONS -- */
10138 function Annotation($text, $x = 0, $y = 0, $icon = 'Note', $author = '', $subject = '', $opacity = 0, $colarray = false, $popup = '', $file = '')
10140 if (is_array($colarray) && count($colarray) == 3) {
10141 $colarray = $this->colorConverter->convert('rgb(' . $colarray[0] . ',' . $colarray[1] . ',' . $colarray[2] . ')', $this->PDFAXwarnings);
10143 if ($colarray === false) {
10144 $colarray = $this->colorConverter->convert('yellow', $this->PDFAXwarnings);
10146 if ($x == 0) {
10147 $x = $this->x;
10149 if ($y == 0) {
10150 $y = $this->y;
10152 $page = $this->page;
10153 if ($page < 1) { // Document has not been started - assume it's for first page
10154 $page = 1;
10155 if ($x == 0) {
10156 $x = $this->lMargin;
10158 if ($y == 0) {
10159 $y = $this->tMargin;
10163 if ($this->PDFA || $this->PDFX) {
10164 if (($this->PDFA && !$this->PDFAauto) || ($this->PDFX && !$this->PDFXauto)) {
10165 $this->PDFAXwarnings[] = "Annotation markers cannot be semi-transparent in PDFA1-b or PDFX/1-a, so they may make underlying text unreadable. (Annotation markers moved to right margin)";
10167 $x = ($this->w) - $this->rMargin * 0.66;
10169 if (!$this->annotMargin) {
10170 $y -= $this->FontSize / 2;
10173 if (!$opacity && $this->annotMargin) {
10174 $opacity = 1;
10175 } elseif (!$opacity) {
10176 $opacity = $this->annotOpacity;
10179 $an = ['txt' => $text, 'x' => $x, 'y' => $y, 'opt' => ['Icon' => $icon, 'T' => $author, 'Subj' => $subject, 'C' => $colarray, 'CA' => $opacity, 'popup' => $popup, 'file' => $file]];
10181 if ($this->keep_block_together) { // don't write yet
10182 return;
10183 } elseif ($this->table_rotate) {
10184 $this->tbrot_Annots[$this->page][] = $an;
10185 return;
10186 } elseif ($this->kwt) {
10187 $this->kwt_Annots[$this->page][] = $an;
10188 return;
10190 if ($this->writingHTMLheader || $this->writingHTMLfooter) {
10191 $this->HTMLheaderPageAnnots[] = $an;
10192 return;
10194 // Put an Annotation on the page
10195 $this->PageAnnots[$page][] = $an;
10196 /* -- COLUMNS -- */
10197 // Save cross-reference to Column buffer
10198 $ref = count($this->PageAnnots[$this->page]) - 1;
10199 $this->columnAnnots[$this->CurrCol][intval($this->x)][intval($this->y)] = $ref;
10200 /* -- END COLUMNS -- */
10203 /* -- END ANNOTATIONS -- */
10205 function _putfonts()
10207 $nf = $this->n;
10208 foreach ($this->FontFiles as $fontkey => $info) {
10209 // TrueType embedded
10210 if (isset($info['type']) && $info['type'] == 'TTF' && !$info['sip'] && !$info['smp']) {
10211 $used = true;
10212 $asSubset = false;
10213 foreach ($this->fonts as $k => $f) {
10214 if (isset($f['fontkey']) && $f['fontkey'] == $fontkey && $f['type'] == 'TTF') {
10215 $used = $f['used'];
10216 if ($used) {
10217 $nChars = (ord($f['cw'][0]) << 8) + ord($f['cw'][1]);
10218 $usage = intval(count($f['subset']) * 100 / $nChars);
10219 $fsize = $info['length1'];
10220 // Always subset the very large TTF files
10221 if ($fsize > ($this->maxTTFFilesize * 1024)) {
10222 $asSubset = true;
10223 } elseif ($usage < $this->percentSubset) {
10224 $asSubset = true;
10227 if ($this->PDFA || $this->PDFX) {
10228 $asSubset = false;
10230 $this->fonts[$k]['asSubset'] = $asSubset;
10231 break;
10234 if ($used && !$asSubset) {
10235 // Font file embedding
10236 $this->_newobj();
10237 $this->FontFiles[$fontkey]['n'] = $this->n;
10238 $originalsize = $info['length1'];
10239 if ($this->repackageTTF || $this->fonts[$fontkey]['TTCfontID'] > 0 || $this->fonts[$fontkey]['useOTL'] > 0) { // mPDF 5.7.1
10240 // First see if there is a cached compressed file
10241 if ($this->fontCache->has($fontkey . '.ps.z')) {
10242 $font = $this->fontCache->load($fontkey . '.ps.z');
10243 include $this->fontCache->tempFilename($fontkey . '.ps.php'); // sets $originalsize (of repackaged font)
10244 } else {
10245 $ttf = new TTFontFile($this->fontCache, $this->fontDescriptor);
10246 $font = $ttf->repackageTTF($this->FontFiles[$fontkey]['ttffile'], $this->fonts[$fontkey]['TTCfontID'], $this->debugfonts, $this->fonts[$fontkey]['useOTL']); // mPDF 5.7.1
10248 $originalsize = strlen($font);
10249 $font = gzcompress($font);
10250 unset($ttf);
10252 $len = "<?php \n";
10253 $len .= '$originalsize=' . $originalsize . ";\n";
10255 $this->fontCache->binaryWrite($fontkey . '.ps.z', $font);
10256 $this->fontCache->write($fontkey . '.ps.php', $len);
10258 } else {
10259 // First see if there is a cached compressed file
10260 if ($this->fontCache->has($fontkey . '.z')) {
10261 $font = $this->fontCache->load($fontkey . '.z', 'rb');
10262 } else {
10263 $font = file_get_contents($this->FontFiles[$fontkey]['ttffile']);
10264 $font = gzcompress($font);
10265 $this->fontCache->binaryWrite($fontkey . '.z', $font);
10269 $this->_out('<</Length ' . strlen($font));
10270 $this->_out('/Filter /FlateDecode');
10271 $this->_out('/Length1 ' . $originalsize);
10272 $this->_out('>>');
10273 $this->_putstream($font);
10274 $this->_out('endobj');
10279 $nfonts = count($this->fonts);
10280 $fctr = 1;
10281 foreach ($this->fonts as $k => $font) {
10282 // Font objects
10283 $type = $font['type'];
10284 $name = $font['name'];
10285 if ((!isset($font['used']) || !$font['used']) && $type == 'TTF') {
10286 continue;
10289 // @log Writing fonts
10291 if (isset($font['asSubset'])) {
10292 $asSubset = $font['asSubset'];
10293 } else {
10294 $asSubset = '';
10296 /* -- CJK-FONTS -- */
10297 if ($type == 'Type0') { // = Adobe CJK Fonts
10298 $this->fonts[$k]['n'] = $this->n + 1;
10299 $this->_newobj();
10300 $this->_out('<</Type /Font');
10301 $this->_putType0($font);
10302 } else { /* -- END CJK-FONTS -- */
10303 if ($type == 'core') {
10304 // Standard font
10305 $this->fonts[$k]['n'] = $this->n + 1;
10306 if ($this->PDFA || $this->PDFX) {
10307 throw new \Mpdf\MpdfException('Core fonts are not allowed in PDF/A1-b or PDFX/1-a files (Times, Helvetica, Courier etc.)');
10309 $this->_newobj();
10310 $this->_out('<</Type /Font');
10311 $this->_out('/BaseFont /' . $name);
10312 $this->_out('/Subtype /Type1');
10313 if ($name != 'Symbol' && $name != 'ZapfDingbats') {
10314 $this->_out('/Encoding /WinAnsiEncoding');
10316 $this->_out('>>');
10317 $this->_out('endobj');
10318 } // TrueType embedded SUBSETS for SIP (CJK extB containing Supplementary Ideographic Plane 2)
10319 // Or Unicode Plane 1 - Supplementary Multilingual Plane
10320 elseif ($type == 'TTF' && ($font['sip'] || $font['smp'])) {
10321 if (!$font['used']) {
10322 continue;
10324 $ssfaid = "AA";
10325 $ttf = new TTFontFile($this->fontCache, $this->fontDescriptor);
10326 for ($sfid = 0; $sfid < count($font['subsetfontids']); $sfid++) {
10327 $this->fonts[$k]['n'][$sfid] = $this->n + 1; // NB an array for subset
10328 $subsetname = 'MPDF' . $ssfaid . '+' . $font['name'];
10329 $ssfaid++;
10331 /* For some strange reason a subset ($sfid > 0) containing less than 97 characters causes an error
10332 so fill up the array */
10333 for ($j = count($font['subsets'][$sfid]); $j < 98; $j++) {
10334 $font['subsets'][$sfid][$j] = 0;
10337 $subset = $font['subsets'][$sfid];
10338 unset($subset[0]);
10339 $ttfontstream = $ttf->makeSubsetSIP($font['ttffile'], $subset, $font['TTCfontID'], $this->debugfonts, $font['useOTL']); // mPDF 5.7.1
10340 $ttfontsize = strlen($ttfontstream);
10341 $fontstream = gzcompress($ttfontstream);
10342 $widthstring = '';
10343 $toUnistring = '';
10346 foreach ($font['subsets'][$sfid] as $cp => $u) {
10347 $w = $this->_getCharWidth($font['cw'], $u);
10348 if ($w !== false) {
10349 $widthstring .= $w . ' ';
10350 } else {
10351 $widthstring .= round($ttf->defaultWidth) . ' ';
10353 if ($u > 65535) {
10354 $utf8 = chr(($u >> 18) + 240) . chr((($u >> 12) & 63) + 128) . chr((($u >> 6) & 63) + 128) . chr(($u & 63) + 128);
10355 $utf16 = mb_convert_encoding($utf8, 'UTF-16BE', 'UTF-8');
10356 $l1 = ord($utf16[0]);
10357 $h1 = ord($utf16[1]);
10358 $l2 = ord($utf16[2]);
10359 $h2 = ord($utf16[3]);
10360 $toUnistring .= sprintf("<%02s> <%02s%02s%02s%02s>\n", strtoupper(dechex($cp)), strtoupper(dechex($l1)), strtoupper(dechex($h1)), strtoupper(dechex($l2)), strtoupper(dechex($h2)));
10361 } else {
10362 $toUnistring .= sprintf("<%02s> <%04s>\n", strtoupper(dechex($cp)), strtoupper(dechex($u)));
10366 // Additional Type1 or TrueType font
10367 $this->_newobj();
10368 $this->_out('<</Type /Font');
10369 $this->_out('/BaseFont /' . $subsetname);
10370 $this->_out('/Subtype /TrueType');
10371 $this->_out('/FirstChar 0 /LastChar ' . (count($font['subsets'][$sfid]) - 1));
10372 $this->_out('/Widths ' . ($this->n + 1) . ' 0 R');
10373 $this->_out('/FontDescriptor ' . ($this->n + 2) . ' 0 R');
10374 $this->_out('/ToUnicode ' . ($this->n + 3) . ' 0 R');
10375 $this->_out('>>');
10376 $this->_out('endobj');
10378 // Widths
10379 $this->_newobj();
10380 $this->_out('[' . $widthstring . ']');
10381 $this->_out('endobj');
10383 // Descriptor
10384 $this->_newobj();
10385 $s = '<</Type /FontDescriptor /FontName /' . $subsetname . "\n";
10386 foreach ($font['desc'] as $kd => $v) {
10387 if ($kd == 'Flags') {
10388 $v = $v | 4;
10389 $v = $v & ~32;
10390 } // SYMBOLIC font flag
10391 $s.=' /' . $kd . ' ' . $v . "\n";
10393 $s.='/FontFile2 ' . ($this->n + 2) . ' 0 R';
10394 $this->_out($s . '>>');
10395 $this->_out('endobj');
10397 // ToUnicode
10398 $this->_newobj();
10399 $toUni = "/CIDInit /ProcSet findresource begin\n";
10400 $toUni .= "12 dict begin\n";
10401 $toUni .= "begincmap\n";
10402 $toUni .= "/CIDSystemInfo\n";
10403 $toUni .= "<</Registry (Adobe)\n";
10404 $toUni .= "/Ordering (UCS)\n";
10405 $toUni .= "/Supplement 0\n";
10406 $toUni .= ">> def\n";
10407 $toUni .= "/CMapName /Adobe-Identity-UCS def\n";
10408 $toUni .= "/CMapType 2 def\n";
10409 $toUni .= "1 begincodespacerange\n";
10410 $toUni .= "<00> <FF>\n";
10411 // $toUni .= sprintf("<00> <%02s>\n", strtoupper(dechex(count($font['subsets'][$sfid])-1)));
10412 $toUni .= "endcodespacerange\n";
10413 $toUni .= count($font['subsets'][$sfid]) . " beginbfchar\n";
10414 $toUni .= $toUnistring;
10415 $toUni .= "endbfchar\n";
10416 $toUni .= "endcmap\n";
10417 $toUni .= "CMapName currentdict /CMap defineresource pop\n";
10418 $toUni .= "end\n";
10419 $toUni .= "end\n";
10420 $this->_out('<</Length ' . (strlen($toUni)) . '>>');
10421 $this->_putstream($toUni);
10422 $this->_out('endobj');
10424 // Font file
10425 $this->_newobj();
10426 $this->_out('<</Length ' . strlen($fontstream));
10427 $this->_out('/Filter /FlateDecode');
10428 $this->_out('/Length1 ' . $ttfontsize);
10429 $this->_out('>>');
10430 $this->_putstream($fontstream);
10431 $this->_out('endobj');
10432 } // foreach subset
10433 unset($ttf);
10434 } // TrueType embedded SUBSETS or FULL
10435 elseif ($type == 'TTF') {
10436 $this->fonts[$k]['n'] = $this->n + 1;
10437 if ($asSubset) {
10438 $ssfaid = "A";
10439 $ttf = new TTFontFile($this->fontCache, $this->fontDescriptor);
10440 $fontname = 'MPDFA' . $ssfaid . '+' . $font['name'];
10441 $subset = $font['subset'];
10442 unset($subset[0]);
10443 $ttfontstream = $ttf->makeSubset($font['ttffile'], $subset, $font['TTCfontID'], $this->debugfonts, $font['useOTL']);
10444 $ttfontsize = strlen($ttfontstream);
10445 $fontstream = gzcompress($ttfontstream);
10446 $codeToGlyph = $ttf->codeToGlyph;
10447 unset($codeToGlyph[0]);
10448 } else {
10449 $fontname = $font['name'];
10451 // Type0 Font
10452 // A composite font - a font composed of other fonts, organized hierarchically
10453 $this->_newobj();
10454 $this->_out('<</Type /Font');
10455 $this->_out('/Subtype /Type0');
10456 $this->_out('/BaseFont /' . $fontname . '');
10457 $this->_out('/Encoding /Identity-H');
10458 $this->_out('/DescendantFonts [' . ($this->n + 1) . ' 0 R]');
10459 $this->_out('/ToUnicode ' . ($this->n + 2) . ' 0 R');
10460 $this->_out('>>');
10461 $this->_out('endobj');
10463 // CIDFontType2
10464 // A CIDFont whose glyph descriptions are based on TrueType font technology
10465 $this->_newobj();
10466 $this->_out('<</Type /Font');
10467 $this->_out('/Subtype /CIDFontType2');
10468 $this->_out('/BaseFont /' . $fontname . '');
10469 $this->_out('/CIDSystemInfo ' . ($this->n + 2) . ' 0 R');
10470 $this->_out('/FontDescriptor ' . ($this->n + 3) . ' 0 R');
10471 if (isset($font['desc']['MissingWidth'])) {
10472 $this->_out('/DW ' . $font['desc']['MissingWidth'] . '');
10475 if (!$asSubset && $this->fontCache->has($font['fontkey'] . '.cw')) {
10476 $w = $this->fontCache->load($font['fontkey'] . '.cw');
10477 $this->_out($w);
10478 } else {
10479 $this->_putTTfontwidths($font, $asSubset, ($asSubset ? $ttf->maxUni : 0));
10482 $this->_out('/CIDToGIDMap ' . ($this->n + 4) . ' 0 R');
10483 $this->_out('>>');
10484 $this->_out('endobj');
10486 // ToUnicode
10487 $this->_newobj();
10488 $toUni = "/CIDInit /ProcSet findresource begin\n";
10489 $toUni .= "12 dict begin\n";
10490 $toUni .= "begincmap\n";
10491 $toUni .= "/CIDSystemInfo\n";
10492 $toUni .= "<</Registry (Adobe)\n";
10493 $toUni .= "/Ordering (UCS)\n";
10494 $toUni .= "/Supplement 0\n";
10495 $toUni .= ">> def\n";
10496 $toUni .= "/CMapName /Adobe-Identity-UCS def\n";
10497 $toUni .= "/CMapType 2 def\n";
10498 $toUni .= "1 begincodespacerange\n";
10499 $toUni .= "<0000> <FFFF>\n";
10500 $toUni .= "endcodespacerange\n";
10501 $toUni .= "1 beginbfrange\n";
10502 $toUni .= "<0000> <FFFF> <0000>\n";
10503 $toUni .= "endbfrange\n";
10504 $toUni .= "endcmap\n";
10505 $toUni .= "CMapName currentdict /CMap defineresource pop\n";
10506 $toUni .= "end\n";
10507 $toUni .= "end\n";
10508 $this->_out('<</Length ' . (strlen($toUni)) . '>>');
10509 $this->_putstream($toUni);
10510 $this->_out('endobj');
10513 // CIDSystemInfo dictionary
10514 $this->_newobj();
10515 $this->_out('<</Registry (Adobe)');
10516 $this->_out('/Ordering (UCS)');
10517 $this->_out('/Supplement 0');
10518 $this->_out('>>');
10519 $this->_out('endobj');
10521 // Font descriptor
10522 $this->_newobj();
10523 $this->_out('<</Type /FontDescriptor');
10524 $this->_out('/FontName /' . $fontname);
10525 foreach ($font['desc'] as $kd => $v) {
10526 if ($asSubset && $kd == 'Flags') {
10527 $v = $v | 4;
10528 $v = $v & ~32;
10529 } // SYMBOLIC font flag
10530 $this->_out(' /' . $kd . ' ' . $v);
10532 if ($font['panose']) {
10533 $this->_out(' /Style << /Panose <' . $font['panose'] . '> >>');
10535 if ($asSubset) {
10536 $this->_out('/FontFile2 ' . ($this->n + 2) . ' 0 R');
10537 } elseif ($font['fontkey']) {
10538 // obj ID of a stream containing a TrueType font program
10539 $this->_out('/FontFile2 ' . $this->FontFiles[$font['fontkey']]['n'] . ' 0 R');
10541 $this->_out('>>');
10542 $this->_out('endobj');
10544 // Embed CIDToGIDMap
10545 // A specification of the mapping from CIDs to glyph indices
10546 if ($asSubset) {
10547 $cidtogidmap = '';
10548 $cidtogidmap = str_pad('', 256 * 256 * 2, "\x00");
10549 foreach ($codeToGlyph as $cc => $glyph) {
10550 $cidtogidmap[$cc * 2] = chr($glyph >> 8);
10551 $cidtogidmap[$cc * 2 + 1] = chr($glyph & 0xFF);
10553 $cidtogidmap = gzcompress($cidtogidmap);
10554 } else {
10555 // First see if there is a cached CIDToGIDMapfile
10556 $cidtogidmap = '';
10557 if ($this->fontCache->has($font['fontkey'] . '.cgm')) {
10558 $cidtogidmap = $this->fontCache->load($font['fontkey'] . '.cgm');
10559 } else {
10560 $ttf = new TTFontFile($this->fontCache, $this->fontDescriptor);
10561 $charToGlyph = $ttf->getCTG($font['ttffile'], $font['TTCfontID'], $this->debugfonts, $font['useOTL']);
10562 $cidtogidmap = str_pad('', 256 * 256 * 2, "\x00");
10563 foreach ($charToGlyph as $cc => $glyph) {
10564 $cidtogidmap[$cc * 2] = chr($glyph >> 8);
10565 $cidtogidmap[$cc * 2 + 1] = chr($glyph & 0xFF);
10567 unset($ttf);
10568 $cidtogidmap = gzcompress($cidtogidmap);
10569 $this->fontCache->binaryWrite($font['fontkey'] . '.cgm', $cidtogidmap);
10572 $this->_newobj();
10573 $this->_out('<</Length ' . strlen($cidtogidmap) . '');
10574 $this->_out('/Filter /FlateDecode');
10575 $this->_out('>>');
10576 $this->_putstream($cidtogidmap);
10577 $this->_out('endobj');
10579 // Font file
10580 if ($asSubset) {
10581 $this->_newobj();
10582 $this->_out('<</Length ' . strlen($fontstream));
10583 $this->_out('/Filter /FlateDecode');
10584 $this->_out('/Length1 ' . $ttfontsize);
10585 $this->_out('>>');
10586 $this->_putstream($fontstream);
10587 $this->_out('endobj');
10588 unset($ttf);
10590 } else {
10591 throw new \Mpdf\MpdfException('Unsupported font type: ' . $type . ' (' . $name . ')');
10597 function _putTTfontwidths(&$font, $asSubset, $maxUni)
10599 if ($asSubset && $this->fontCache->has($font['fontkey'] . '.cw127.php')) {
10600 include $this->fontCache->tempFilename($font['fontkey'] . '.cw127.php');
10601 $startcid = 128;
10602 } else {
10603 $rangeid = 0;
10604 $range = [];
10605 $prevcid = -2;
10606 $prevwidth = -1;
10607 $interval = false;
10608 $startcid = 1;
10610 if ($asSubset) {
10611 $cwlen = $maxUni + 1;
10612 } else {
10613 $cwlen = (strlen($font['cw']) / 2);
10616 // for each character
10617 for ($cid = $startcid; $cid < $cwlen; $cid++) {
10618 if ($cid == 128 && $asSubset && (!$this->fontCache->has($font['fontkey'] . '.cw127.php'))) {
10619 $cw127 = '<?php' . "\n";
10620 $cw127 .= '$rangeid=' . $rangeid . ";\n";
10621 $cw127 .= '$prevcid=' . $prevcid . ";\n";
10622 $cw127 .= '$prevwidth=' . $prevwidth . ";\n";
10623 if ($interval) {
10624 $cw127 .= '$interval=true' . ";\n";
10625 } else {
10626 $cw127 .= '$interval=false' . ";\n";
10628 $cw127 .= '$range=' . var_export($range, true) . ";\n";
10629 $this->fontCache->write($font['fontkey'] . '.cw127.php', $cw127);
10632 $character1 = isset($font['cw'][$cid * 2]) ? $font['cw'][$cid * 2] : '';
10633 $character2 = isset($font['cw'][$cid * 2 + 1]) ? $font['cw'][$cid * 2 + 1] : '';
10635 if ($character1 == "\00" && $character2 == "\00") {
10636 continue;
10639 $width = (ord($character1) << 8) + ord($character2);
10641 if ($width == 65535) {
10642 $width = 0;
10645 if ($asSubset && $cid > 255 && (!isset($font['subset'][$cid]) || !$font['subset'][$cid])) {
10646 continue;
10649 if ($asSubset && $cid > 0xFFFF) {
10650 continue;
10651 } // mPDF 6
10653 if (!isset($font['dw']) || (isset($font['dw']) && $width != $font['dw'])) {
10654 if ($cid == ($prevcid + 1)) {
10655 // consecutive CID
10656 if ($width == $prevwidth) {
10657 if ($width == $range[$rangeid][0]) {
10658 $range[$rangeid][] = $width;
10659 } else {
10660 array_pop($range[$rangeid]);
10661 // new range
10662 $rangeid = $prevcid;
10663 $range[$rangeid] = [];
10664 $range[$rangeid][] = $prevwidth;
10665 $range[$rangeid][] = $width;
10667 $interval = true;
10668 $range[$rangeid]['interval'] = true;
10669 } else {
10670 if ($interval) {
10671 // new range
10672 $rangeid = $cid;
10673 $range[$rangeid] = [];
10674 $range[$rangeid][] = $width;
10675 } else {
10676 $range[$rangeid][] = $width;
10678 $interval = false;
10680 } else {
10681 // new range
10682 $rangeid = $cid;
10683 $range[$rangeid] = [];
10684 $range[$rangeid][] = $width;
10685 $interval = false;
10687 $prevcid = $cid;
10688 $prevwidth = $width;
10691 $w = $this->_putfontranges($range);
10692 $this->_out($w);
10693 if (!$asSubset) {
10694 $this->fontCache->binaryWrite($font['fontkey'] . '.cw', $w);
10698 function _putfontranges(&$range)
10700 // optimize ranges
10701 $prevk = -1;
10702 $nextk = -1;
10703 $prevint = false;
10704 foreach ($range as $k => $ws) {
10705 $cws = count($ws);
10706 if (($k == $nextk) and ( !$prevint) and ( (!isset($ws['interval'])) or ( $cws < 4))) {
10707 if (isset($range[$k]['interval'])) {
10708 unset($range[$k]['interval']);
10710 $range[$prevk] = array_merge($range[$prevk], $range[$k]);
10711 unset($range[$k]);
10712 } else {
10713 $prevk = $k;
10715 $nextk = $k + $cws;
10716 if (isset($ws['interval'])) {
10717 if ($cws > 3) {
10718 $prevint = true;
10719 } else {
10720 $prevint = false;
10722 unset($range[$k]['interval']);
10723 --$nextk;
10724 } else {
10725 $prevint = false;
10728 // output data
10729 $w = '';
10730 foreach ($range as $k => $ws) {
10731 if (count(array_count_values($ws)) == 1) {
10732 // interval mode is more compact
10733 $w .= ' ' . $k . ' ' . ($k + count($ws) - 1) . ' ' . $ws[0];
10734 } else {
10735 // range mode
10736 $w .= ' ' . $k . ' [ ' . implode(' ', $ws) . ' ]' . "\n";
10739 return '/W [' . $w . ' ]';
10742 function _putfontwidths(&$font, $cidoffset = 0)
10744 ksort($font['cw']);
10745 unset($font['cw'][65535]);
10746 $rangeid = 0;
10747 $range = [];
10748 $prevcid = -2;
10749 $prevwidth = -1;
10750 $interval = false;
10751 // for each character
10752 foreach ($font['cw'] as $cid => $width) {
10753 $cid -= $cidoffset;
10754 if (!isset($font['dw']) || (isset($font['dw']) && $width != $font['dw'])) {
10755 if ($cid == ($prevcid + 1)) {
10756 // consecutive CID
10757 if ($width == $prevwidth) {
10758 if ($width == $range[$rangeid][0]) {
10759 $range[$rangeid][] = $width;
10760 } else {
10761 array_pop($range[$rangeid]);
10762 // new range
10763 $rangeid = $prevcid;
10764 $range[$rangeid] = [];
10765 $range[$rangeid][] = $prevwidth;
10766 $range[$rangeid][] = $width;
10768 $interval = true;
10769 $range[$rangeid]['interval'] = true;
10770 } else {
10771 if ($interval) {
10772 // new range
10773 $rangeid = $cid;
10774 $range[$rangeid] = [];
10775 $range[$rangeid][] = $width;
10776 } else {
10777 $range[$rangeid][] = $width;
10779 $interval = false;
10781 } else {
10782 // new range
10783 $rangeid = $cid;
10784 $range[$rangeid] = [];
10785 $range[$rangeid][] = $width;
10786 $interval = false;
10788 $prevcid = $cid;
10789 $prevwidth = $width;
10792 $this->_out($this->_putfontranges($range));
10795 /* -- CJK-FONTS -- */
10797 // from class PDF_Chinese CJK EXTENSIONS
10798 function _putType0(&$font)
10800 // Type0
10801 $this->_out('/Subtype /Type0');
10802 $this->_out('/BaseFont /' . $font['name'] . '-' . $font['CMap']);
10803 $this->_out('/Encoding /' . $font['CMap']);
10804 $this->_out('/DescendantFonts [' . ($this->n + 1) . ' 0 R]');
10805 $this->_out('>>');
10806 $this->_out('endobj');
10807 // CIDFont
10808 $this->_newobj();
10809 $this->_out('<</Type /Font');
10810 $this->_out('/Subtype /CIDFontType0');
10811 $this->_out('/BaseFont /' . $font['name']);
10813 $cidinfo = '/Registry ' . $this->_textstring('Adobe');
10814 $cidinfo .= ' /Ordering ' . $this->_textstring($font['registry']['ordering']);
10815 $cidinfo .= ' /Supplement ' . $font['registry']['supplement'];
10816 $this->_out('/CIDSystemInfo <<' . $cidinfo . '>>');
10818 $this->_out('/FontDescriptor ' . ($this->n + 1) . ' 0 R');
10819 if (isset($font['MissingWidth'])) {
10820 $this->_out('/DW ' . $font['MissingWidth'] . '');
10822 $this->_putfontwidths($font, 31);
10823 $this->_out('>>');
10824 $this->_out('endobj');
10826 // Font descriptor
10827 $this->_newobj();
10828 $s = '<</Type /FontDescriptor /FontName /' . $font['name'];
10829 foreach ($font['desc'] as $k => $v) {
10830 if ($k != 'Style') {
10831 $s .= ' /' . $k . ' ' . $v . '';
10834 $this->_out($s . '>>');
10835 $this->_out('endobj');
10838 /* -- END CJK-FONTS -- */
10840 function _putimages()
10842 $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
10844 foreach ($this->images as $file => $info) {
10846 $this->_newobj();
10848 $this->images[$file]['n'] = $this->n;
10850 $this->_out('<</Type /XObject');
10851 $this->_out('/Subtype /Image');
10852 $this->_out('/Width ' . $info['w']);
10853 $this->_out('/Height ' . $info['h']);
10855 if (isset($info['interpolation']) && $info['interpolation']) {
10856 $this->_out('/Interpolate true'); // mPDF 6 - image interpolation shall be performed by a conforming reader
10859 if (isset($info['masked'])) {
10860 $this->_out('/SMask ' . ($this->n - 1) . ' 0 R');
10863 // set color space
10864 $icc = false;
10865 if (isset($info['icc']) and ( $info['icc'] !== false)) {
10866 // ICC Colour Space
10867 $icc = true;
10868 $this->_out('/ColorSpace [/ICCBased ' . ($this->n + 1) . ' 0 R]');
10869 } elseif ($info['cs'] == 'Indexed') {
10870 if ($this->PDFX || ($this->PDFA && $this->restrictColorSpace == 3)) {
10871 throw new \Mpdf\MpdfException("PDFA1-b and PDFX/1-a files do not permit using mixed colour space (" . $file . ").");
10873 $this->_out('/ColorSpace [/Indexed /DeviceRGB ' . (strlen($info['pal']) / 3 - 1) . ' ' . ($this->n + 1) . ' 0 R]');
10874 } else {
10875 $this->_out('/ColorSpace /' . $info['cs']);
10876 if ($info['cs'] == 'DeviceCMYK') {
10877 if ($this->PDFA && $this->restrictColorSpace != 3) {
10878 throw new \Mpdf\MpdfException("PDFA1-b does not permit Images using mixed colour space (" . $file . ").");
10880 if ($info['type'] == 'jpg') {
10881 $this->_out('/Decode [1 0 1 0 1 0 1 0]');
10883 } elseif ($info['cs'] == 'DeviceRGB' && ($this->PDFX || ($this->PDFA && $this->restrictColorSpace == 3))) {
10884 throw new \Mpdf\MpdfException("PDFA1-b and PDFX/1-a files do not permit using mixed colour space (" . $file . ").");
10888 $this->_out('/BitsPerComponent ' . $info['bpc']);
10890 if (isset($info['f']) && $info['f']) {
10891 $this->_out('/Filter /' . $info['f']);
10894 if (isset($info['parms'])) {
10895 $this->_out($info['parms']);
10898 if (isset($info['trns']) and is_array($info['trns'])) {
10899 $trns = '';
10900 for ($i = 0; $i < count($info['trns']); $i++) {
10901 $trns.=$info['trns'][$i] . ' ' . $info['trns'][$i] . ' ';
10903 $this->_out('/Mask [' . $trns . ']');
10906 $this->_out('/Length ' . strlen($info['data']) . '>>');
10907 $this->_putstream($info['data']);
10909 unset($this->images[$file]['data']);
10911 $this->_out('endobj');
10913 if ($icc) { // ICC colour profile
10914 $this->_newobj();
10915 $icc = ($this->compress) ? gzcompress($info['icc']) : $info['icc'];
10916 $this->_out('<</N ' . $info['ch'] . ' ' . $filter . '/Length ' . strlen($icc) . '>>');
10917 $this->_putstream($icc);
10918 $this->_out('endobj');
10919 } elseif ($info['cs'] == 'Indexed') { // Palette
10920 $this->_newobj();
10921 $pal = ($this->compress) ? gzcompress($info['pal']) : $info['pal'];
10922 $this->_out('<<' . $filter . '/Length ' . strlen($pal) . '>>');
10923 $this->_putstream($pal);
10924 $this->_out('endobj');
10929 private function getVersionString()
10931 $return = self::VERSION;
10932 $headFile = __DIR__ . '/../.git/HEAD';
10933 if (file_exists($headFile)) {
10934 $ref = file($headFile);
10935 $path = explode('/', $ref[0], 3);
10936 $branch = isset($path[2]) ? trim($path[2]) : '';
10937 $revFile = __DIR__ . '/../.git/refs/heads/' . $branch;
10938 if ($branch && file_exists($revFile)) {
10939 $rev = file($revFile);
10940 $rev = substr($rev[0], 0, 7);
10941 $return .= ' (' . $rev . ')';
10945 return $return;
10948 function _putinfo()
10950 $this->_out('/Producer ' . $this->_UTF16BEtextstring('mPDF ' . $this->getVersionString()));
10951 if (!empty($this->title)) {
10952 $this->_out('/Title ' . $this->_UTF16BEtextstring($this->title));
10954 if (!empty($this->subject)) {
10955 $this->_out('/Subject ' . $this->_UTF16BEtextstring($this->subject));
10957 if (!empty($this->author)) {
10958 $this->_out('/Author ' . $this->_UTF16BEtextstring($this->author));
10960 if (!empty($this->keywords)) {
10961 $this->_out('/Keywords ' . $this->_UTF16BEtextstring($this->keywords));
10963 if (!empty($this->creator)) {
10964 $this->_out('/Creator ' . $this->_UTF16BEtextstring($this->creator));
10966 foreach ($this->customProperties as $key => $value) {
10967 $this->_out('/' . $key . ' ' . $this->_UTF16BEtextstring($value));
10970 $z = date('O'); // +0200
10971 $offset = substr($z, 0, 3) . "'" . substr($z, 3, 2) . "'";
10972 $this->_out('/CreationDate ' . $this->_textstring(date('YmdHis') . $offset));
10973 $this->_out('/ModDate ' . $this->_textstring(date('YmdHis') . $offset));
10974 if ($this->PDFX) {
10975 $this->_out('/Trapped/False');
10976 $this->_out('/GTS_PDFXVersion(PDF/X-1a:2003)');
10980 function _putmetadata()
10982 $this->_newobj();
10983 $this->MetadataRoot = $this->n;
10984 $Producer = 'mPDF ' . self::VERSION;
10985 $z = date('O'); // +0200
10986 $offset = substr($z, 0, 3) . ':' . substr($z, 3, 2);
10987 $CreationDate = date('Y-m-d\TH:i:s') . $offset; // 2006-03-10T10:47:26-05:00 2006-06-19T09:05:17Z
10988 $uuid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', random_int(0, 0xffff), random_int(0, 0xffff), random_int(0, 0xffff), random_int(0, 0x0fff) | 0x4000, random_int(0, 0x3fff) | 0x8000, random_int(0, 0xffff), random_int(0, 0xffff), random_int(0, 0xffff));
10991 $m = '<?xpacket begin="' . chr(239) . chr(187) . chr(191) . '" id="W5M0MpCehiHzreSzNTczkc9d"?>' . "\n"; // begin = FEFF BOM
10992 $m .= ' <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="3.1-701">' . "\n";
10993 $m .= ' <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">' . "\n";
10994 $m .= ' <rdf:Description rdf:about="uuid:' . $uuid . '" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">' . "\n";
10995 $m .= ' <pdf:Producer>' . $Producer . '</pdf:Producer>' . "\n";
10996 if (!empty($this->keywords)) {
10997 $m .= ' <pdf:Keywords>' . $this->keywords . '</pdf:Keywords>' . "\n";
10999 $m .= ' </rdf:Description>' . "\n";
11001 $m .= ' <rdf:Description rdf:about="uuid:' . $uuid . '" xmlns:xmp="http://ns.adobe.com/xap/1.0/">' . "\n";
11002 $m .= ' <xmp:CreateDate>' . $CreationDate . '</xmp:CreateDate>' . "\n";
11003 $m .= ' <xmp:ModifyDate>' . $CreationDate . '</xmp:ModifyDate>' . "\n";
11004 $m .= ' <xmp:MetadataDate>' . $CreationDate . '</xmp:MetadataDate>' . "\n";
11005 if (!empty($this->creator)) {
11006 $m .= ' <xmp:CreatorTool>' . $this->creator . '</xmp:CreatorTool>' . "\n";
11008 $m .= ' </rdf:Description>' . "\n";
11010 // DC elements
11011 $m .= ' <rdf:Description rdf:about="uuid:' . $uuid . '" xmlns:dc="http://purl.org/dc/elements/1.1/">' . "\n";
11012 $m .= ' <dc:format>application/pdf</dc:format>' . "\n";
11013 if (!empty($this->title)) {
11014 $m .= ' <dc:title>
11015 <rdf:Alt>
11016 <rdf:li xml:lang="x-default">' . $this->title . '</rdf:li>
11017 </rdf:Alt>
11018 </dc:title>' . "\n";
11020 if (!empty($this->keywords)) {
11021 $m .= ' <dc:subject>
11022 <rdf:Bag>
11023 <rdf:li>' . $this->keywords . '</rdf:li>
11024 </rdf:Bag>
11025 </dc:subject>' . "\n";
11027 if (!empty($this->subject)) {
11028 $m .= ' <dc:description>
11029 <rdf:Alt>
11030 <rdf:li xml:lang="x-default">' . $this->subject . '</rdf:li>
11031 </rdf:Alt>
11032 </dc:description>' . "\n";
11034 if (!empty($this->author)) {
11035 $m .= ' <dc:creator>
11036 <rdf:Seq>
11037 <rdf:li>' . $this->author . '</rdf:li>
11038 </rdf:Seq>
11039 </dc:creator>' . "\n";
11041 $m .= ' </rdf:Description>' . "\n";
11043 if (!empty($this->additionalXmpRdf)) {
11044 $m .= $this->additionalXmpRdf;
11047 // This bit is specific to PDFX-1a
11048 if ($this->PDFX) {
11049 $m .= ' <rdf:Description rdf:about="uuid:' . $uuid . '" xmlns:pdfx="http://ns.adobe.com/pdfx/1.3/" pdfx:Apag_PDFX_Checkup="1.3" pdfx:GTS_PDFXConformance="PDF/X-1a:2003" pdfx:GTS_PDFXVersion="PDF/X-1:2003"/>' . "\n";
11050 } // This bit is specific to PDFA-1b
11051 elseif ($this->PDFA) {
11053 if (strpos($this->PDFAversion, '-') === false) {
11054 throw new \Mpdf\MpdfException(sprintf('PDFA version (%s) is not valid. (Use: 1-B, 3-B, etc.)', $this->PDFAversion));
11057 list($part, $conformance) = explode('-', strtoupper($this->PDFAversion));
11058 $m .= ' <rdf:Description rdf:about="uuid:' . $uuid . '" xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/" >' . "\n";
11059 $m .= ' <pdfaid:part>' . $part . '</pdfaid:part>' . "\n";
11060 $m .= ' <pdfaid:conformance>' . $conformance . '</pdfaid:conformance>' . "\n";
11061 if ($part === '1' && $conformance === 'B') {
11062 $m .= ' <pdfaid:amd>2005</pdfaid:amd>' . "\n";
11064 $m .= ' </rdf:Description>' . "\n";
11067 $m .= ' <rdf:Description rdf:about="uuid:' . $uuid . '" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/">' . "\n";
11068 $m .= ' <xmpMM:DocumentID>uuid:' . $uuid . '</xmpMM:DocumentID>' . "\n";
11069 $m .= ' </rdf:Description>' . "\n";
11070 $m .= ' </rdf:RDF>' . "\n";
11071 $m .= ' </x:xmpmeta>' . "\n";
11072 $m .= str_repeat(str_repeat(' ', 100) . "\n", 20); // 2-4kB whitespace padding required
11073 $m .= '<?xpacket end="w"?>'; // "r" read only
11074 $this->_out('<</Type/Metadata/Subtype/XML/Length ' . strlen($m) . '>>');
11075 $this->_putstream($m);
11076 $this->_out('endobj');
11079 function _putoutputintent()
11081 $this->_newobj();
11082 $this->OutputIntentRoot = $this->n;
11083 $this->_out('<</Type /OutputIntent');
11085 $ICCProfile = preg_replace('/_/', ' ', basename($this->ICCProfile, '.icc'));
11087 if ($this->PDFA) {
11088 $this->_out('/S /GTS_PDFA1');
11089 if ($this->ICCProfile) {
11090 $this->_out('/Info (' . $ICCProfile . ')');
11091 $this->_out('/OutputConditionIdentifier (Custom)');
11092 $this->_out('/OutputCondition ()');
11093 } else {
11094 $this->_out('/Info (sRGB IEC61966-2.1)');
11095 $this->_out('/OutputConditionIdentifier (sRGB IEC61966-2.1)');
11096 $this->_out('/OutputCondition ()');
11098 $this->_out('/DestOutputProfile ' . ($this->n + 1) . ' 0 R');
11099 } elseif ($this->PDFX) { // always a CMYK profile
11100 $this->_out('/S /GTS_PDFX');
11101 if ($this->ICCProfile) {
11102 $this->_out('/Info (' . $ICCProfile . ')');
11103 $this->_out('/OutputConditionIdentifier (Custom)');
11104 $this->_out('/OutputCondition ()');
11105 $this->_out('/DestOutputProfile ' . ($this->n + 1) . ' 0 R');
11106 } else {
11107 $this->_out('/Info (CGATS TR 001)');
11108 $this->_out('/OutputConditionIdentifier (CGATS TR 001)');
11109 $this->_out('/OutputCondition (CGATS TR 001 (SWOP))');
11110 $this->_out('/RegistryName (http://www.color.org)');
11113 $this->_out('>>');
11114 $this->_out('endobj');
11116 if ($this->PDFX && !$this->ICCProfile) {
11117 return;
11120 $this->_newobj();
11122 if ($this->ICCProfile) {
11123 if (!file_exists($this->ICCProfile)) {
11124 throw new \Mpdf\MpdfException(sprintf('Unable to find ICC profile "%s"', $this->ICCProfile));
11126 $s = file_get_contents($this->ICCProfile);
11127 } else {
11128 $s = file_get_contents(__DIR__ . '/../data/iccprofiles/sRGB_IEC61966-2-1.icc');
11131 if ($this->compress) {
11132 $s = gzcompress($s);
11135 $this->_out('<<');
11137 if ($this->PDFX || ($this->PDFA && $this->restrictColorSpace == 3)) {
11138 $this->_out('/N 4');
11139 } else {
11140 $this->_out('/N 3');
11143 if ($this->compress) {
11144 $this->_out('/Filter /FlateDecode ');
11147 $this->_out('/Length ' . strlen($s) . '>>');
11148 $this->_putstream($s);
11149 $this->_out('endobj');
11152 function _putAssociatedFiles()
11154 if (!function_exists('gzcompress')) {
11155 throw new \Mpdf\MpdfException('ext-zlib is required for compression of associated files');
11158 // for each file, we create the spec object + the stream object
11159 foreach ($this->associatedFiles as $k => $file) {
11160 // spec
11161 $this->_newobj();
11162 $this->associatedFiles[$k]['_root'] = $this->n; // we store the root ref of object for future reference (e.g. /EmbeddedFiles catalog)
11163 $this->_out('<</F ' . $this->_textstring($file['name']));
11164 if ($file['description']) {
11165 $this->_out('/Desc ' . $this->_textstring($file['description']));
11167 $this->_out('/Type /Filespec');
11168 $this->_out('/EF <<');
11169 $this->_out('/F ' . ($this->n + 1) . ' 0 R');
11170 $this->_out('/UF ' . ($this->n + 1) . ' 0 R');
11171 $this->_out('>>');
11172 if ($file['AFRelationship']) {
11173 $this->_out('/AFRelationship /' . $file['AFRelationship']);
11175 $this->_out('/UF ' . $this->_textstring($file['name']));
11176 $this->_out('>>');
11177 $this->_out('endobj');
11179 $fileContent = null;
11180 if (isset($file['path'])) {
11181 $fileContent = @file_get_contents($file['path']);
11182 } elseif (isset($file['content'])) {
11183 $fileContent = $file['content'];
11186 if (!$fileContent) {
11187 throw new \Mpdf\MpdfException(sprintf('Cannot access associated file - %s', $file['path']));
11190 $filestream = gzcompress($fileContent);
11191 $this->_newobj();
11192 $this->_out('<</Type /EmbeddedFile');
11193 if ($file['mime']) {
11194 $this->_out('/Subtype /' . $this->_escapeName($file['mime']));
11196 $this->_out('/Length '.strlen($filestream));
11197 $this->_out('/Filter /FlateDecode');
11198 if (isset($file['path'])) {
11199 $this->_out('/Params <</ModDate '.$this->_textstring('D:'.PdfDate::format(filemtime($file['path']))).' >>');
11200 } else {
11201 $this->_out('/Params <</ModDate '.$this->_textstring('D:'.PdfDate::format(time())).' >>');
11204 $this->_out('>>');
11205 $this->_putstream($filestream);
11206 $this->_out('endobj');
11209 // AF array
11210 $this->_newobj();
11211 $refs = [];
11212 foreach ($this->associatedFiles as $file) {
11213 array_push($refs, '' . $file['_root'] . ' 0 R');
11215 $this->_out('[' . join(' ', $refs) . ']');
11216 $this->_out('endobj');
11217 $this->associatedFilesRoot = $this->n;
11220 function _putcatalog()
11222 $this->_out('/Type /Catalog');
11223 $this->_out('/Pages 1 0 R');
11224 if ($this->ZoomMode == 'fullpage') {
11225 $this->_out('/OpenAction [3 0 R /Fit]');
11226 } elseif ($this->ZoomMode == 'fullwidth') {
11227 $this->_out('/OpenAction [3 0 R /FitH null]');
11228 } elseif ($this->ZoomMode == 'real') {
11229 $this->_out('/OpenAction [3 0 R /XYZ null null 1]');
11230 } elseif (!is_string($this->ZoomMode)) {
11231 $this->_out('/OpenAction [3 0 R /XYZ null null ' . ($this->ZoomMode / 100) . ']');
11232 } else {
11233 $this->_out('/OpenAction [3 0 R /XYZ null null null]');
11235 if ($this->LayoutMode == 'single') {
11236 $this->_out('/PageLayout /SinglePage');
11237 } elseif ($this->LayoutMode == 'continuous') {
11238 $this->_out('/PageLayout /OneColumn');
11239 } elseif ($this->LayoutMode == 'twoleft') {
11240 $this->_out('/PageLayout /TwoColumnLeft');
11241 } elseif ($this->LayoutMode == 'tworight') {
11242 $this->_out('/PageLayout /TwoColumnRight');
11243 } elseif ($this->LayoutMode == 'two') {
11244 if ($this->mirrorMargins) {
11245 $this->_out('/PageLayout /TwoColumnRight');
11246 } else {
11247 $this->_out('/PageLayout /TwoColumnLeft');
11251 // Bookmarks
11252 if (count($this->BMoutlines) > 0) {
11253 $this->_out('/Outlines ' . $this->OutlineRoot . ' 0 R');
11254 $this->_out('/PageMode /UseOutlines');
11257 // Fullscreen
11258 if (is_int(strpos($this->DisplayPreferences, 'FullScreen'))) {
11259 $this->_out('/PageMode /FullScreen');
11262 // Metadata
11263 if ($this->PDFA || $this->PDFX) {
11264 $this->_out('/Metadata ' . $this->MetadataRoot . ' 0 R');
11267 // OutputIntents
11268 if ($this->PDFA || $this->PDFX || $this->ICCProfile) {
11269 $this->_out('/OutputIntents [' . $this->OutputIntentRoot . ' 0 R]');
11272 // Associated files
11273 if ($this->associatedFilesRoot) {
11274 $this->_out('/AF '. $this->associatedFilesRoot .' 0 R');
11276 $names = [];
11277 foreach ($this->associatedFiles as $file) {
11278 array_push($names, $this->_textstring($file['name']) . ' ' . $file['_root'] . ' 0 R');
11280 $this->_out('/Names << /EmbeddedFiles << /Names [' . join(' ', $names) . '] >> >>');
11283 // Forms
11284 if (count($this->form->forms) > 0) {
11285 $this->form->_putFormsCatalog();
11288 if (isset($this->js)) {
11289 $this->_out('/Names << /JavaScript ' . ($this->n_js) . ' 0 R >> ');
11292 if ($this->DisplayPreferences || $this->directionality == 'rtl' || $this->mirrorMargins) {
11293 $this->_out('/ViewerPreferences<<');
11294 if (is_int(strpos($this->DisplayPreferences, 'HideMenubar'))) {
11295 $this->_out('/HideMenubar true');
11297 if (is_int(strpos($this->DisplayPreferences, 'HideToolbar'))) {
11298 $this->_out('/HideToolbar true');
11300 if (is_int(strpos($this->DisplayPreferences, 'HideWindowUI'))) {
11301 $this->_out('/HideWindowUI true');
11303 if (is_int(strpos($this->DisplayPreferences, 'DisplayDocTitle'))) {
11304 $this->_out('/DisplayDocTitle true');
11306 if (is_int(strpos($this->DisplayPreferences, 'CenterWindow'))) {
11307 $this->_out('/CenterWindow true');
11309 if (is_int(strpos($this->DisplayPreferences, 'FitWindow'))) {
11310 $this->_out('/FitWindow true');
11312 ///PrintScaling is PDF 1.6 spec.
11313 if (is_int(strpos($this->DisplayPreferences, 'NoPrintScaling')) && !$this->PDFA && !$this->PDFX) {
11314 $this->_out('/PrintScaling /None');
11316 if ($this->directionality == 'rtl') {
11317 $this->_out('/Direction /R2L');
11319 ///Duplex is PDF 1.7 spec.
11320 if ($this->mirrorMargins && !$this->PDFA && !$this->PDFX) {
11321 // if ($this->DefOrientation=='P') $this->_out('/Duplex /DuplexFlipShortEdge');
11322 $this->_out('/Duplex /DuplexFlipLongEdge'); // PDF v1.7+
11324 $this->_out('>>');
11327 if ($this->open_layer_pane && ($this->hasOC || count($this->layers))) {
11328 $this->_out('/PageMode /UseOC');
11331 if ($this->hasOC || count($this->layers)) {
11332 $p = $v = $h = $l = $loff = $lall = $as = '';
11333 if ($this->hasOC) {
11334 if (($this->hasOC & 1) == 1) {
11335 $p = $this->n_ocg_print . ' 0 R';
11337 if (($this->hasOC & 2) == 2) {
11338 $v = $this->n_ocg_view . ' 0 R';
11340 if (($this->hasOC & 4) == 4) {
11341 $h = $this->n_ocg_hidden . ' 0 R';
11343 $as = "<</Event /Print /OCGs [$p $v $h] /Category [/Print]>> <</Event /View /OCGs [$p $v $h] /Category [/View]>>";
11346 if (count($this->layers)) {
11347 foreach ($this->layers as $k => $layer) {
11348 if (strtolower($this->layerDetails[$k]['state']) == 'hidden') {
11349 $loff .= $layer['n'] . ' 0 R ';
11350 } else {
11351 $l .= $layer['n'] . ' 0 R ';
11353 $lall .= $layer['n'] . ' 0 R ';
11356 $this->_out("/OCProperties <</OCGs [$p $v $h $lall] /D <</ON [$p $l] /OFF [$v $h $loff] ");
11357 $this->_out("/Order [$v $p $h $lall] ");
11358 if ($as) {
11359 $this->_out("/AS [$as] ");
11361 $this->_out(">>>>");
11365 function _enddoc()
11367 // @log Writing Headers & Footers
11369 $this->_puthtmlheaders();
11371 // @log Writing Pages
11373 // Remove references to unused fonts (usually default font)
11374 foreach ($this->fonts as $fk => $font) {
11375 if (isset($font['type']) && $font['type'] == 'TTF' && !$font['used']) {
11376 if ($font['sip'] || $font['smp']) {
11377 foreach ($font['subsetfontids'] as $k => $fid) {
11378 foreach ($this->pages as $pn => $page) {
11379 $this->pages[$pn] = preg_replace('/\s\/F' . $fid . ' \d[\d.]* Tf\s/is', ' ', $this->pages[$pn]);
11382 } else {
11383 foreach ($this->pages as $pn => $page) {
11384 $this->pages[$pn] = preg_replace('/\s\/F' . $font['i'] . ' \d[\d.]* Tf\s/is', ' ', $this->pages[$pn]);
11390 if (count($this->layers)) {
11391 foreach ($this->pages as $pn => $page) {
11392 preg_match_all('/\/OCZ-index \/ZI(\d+) BDC(.*?)(EMCZ)-index/is', $this->pages[$pn], $m1);
11393 preg_match_all('/\/OCBZ-index \/ZI(\d+) BDC(.*?)(EMCBZ)-index/is', $this->pages[$pn], $m2);
11394 preg_match_all('/\/OCGZ-index \/ZI(\d+) BDC(.*?)(EMCGZ)-index/is', $this->pages[$pn], $m3);
11395 $m = [];
11396 for ($i = 0; $i < 4; $i++) {
11397 $m[$i] = array_merge($m1[$i], $m2[$i], $m3[$i]);
11399 if (count($m[0])) {
11400 $sortarr = [];
11401 for ($i = 0; $i < count($m[0]); $i++) {
11402 $key = $m[1][$i] * 2;
11403 if ($m[3][$i] == 'EMCZ') {
11404 $key +=2; // background first then gradient then normal
11405 } elseif ($m[3][$i] == 'EMCGZ') {
11406 $key +=1;
11408 $sortarr[$i] = $key;
11410 asort($sortarr);
11411 foreach ($sortarr as $i => $k) {
11412 $this->pages[$pn] = str_replace($m[0][$i], '', $this->pages[$pn]);
11413 $this->pages[$pn] .= "\n" . $m[0][$i] . "\n";
11415 $this->pages[$pn] = preg_replace('/\/OC[BG]{0,1}Z-index \/ZI(\d+) BDC/is', '/OC /ZI\\1 BDC ', $this->pages[$pn]);
11416 $this->pages[$pn] = preg_replace('/EMC[BG]{0,1}Z-index/is', 'EMC', $this->pages[$pn]);
11421 $this->_putpages();
11423 // @log Writing document resources
11425 $this->_putresources();
11426 // Info
11427 $this->_newobj();
11428 $this->InfoRoot = $this->n;
11429 $this->_out('<<');
11431 // @log Writing document info
11433 $this->_putinfo();
11434 $this->_out('>>');
11435 $this->_out('endobj');
11437 // METADATA
11438 if ($this->PDFA || $this->PDFX) {
11439 $this->_putmetadata();
11442 // OUTPUTINTENT
11443 if ($this->PDFA || $this->PDFX || $this->ICCProfile) {
11444 $this->_putoutputintent();
11447 // Associated files
11448 if ($this->associatedFiles) {
11449 $this->_putAssociatedFiles();
11452 // Catalog
11453 $this->_newobj();
11454 $this->_out('<<');
11456 // @log Writing document catalog
11458 $this->_putcatalog();
11459 $this->_out('>>');
11460 $this->_out('endobj');
11462 // Cross-ref
11463 $o = strlen($this->buffer);
11464 $this->_out('xref');
11465 $this->_out('0 ' . ($this->n + 1));
11466 $this->_out('0000000000 65535 f ');
11468 for ($i = 1; $i <= $this->n; $i++) {
11469 $this->_out(sprintf('%010d 00000 n ', $this->offsets[$i]));
11472 // Trailer
11473 $this->_out('trailer');
11474 $this->_out('<<');
11475 $this->_puttrailer();
11476 $this->_out('>>');
11477 $this->_out('startxref');
11478 $this->_out($o);
11480 $this->buffer .= '%%EOF';
11481 $this->state = 3;
11483 // Imports
11484 if ($this->enableImports && count($this->parsers) > 0) {
11485 foreach ($this->parsers as $k => $_) {
11486 $this->parsers[$k]->closeFile();
11487 $this->parsers[$k] = null;
11488 unset($this->parsers[$k]);
11493 function _beginpage(
11494 $orientation,
11495 $mgl = '',
11496 $mgr = '',
11497 $mgt = '',
11498 $mgb = '',
11499 $mgh = '',
11500 $mgf = '',
11501 $ohname = '',
11502 $ehname = '',
11503 $ofname = '',
11504 $efname = '',
11505 $ohvalue = 0,
11506 $ehvalue = 0,
11507 $ofvalue = 0,
11508 $efvalue = 0,
11509 $pagesel = '',
11510 $newformat = ''
11512 if (!($pagesel && $this->page == 1 && (sprintf("%0.4f", $this->y) == sprintf("%0.4f", $this->tMargin)))) {
11513 $this->page++;
11514 $this->pages[$this->page] = '';
11516 $this->state = 2;
11517 $resetHTMLHeadersrequired = false;
11519 if ($newformat) {
11520 $this->_setPageSize($newformat, $orientation);
11523 /* -- CSS-PAGE -- */
11524 // Paged media (page-box)
11525 if ($pagesel || (isset($this->page_box['using']) && $this->page_box['using'])) {
11526 if ($pagesel || $this->page == 1) {
11527 $first = true;
11528 } else {
11529 $first = false;
11531 if ($this->mirrorMargins && ($this->page % 2 == 0)) {
11532 $oddEven = 'E';
11533 } else {
11534 $oddEven = 'O';
11536 if ($pagesel) {
11537 $psel = $pagesel;
11538 } elseif ($this->page_box['current']) {
11539 $psel = $this->page_box['current'];
11540 } else {
11541 $psel = '';
11543 list($orientation, $mgl, $mgr, $mgt, $mgb, $mgh, $mgf, $hname, $fname, $bg, $resetpagenum, $pagenumstyle, $suppress, $marks, $newformat) = $this->SetPagedMediaCSS($psel, $first, $oddEven);
11544 if ($this->mirrorMargins && ($this->page % 2 == 0)) {
11545 if ($hname) {
11546 $ehvalue = 1;
11547 $ehname = $hname;
11548 } else {
11549 $ehvalue = -1;
11551 if ($fname) {
11552 $efvalue = 1;
11553 $efname = $fname;
11554 } else {
11555 $efvalue = -1;
11557 } else {
11558 if ($hname) {
11559 $ohvalue = 1;
11560 $ohname = $hname;
11561 } else {
11562 $ohvalue = -1;
11564 if ($fname) {
11565 $ofvalue = 1;
11566 $ofname = $fname;
11567 } else {
11568 $ofvalue = -1;
11571 if ($resetpagenum || $pagenumstyle || $suppress) {
11572 $this->PageNumSubstitutions[] = ['from' => ($this->page), 'reset' => $resetpagenum, 'type' => $pagenumstyle, 'suppress' => $suppress];
11574 // PAGED MEDIA - CROP / CROSS MARKS from @PAGE
11575 $this->show_marks = $marks;
11577 // Background color
11578 if (isset($bg['BACKGROUND-COLOR'])) {
11579 $cor = $this->colorConverter->convert($bg['BACKGROUND-COLOR'], $this->PDFAXwarnings);
11580 if ($cor) {
11581 $this->bodyBackgroundColor = $cor;
11583 } else {
11584 $this->bodyBackgroundColor = false;
11587 /* -- BACKGROUNDS -- */
11588 if (isset($bg['BACKGROUND-GRADIENT'])) {
11589 $this->bodyBackgroundGradient = $bg['BACKGROUND-GRADIENT'];
11590 } else {
11591 $this->bodyBackgroundGradient = false;
11594 // Tiling Patterns
11595 if (isset($bg['BACKGROUND-IMAGE']) && $bg['BACKGROUND-IMAGE']) {
11596 $ret = $this->SetBackground($bg, $this->pgwidth);
11597 if ($ret) {
11598 $this->bodyBackgroundImage = $ret;
11600 } else {
11601 $this->bodyBackgroundImage = false;
11603 /* -- END BACKGROUNDS -- */
11605 $this->page_box['current'] = $psel;
11606 $this->page_box['using'] = true;
11608 /* -- END CSS-PAGE -- */
11610 // Page orientation
11611 if (!$orientation) {
11612 $orientation = $this->DefOrientation;
11613 } else {
11614 $orientation = strtoupper(substr($orientation, 0, 1));
11615 if ($orientation != $this->DefOrientation) {
11616 $this->OrientationChanges[$this->page] = true;
11620 if ($orientation != $this->CurOrientation || $newformat) {
11622 // Change orientation
11623 if ($orientation == 'P') {
11624 $this->wPt = $this->fwPt;
11625 $this->hPt = $this->fhPt;
11626 $this->w = $this->fw;
11627 $this->h = $this->fh;
11628 if (($this->forcePortraitHeaders || $this->forcePortraitMargins) && $this->DefOrientation == 'P') {
11629 $this->tMargin = $this->orig_tMargin;
11630 $this->bMargin = $this->orig_bMargin;
11631 $this->DeflMargin = $this->orig_lMargin;
11632 $this->DefrMargin = $this->orig_rMargin;
11633 $this->margin_header = $this->orig_hMargin;
11634 $this->margin_footer = $this->orig_fMargin;
11635 } else {
11636 $resetHTMLHeadersrequired = true;
11638 } else {
11639 $this->wPt = $this->fhPt;
11640 $this->hPt = $this->fwPt;
11641 $this->w = $this->fh;
11642 $this->h = $this->fw;
11644 if (($this->forcePortraitHeaders || $this->forcePortraitMargins) && $this->DefOrientation == 'P') {
11645 $this->tMargin = $this->orig_lMargin;
11646 $this->bMargin = $this->orig_rMargin;
11647 $this->DeflMargin = $this->orig_bMargin;
11648 $this->DefrMargin = $this->orig_tMargin;
11649 $this->margin_header = $this->orig_hMargin;
11650 $this->margin_footer = $this->orig_fMargin;
11651 } else {
11652 $resetHTMLHeadersrequired = true;
11656 $this->CurOrientation = $orientation;
11657 $this->ResetMargins();
11658 $this->pgwidth = $this->w - $this->lMargin - $this->rMargin;
11659 $this->PageBreakTrigger = $this->h - $this->bMargin;
11662 $this->pageDim[$this->page]['w'] = $this->w;
11663 $this->pageDim[$this->page]['h'] = $this->h;
11665 $this->pageDim[$this->page]['outer_width_LR'] = isset($this->page_box['outer_width_LR']) ? $this->page_box['outer_width_LR'] : 0;
11666 $this->pageDim[$this->page]['outer_width_TB'] = isset($this->page_box['outer_width_TB']) ? $this->page_box['outer_width_TB'] : 0;
11668 if (!isset($this->page_box['outer_width_LR']) && !isset($this->page_box['outer_width_TB'])) {
11669 $this->pageDim[$this->page]['bleedMargin'] = 0;
11670 } elseif ($this->bleedMargin <= $this->page_box['outer_width_LR'] && $this->bleedMargin <= $this->page_box['outer_width_TB']) {
11671 $this->pageDim[$this->page]['bleedMargin'] = $this->bleedMargin;
11672 } else {
11673 $this->pageDim[$this->page]['bleedMargin'] = min($this->page_box['outer_width_LR'], $this->page_box['outer_width_TB']) - 0.01;
11676 // If Page Margins are re-defined
11677 // strlen()>0 is used to pick up (integer) 0, (string) '0', or set value
11678 if ((strlen($mgl) > 0 && $this->DeflMargin != $mgl) || (strlen($mgr) > 0 && $this->DefrMargin != $mgr) || (strlen($mgt) > 0 && $this->tMargin != $mgt) || (strlen($mgb) > 0 && $this->bMargin != $mgb) || (strlen($mgh) > 0 && $this->margin_header != $mgh) || (strlen($mgf) > 0 && $this->margin_footer != $mgf)) {
11680 if (strlen($mgl) > 0) {
11681 $this->DeflMargin = $mgl;
11684 if (strlen($mgr) > 0) {
11685 $this->DefrMargin = $mgr;
11688 if (strlen($mgt) > 0) {
11689 $this->tMargin = $mgt;
11692 if (strlen($mgb) > 0) {
11693 $this->bMargin = $mgb;
11696 if (strlen($mgh) > 0) {
11697 $this->margin_header = $mgh;
11700 if (strlen($mgf) > 0) {
11701 $this->margin_footer = $mgf;
11704 $this->ResetMargins();
11705 $this->SetAutoPageBreak($this->autoPageBreak, $this->bMargin);
11707 $this->pgwidth = $this->w - $this->lMargin - $this->rMargin;
11708 $resetHTMLHeadersrequired = true;
11711 $this->ResetMargins();
11712 $this->pgwidth = $this->w - $this->lMargin - $this->rMargin;
11713 $this->SetAutoPageBreak($this->autoPageBreak, $this->bMargin);
11715 // Reset column top margin
11716 $this->y0 = $this->tMargin;
11718 $this->x = $this->lMargin;
11719 $this->y = $this->tMargin;
11720 $this->FontFamily = '';
11722 // HEADERS AND FOOTERS // mPDF 6
11723 if ($ohvalue < 0 || strtoupper($ohvalue) == 'OFF') {
11724 $this->HTMLHeader = '';
11725 $resetHTMLHeadersrequired = true;
11726 } elseif ($ohname && $ohvalue > 0) {
11727 if (preg_match('/^html_(.*)$/i', $ohname, $n)) {
11728 $name = $n[1];
11729 } else {
11730 $name = $ohname;
11732 if (isset($this->pageHTMLheaders[$name])) {
11733 $this->HTMLHeader = $this->pageHTMLheaders[$name];
11734 } else {
11735 $this->HTMLHeader = '';
11737 $resetHTMLHeadersrequired = true;
11740 if ($ehvalue < 0 || strtoupper($ehvalue) == 'OFF') {
11741 $this->HTMLHeaderE = '';
11742 $resetHTMLHeadersrequired = true;
11743 } elseif ($ehname && $ehvalue > 0) {
11744 if (preg_match('/^html_(.*)$/i', $ehname, $n)) {
11745 $name = $n[1];
11746 } else {
11747 $name = $ehname;
11749 if (isset($this->pageHTMLheaders[$name])) {
11750 $this->HTMLHeaderE = $this->pageHTMLheaders[$name];
11751 } else {
11752 $this->HTMLHeaderE = '';
11754 $resetHTMLHeadersrequired = true;
11757 if ($ofvalue < 0 || strtoupper($ofvalue) == 'OFF') {
11758 $this->HTMLFooter = '';
11759 $resetHTMLHeadersrequired = true;
11760 } elseif ($ofname && $ofvalue > 0) {
11761 if (preg_match('/^html_(.*)$/i', $ofname, $n)) {
11762 $name = $n[1];
11763 } else {
11764 $name = $ofname;
11766 if (isset($this->pageHTMLfooters[$name])) {
11767 $this->HTMLFooter = $this->pageHTMLfooters[$name];
11768 } else {
11769 $this->HTMLFooter = '';
11771 $resetHTMLHeadersrequired = true;
11774 if ($efvalue < 0 || strtoupper($efvalue) == 'OFF') {
11775 $this->HTMLFooterE = '';
11776 $resetHTMLHeadersrequired = true;
11777 } elseif ($efname && $efvalue > 0) {
11778 if (preg_match('/^html_(.*)$/i', $efname, $n)) {
11779 $name = $n[1];
11780 } else {
11781 $name = $efname;
11783 if (isset($this->pageHTMLfooters[$name])) {
11784 $this->HTMLFooterE = $this->pageHTMLfooters[$name];
11785 } else {
11786 $this->HTMLFooterE = '';
11788 $resetHTMLHeadersrequired = true;
11791 if ($resetHTMLHeadersrequired) {
11792 $this->SetHTMLHeader($this->HTMLHeader);
11793 $this->SetHTMLHeader($this->HTMLHeaderE, 'E');
11794 $this->SetHTMLFooter($this->HTMLFooter);
11795 $this->SetHTMLFooter($this->HTMLFooterE, 'E');
11799 if (($this->mirrorMargins) && (($this->page) % 2 == 0)) { // EVEN
11800 $this->_setAutoHeaderHeight($this->HTMLHeaderE);
11801 $this->_setAutoFooterHeight($this->HTMLFooterE);
11802 } else { // ODD or DEFAULT
11803 $this->_setAutoHeaderHeight($this->HTMLHeader);
11804 $this->_setAutoFooterHeight($this->HTMLFooter);
11807 // Reset column top margin
11808 $this->y0 = $this->tMargin;
11810 $this->x = $this->lMargin;
11811 $this->y = $this->tMargin;
11814 // mPDF 6
11815 function _setAutoHeaderHeight(&$htmlh)
11817 /* When the setAutoTopMargin option is set to pad/stretch, only apply auto header height when a header exists */
11818 if ($this->HTMLHeader === '' && $this->HTMLHeaderE === '') {
11819 return;
11822 if ($this->setAutoTopMargin == 'pad') {
11823 if (isset($htmlh['h']) && $htmlh['h']) {
11824 $h = $htmlh['h'];
11825 } // 5.7.3
11826 else {
11827 $h = 0;
11829 $this->tMargin = $this->margin_header + $h + $this->orig_tMargin;
11830 } elseif ($this->setAutoTopMargin == 'stretch') {
11831 if (isset($htmlh['h']) && $htmlh['h']) {
11832 $h = $htmlh['h'];
11833 } // 5.7.3
11834 else {
11835 $h = 0;
11837 $this->tMargin = max($this->orig_tMargin, $this->margin_header + $h + $this->autoMarginPadding);
11841 // mPDF 6
11842 function _setAutoFooterHeight(&$htmlf)
11844 /* When the setAutoTopMargin option is set to pad/stretch, only apply auto footer height when a footer exists */
11845 if ($this->HTMLFooter === '' && $this->HTMLFooterE === '') {
11846 return;
11849 if ($this->setAutoBottomMargin == 'pad') {
11850 if (isset($htmlf['h']) && $htmlf['h']) {
11851 $h = $htmlf['h'];
11852 } // 5.7.3
11853 else {
11854 $h = 0;
11856 $this->bMargin = $this->margin_footer + $h + $this->orig_bMargin;
11857 $this->PageBreakTrigger = $this->h - $this->bMargin;
11858 } elseif ($this->setAutoBottomMargin == 'stretch') {
11859 if (isset($htmlf['h']) && $htmlf['h']) {
11860 $h = $htmlf['h'];
11861 } // 5.7.3
11862 else {
11863 $h = 0;
11865 $this->bMargin = max($this->orig_bMargin, $this->margin_footer + $h + $this->autoMarginPadding);
11866 $this->PageBreakTrigger = $this->h - $this->bMargin;
11870 function _endpage()
11872 /* -- CSS-IMAGE-FLOAT -- */
11873 $this->printfloatbuffer();
11874 /* -- END CSS-IMAGE-FLOAT -- */
11876 if ($this->visibility != 'visible') {
11877 $this->SetVisibility('visible');
11879 $this->EndLayer();
11880 // End of page contents
11881 $this->state = 1;
11884 function _newobj($obj_id = false, $onlynewobj = false)
11886 if (!$obj_id) {
11887 $obj_id = ++$this->n;
11889 // Begin a new object
11890 if (!$onlynewobj) {
11891 $this->offsets[$obj_id] = strlen($this->buffer);
11892 $this->_out($obj_id . ' 0 obj');
11893 $this->_current_obj_id = $obj_id; // for later use with encryption
11897 function _dounderline($x, $y, $txt, $OTLdata = false, $textvar = 0)
11899 // Now print line exactly where $y secifies - called from Text() and Cell() - adjust position there
11900 // WORD SPACING
11901 $w = ($this->GetStringWidth($txt, false, $OTLdata, $textvar) * Mpdf::SCALE) + ($this->charspacing * mb_strlen($txt, $this->mb_enc)) + ( $this->ws * mb_substr_count($txt, ' ', $this->mb_enc));
11902 // Draw a line
11903 return sprintf('%.3F %.3F m %.3F %.3F l S', $x * Mpdf::SCALE, ($this->h - $y) * Mpdf::SCALE, ($x * Mpdf::SCALE) + $w, ($this->h - $y) * Mpdf::SCALE);
11906 function getFileContentsByCurl($url, &$data)
11908 $this->logger->debug(sprintf('Fetching (cURL) content of remote URL "%s"', $url), ['context' => LogContext::REMOTE_CONTENT]);
11910 $ch = curl_init($url);
11912 curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:13.0) Gecko/20100101 Firefox/13.0.1'); // mPDF 5.7.4
11913 curl_setopt($ch, CURLOPT_HEADER, 0);
11914 curl_setopt($ch, CURLOPT_NOBODY, 0);
11915 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
11916 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->curlTimeout);
11918 if ($this->curlFollowLocation) {
11919 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
11922 if ($this->curlAllowUnsafeSslRequests) {
11923 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
11924 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
11927 $data = curl_exec($ch);
11928 curl_close($ch);
11931 function getFileContentsBySocket($url, &$data)
11933 $this->logger->debug(sprintf('Fetching (socket) content of remote URL "%s"', $url), ['context' => LogContext::REMOTE_CONTENT]);
11934 // mPDF 5.7.3
11936 $timeout = 1;
11937 $p = parse_url($url);
11938 $file = $p['path'];
11939 if ($p['scheme'] == 'https') {
11940 $prefix = 'ssl://';
11941 $port = ($p['port'] ? $p['port'] : 443);
11942 } else {
11943 $prefix = '';
11944 $port = ($p['port'] ? $p['port'] : 80);
11946 if ($p['query']) {
11947 $file .= '?' . $p['query'];
11949 if (!($fh = @fsockopen($prefix . $p['host'], $port, $errno, $errstr, $timeout))) {
11950 return false;
11953 $getstring = "GET " . $file . " HTTP/1.0 \r\n" .
11954 "Host: " . $p['host'] . " \r\n" .
11955 "Connection: close\r\n\r\n";
11957 fwrite($fh, $getstring);
11959 // Get rid of HTTP header
11960 $s = fgets($fh, 1024);
11961 if (!$s) {
11962 return false;
11964 while (!feof($fh)) {
11965 $s = fgets($fh, 1024);
11966 if ($s == "\r\n") {
11967 break;
11970 $data = '';
11972 while (!feof($fh)) {
11973 $data .= fgets($fh, 1024);
11976 fclose($fh);
11979 // ==============================================================
11980 // Moved outside WMF as also needed for SVG
11981 function _putformobjects()
11983 foreach ($this->formobjects as $file => $info) {
11985 $this->_newobj();
11987 $this->formobjects[$file]['n'] = $this->n;
11989 $this->_out('<</Type /XObject');
11990 $this->_out('/Subtype /Form');
11991 $this->_out('/Group ' . ($this->n + 1) . ' 0 R');
11992 $this->_out('/BBox [' . $info['x'] . ' ' . $info['y'] . ' ' . ($info['w'] + $info['x']) . ' ' . ($info['h'] + $info['y']) . ']');
11994 if ($this->compress) {
11995 $this->_out('/Filter /FlateDecode');
11998 $data = ($this->compress) ? gzcompress($info['data']) : $info['data'];
11999 $this->_out('/Length ' . strlen($data) . '>>');
12000 $this->_putstream($data);
12002 unset($this->formobjects[$file]['data']);
12004 $this->_out('endobj');
12006 // Required for SVG transparency (opacity) to work
12007 $this->_newobj();
12008 $this->_out('<</Type /Group');
12009 $this->_out('/S /Transparency');
12010 $this->_out('>>');
12011 $this->_out('endobj');
12015 function _freadint($f)
12017 $i = ord(fread($f, 1)) << 24;
12018 $i += ord(fread($f, 1)) << 16;
12019 $i += ord(fread($f, 1)) << 8;
12020 $i += ord(fread($f, 1));
12022 return $i;
12025 function _UTF16BEtextstring($s)
12027 $s = $this->UTF8ToUTF16BE($s, true);
12028 if ($this->encrypted) {
12029 $s = $this->protection->rc4($this->protection->objectKey($this->_current_obj_id), $s);
12032 return '(' . $this->_escape($s) . ')';
12035 function _textstring($s)
12037 if ($this->encrypted) {
12038 $s = $this->protection->rc4($this->protection->objectKey($this->_current_obj_id), $s);
12041 return '(' . $this->_escape($s) . ')';
12044 function _escape($s)
12046 return strtr($s, [')' => '\\)', '(' => '\\(', '\\' => '\\\\', chr(13) => '\r']);
12049 function _escapeName($s)
12051 return strtr($s, array('/' => '#2F'));
12054 function _putstream($s)
12056 if ($this->encrypted) {
12057 $s = $this->protection->rc4($this->protection->objectKey($this->_current_obj_id), $s);
12060 $this->_out('stream');
12061 $this->_out($s);
12062 $this->_out('endstream');
12065 function _out($s, $ln = true)
12067 if ($this->state == 2) {
12068 if ($this->bufferoutput) {
12069 $this->headerbuffer.= $s . "\n";
12070 } /* -- COLUMNS -- */ elseif (($this->ColActive) && !$this->processingHeader && !$this->processingFooter) {
12071 // Captures everything in buffer for columns; Almost everything is sent from fn. Cell() except:
12072 // Images sent from Image() or
12073 // later sent as _out($textto) in printbuffer
12074 // Line()
12075 if (preg_match('/q \d+\.\d\d+ 0 0 (\d+\.\d\d+) \d+\.\d\d+ \d+\.\d\d+ cm \/(I|FO)\d+ Do Q/', $s, $m)) { // Image data
12076 $h = ($m[1] / Mpdf::SCALE);
12077 // Update/overwrite the lowest bottom of printing y value for a column
12078 $this->ColDetails[$this->CurrCol]['bottom_margin'] = $this->y + $h;
12079 } /* -- TABLES -- */ elseif (preg_match('/\d+\.\d\d+ \d+\.\d\d+ \d+\.\d\d+ ([\-]{0,1}\d+\.\d\d+) re/', $s, $m) && $this->tableLevel > 0) { // Rect in table
12080 $h = ($m[1] / Mpdf::SCALE);
12081 // Update/overwrite the lowest bottom of printing y value for a column
12082 $this->ColDetails[$this->CurrCol]['bottom_margin'] = max($this->ColDetails[$this->CurrCol]['bottom_margin'], ($this->y + $h));
12083 } /* -- END TABLES -- */ else { // Td Text Set in Cell()
12084 if (isset($this->ColDetails[$this->CurrCol]['bottom_margin'])) {
12085 $h = $this->ColDetails[$this->CurrCol]['bottom_margin'] - $this->y;
12086 } else {
12087 $h = 0;
12090 if ($h < 0) {
12091 $h = -$h;
12093 $this->columnbuffer[] = [
12094 's' => $s, // Text string to output
12095 'col' => $this->CurrCol, // Column when printed
12096 'x' => $this->x, // x when printed
12097 'y' => $this->y, // this->y when printed (after column break)
12098 'h' => $h // actual y at bottom when printed = y+h
12100 } /* -- END COLUMNS -- */
12101 /* -- TABLES -- */ elseif ($this->table_rotate && !$this->processingHeader && !$this->processingFooter) {
12102 // Captures eveything in buffer for rotated tables;
12103 $this->tablebuffer .= $s . "\n";
12104 } /* -- END TABLES -- */ elseif ($this->kwt && !$this->processingHeader && !$this->processingFooter) {
12105 // Captures eveything in buffer for keep-with-table (h1-6);
12106 $this->kwt_buffer[] = [
12107 's' => $s, // Text string to output
12108 'x' => $this->x, // x when printed
12109 'y' => $this->y, // y when printed
12111 } elseif (($this->keep_block_together) && !$this->processingHeader && !$this->processingFooter) {
12112 // do nothing
12113 } else {
12114 $this->pages[$this->page] .= $s . ($ln == true ? "\n" : '');
12116 } else {
12117 $this->buffer .= $s . ($ln == true ? "\n" : '');
12121 /* -- WATERMARK -- */
12123 // add a watermark
12124 function watermark($texte, $angle = 45, $fontsize = 96, $alpha = 0.2)
12126 if ($this->PDFA || $this->PDFX) {
12127 throw new \Mpdf\MpdfException('PDFA and PDFX do not permit transparency, so mPDF does not allow Watermarks!');
12130 if (!$this->watermark_font) {
12131 $this->watermark_font = $this->default_font;
12134 $this->SetFont($this->watermark_font, "B", $fontsize, false); // Don't output
12135 $texte = $this->purify_utf8_text($texte);
12137 if ($this->text_input_as_HTML) {
12138 $texte = $this->all_entities_to_utf8($texte);
12141 if ($this->usingCoreFont) {
12142 $texte = mb_convert_encoding($texte, $this->mb_enc, 'UTF-8');
12145 // DIRECTIONALITY
12146 if (preg_match("/([" . $this->pregRTLchars . "])/u", $texte)) {
12147 $this->biDirectional = true;
12148 } // *OTL*
12150 $textvar = 0;
12151 $save_OTLtags = $this->OTLtags;
12152 $this->OTLtags = [];
12153 if ($this->useKerning) {
12154 if ($this->CurrentFont['haskernGPOS']) {
12155 $this->OTLtags['Plus'] .= ' kern';
12156 } else {
12157 $textvar = ($textvar | TextVars::FC_KERNING);
12161 /* -- OTL -- */
12162 // Use OTL OpenType Table Layout - GSUB & GPOS
12163 if (isset($this->CurrentFont['useOTL']) && $this->CurrentFont['useOTL']) {
12164 $texte = $this->otl->applyOTL($texte, $this->CurrentFont['useOTL']);
12165 $OTLdata = $this->otl->OTLdata;
12167 /* -- END OTL -- */
12168 $this->OTLtags = $save_OTLtags;
12170 $this->magic_reverse_dir($texte, $this->directionality, $OTLdata);
12172 $this->SetAlpha($alpha);
12174 $this->SetTColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
12176 $szfont = $fontsize;
12177 $loop = 0;
12178 $maxlen = (min($this->w, $this->h) ); // sets max length of text as 7/8 width/height of page
12180 while ($loop == 0) {
12181 $this->SetFont($this->watermark_font, "B", $szfont, false); // Don't output
12182 $offset = ((sin(deg2rad($angle))) * ($szfont / Mpdf::SCALE));
12184 $strlen = $this->GetStringWidth($texte, true, $OTLdata, $textvar);
12185 if ($strlen > $maxlen - $offset) {
12186 $szfont --;
12187 } else {
12188 $loop ++;
12192 $this->SetFont($this->watermark_font, "B", $szfont - 0.1, true, true); // Output The -0.1 is because SetFont above is not written to PDF
12194 // Repeating it will not output anything as mPDF thinks it is set
12195 $adj = ((cos(deg2rad($angle))) * ($strlen / 2));
12196 $opp = ((sin(deg2rad($angle))) * ($strlen / 2));
12198 $wx = ($this->w / 2) - $adj + $offset / 3;
12199 $wy = ($this->h / 2) + $opp;
12201 $this->Rotate($angle, $wx, $wy);
12202 $this->Text($wx, $wy, $texte, $OTLdata, $textvar);
12203 $this->Rotate(0);
12204 $this->SetTColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
12206 $this->SetAlpha(1);
12209 function watermarkImg($src, $alpha = 0.2)
12211 if ($this->PDFA || $this->PDFX) {
12212 throw new \Mpdf\MpdfException('PDFA and PDFX do not permit transparency, so mPDF does not allow Watermarks!');
12215 if ($this->watermarkImgBehind) {
12216 $this->watermarkImgAlpha = $this->SetAlpha($alpha, 'Normal', true);
12217 } else {
12218 $this->SetAlpha($alpha, $this->watermarkImgAlphaBlend);
12221 $this->Image($src, 0, 0, 0, 0, '', '', true, true, true);
12223 if (!$this->watermarkImgBehind) {
12224 $this->SetAlpha(1);
12228 /* -- END WATERMARK -- */
12230 function Rotate($angle, $x = -1, $y = -1)
12232 if ($x == -1) {
12233 $x = $this->x;
12235 if ($y == -1) {
12236 $y = $this->y;
12238 if ($this->angle != 0) {
12239 $this->_out('Q');
12241 $this->angle = $angle;
12242 if ($angle != 0) {
12243 $angle*=M_PI / 180;
12244 $c = cos($angle);
12245 $s = sin($angle);
12246 $cx = $x * Mpdf::SCALE;
12247 $cy = ($this->h - $y) * Mpdf::SCALE;
12248 $this->_out(sprintf('q %.5F %.5F %.5F %.5F %.3F %.3F cm 1 0 0 1 %.3F %.3F cm', $c, $s, -$s, $c, $cx, $cy, -$cx, -$cy));
12252 function CircularText($x, $y, $r, $text, $align = 'top', $fontfamily = '', $fontsize = 0, $fontstyle = '', $kerning = 120, $fontwidth = 100, $divider = '')
12254 if (empty($this->directWrite)) {
12255 $this->directWrite = new DirectWrite($this, $this->otl, $this->sizeConverter, $this->colorConverter);
12258 $this->directWrite->CircularText($x, $y, $r, $text, $align, $fontfamily, $fontsize, $fontstyle, $kerning, $fontwidth, $divider);
12261 // From Invoice
12262 function RoundedRect($x, $y, $w, $h, $r, $style = '')
12264 $hp = $this->h;
12266 if ($style == 'F') {
12267 $op = 'f';
12268 } elseif ($style == 'FD' or $style == 'DF') {
12269 $op = 'B';
12270 } else {
12271 $op = 'S';
12274 $MyArc = 4 / 3 * (sqrt(2) - 1);
12275 $this->_out(sprintf('%.3F %.3F m', ($x + $r) * Mpdf::SCALE, ($hp - $y) * Mpdf::SCALE));
12276 $xc = $x + $w - $r;
12277 $yc = $y + $r;
12278 $this->_out(sprintf('%.3F %.3F l', $xc * Mpdf::SCALE, ($hp - $y) * Mpdf::SCALE));
12280 $this->_Arc($xc + $r * $MyArc, $yc - $r, $xc + $r, $yc - $r * $MyArc, $xc + $r, $yc);
12281 $xc = $x + $w - $r;
12282 $yc = $y + $h - $r;
12283 $this->_out(sprintf('%.3F %.3F l', ($x + $w) * Mpdf::SCALE, ($hp - $yc) * Mpdf::SCALE));
12285 $this->_Arc($xc + $r, $yc + $r * $MyArc, $xc + $r * $MyArc, $yc + $r, $xc, $yc + $r);
12286 $xc = $x + $r;
12287 $yc = $y + $h - $r;
12288 $this->_out(sprintf('%.3F %.3F l', $xc * Mpdf::SCALE, ($hp - ($y + $h)) * Mpdf::SCALE));
12290 $this->_Arc($xc - $r * $MyArc, $yc + $r, $xc - $r, $yc + $r * $MyArc, $xc - $r, $yc);
12291 $xc = $x + $r;
12292 $yc = $y + $r;
12293 $this->_out(sprintf('%.3F %.3F l', ($x) * Mpdf::SCALE, ($hp - $yc) * Mpdf::SCALE));
12295 $this->_Arc($xc - $r, $yc - $r * $MyArc, $xc - $r * $MyArc, $yc - $r, $xc, $yc - $r);
12296 $this->_out($op);
12299 function _Arc($x1, $y1, $x2, $y2, $x3, $y3)
12301 $h = $this->h;
12302 $this->_out(sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', $x1 * Mpdf::SCALE, ($h - $y1) * Mpdf::SCALE, $x2 * Mpdf::SCALE, ($h - $y2) * Mpdf::SCALE, $x3 * Mpdf::SCALE, ($h - $y3) * Mpdf::SCALE));
12305 // ====================================================
12309 /* -- DIRECTW -- */
12310 function Shaded_box($text, $font = '', $fontstyle = 'B', $szfont = '', $width = '70%', $style = 'DF', $radius = 2.5, $fill = '#FFFFFF', $color = '#000000', $pad = 2)
12312 // F (shading - no line),S (line, no shading),DF (both)
12313 if (empty($this->directWrite)) {
12314 $this->directWrite = new DirectWrite($this, $this->otl, $this->sizeConverter, $this->colorConverter);
12316 $this->directWrite->Shaded_box($text, $font, $fontstyle, $szfont, $width, $style, $radius, $fill, $color, $pad);
12319 /* -- END DIRECTW -- */
12321 function UTF8StringToArray($str, $addSubset = true)
12323 $out = [];
12324 $len = strlen($str);
12325 for ($i = 0; $i < $len; $i++) {
12326 $uni = -1;
12327 $h = ord($str[$i]);
12328 if ($h <= 0x7F) {
12329 $uni = $h;
12330 } elseif ($h >= 0xC2) {
12331 if (($h <= 0xDF) && ($i < $len - 1)) {
12332 $uni = ($h & 0x1F) << 6 | (ord($str[++$i]) & 0x3F);
12333 } elseif (($h <= 0xEF) && ($i < $len - 2)) {
12334 $uni = ($h & 0x0F) << 12 | (ord($str[++$i]) & 0x3F) << 6 | (ord($str[++$i]) & 0x3F);
12335 } elseif (($h <= 0xF4) && ($i < $len - 3)) {
12336 $uni = ($h & 0x0F) << 18 | (ord($str[++$i]) & 0x3F) << 12 | (ord($str[++$i]) & 0x3F) << 6 | (ord($str[++$i]) & 0x3F);
12339 if ($uni >= 0) {
12340 $out[] = $uni;
12341 if ($addSubset && isset($this->CurrentFont['subset'])) {
12342 $this->CurrentFont['subset'][$uni] = $uni;
12346 return $out;
12349 // Convert utf-8 string to <HHHHHH> for Font Subsets
12350 function UTF8toSubset($str)
12352 $ret = '<';
12353 // $str = preg_replace('/'.preg_quote($this->aliasNbPg,'/').'/', chr(7), $str ); // mPDF 6 deleted
12354 // $str = preg_replace('/'.preg_quote($this->aliasNbPgGp,'/').'/', chr(8), $str ); // mPDF 6 deleted
12355 $unicode = $this->UTF8StringToArray($str);
12356 $orig_fid = $this->CurrentFont['subsetfontids'][0];
12357 $last_fid = $this->CurrentFont['subsetfontids'][0];
12358 foreach ($unicode as $c) {
12359 /* // mPDF 6 deleted
12360 if ($c == 7 || $c == 8) {
12361 if ($orig_fid != $last_fid) {
12362 $ret .= '> Tj /F'.$orig_fid.' '.$this->FontSizePt.' Tf <';
12363 $last_fid = $orig_fid;
12365 if ($c == 7) { $ret .= $this->aliasNbPgHex; }
12366 else { $ret .= $this->aliasNbPgGpHex; }
12367 continue;
12370 if (!$this->_charDefined($this->CurrentFont['cw'], $c)) {
12371 $c = 0;
12372 } // mPDF 6
12373 for ($i = 0; $i < 99; $i++) {
12374 // return c as decimal char
12375 $init = array_search($c, $this->CurrentFont['subsets'][$i]);
12376 if ($init !== false) {
12377 if ($this->CurrentFont['subsetfontids'][$i] != $last_fid) {
12378 $ret .= '> Tj /F' . $this->CurrentFont['subsetfontids'][$i] . ' ' . $this->FontSizePt . ' Tf <';
12379 $last_fid = $this->CurrentFont['subsetfontids'][$i];
12381 $ret .= sprintf("%02s", strtoupper(dechex($init)));
12382 break;
12383 } // TrueType embedded SUBSETS
12384 elseif (count($this->CurrentFont['subsets'][$i]) < 255) {
12385 $n = count($this->CurrentFont['subsets'][$i]);
12386 $this->CurrentFont['subsets'][$i][$n] = $c;
12387 if ($this->CurrentFont['subsetfontids'][$i] != $last_fid) {
12388 $ret .= '> Tj /F' . $this->CurrentFont['subsetfontids'][$i] . ' ' . $this->FontSizePt . ' Tf <';
12389 $last_fid = $this->CurrentFont['subsetfontids'][$i];
12391 $ret .= sprintf("%02s", strtoupper(dechex($n)));
12392 break;
12393 } elseif (!isset($this->CurrentFont['subsets'][($i + 1)])) {
12394 // TrueType embedded SUBSETS
12395 $this->CurrentFont['subsets'][($i + 1)] = [0 => 0];
12396 $new_fid = count($this->fonts) + $this->extraFontSubsets + 1;
12397 $this->CurrentFont['subsetfontids'][($i + 1)] = $new_fid;
12398 $this->extraFontSubsets++;
12402 $ret .= '>';
12403 if ($last_fid != $orig_fid) {
12404 $ret .= ' Tj /F' . $orig_fid . ' ' . $this->FontSizePt . ' Tf <> ';
12406 return $ret;
12409 // Converts UTF-8 strings to UTF16-BE.
12410 function UTF8ToUTF16BE($str, $setbom = true)
12412 if ($this->checkSIP && preg_match("/([\x{20000}-\x{2FFFF}])/u", $str)) {
12413 if (!in_array($this->currentfontfamily, ['gb', 'big5', 'sjis', 'uhc', 'gbB', 'big5B', 'sjisB', 'uhcB', 'gbI', 'big5I', 'sjisI', 'uhcI',
12414 'gbBI', 'big5BI', 'sjisBI', 'uhcBI'])) {
12415 $str = preg_replace("/[\x{20000}-\x{2FFFF}]/u", chr(0), $str);
12418 if ($this->checkSMP && preg_match("/([\x{10000}-\x{1FFFF}])/u", $str)) {
12419 $str = preg_replace("/[\x{10000}-\x{1FFFF}]/u", chr(0), $str);
12421 $outstr = ""; // string to be returned
12422 if ($setbom) {
12423 $outstr .= "\xFE\xFF"; // Byte Order Mark (BOM)
12425 $outstr .= mb_convert_encoding($str, 'UTF-16BE', 'UTF-8');
12426 return $outstr;
12429 /* -- CJK-FONTS -- */
12431 // from class PDF_Chinese CJK EXTENSIONS
12432 function AddCIDFont($family, $style, $name, &$cw, $CMap, $registry, $desc)
12434 $fontkey = strtolower($family) . strtoupper($style);
12435 if (isset($this->fonts[$fontkey])) {
12436 throw new \Mpdf\MpdfException("Font already added: $family $style");
12438 $i = count($this->fonts) + $this->extraFontSubsets + 1;
12439 $name = str_replace(' ', '', $name);
12440 if ($family == 'sjis') {
12441 $up = -120;
12442 } else {
12443 $up = -130;
12445 // ? 'up' and 'ut' do not seem to be referenced anywhere
12446 $this->fonts[$fontkey] = ['i' => $i, 'type' => 'Type0', 'name' => $name, 'up' => $up, 'ut' => 40, 'cw' => $cw, 'CMap' => $CMap, 'registry' => $registry, 'MissingWidth' => 1000, 'desc' => $desc];
12449 function AddCJKFont($family)
12452 if ($this->PDFA || $this->PDFX) {
12453 throw new \Mpdf\MpdfException("Adobe CJK fonts cannot be embedded in mPDF (required for PDFA1-b and PDFX/1-a).");
12455 if ($family == 'big5') {
12456 $this->AddBig5Font();
12457 } elseif ($family == 'gb') {
12458 $this->AddGBFont();
12459 } elseif ($family == 'sjis') {
12460 $this->AddSJISFont();
12461 } elseif ($family == 'uhc') {
12462 $this->AddUHCFont();
12466 function AddBig5Font()
12468 // Add Big5 font with proportional Latin
12469 $family = 'big5';
12470 $name = 'MSungStd-Light-Acro';
12471 $cw = $this->Big5_widths;
12472 $CMap = 'UniCNS-UTF16-H';
12473 $registry = ['ordering' => 'CNS1', 'supplement' => 4];
12474 $desc = [
12475 'Ascent' => 880,
12476 'Descent' => -120,
12477 'CapHeight' => 880,
12478 'Flags' => 6,
12479 'FontBBox' => '[-160 -249 1015 1071]',
12480 'ItalicAngle' => 0,
12481 'StemV' => 93,
12483 $this->AddCIDFont($family, '', $name, $cw, $CMap, $registry, $desc);
12484 $this->AddCIDFont($family, 'B', $name . ',Bold', $cw, $CMap, $registry, $desc);
12485 $this->AddCIDFont($family, 'I', $name . ',Italic', $cw, $CMap, $registry, $desc);
12486 $this->AddCIDFont($family, 'BI', $name . ',BoldItalic', $cw, $CMap, $registry, $desc);
12489 function AddGBFont()
12491 // Add GB font with proportional Latin
12492 $family = 'gb';
12493 $name = 'STSongStd-Light-Acro';
12494 $cw = $this->GB_widths;
12495 $CMap = 'UniGB-UTF16-H';
12496 $registry = ['ordering' => 'GB1', 'supplement' => 4];
12497 $desc = [
12498 'Ascent' => 880,
12499 'Descent' => -120,
12500 'CapHeight' => 737,
12501 'Flags' => 6,
12502 'FontBBox' => '[-25 -254 1000 880]',
12503 'ItalicAngle' => 0,
12504 'StemV' => 58,
12505 'Style' => '<< /Panose <000000000400000000000000> >>',
12507 $this->AddCIDFont($family, '', $name, $cw, $CMap, $registry, $desc);
12508 $this->AddCIDFont($family, 'B', $name . ',Bold', $cw, $CMap, $registry, $desc);
12509 $this->AddCIDFont($family, 'I', $name . ',Italic', $cw, $CMap, $registry, $desc);
12510 $this->AddCIDFont($family, 'BI', $name . ',BoldItalic', $cw, $CMap, $registry, $desc);
12513 function AddSJISFont()
12515 // Add SJIS font with proportional Latin
12516 $family = 'sjis';
12517 $name = 'KozMinPro-Regular-Acro';
12518 $cw = $this->SJIS_widths;
12519 $CMap = 'UniJIS-UTF16-H';
12520 $registry = ['ordering' => 'Japan1', 'supplement' => 5];
12521 $desc = [
12522 'Ascent' => 880,
12523 'Descent' => -120,
12524 'CapHeight' => 740,
12525 'Flags' => 6,
12526 'FontBBox' => '[-195 -272 1110 1075]',
12527 'ItalicAngle' => 0,
12528 'StemV' => 86,
12529 'XHeight' => 502,
12531 $this->AddCIDFont($family, '', $name, $cw, $CMap, $registry, $desc);
12532 $this->AddCIDFont($family, 'B', $name . ',Bold', $cw, $CMap, $registry, $desc);
12533 $this->AddCIDFont($family, 'I', $name . ',Italic', $cw, $CMap, $registry, $desc);
12534 $this->AddCIDFont($family, 'BI', $name . ',BoldItalic', $cw, $CMap, $registry, $desc);
12537 function AddUHCFont()
12539 // Add UHC font with proportional Latin
12540 $family = 'uhc';
12541 $name = 'HYSMyeongJoStd-Medium-Acro';
12542 $cw = $this->UHC_widths;
12543 $CMap = 'UniKS-UTF16-H';
12544 $registry = ['ordering' => 'Korea1', 'supplement' => 2];
12545 $desc = [
12546 'Ascent' => 880,
12547 'Descent' => -120,
12548 'CapHeight' => 720,
12549 'Flags' => 6,
12550 'FontBBox' => '[-28 -148 1001 880]',
12551 'ItalicAngle' => 0,
12552 'StemV' => 60,
12553 'Style' => '<< /Panose <000000000600000000000000> >>',
12555 $this->AddCIDFont($family, '', $name, $cw, $CMap, $registry, $desc);
12556 $this->AddCIDFont($family, 'B', $name . ',Bold', $cw, $CMap, $registry, $desc);
12557 $this->AddCIDFont($family, 'I', $name . ',Italic', $cw, $CMap, $registry, $desc);
12558 $this->AddCIDFont($family, 'BI', $name . ',BoldItalic', $cw, $CMap, $registry, $desc);
12561 /* -- END CJK-FONTS -- */
12563 //////////////////////////////////////////////////////////////////////////////
12564 //////////////////////////////////////////////////////////////////////////////
12565 //////////////////////////////////////////////////////////////////////////////
12566 //////////////////////////////////////////////////////////////////////////////
12567 //////////////////////////////////////////////////////////////////////////////
12568 //////////////////////////////////////////////////////////////////////////////
12569 //////////////////////////////////////////////////////////////////////////////
12571 function SetDefaultFont($font)
12573 // Disallow embedded fonts to be used as defaults in PDFA
12574 if ($this->PDFA || $this->PDFX) {
12575 if (strtolower($font) == 'ctimes') {
12576 $font = 'serif';
12578 if (strtolower($font) == 'ccourier') {
12579 $font = 'monospace';
12581 if (strtolower($font) == 'chelvetica') {
12582 $font = 'sans-serif';
12585 $font = $this->SetFont($font); // returns substituted font if necessary
12586 $this->default_font = $font;
12587 $this->original_default_font = $font;
12588 if (!$this->watermark_font) {
12589 $this->watermark_font = $font;
12590 } // *WATERMARK*
12591 $this->defaultCSS['BODY']['FONT-FAMILY'] = $font;
12592 $this->cssManager->CSS['BODY']['FONT-FAMILY'] = $font;
12595 function SetDefaultFontSize($fontsize)
12597 $this->default_font_size = $fontsize;
12598 $this->original_default_font_size = $fontsize;
12599 $this->SetFontSize($fontsize);
12600 $this->defaultCSS['BODY']['FONT-SIZE'] = $fontsize . 'pt';
12601 $this->cssManager->CSS['BODY']['FONT-SIZE'] = $fontsize . 'pt';
12604 function SetDefaultBodyCSS($prop, $val)
12606 if ($prop) {
12607 $this->defaultCSS['BODY'][strtoupper($prop)] = $val;
12608 $this->cssManager->CSS['BODY'][strtoupper($prop)] = $val;
12612 function SetDirectionality($dir = 'ltr')
12614 /* -- OTL -- */
12615 if (strtolower($dir) == 'rtl') {
12616 if ($this->directionality != 'rtl') {
12617 // Swop L/R Margins so page 1 RTL is an 'even' page
12618 $tmp = $this->DeflMargin;
12619 $this->DeflMargin = $this->DefrMargin;
12620 $this->DefrMargin = $tmp;
12621 $this->orig_lMargin = $this->DeflMargin;
12622 $this->orig_rMargin = $this->DefrMargin;
12624 $this->SetMargins($this->DeflMargin, $this->DefrMargin, $this->tMargin);
12626 $this->directionality = 'rtl';
12627 $this->defaultAlign = 'R';
12628 $this->defaultTableAlign = 'R';
12629 } else {
12630 /* -- END OTL -- */
12631 $this->directionality = 'ltr';
12632 $this->defaultAlign = 'L';
12633 $this->defaultTableAlign = 'L';
12634 } // *OTL*
12635 $this->cssManager->CSS['BODY']['DIRECTION'] = $this->directionality;
12638 // Return either a number (factor) - based on current set fontsize (if % or em) - or exact lineheight (with 'mm' after it)
12639 function fixLineheight($v)
12641 $lh = false;
12642 if (preg_match('/^[0-9\.,]*$/', $v) && $v >= 0) {
12643 return ($v + 0);
12644 } elseif (strtoupper($v) == 'NORMAL' || $v == 'N') {
12645 return 'N'; // mPDF 6
12646 } else {
12647 $tlh = $this->sizeConverter->convert($v, $this->FontSize, $this->FontSize, true);
12648 if ($tlh) {
12649 return ($tlh . 'mm');
12652 return $this->normalLineheight;
12655 function _getNormalLineheight($desc = false)
12657 if (!$desc) {
12658 $desc = $this->CurrentFont['desc'];
12660 if (!isset($desc['Leading'])) {
12661 $desc['Leading'] = 0;
12663 if ($this->useFixedNormalLineHeight) {
12664 $lh = $this->normalLineheight;
12665 } elseif (isset($desc['Ascent']) && $desc['Ascent']) {
12666 $lh = ($this->adjustFontDescLineheight * ($desc['Ascent'] - $desc['Descent'] + $desc['Leading']) / 1000);
12667 } else {
12668 $lh = $this->normalLineheight;
12670 return $lh;
12673 // Set a (fixed) lineheight to an actual value - either to named fontsize(pts) or default
12674 function SetLineHeight($FontPt = '', $lh = '')
12676 if (!$FontPt) {
12677 $FontPt = $this->FontSizePt;
12679 $fs = $FontPt / Mpdf::SCALE;
12680 $this->lineheight = $this->_computeLineheight($lh, $fs);
12683 function _computeLineheight($lh, $fs = '')
12685 if ($this->shrin_k > 1) {
12686 $k = $this->shrin_k;
12687 } else {
12688 $k = 1;
12690 if (!$fs) {
12691 $fs = $this->FontSize;
12693 if ($lh == 'N') {
12694 $lh = $this->_getNormalLineheight();
12696 if (preg_match('/mm/', $lh)) {
12697 return (((float) $lh) / $k); // convert to number
12698 } elseif ($lh > 0) {
12699 return ($fs * $lh);
12701 return ($fs * $this->normalLineheight);
12704 function _setLineYpos(&$fontsize, &$fontdesc, &$CSSlineheight, $blockYpos = false)
12706 $ypos['glyphYorigin'] = 0;
12707 $ypos['baseline-shift'] = 0;
12708 $linegap = 0;
12709 $leading = 0;
12711 if (isset($fontdesc['Ascent']) && $fontdesc['Ascent'] && !$this->useFixedTextBaseline) {
12712 // Fontsize uses font metrics - this method seems to produce results compatible with browsers (except IE9)
12713 $ypos['boxtop'] = $fontdesc['Ascent'] / 1000 * $fontsize;
12714 $ypos['boxbottom'] = $fontdesc['Descent'] / 1000 * $fontsize;
12715 if (isset($fontdesc['Leading'])) {
12716 $linegap = $fontdesc['Leading'] / 1000 * $fontsize;
12718 } // Default if not set - uses baselineC
12719 else {
12720 $ypos['boxtop'] = (0.5 + $this->baselineC) * $fontsize;
12721 $ypos['boxbottom'] = -(0.5 - $this->baselineC) * $fontsize;
12723 $fontheight = $ypos['boxtop'] - $ypos['boxbottom'];
12725 if ($this->shrin_k > 1) {
12726 $shrin_k = $this->shrin_k;
12727 } else {
12728 $shrin_k = 1;
12731 $leading = 0;
12732 if ($CSSlineheight == 'N') {
12733 $lh = $this->_getNormalLineheight($fontdesc);
12734 $lineheight = ($fontsize * $lh);
12735 $leading += $linegap; // specified in hhea or sTypo in OpenType tables
12736 } elseif (preg_match('/mm/', $CSSlineheight)) {
12737 $lineheight = (((float) $CSSlineheight) / $shrin_k); // convert to number
12738 } // ??? If lineheight is a factor e.g. 1.3 ?? use factor x 1em or ? use 'normal' lineheight * factor
12739 // Could depend on value for $text_height - a draft CSS value as set above for now
12740 elseif ($CSSlineheight > 0) {
12741 $lineheight = ($fontsize * $CSSlineheight);
12742 } else {
12743 $lineheight = ($fontsize * $this->normalLineheight);
12746 // In general, calculate the "leading" - the difference between the fontheight and the lineheight
12747 // and add half to the top and half to the bottom. BUT
12748 // If an inline element has a font-size less than the block element, and the line-height is set as an em or % value
12749 // it will add too much leading below the font and expand the height of the line - so just use the block element exttop/extbottom:
12750 if (preg_match('/mm/', $CSSlineheight) && $ypos['boxtop'] < $blockYpos['boxtop'] && $ypos['boxbottom'] > $blockYpos['boxbottom']) {
12751 $ypos['exttop'] = $blockYpos['exttop'];
12752 $ypos['extbottom'] = $blockYpos['extbottom'];
12753 } else {
12754 $leading += ($lineheight - $fontheight);
12756 $ypos['exttop'] = $ypos['boxtop'] + $leading / 2;
12757 $ypos['extbottom'] = $ypos['boxbottom'] - $leading / 2;
12761 // TEMP ONLY FOR DEBUGGING *********************************
12762 // $ypos['lineheight'] = $lineheight;
12763 // $ypos['fontheight'] = $fontheight;
12764 // $ypos['leading'] = $leading;
12766 return $ypos;
12769 /* Called from WriteFlowingBlock() and finishFlowingBlock()
12770 Determines the line hieght and glyph/writing position
12771 for each element in the line to be written */
12773 function _setInlineBlockHeights(&$lineBox, &$stackHeight, &$content, &$font, $is_table)
12775 if ($this->shrin_k > 1) {
12776 $shrin_k = $this->shrin_k;
12777 } else {
12778 $shrin_k = 1;
12781 $ypos = [];
12782 $bordypos = [];
12783 $bgypos = [];
12785 if ($is_table) {
12786 // FOR TABLE
12787 $fontsize = $this->FontSize;
12788 $fontkey = $this->FontFamily . $this->FontStyle;
12789 $fontdesc = $this->fonts[$fontkey]['desc'];
12790 $CSSlineheight = $this->cellLineHeight;
12791 $line_stacking_strategy = $this->cellLineStackingStrategy; // inline-line-height [default] | block-line-height | max-height | grid-height
12792 $line_stacking_shift = $this->cellLineStackingShift; // consider-shifts [default] | disregard-shifts
12793 } else {
12794 // FOR BLOCK FONT
12795 $fontsize = $this->blk[$this->blklvl]['InlineProperties']['size'];
12796 $fontkey = $this->blk[$this->blklvl]['InlineProperties']['family'] . $this->blk[$this->blklvl]['InlineProperties']['style'];
12797 $fontdesc = $this->fonts[$fontkey]['desc'];
12798 $CSSlineheight = $this->blk[$this->blklvl]['line_height'];
12799 // inline-line-height | block-line-height | max-height | grid-height
12800 $line_stacking_strategy = (isset($this->blk[$this->blklvl]['line_stacking_strategy']) ? $this->blk[$this->blklvl]['line_stacking_strategy'] : 'inline-line-height');
12801 // consider-shifts | disregard-shifts
12802 $line_stacking_shift = (isset($this->blk[$this->blklvl]['line_stacking_shift']) ? $this->blk[$this->blklvl]['line_stacking_shift'] : 'consider-shifts');
12804 $boxLineHeight = $this->_computeLineheight($CSSlineheight, $fontsize);
12807 // First, set a "strut" using block font at index $lineBox[-1]
12808 $ypos[-1] = $this->_setLineYpos($fontsize, $fontdesc, $CSSlineheight);
12810 // for the block element - always taking the block EXTENDED progression including leading - which may be negative
12811 if ($line_stacking_strategy == 'block-line-height') {
12812 $topy = $ypos[-1]['exttop'];
12813 $bottomy = $ypos[-1]['extbottom'];
12814 } else {
12815 $topy = 0;
12816 $bottomy = 0;
12819 // Get text-middle for aligning images/objects
12820 $midpoint = $ypos[-1]['boxtop'] - (($ypos[-1]['boxtop'] - $ypos[-1]['boxbottom']) / 2);
12822 // for images / inline objects / replaced elements
12823 $mta = 0; // Maximum top-aligned
12824 $mba = 0; // Maximum bottom-aligned
12825 foreach ($content as $k => $chunk) {
12826 if (isset($this->objectbuffer[$k]) && $this->objectbuffer[$k]['type'] == 'listmarker') {
12827 $ypos[$k] = $ypos[-1];
12828 // UPDATE Maximums
12829 if ($line_stacking_strategy == 'block-line-height' || $line_stacking_strategy == 'grid-height' || $line_stacking_strategy == 'max-height') { // don't include extended block progression of all inline elements
12830 if ($ypos[$k]['boxtop'] > $topy) {
12831 $topy = $ypos[$k]['boxtop'];
12833 if ($ypos[$k]['boxbottom'] < $bottomy) {
12834 $bottomy = $ypos[$k]['boxbottom'];
12836 } else {
12837 if ($ypos[$k]['exttop'] > $topy) {
12838 $topy = $ypos[$k]['exttop'];
12840 if ($ypos[$k]['extbottom'] < $bottomy) {
12841 $bottomy = $ypos[$k]['extbottom'];
12844 } elseif (isset($this->objectbuffer[$k]) && $this->objectbuffer[$k]['type'] == 'dottab') { // mPDF 6 DOTTAB
12845 $fontsize = $font[$k]['size'];
12846 $fontdesc = $font[$k]['curr']['desc'];
12847 $lh = 1;
12848 $ypos[$k] = $this->_setLineYpos($fontsize, $fontdesc, $lh, $ypos[-1]); // Lineheight=1 fixed
12849 } elseif (isset($this->objectbuffer[$k])) {
12850 $oh = $this->objectbuffer[$k]['OUTER-HEIGHT'];
12851 $va = $this->objectbuffer[$k]['vertical-align'];
12853 if ($va == 'BS') { // (BASELINE default)
12854 if ($oh > $topy) {
12855 $topy = $oh;
12857 } elseif ($va == 'M') {
12858 if (($midpoint + $oh / 2) > $topy) {
12859 $topy = $midpoint + $oh / 2;
12861 if (($midpoint - $oh / 2) < $bottomy) {
12862 $bottomy = $midpoint - $oh / 2;
12864 } elseif ($va == 'TT') {
12865 if (($ypos[-1]['boxtop'] - $oh) < $bottomy) {
12866 $bottomy = $ypos[-1]['boxtop'] - $oh;
12867 $topy = max($topy, $ypos[-1]['boxtop']);
12869 } elseif ($va == 'TB') {
12870 if (($ypos[-1]['boxbottom'] + $oh) > $topy) {
12871 $topy = $ypos[-1]['boxbottom'] + $oh;
12872 $bottomy = min($bottomy, $ypos[-1]['boxbottom']);
12874 } elseif ($va == 'T') {
12875 if ($oh > $mta) {
12876 $mta = $oh;
12878 } elseif ($va == 'B') {
12879 if ($oh > $mba) {
12880 $mba = $oh;
12883 } elseif ($content[$k] || $content[$k] === '0') {
12884 // FOR FLOWING BLOCK
12885 $fontsize = $font[$k]['size'];
12886 $fontdesc = $font[$k]['curr']['desc'];
12887 // In future could set CSS line-height from inline elements; for now, use block level:
12888 $ypos[$k] = $this->_setLineYpos($fontsize, $fontdesc, $CSSlineheight, $ypos[-1]);
12890 if (isset($font[$k]['textparam']['text-baseline']) && $font[$k]['textparam']['text-baseline'] != 0) {
12891 $ypos[$k]['baseline-shift'] = $font[$k]['textparam']['text-baseline'];
12894 // DO ALIGNMENT FOR BASELINES *******************
12895 // Until most fonts have OpenType BASE tables, this won't work
12896 // $ypos[$k] compared to $ypos[-1] or $ypos[$k-1] using $dominant_baseline and $baseline_table
12897 // UPDATE Maximums
12898 if ($line_stacking_strategy == 'block-line-height' || $line_stacking_strategy == 'grid-height' || $line_stacking_strategy == 'max-height') { // don't include extended block progression of all inline elements
12899 if ($line_stacking_shift == 'disregard-shifts') {
12900 if ($ypos[$k]['boxtop'] > $topy) {
12901 $topy = $ypos[$k]['boxtop'];
12903 if ($ypos[$k]['boxbottom'] < $bottomy) {
12904 $bottomy = $ypos[$k]['boxbottom'];
12906 } else {
12907 if (($ypos[$k]['boxtop'] + $ypos[$k]['baseline-shift']) > $topy) {
12908 $topy = $ypos[$k]['boxtop'] + $ypos[$k]['baseline-shift'];
12910 if (($ypos[$k]['boxbottom'] + $ypos[$k]['baseline-shift']) < $bottomy) {
12911 $bottomy = $ypos[$k]['boxbottom'] + $ypos[$k]['baseline-shift'];
12914 } else {
12915 if ($line_stacking_shift == 'disregard-shifts') {
12916 if ($ypos[$k]['exttop'] > $topy) {
12917 $topy = $ypos[$k]['exttop'];
12919 if ($ypos[$k]['extbottom'] < $bottomy) {
12920 $bottomy = $ypos[$k]['extbottom'];
12922 } else {
12923 if (($ypos[$k]['exttop'] + $ypos[$k]['baseline-shift']) > $topy) {
12924 $topy = $ypos[$k]['exttop'] + $ypos[$k]['baseline-shift'];
12926 if (($ypos[$k]['extbottom'] + $ypos[$k]['baseline-shift']) < $bottomy) {
12927 $bottomy = $ypos[$k]['extbottom'] + $ypos[$k]['baseline-shift'];
12932 // If BORDER set on inline element
12933 if (isset($font[$k]['bord']) && $font[$k]['bord']) {
12934 $bordfontsize = $font[$k]['textparam']['bord-decoration']['fontsize'] / $shrin_k;
12935 $bordfontkey = $font[$k]['textparam']['bord-decoration']['fontkey'];
12936 if ($bordfontkey != $fontkey || $bordfontsize != $fontsize || isset($font[$k]['textparam']['bord-decoration']['baseline'])) {
12937 $bordfontdesc = $this->fonts[$bordfontkey]['desc'];
12938 $bordypos[$k] = $this->_setLineYpos($bordfontsize, $bordfontdesc, $CSSlineheight, $ypos[-1]);
12939 if (isset($font[$k]['textparam']['bord-decoration']['baseline']) && $font[$k]['textparam']['bord-decoration']['baseline'] != 0) {
12940 $bordypos[$k]['baseline-shift'] = $font[$k]['textparam']['bord-decoration']['baseline'] / $shrin_k;
12944 // If BACKGROUND set on inline element
12945 if (isset($font[$k]['spanbgcolor']) && $font[$k]['spanbgcolor']) {
12946 $bgfontsize = $font[$k]['textparam']['bg-decoration']['fontsize'] / $shrin_k;
12947 $bgfontkey = $font[$k]['textparam']['bg-decoration']['fontkey'];
12948 if ($bgfontkey != $fontkey || $bgfontsize != $fontsize || isset($font[$k]['textparam']['bg-decoration']['baseline'])) {
12949 $bgfontdesc = $this->fonts[$bgfontkey]['desc'];
12950 $bgypos[$k] = $this->_setLineYpos($bgfontsize, $bgfontdesc, $CSSlineheight, $ypos[-1]);
12951 if (isset($font[$k]['textparam']['bg-decoration']['baseline']) && $font[$k]['textparam']['bg-decoration']['baseline'] != 0) {
12952 $bgypos[$k]['baseline-shift'] = $font[$k]['textparam']['bg-decoration']['baseline'] / $shrin_k;
12960 // TOP or BOTTOM aligned images
12961 if ($mta > ($topy - $bottomy)) {
12962 if (($topy - $mta) < $bottomy) {
12963 $bottomy = $topy - $mta;
12966 if ($mba > ($topy - $bottomy)) {
12967 if (($bottomy + $mba) > $topy) {
12968 $topy = $bottomy + $mba;
12972 if ($line_stacking_strategy == 'block-line-height') { // fixed height set by block element (whether present or not)
12973 $topy = $ypos[-1]['exttop'];
12974 $bottomy = $ypos[-1]['extbottom'];
12977 $inclusiveHeight = $topy - $bottomy;
12979 // SET $stackHeight taking note of line_stacking_strategy
12980 // NB inclusive height already takes account of need to consider block progression height (excludes leading set by lineheight)
12981 // or extended block progression height (includes leading set by lineheight)
12982 if ($line_stacking_strategy == 'block-line-height') { // fixed = extended block progression height of block element
12983 $stackHeight = $boxLineHeight;
12984 } elseif ($line_stacking_strategy == 'max-height') { // smallest height which includes extended block progression height of block element
12985 // and block progression heights of inline elements (NOT extended)
12986 $stackHeight = $inclusiveHeight;
12987 } elseif ($line_stacking_strategy == 'grid-height') { // smallest multiple of block element lineheight to include
12988 // block progression heights of inline elements (NOT extended)
12989 $stackHeight = $boxLineHeight;
12990 while ($stackHeight < $inclusiveHeight) {
12991 $stackHeight += $boxLineHeight;
12993 } else { // 'inline-line-height' = default // smallest height which includes extended block progression height of block element
12994 // AND extended block progression heights of inline elements
12995 $stackHeight = $inclusiveHeight;
12998 $diff = $stackHeight - $inclusiveHeight;
12999 $topy += $diff / 2;
13000 $bottomy -= $diff / 2;
13002 // ADJUST $ypos => lineBox using $stackHeight; lineBox are all offsets from the top of stackHeight in mm
13003 // and SET IMAGE OFFSETS
13004 $lineBox[-1]['boxtop'] = $topy - $ypos[-1]['boxtop'];
13005 $lineBox[-1]['boxbottom'] = $topy - $ypos[-1]['boxbottom'];
13006 // $lineBox[-1]['exttop'] = $topy - $ypos[-1]['exttop'];
13007 // $lineBox[-1]['extbottom'] = $topy - $ypos[-1]['extbottom'];
13008 $lineBox[-1]['glyphYorigin'] = $topy - $ypos[-1]['glyphYorigin'];
13009 $lineBox[-1]['baseline-shift'] = $ypos[-1]['baseline-shift'];
13011 $midpoint = $lineBox[-1]['boxbottom'] - (($lineBox[-1]['boxbottom'] - $lineBox[-1]['boxtop']) / 2);
13013 foreach ($content as $k => $chunk) {
13014 if (isset($this->objectbuffer[$k])) {
13015 $oh = $this->objectbuffer[$k]['OUTER-HEIGHT'];
13016 // LIST MARKERS
13017 if ($this->objectbuffer[$k]['type'] == 'listmarker') {
13018 $oh = $fontsize;
13019 } elseif ($this->objectbuffer[$k]['type'] == 'dottab') { // mPDF 6 DOTTAB
13020 $oh = $font[$k]['size']; // == $this->objectbuffer[$k]['fontsize']/Mpdf::SCALE;
13021 $lineBox[$k]['boxtop'] = $topy - $ypos[$k]['boxtop'];
13022 $lineBox[$k]['boxbottom'] = $topy - $ypos[$k]['boxbottom'];
13023 $lineBox[$k]['glyphYorigin'] = $topy - $ypos[$k]['glyphYorigin'];
13024 $lineBox[$k]['baseline-shift'] = 0;
13025 // continue;
13027 $va = $this->objectbuffer[$k]['vertical-align']; // = $objattr['vertical-align'] = set as M,T,B,S
13029 if ($va == 'BS') { // (BASELINE default)
13030 $lineBox[$k]['top'] = $lineBox[-1]['glyphYorigin'] - $oh;
13031 } elseif ($va == 'M') {
13032 $lineBox[$k]['top'] = $midpoint - $oh / 2;
13033 } elseif ($va == 'TT') {
13034 $lineBox[$k]['top'] = $lineBox[-1]['boxtop'];
13035 } elseif ($va == 'TB') {
13036 $lineBox[$k]['top'] = $lineBox[-1]['boxbottom'] - $oh;
13037 } elseif ($va == 'T') {
13038 $lineBox[$k]['top'] = 0;
13039 } elseif ($va == 'B') {
13040 $lineBox[$k]['top'] = $stackHeight - $oh;
13042 } elseif ($content[$k] || $content[$k] === '0') {
13043 $lineBox[$k]['boxtop'] = $topy - $ypos[$k]['boxtop'];
13044 $lineBox[$k]['boxbottom'] = $topy - $ypos[$k]['boxbottom'];
13045 // $lineBox[$k]['exttop'] = $topy - $ypos[$k]['exttop'];
13046 // $lineBox[$k]['extbottom'] = $topy - $ypos[$k]['extbottom'];
13047 $lineBox[$k]['glyphYorigin'] = $topy - $ypos[$k]['glyphYorigin'];
13048 $lineBox[$k]['baseline-shift'] = $ypos[$k]['baseline-shift'];
13049 if (isset($bordypos[$k]['boxtop'])) {
13050 $lineBox[$k]['border-boxtop'] = $topy - $bordypos[$k]['boxtop'];
13051 $lineBox[$k]['border-boxbottom'] = $topy - $bordypos[$k]['boxbottom'];
13052 $lineBox[$k]['border-baseline-shift'] = $bordypos[$k]['baseline-shift'];
13054 if (isset($bgypos[$k]['boxtop'])) {
13055 $lineBox[$k]['background-boxtop'] = $topy - $bgypos[$k]['boxtop'];
13056 $lineBox[$k]['background-boxbottom'] = $topy - $bgypos[$k]['boxbottom'];
13057 $lineBox[$k]['background-baseline-shift'] = $bgypos[$k]['baseline-shift'];
13063 function SetBasePath($str = '')
13065 if (isset($_SERVER['HTTP_HOST'])) {
13066 $host = $_SERVER['HTTP_HOST'];
13067 } elseif (isset($_SERVER['SERVER_NAME'])) {
13068 $host = $_SERVER['SERVER_NAME'];
13069 } else {
13070 $host = '';
13072 if (!$str) {
13073 if ($_SERVER['SCRIPT_NAME']) {
13074 $currentPath = dirname($_SERVER['SCRIPT_NAME']);
13075 } else {
13076 $currentPath = dirname($_SERVER['PHP_SELF']);
13078 $currentPath = str_replace("\\", "/", $currentPath);
13079 if ($currentPath == '/') {
13080 $currentPath = '';
13082 if ($host) { // mPDF 6
13083 if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] && $_SERVER['HTTPS'] !== 'off') {
13084 $currpath = 'https://' . $host . $currentPath . '/';
13085 } else {
13086 $currpath = 'http://' . $host . $currentPath . '/';
13088 } else {
13089 $currpath = '';
13091 $this->basepath = $currpath;
13092 $this->basepathIsLocal = true;
13093 return;
13095 $str = preg_replace('/\?.*/', '', $str);
13096 if (!preg_match('/(http|https|ftp):\/\/.*\//i', $str)) {
13097 $str .= '/';
13099 $str .= 'xxx'; // in case $str ends in / e.g. http://www.bbc.co.uk/
13100 $this->basepath = dirname($str) . "/"; // returns e.g. e.g. http://www.google.com/dir1/dir2/dir3/
13101 $this->basepath = str_replace("\\", "/", $this->basepath); // If on Windows
13102 $tr = parse_url($this->basepath);
13103 if (isset($tr['host']) && ($tr['host'] == $host)) {
13104 $this->basepathIsLocal = true;
13105 } else {
13106 $this->basepathIsLocal = false;
13110 public function GetFullPath(&$path, $basepath = '')
13112 // When parsing CSS need to pass temporary basepath - so links are relative to current stylesheet
13113 if (!$basepath) {
13114 $basepath = $this->basepath;
13117 // Fix path value
13118 $path = str_replace("\\", '/', $path); // If on Windows
13120 // mPDF 5.7.2
13121 if (substr($path, 0, 2) === '//') {
13122 $scheme = parse_url($basepath, PHP_URL_SCHEME);
13123 $scheme = $scheme ?: 'http';
13124 $path = $scheme . ':' . $path;
13127 $path = preg_replace('|^./|', '', $path); // Inadvertently corrects "./path/etc" and "//www.domain.com/etc"
13129 if (substr($path, 0, 1) == '#') {
13130 return;
13133 if (preg_match('@^(mailto|tel|fax):.*@i', $path)) {
13134 return;
13137 if (substr($path, 0, 3) == "../") { // It is a relative link
13139 $backtrackamount = substr_count($path, "../");
13140 $maxbacktrack = substr_count($basepath, "/") - 3;
13141 $filepath = str_replace("../", '', $path);
13142 $path = $basepath;
13144 // If it is an invalid relative link, then make it go to directory root
13145 if ($backtrackamount > $maxbacktrack) {
13146 $backtrackamount = $maxbacktrack;
13149 // Backtrack some directories
13150 for ($i = 0; $i < $backtrackamount + 1; $i++) {
13151 $path = substr($path, 0, strrpos($path, "/"));
13154 $path = $path . "/" . $filepath; // Make it an absolute path
13156 } elseif ((strpos($path, ":/") === false || strpos($path, ":/") > 10) && !is_file($path)) { // It is a local link
13158 if (substr($path, 0, 1) == "/") {
13160 $tr = parse_url($basepath);
13162 // mPDF 5.7.2
13163 $root = '';
13164 if (!empty($tr['scheme'])) {
13165 $root .= $tr['scheme'] . '://';
13168 $root .= isset($tr['host']) ? $tr['host'] : '';
13169 $root .= ((isset($tr['port']) && $tr['port']) ? (':' . $tr['port']) : ''); // mPDF 5.7.3
13171 $path = $root . $path;
13173 } else {
13174 $path = $basepath . $path;
13177 // Do nothing if it is an Absolute Link
13180 function docPageNum($num = 0, $extras = false)
13182 if ($num < 1) {
13183 $num = $this->page;
13186 $type = $this->defaultPageNumStyle; // set default Page Number Style
13187 $ppgno = $num;
13188 $suppress = 0;
13189 $offset = 0;
13190 $lastreset = 0;
13192 foreach ($this->PageNumSubstitutions as $psarr) {
13193 if ($num >= $psarr['from']) {
13194 if ($psarr['reset']) {
13195 if ($psarr['reset'] > 1) {
13196 $offset = $psarr['reset'] - 1;
13198 $ppgno = $num - $psarr['from'] + 1 + $offset;
13199 $lastreset = $psarr['from'];
13201 if ($psarr['type']) {
13202 $type = $psarr['type'];
13204 if (strtoupper($psarr['suppress']) == 'ON' || $psarr['suppress'] == 1) {
13205 $suppress = 1;
13206 } elseif (strtoupper($psarr['suppress']) == 'OFF') {
13207 $suppress = 0;
13212 if ($suppress) {
13213 return '';
13216 $ppgno = $this->_getStyledNumber($ppgno, $type);
13218 if ($extras) {
13219 $ppgno = $this->pagenumPrefix . $ppgno . $this->pagenumSuffix;
13222 return $ppgno;
13225 function docPageNumTotal($num = 0, $extras = false)
13227 if ($num < 1) {
13228 $num = $this->page;
13231 $type = $this->defaultPageNumStyle; // set default Page Number Style
13232 $ppgstart = 1;
13233 $ppgend = count($this->pages) + 1;
13234 $suppress = 0;
13235 $offset = 0;
13237 foreach ($this->PageNumSubstitutions as $psarr) {
13238 if ($num >= $psarr['from']) {
13239 if ($psarr['reset']) {
13240 if ($psarr['reset'] > 1) {
13241 $offset = $psarr['reset'] - 1;
13243 $ppgstart = $psarr['from'] + $offset;
13244 $ppgend = count($this->pages) + 1 + $offset;
13246 if ($psarr['type']) {
13247 $type = $psarr['type'];
13249 if (strtoupper($psarr['suppress']) == 'ON' || $psarr['suppress'] == 1) {
13250 $suppress = 1;
13251 } elseif (strtoupper($psarr['suppress']) == 'OFF') {
13252 $suppress = 0;
13255 if ($num < $psarr['from']) {
13256 if ($psarr['reset']) {
13257 $ppgend = $psarr['from'] + $offset;
13258 break;
13263 if ($suppress) {
13264 return '';
13267 $ppgno = $ppgend - $ppgstart + $offset;
13268 $ppgno = $this->_getStyledNumber($ppgno, $type);
13270 if ($extras) {
13271 $ppgno = $this->pagenumPrefix . $ppgno . $this->pagenumSuffix;
13274 return $ppgno;
13277 // mPDF 6
13278 function _getStyledNumber($ppgno, $type, $listmarker = false)
13280 if ($listmarker) {
13281 $reverse = true; // Reverse RTL numerals (Hebrew) when using for list
13282 $checkfont = true; // Using list - font is set, so check if character is available
13283 } else {
13284 $reverse = false; // For pagenumbers, RTL numerals (Hebrew) will get reversed later by bidi
13285 $checkfont = false; // For pagenumbers - font is not set, so no check
13288 $decToAlpha = new Conversion\DecToAlpha();
13289 $decToCjk = new Conversion\DecToCjk();
13290 $decToHebrew = new Conversion\DecToHebrew();
13291 $decToRoman = new Conversion\DecToRoman();
13292 $decToOther = new Conversion\DecToOther($this);
13294 $lowertype = strtolower($type);
13296 if ($lowertype == 'upper-latin' || $lowertype == 'upper-alpha' || $type == 'A') {
13298 $ppgno = $decToAlpha->convert($ppgno, true);
13300 } elseif ($lowertype == 'lower-latin' || $lowertype == 'lower-alpha' || $type == 'a') {
13302 $ppgno = $decToAlpha->convert($ppgno, false);
13304 } elseif ($lowertype == 'upper-roman' || $type == 'I') {
13306 $ppgno = $decToRoman->convert($ppgno, true);
13308 } elseif ($lowertype == 'lower-roman' || $type == 'i') {
13310 $ppgno = $decToRoman->convert($ppgno, false);
13312 } elseif ($lowertype == 'hebrew') {
13314 $ppgno = $decToHebrew->convert($ppgno, $reverse);
13316 } elseif (preg_match('/(arabic-indic|bengali|devanagari|gujarati|gurmukhi|kannada|malayalam|oriya|persian|tamil|telugu|thai|urdu|cambodian|khmer|lao)/i', $lowertype, $m)) {
13318 $cp = $decToOther->getCodePage($m[1]);
13319 $ppgno = $decToOther->convert($ppgno, $cp, $checkfont);
13321 } elseif ($lowertype == 'cjk-decimal') {
13323 $ppgno = $decToCjk->convert($ppgno);
13327 return $ppgno;
13330 function docPageSettings($num = 0)
13332 // Returns current type (numberstyle), suppression state for this page number;
13333 // reset is only returned if set for this page number
13334 if ($num < 1) {
13335 $num = $this->page;
13338 $type = $this->defaultPageNumStyle; // set default Page Number Style
13339 $ppgno = $num;
13340 $suppress = 0;
13341 $offset = 0;
13342 $reset = '';
13344 foreach ($this->PageNumSubstitutions as $psarr) {
13345 if ($num >= $psarr['from']) {
13346 if ($psarr['reset']) {
13347 if ($psarr['reset'] > 1) {
13348 $offset = $psarr['reset'] - 1;
13350 $ppgno = $num - $psarr['from'] + 1 + $offset;
13352 if ($psarr['type']) {
13353 $type = $psarr['type'];
13355 if (strtoupper($psarr['suppress']) == 'ON' || $psarr['suppress'] == 1) {
13356 $suppress = 1;
13357 } elseif (strtoupper($psarr['suppress']) == 'OFF') {
13358 $suppress = 0;
13361 if ($num == $psarr['from']) {
13362 $reset = $psarr['reset'];
13366 if ($suppress) {
13367 $suppress = 'on';
13368 } else {
13369 $suppress = 'off';
13372 return [$type, $suppress, $reset];
13375 function RestartDocTemplate()
13377 $this->docTemplateStart = $this->page;
13380 // Page header
13381 function Header($content = '')
13384 $this->cMarginL = 0;
13385 $this->cMarginR = 0;
13388 if (($this->mirrorMargins && ($this->page % 2 == 0) && $this->HTMLHeaderE) || ($this->mirrorMargins && ($this->page % 2 == 1) && $this->HTMLHeader) || (!$this->mirrorMargins && $this->HTMLHeader)) {
13389 $this->writeHTMLHeaders();
13390 return;
13394 /* -- TABLES -- */
13395 function TableHeaderFooter($content = '', $tablestartpage = '', $tablestartcolumn = '', $horf = 'H', $level = 0, $firstSpread = true, $finalSpread = true)
13397 if (($horf == 'H' || $horf == 'F') && !empty($content)) { // mPDF 5.7.2
13398 $table = &$this->table[1][1];
13400 // mPDF 5.7.2
13401 if ($horf == 'F') { // Table Footer
13402 $firstrow = count($table['cells']) - $table['footernrows'];
13403 $lastrow = count($table['cells']) - 1;
13404 } else { // Table Header
13405 $firstrow = 0;
13406 $lastrow = $table['headernrows'] - 1;
13408 if (empty($content[$firstrow])) {
13409 if ($this->debug) {
13410 throw new \Mpdf\MpdfException("<tfoot> must precede <tbody> in a table");
13411 } else {
13412 return;
13417 // Advance down page by half width of top border
13418 if ($horf == 'H') { // Only if header
13419 if ($table['borders_separate']) {
13420 $adv = $table['border_spacing_V'] / 2 + $table['border_details']['T']['w'] + $table['padding']['T'];
13421 } else {
13422 $adv = $table['max_cell_border_width']['T'] / 2;
13424 if ($adv) {
13425 if ($this->table_rotate) {
13426 $this->y += ($adv);
13427 } else {
13428 $this->DivLn($adv, $this->blklvl, true);
13433 $topy = $content[$firstrow][0]['y'] - $this->y;
13435 for ($i = $firstrow; $i <= $lastrow; $i++) {
13436 $y = $this->y;
13438 /* -- COLUMNS -- */
13439 // If outside columns, this is done in PaintDivBB
13440 if ($this->ColActive) {
13441 // OUTER FILL BGCOLOR of DIVS
13442 if ($this->blklvl > 0) {
13443 $firstblockfill = $this->GetFirstBlockFill();
13444 if ($firstblockfill && $this->blklvl >= $firstblockfill) {
13445 $divh = $content[$i][0]['h'];
13446 $bak_x = $this->x;
13447 $this->DivLn($divh, -3, false);
13448 // Reset current block fill
13449 $bcor = $this->blk[$this->blklvl]['bgcolorarray'];
13450 $this->SetFColor($bcor);
13451 $this->x = $bak_x;
13455 /* -- END COLUMNS -- */
13457 $colctr = 0;
13458 foreach ($content[$i] as $tablehf) {
13459 $colctr++;
13460 $y = Arrays::get($tablehf, 'y', null) - $topy;
13461 $this->y = $y;
13462 // Set some cell values
13463 $x = Arrays::get($tablehf, 'x', null);
13464 if (($this->mirrorMargins) && ($tablestartpage == 'ODD') && (($this->page) % 2 == 0)) { // EVEN
13465 $x = $x + $this->MarginCorrection;
13466 } elseif (($this->mirrorMargins) && ($tablestartpage == 'EVEN') && (($this->page) % 2 == 1)) { // ODD
13467 $x = $x + $this->MarginCorrection;
13469 /* -- COLUMNS -- */
13470 // Added to correct for Columns
13471 if ($this->ColActive) {
13472 if ($this->directionality == 'rtl') { // *OTL*
13473 $x -= ($this->CurrCol - $tablestartcolumn) * ($this->ColWidth + $this->ColGap); // *OTL*
13474 } // *OTL*
13475 else { // *OTL*
13476 $x += ($this->CurrCol - $tablestartcolumn) * ($this->ColWidth + $this->ColGap);
13477 } // *OTL*
13479 /* -- END COLUMNS -- */
13481 if ($colctr == 1) {
13482 $x0 = $x;
13485 // mPDF ITERATION
13486 if ($this->iterationCounter) {
13487 foreach ($tablehf['textbuffer'] as $k => $t) {
13488 if (!is_array($t[0]) && preg_match('/{iteration ([a-zA-Z0-9_]+)}/', $t[0], $m)) {
13489 $vname = '__' . $m[1] . '_';
13490 if (!isset($this->$vname)) {
13491 $this->$vname = 1;
13492 } else {
13493 $this->$vname++;
13495 $tablehf['textbuffer'][$k][0] = preg_replace('/{iteration ' . $m[1] . '}/', $this->$vname, $tablehf['textbuffer'][$k][0]);
13500 $w = Arrays::get($tablehf, 'w', null);
13501 $h = Arrays::get($tablehf, 'h', null);
13502 $va = Arrays::get($tablehf, 'va', null);
13503 $R = Arrays::get($tablehf, 'R', null);
13504 $direction = Arrays::get($tablehf, 'direction', null);
13505 $mih = Arrays::get($tablehf, 'mih', null);
13506 $border = Arrays::get($tablehf, 'border', null);
13507 $border_details = Arrays::get($tablehf, 'border_details', null);
13508 $padding = Arrays::get($tablehf, 'padding', null);
13509 $this->tabletheadjustfinished = true;
13511 $textbuffer = Arrays::get($tablehf, 'textbuffer', null);
13513 // Align
13514 $align = Arrays::get($tablehf, 'a', null);
13515 $this->cellTextAlign = $align;
13517 $this->cellLineHeight = Arrays::get($tablehf, 'cellLineHeight', null);
13518 $this->cellLineStackingStrategy = Arrays::get($tablehf, 'cellLineStackingStrategy', null);
13519 $this->cellLineStackingShift = Arrays::get($tablehf, 'cellLineStackingShift', null);
13521 $this->x = $x;
13523 if ($this->ColActive) {
13524 if ($table['borders_separate']) {
13525 $tablefill = isset($table['bgcolor'][-1]) ? $table['bgcolor'][-1] : 0;
13526 if ($tablefill) {
13527 $color = $this->colorConverter->convert($tablefill, $this->PDFAXwarnings);
13528 if ($color) {
13529 $xadj = ($table['border_spacing_H'] / 2);
13530 $yadj = ($table['border_spacing_V'] / 2);
13531 $wadj = $table['border_spacing_H'];
13532 $hadj = $table['border_spacing_V'];
13533 if ($i == $firstrow && $horf == 'H') { // Top
13534 $yadj += $table['padding']['T'] + $table['border_details']['T']['w'];
13535 $hadj += $table['padding']['T'] + $table['border_details']['T']['w'];
13537 if (($i == ($lastrow) || (isset($tablehf['rowspan']) && ($i + $tablehf['rowspan']) == ($lastrow + 1)) || (!isset($tablehf['rowspan']) && ($i + 1) == ($lastrow + 1))) && $horf == 'F') { // Bottom
13538 $hadj += $table['padding']['B'] + $table['border_details']['B']['w'];
13540 if ($colctr == 1) { // Left
13541 $xadj += $table['padding']['L'] + $table['border_details']['L']['w'];
13542 $wadj += $table['padding']['L'] + $table['border_details']['L']['w'];
13544 if ($colctr == count($content[$i])) { // Right
13545 $wadj += $table['padding']['R'] + $table['border_details']['R']['w'];
13547 $this->SetFColor($color);
13548 $this->Rect($x - $xadj, $y - $yadj, $w + $wadj, $h + $hadj, 'F');
13554 if ($table['empty_cells'] != 'hide' || !empty($textbuffer) || !$table['borders_separate']) {
13555 $paintcell = true;
13556 } else {
13557 $paintcell = false;
13560 // Vertical align
13561 if ($R && intval($R) > 0 && isset($va) && $va != 'B') {
13562 $va = 'B';
13565 if (!isset($va) || empty($va) || $va == 'M') {
13566 $this->y += ($h - $mih) / 2;
13567 } elseif (isset($va) && $va == 'B') {
13568 $this->y += $h - $mih;
13572 // TABLE ROW OR CELL FILL BGCOLOR
13573 $fill = 0;
13574 if (isset($tablehf['bgcolor']) && $tablehf['bgcolor'] && $tablehf['bgcolor'] != 'transparent') {
13575 $fill = $tablehf['bgcolor'];
13576 $leveladj = 6;
13577 } elseif (isset($content[$i][0]['trbgcolor']) && $content[$i][0]['trbgcolor'] && $content[$i][0]['trbgcolor'] != 'transparent') { // Row color
13578 $fill = $content[$i][0]['trbgcolor'];
13579 $leveladj = 3;
13581 if ($fill && $paintcell) {
13582 $color = $this->colorConverter->convert($fill, $this->PDFAXwarnings);
13583 if ($color) {
13584 if ($table['borders_separate']) {
13585 if ($this->ColActive) {
13586 $this->SetFColor($color);
13587 $this->Rect($x + ($table['border_spacing_H'] / 2), $y + ($table['border_spacing_V'] / 2), $w - $table['border_spacing_H'], $h - $table['border_spacing_V'], 'F');
13588 } else {
13589 $this->tableBackgrounds[$level * 9 + $leveladj][] = ['gradient' => false, 'x' => ($x + ($table['border_spacing_H'] / 2)), 'y' => ($y + ($table['border_spacing_V'] / 2)), 'w' => ($w - $table['border_spacing_H']), 'h' => ($h - $table['border_spacing_V']), 'col' => $color];
13591 } else {
13592 if ($this->ColActive) {
13593 $this->SetFColor($color);
13594 $this->Rect($x, $y, $w, $h, 'F');
13595 } else {
13596 $this->tableBackgrounds[$level * 9 + $leveladj][] = ['gradient' => false, 'x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'col' => $color];
13603 /* -- BACKGROUNDS -- */
13604 if (isset($tablehf['gradient']) && $tablehf['gradient'] && $paintcell) {
13605 $g = $this->gradient->parseBackgroundGradient($tablehf['gradient']);
13606 if ($g) {
13607 if ($table['borders_separate']) {
13608 $px = $x + ($table['border_spacing_H'] / 2);
13609 $py = $y + ($table['border_spacing_V'] / 2);
13610 $pw = $w - $table['border_spacing_H'];
13611 $ph = $h - $table['border_spacing_V'];
13612 } else {
13613 $px = $x;
13614 $py = $y;
13615 $pw = $w;
13616 $ph = $h;
13618 if ($this->ColActive) {
13619 $this->gradient->Gradient($px, $py, $pw, $ph, $g['type'], $g['stops'], $g['colorspace'], $g['coords'], $g['extend']);
13620 } else {
13621 $this->tableBackgrounds[$level * 9 + 7][] = ['gradient' => true, 'x' => $px, 'y' => $py, 'w' => $pw, 'h' => $ph, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => ''];
13626 if (isset($tablehf['background-image']) && $paintcell) {
13627 if ($tablehf['background-image']['gradient'] && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $tablehf['background-image']['gradient'])) {
13628 $g = $this->gradient->parseMozGradient($tablehf['background-image']['gradient']);
13629 if ($g) {
13630 if ($table['borders_separate']) {
13631 $px = $x + ($table['border_spacing_H'] / 2);
13632 $py = $y + ($table['border_spacing_V'] / 2);
13633 $pw = $w - $table['border_spacing_H'];
13634 $ph = $h - $table['border_spacing_V'];
13635 } else {
13636 $px = $x;
13637 $py = $y;
13638 $pw = $w;
13639 $ph = $h;
13641 if ($this->ColActive) {
13642 $this->gradient->Gradient($px, $py, $pw, $ph, $g['type'], $g['stops'], $g['colorspace'], $g['coords'], $g['extend']);
13643 } else {
13644 $this->tableBackgrounds[$level * 9 + 7][] = ['gradient' => true, 'x' => $px, 'y' => $py, 'w' => $pw, 'h' => $ph, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => ''];
13647 } elseif ($tablehf['background-image']['image_id']) { // Background pattern
13648 $n = count($this->patterns) + 1;
13649 if ($table['borders_separate']) {
13650 $px = $x + ($table['border_spacing_H'] / 2);
13651 $py = $y + ($table['border_spacing_V'] / 2);
13652 $pw = $w - $table['border_spacing_H'];
13653 $ph = $h - $table['border_spacing_V'];
13654 } else {
13655 $px = $x;
13656 $py = $y;
13657 $pw = $w;
13658 $ph = $h;
13660 if ($this->ColActive) {
13661 list($orig_w, $orig_h, $x_repeat, $y_repeat) = $this->_resizeBackgroundImage($tablehf['background-image']['orig_w'], $tablehf['background-image']['orig_h'], $pw, $ph, $tablehf['background-image']['resize'], $tablehf['background-image']['x_repeat'], $tablehf['background-image']['y_repeat']);
13662 $this->patterns[$n] = ['x' => $px, 'y' => $py, 'w' => $pw, 'h' => $ph, 'pgh' => $this->h, 'image_id' => $tablehf['background-image']['image_id'], 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $tablehf['background-image']['x_pos'], 'y_pos' => $tablehf['background-image']['y_pos'], 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'itype' => $tablehf['background-image']['itype']];
13663 if ($tablehf['background-image']['opacity'] > 0 && $tablehf['background-image']['opacity'] < 1) {
13664 $opac = $this->SetAlpha($tablehf['background-image']['opacity'], 'Normal', true);
13665 } else {
13666 $opac = '';
13668 $this->_out(sprintf('q /Pattern cs /P%d scn %s %.3F %.3F %.3F %.3F re f Q', $n, $opac, $px * Mpdf::SCALE, ($this->h - $py) * Mpdf::SCALE, $pw * Mpdf::SCALE, -$ph * Mpdf::SCALE));
13669 } else {
13670 $this->tableBackgrounds[$level * 9 + 8][] = ['x' => $px, 'y' => $py, 'w' => $pw, 'h' => $ph, 'image_id' => $tablehf['background-image']['image_id'], 'orig_w' => $tablehf['background-image']['orig_w'], 'orig_h' => $tablehf['background-image']['orig_h'], 'x_pos' => $tablehf['background-image']['x_pos'], 'y_pos' => $tablehf['background-image']['y_pos'], 'x_repeat' => $tablehf['background-image']['x_repeat'], 'y_repeat' => $tablehf['background-image']['y_repeat'], 'clippath' => '', 'resize' => $tablehf['background-image']['resize'], 'opacity' => $tablehf['background-image']['opacity'], 'itype' => $tablehf['background-image']['itype']];
13674 /* -- END BACKGROUNDS -- */
13676 // Cell Border
13677 if ($table['borders_separate'] && $paintcell && $border) {
13678 $this->_tableRect($x + ($table['border_spacing_H'] / 2) + ($border_details['L']['w'] / 2), $y + ($table['border_spacing_V'] / 2) + ($border_details['T']['w'] / 2), $w - $table['border_spacing_H'] - ($border_details['L']['w'] / 2) - ($border_details['R']['w'] / 2), $h - $table['border_spacing_V'] - ($border_details['T']['w'] / 2) - ($border_details['B']['w'] / 2), $border, $border_details, false, $table['borders_separate']);
13679 } elseif ($paintcell && $border) {
13680 $this->_tableRect($x, $y, $w, $h, $border, $border_details, true, $table['borders_separate']); // true causes buffer
13683 // Print cell content
13684 if (!empty($textbuffer)) {
13685 if ($horf == 'F' && preg_match('/{colsum([0-9]*)[_]*}/', $textbuffer[0][0], $m)) {
13686 $rep = sprintf("%01." . intval($m[1]) . "f", $this->colsums[$colctr - 1]);
13687 $textbuffer[0][0] = preg_replace('/{colsum[0-9_]*}/', $rep, $textbuffer[0][0]);
13690 if ($R) {
13691 $cellPtSize = $textbuffer[0][11] / $this->shrin_k;
13692 if (!$cellPtSize) {
13693 $cellPtSize = $this->default_font_size;
13695 $cellFontHeight = ($cellPtSize / Mpdf::SCALE);
13696 $opx = $this->x;
13697 $opy = $this->y;
13698 $angle = intval($R);
13700 // Only allow 45 - 90 degrees (when bottom-aligned) or -90
13701 if ($angle > 90) {
13702 $angle = 90;
13703 } elseif ($angle > 0 && (isset($va) && $va != 'B')) {
13704 $angle = 90;
13705 } elseif ($angle > 0 && $angle < 45) {
13706 $angle = 45;
13707 } elseif ($angle < 0) {
13708 $angle = -90;
13711 $offset = ((sin(deg2rad($angle))) * 0.37 * $cellFontHeight);
13712 if (isset($align) && $align == 'R') {
13713 $this->x += ($w) + ($offset) - ($cellFontHeight / 3) - ($padding['R'] + $border_details['R']['w']);
13714 } elseif (!isset($align) || $align == 'C') {
13715 $this->x += ($w / 2) + ($offset);
13716 } else {
13717 $this->x += ($offset) + ($cellFontHeight / 3) + ($padding['L'] + $border_details['L']['w']);
13719 $str = '';
13720 foreach ($tablehf['textbuffer'] as $t) {
13721 $str .= $t[0] . ' ';
13723 $str = rtrim($str);
13725 if (!isset($va) || $va == 'M') {
13726 $this->y -= ($h - $mih) / 2; // Undo what was added earlier VERTICAL ALIGN
13727 if ($angle > 0) {
13728 $this->y += (($h - $mih) / 2) + ($padding['T'] + $border_details['T']['w']) + ($mih - ($padding['T'] + $border_details['T']['w'] + $border_details['B']['w'] + $padding['B']));
13729 } elseif ($angle < 0) {
13730 $this->y += (($h - $mih) / 2) + ($padding['T'] + $border_details['T']['w']);
13732 } elseif (isset($va) && $va == 'B') {
13733 $this->y -= $h - $mih; // Undo what was added earlier VERTICAL ALIGN
13734 if ($angle > 0) {
13735 $this->y += $h - ($border_details['B']['w'] + $padding['B']);
13736 } elseif ($angle < 0) {
13737 $this->y += $h - $mih + ($padding['T'] + $border_details['T']['w']);
13739 } elseif (isset($va) && $va == 'T') {
13740 if ($angle > 0) {
13741 $this->y += $mih - ($border_details['B']['w'] + $padding['B']);
13742 } elseif ($angle < 0) {
13743 $this->y += ($padding['T'] + $border_details['T']['w']);
13747 $this->Rotate($angle, $this->x, $this->y);
13748 $s_fs = $this->FontSizePt;
13749 $s_f = $this->FontFamily;
13750 $s_st = $this->FontStyle;
13751 if (!empty($textbuffer[0][3])) { // Font Color
13752 $cor = $textbuffer[0][3];
13753 $this->SetTColor($cor);
13755 $this->SetFont($textbuffer[0][4], $textbuffer[0][2], $cellPtSize, true, true);
13757 $this->magic_reverse_dir($str, $this->directionality, $textbuffer[0][18]);
13758 $this->Text($this->x, $this->y, $str, $textbuffer[0][18], $textbuffer[0][8]); // textvar
13759 $this->Rotate(0);
13760 $this->SetFont($s_f, $s_st, $s_fs, true, true);
13761 $this->SetTColor(0);
13762 $this->x = $opx;
13763 $this->y = $opy;
13764 } else {
13765 if ($table['borders_separate']) { // NB twice border width
13766 $xadj = $border_details['L']['w'] + $padding['L'] + ($table['border_spacing_H'] / 2);
13767 $wadj = $border_details['L']['w'] + $border_details['R']['w'] + $padding['L'] + $padding['R'] + $table['border_spacing_H'];
13768 $yadj = $border_details['T']['w'] + $padding['T'] + ($table['border_spacing_H'] / 2);
13769 } else {
13770 $xadj = $border_details['L']['w'] / 2 + $padding['L'];
13771 $wadj = ($border_details['L']['w'] + $border_details['R']['w']) / 2 + $padding['L'] + $padding['R'];
13772 $yadj = $border_details['T']['w'] / 2 + $padding['T'];
13775 $this->divwidth = $w - ($wadj);
13776 $this->x += $xadj;
13777 $this->y += $yadj;
13778 $this->printbuffer($textbuffer, '', true, false, $direction);
13781 $textbuffer = [];
13783 /* -- BACKGROUNDS -- */
13784 if (!$this->ColActive) {
13785 if (isset($content[$i][0]['trgradients']) && ($colctr == 1 || $table['borders_separate'])) {
13786 $g = $this->gradient->parseBackgroundGradient($content[$i][0]['trgradients']);
13787 if ($g) {
13788 $gx = $x0;
13789 $gy = $y;
13790 $gh = $h;
13791 $gw = $table['w'] - ($table['max_cell_border_width']['L'] / 2) - ($table['max_cell_border_width']['R'] / 2) - $table['margin']['L'] - $table['margin']['R'];
13792 if ($table['borders_separate']) {
13793 $gw -= ($table['padding']['L'] + $table['border_details']['L']['w'] + $table['padding']['R'] + $table['border_details']['R']['w'] + $table['border_spacing_H']);
13794 $clx = $x + ($table['border_spacing_H'] / 2);
13795 $cly = $y + ($table['border_spacing_V'] / 2);
13796 $clw = $w - $table['border_spacing_H'];
13797 $clh = $h - $table['border_spacing_V'];
13798 // Set clipping path
13799 $s = $this->_setClippingPath($clx, $cly, $clw, $clh); // mPDF 6
13800 $this->tableBackgrounds[$level * 9 + 4][] = ['gradient' => true, 'x' => $gx + ($table['border_spacing_H'] / 2), 'y' => $gy + ($table['border_spacing_V'] / 2), 'w' => $gw - $table['border_spacing_V'], 'h' => $gh - $table['border_spacing_H'], 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => $s];
13801 } else {
13802 $this->tableBackgrounds[$level * 9 + 4][] = ['gradient' => true, 'x' => $gx, 'y' => $gy, 'w' => $gw, 'h' => $gh, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => ''];
13807 if (isset($content[$i][0]['trbackground-images']) && ($colctr == 1 || $table['borders_separate'])) {
13808 if ($content[$i][0]['trbackground-images']['gradient'] && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $content[$i][0]['trbackground-images']['gradient'])) {
13809 $g = $this->gradient->parseMozGradient($content[$i][0]['trbackground-images']['gradient']);
13810 if ($g) {
13811 $gx = $x0;
13812 $gy = $y;
13813 $gh = $h;
13814 $gw = $table['w'] - ($table['max_cell_border_width']['L'] / 2) - ($table['max_cell_border_width']['R'] / 2) - $table['margin']['L'] - $table['margin']['R'];
13815 if ($table['borders_separate']) {
13816 $gw -= ($table['padding']['L'] + $table['border_details']['L']['w'] + $table['padding']['R'] + $table['border_details']['R']['w'] + $table['border_spacing_H']);
13817 $clx = $x + ($table['border_spacing_H'] / 2);
13818 $cly = $y + ($table['border_spacing_V'] / 2);
13819 $clw = $w - $table['border_spacing_H'];
13820 $clh = $h - $table['border_spacing_V'];
13821 // Set clipping path
13822 $s = $this->_setClippingPath($clx, $cly, $clw, $clh); // mPDF 6
13823 $this->tableBackgrounds[$level * 9 + 4][] = ['gradient' => true, 'x' => $gx + ($table['border_spacing_H'] / 2), 'y' => $gy + ($table['border_spacing_V'] / 2), 'w' => $gw - $table['border_spacing_V'], 'h' => $gh - $table['border_spacing_H'], 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => $s];
13824 } else {
13825 $this->tableBackgrounds[$level * 9 + 4][] = ['gradient' => true, 'x' => $gx, 'y' => $gy, 'w' => $gw, 'h' => $gh, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => ''];
13828 } else {
13829 $image_id = $content[$i][0]['trbackground-images']['image_id'];
13830 $orig_w = $content[$i][0]['trbackground-images']['orig_w'];
13831 $orig_h = $content[$i][0]['trbackground-images']['orig_h'];
13832 $x_pos = $content[$i][0]['trbackground-images']['x_pos'];
13833 $y_pos = $content[$i][0]['trbackground-images']['y_pos'];
13834 $x_repeat = $content[$i][0]['trbackground-images']['x_repeat'];
13835 $y_repeat = $content[$i][0]['trbackground-images']['y_repeat'];
13836 $resize = $content[$i][0]['trbackground-images']['resize'];
13837 $opacity = $content[$i][0]['trbackground-images']['opacity'];
13838 $itype = $content[$i][0]['trbackground-images']['itype'];
13840 $clippath = '';
13841 $gx = $x0;
13842 $gy = $y;
13843 $gh = $h;
13844 $gw = $table['w'] - ($table['max_cell_border_width']['L'] / 2) - ($table['max_cell_border_width']['R'] / 2) - $table['margin']['L'] - $table['margin']['R'];
13845 if ($table['borders_separate']) {
13846 $gw -= ($table['padding']['L'] + $table['border_details']['L']['w'] + $table['padding']['R'] + $table['border_details']['R']['w'] + $table['border_spacing_H']);
13847 $clx = $x + ($table['border_spacing_H'] / 2);
13848 $cly = $y + ($table['border_spacing_V'] / 2);
13849 $clw = $w - $table['border_spacing_H'];
13850 $clh = $h - $table['border_spacing_V'];
13851 // Set clipping path
13852 $s = $this->_setClippingPath($clx, $cly, $clw, $clh); // mPDF 6
13853 $this->tableBackgrounds[$level * 9 + 5][] = ['x' => $gx + ($table['border_spacing_H'] / 2), 'y' => $gy + ($table['border_spacing_V'] / 2), 'w' => $gw - $table['border_spacing_V'], 'h' => $gh - $table['border_spacing_H'], 'image_id' => $image_id, 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $x_pos, 'y_pos' => $y_pos, 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'clippath' => $s, 'resize' => $resize, 'opacity' => $opacity, 'itype' => $itype];
13854 } else {
13855 $this->tableBackgrounds[$level * 9 + 5][] = ['x' => $gx, 'y' => $gy, 'w' => $gw, 'h' => $gh, 'image_id' => $image_id, 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $x_pos, 'y_pos' => $y_pos, 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'clippath' => '', 'resize' => $resize, 'opacity' => $opacity, 'itype' => $itype];
13860 /* -- END BACKGROUNDS -- */
13862 // TABLE BORDER - if separate OR collapsed and only table border
13863 if (($table['borders_separate'] || ($this->simpleTables && !$table['simple']['border'])) && $table['border']) {
13864 $halfspaceL = $table['padding']['L'] + ($table['border_spacing_H'] / 2);
13865 $halfspaceR = $table['padding']['R'] + ($table['border_spacing_H'] / 2);
13866 $halfspaceT = $table['padding']['T'] + ($table['border_spacing_V'] / 2);
13867 $halfspaceB = $table['padding']['B'] + ($table['border_spacing_V'] / 2);
13868 $tbx = $x;
13869 $tby = $y;
13870 $tbw = $w;
13871 $tbh = $h;
13872 $tab_bord = 0;
13873 $corner = '';
13874 if ($i == $firstrow && $horf == 'H') { // Top
13875 $tby -= $halfspaceT + ($table['border_details']['T']['w'] / 2);
13876 $tbh += $halfspaceT + ($table['border_details']['T']['w'] / 2);
13877 $this->setBorder($tab_bord, Border::TOP);
13878 $corner .= 'T';
13880 if (($i == ($lastrow) || (isset($tablehf['rowspan']) && ($i + $tablehf['rowspan']) == ($lastrow + 1))) && $horf == 'F') { // Bottom
13881 $tbh += $halfspaceB + ($table['border_details']['B']['w'] / 2);
13882 $this->setBorder($tab_bord, Border::BOTTOM);
13883 $corner .= 'B';
13885 if ($colctr == 1 && $firstSpread) { // Left
13886 $tbx -= $halfspaceL + ($table['border_details']['L']['w'] / 2);
13887 $tbw += $halfspaceL + ($table['border_details']['L']['w'] / 2);
13888 $this->setBorder($tab_bord, Border::LEFT);
13889 $corner .= 'L';
13891 if ($colctr == count($content[$i]) && $finalSpread) { // Right
13892 $tbw += $halfspaceR + ($table['border_details']['R']['w'] / 2);
13893 $this->setBorder($tab_bord, Border::RIGHT);
13894 $corner .= 'R';
13896 $this->_tableRect($tbx, $tby, $tbw, $tbh, $tab_bord, $table['border_details'], false, $table['borders_separate'], 'table', $corner, $table['border_spacing_V'], $table['border_spacing_H']);
13898 }// end column $content
13899 $this->y = $y + $h; // Update y coordinate
13900 }// end row $i
13901 unset($table);
13902 $this->colsums = [];
13906 /* -- END TABLES -- */
13908 function SetHTMLHeader($header = '', $OE = '', $write = false)
13911 $height = 0;
13912 if (is_array($header) && isset($header['html']) && $header['html']) {
13913 $Hhtml = $header['html'];
13914 if ($this->setAutoTopMargin) {
13915 if (isset($header['h'])) {
13916 $height = $header['h'];
13917 } else {
13918 $height = $this->_getHtmlHeight($Hhtml);
13921 } elseif (!is_array($header) && $header) {
13922 $Hhtml = $header;
13923 if ($this->setAutoTopMargin) {
13924 $height = $this->_getHtmlHeight($Hhtml);
13926 } else {
13927 $Hhtml = '';
13930 if ($OE !== 'E') {
13931 $OE = 'O';
13934 if ($OE === 'E') {
13935 if ($Hhtml) {
13936 $this->HTMLHeaderE = [];
13937 $this->HTMLHeaderE['html'] = $Hhtml;
13938 $this->HTMLHeaderE['h'] = $height;
13939 } else {
13940 $this->HTMLHeaderE = '';
13942 } else {
13943 if ($Hhtml) {
13944 $this->HTMLHeader = [];
13945 $this->HTMLHeader['html'] = $Hhtml;
13946 $this->HTMLHeader['h'] = $height;
13947 } else {
13948 $this->HTMLHeader = '';
13952 if (!$this->mirrorMargins && $OE == 'E') {
13953 return;
13955 if ($Hhtml == '') {
13956 return;
13959 if ($this->setAutoTopMargin == 'pad') {
13960 $this->tMargin = $this->margin_header + $height + $this->orig_tMargin;
13961 if (isset($this->saveHTMLHeader[$this->page][$OE]['mt'])) {
13962 $this->saveHTMLHeader[$this->page][$OE]['mt'] = $this->tMargin;
13964 } elseif ($this->setAutoTopMargin == 'stretch') {
13965 $this->tMargin = max($this->orig_tMargin, $this->margin_header + $height + $this->autoMarginPadding);
13966 if (isset($this->saveHTMLHeader[$this->page][$OE]['mt'])) {
13967 $this->saveHTMLHeader[$this->page][$OE]['mt'] = $this->tMargin;
13970 if ($write && $this->state != 0 && (($this->mirrorMargins && $OE == 'E' && ($this->page) % 2 == 0) || ($this->mirrorMargins && $OE != 'E' && ($this->page) % 2 == 1) || !$this->mirrorMargins)) {
13971 $this->writeHTMLHeaders();
13975 function SetHTMLFooter($footer = '', $OE = '')
13977 $height = 0;
13978 if (is_array($footer) && isset($footer['html']) && $footer['html']) {
13979 $Fhtml = $footer['html'];
13980 if ($this->setAutoBottomMargin) {
13981 if (isset($footer['h'])) {
13982 $height = $footer['h'];
13983 } else {
13984 $height = $this->_getHtmlHeight($Fhtml);
13987 } elseif (!is_array($footer) && $footer) {
13988 $Fhtml = $footer;
13989 if ($this->setAutoBottomMargin) {
13990 $height = $this->_getHtmlHeight($Fhtml);
13992 } else {
13993 $Fhtml = '';
13996 if ($OE !== 'E') {
13997 $OE = 'O';
14000 if ($OE === 'E') {
14001 if ($Fhtml) {
14002 $this->HTMLFooterE = [];
14003 $this->HTMLFooterE['html'] = $Fhtml;
14004 $this->HTMLFooterE['h'] = $height;
14005 } else {
14006 $this->HTMLFooterE = '';
14008 } else {
14009 if ($Fhtml) {
14010 $this->HTMLFooter = [];
14011 $this->HTMLFooter['html'] = $Fhtml;
14012 $this->HTMLFooter['h'] = $height;
14013 } else {
14014 $this->HTMLFooter = '';
14018 if (!$this->mirrorMargins && $OE == 'E') {
14019 return;
14022 if ($Fhtml == '') {
14023 return false;
14026 if ($this->setAutoBottomMargin == 'pad') {
14027 $this->bMargin = $this->margin_footer + $height + $this->orig_bMargin;
14028 $this->PageBreakTrigger = $this->h - $this->bMargin;
14029 if (isset($this->saveHTMLHeader[$this->page][$OE]['mb'])) {
14030 $this->saveHTMLHeader[$this->page][$OE]['mb'] = $this->bMargin;
14032 } elseif ($this->setAutoBottomMargin == 'stretch') {
14033 $this->bMargin = max($this->orig_bMargin, $this->margin_footer + $height + $this->autoMarginPadding);
14034 $this->PageBreakTrigger = $this->h - $this->bMargin;
14035 if (isset($this->saveHTMLHeader[$this->page][$OE]['mb'])) {
14036 $this->saveHTMLHeader[$this->page][$OE]['mb'] = $this->bMargin;
14041 function _getHtmlHeight($html)
14043 $save_state = $this->state;
14044 if ($this->state == 0) {
14045 $this->AddPage($this->CurOrientation);
14047 $this->state = 2;
14048 $this->Reset();
14049 $this->pageoutput[$this->page] = [];
14050 $save_x = $this->x;
14051 $save_y = $this->y;
14052 $this->x = $this->lMargin;
14053 $this->y = $this->margin_header;
14054 $html = str_replace('{PAGENO}', $this->pagenumPrefix . $this->docPageNum($this->page) . $this->pagenumSuffix, $html);
14055 $html = str_replace($this->aliasNbPgGp, $this->nbpgPrefix . $this->docPageNumTotal($this->page) . $this->nbpgSuffix, $html);
14056 $html = str_replace($this->aliasNbPg, $this->page, $html);
14057 $html = preg_replace_callback('/\{DATE\s+(.*?)\}/', [$this, 'date_callback'], $html); // mPDF 5.7
14058 $this->HTMLheaderPageLinks = [];
14059 $this->HTMLheaderPageAnnots = [];
14060 $this->HTMLheaderPageForms = [];
14061 $savepb = $this->pageBackgrounds;
14062 $this->writingHTMLheader = true;
14063 $this->WriteHTML($html, 4); // parameter 4 saves output to $this->headerbuffer
14064 $this->writingHTMLheader = false;
14065 $h = ($this->y - $this->margin_header);
14066 $this->Reset();
14067 // mPDF 5.7.2 - Clear in case Float used in Header/Footer
14068 $this->blk[0]['blockContext'] = 0;
14069 $this->blk[0]['float_endpos'] = 0;
14071 $this->pageoutput[$this->page] = [];
14072 $this->headerbuffer = '';
14073 $this->pageBackgrounds = $savepb;
14074 $this->x = $save_x;
14075 $this->y = $save_y;
14076 $this->state = $save_state;
14077 if ($save_state == 0) {
14078 unset($this->pages[1]);
14079 $this->page = 0;
14081 return $h;
14084 // Called internally from Header
14085 function writeHTMLHeaders()
14088 if ($this->mirrorMargins && ($this->page) % 2 == 0) {
14089 $OE = 'E';
14090 } else {
14091 $OE = 'O';
14094 if ($OE === 'E') {
14095 $this->saveHTMLHeader[$this->page][$OE]['html'] = $this->HTMLHeaderE['html'];
14096 } else {
14097 $this->saveHTMLHeader[$this->page][$OE]['html'] = $this->HTMLHeader['html'];
14100 if ($this->forcePortraitHeaders && $this->CurOrientation == 'L' && $this->CurOrientation != $this->DefOrientation) {
14101 $this->saveHTMLHeader[$this->page][$OE]['rotate'] = true;
14102 $this->saveHTMLHeader[$this->page][$OE]['ml'] = $this->tMargin;
14103 $this->saveHTMLHeader[$this->page][$OE]['mr'] = $this->bMargin;
14104 $this->saveHTMLHeader[$this->page][$OE]['mh'] = $this->margin_header;
14105 $this->saveHTMLHeader[$this->page][$OE]['mf'] = $this->margin_footer;
14106 $this->saveHTMLHeader[$this->page][$OE]['pw'] = $this->h;
14107 $this->saveHTMLHeader[$this->page][$OE]['ph'] = $this->w;
14108 } else {
14109 $this->saveHTMLHeader[$this->page][$OE]['ml'] = $this->lMargin;
14110 $this->saveHTMLHeader[$this->page][$OE]['mr'] = $this->rMargin;
14111 $this->saveHTMLHeader[$this->page][$OE]['mh'] = $this->margin_header;
14112 $this->saveHTMLHeader[$this->page][$OE]['mf'] = $this->margin_footer;
14113 $this->saveHTMLHeader[$this->page][$OE]['pw'] = $this->w;
14114 $this->saveHTMLHeader[$this->page][$OE]['ph'] = $this->h;
14118 function writeHTMLFooters()
14121 if ($this->mirrorMargins && ($this->page) % 2 == 0) {
14122 $OE = 'E';
14123 } else {
14124 $OE = 'O';
14127 if ($OE === 'E') {
14128 $this->saveHTMLFooter[$this->page][$OE]['html'] = $this->HTMLFooterE['html'];
14129 } else {
14130 $this->saveHTMLFooter[$this->page][$OE]['html'] = $this->HTMLFooter['html'];
14133 if ($this->forcePortraitHeaders && $this->CurOrientation == 'L' && $this->CurOrientation != $this->DefOrientation) {
14134 $this->saveHTMLFooter[$this->page][$OE]['rotate'] = true;
14135 $this->saveHTMLFooter[$this->page][$OE]['ml'] = $this->tMargin;
14136 $this->saveHTMLFooter[$this->page][$OE]['mr'] = $this->bMargin;
14137 $this->saveHTMLFooter[$this->page][$OE]['mt'] = $this->rMargin;
14138 $this->saveHTMLFooter[$this->page][$OE]['mb'] = $this->lMargin;
14139 $this->saveHTMLFooter[$this->page][$OE]['mh'] = $this->margin_header;
14140 $this->saveHTMLFooter[$this->page][$OE]['mf'] = $this->margin_footer;
14141 $this->saveHTMLFooter[$this->page][$OE]['pw'] = $this->h;
14142 $this->saveHTMLFooter[$this->page][$OE]['ph'] = $this->w;
14143 } else {
14144 $this->saveHTMLFooter[$this->page][$OE]['ml'] = $this->lMargin;
14145 $this->saveHTMLFooter[$this->page][$OE]['mr'] = $this->rMargin;
14146 $this->saveHTMLFooter[$this->page][$OE]['mt'] = $this->tMargin;
14147 $this->saveHTMLFooter[$this->page][$OE]['mb'] = $this->bMargin;
14148 $this->saveHTMLFooter[$this->page][$OE]['mh'] = $this->margin_header;
14149 $this->saveHTMLFooter[$this->page][$OE]['mf'] = $this->margin_footer;
14150 $this->saveHTMLFooter[$this->page][$OE]['pw'] = $this->w;
14151 $this->saveHTMLFooter[$this->page][$OE]['ph'] = $this->h;
14155 // mPDF 6
14156 function _shareHeaderFooterWidth($cl, $cc, $cr)
14158 // mPDF 6
14159 $l = mb_strlen($cl, 'UTF-8');
14160 $c = mb_strlen($cc, 'UTF-8');
14161 $r = mb_strlen($cr, 'UTF-8');
14162 $s = max($l, $r);
14163 $tw = $c + 2 * $s;
14164 if ($tw > 0) {
14165 return [intval($s * 100 / $tw), intval($c * 100 / $tw), intval($s * 100 / $tw)];
14166 } else {
14167 return [33, 33, 33];
14171 // mPDF 6
14172 // Create an HTML header/footer from array (non-HTML header/footer)
14173 function _createHTMLheaderFooter($arr, $hf)
14175 $lContent = (isset($arr['L']['content']) ? $arr['L']['content'] : '');
14176 $cContent = (isset($arr['C']['content']) ? $arr['C']['content'] : '');
14177 $rContent = (isset($arr['R']['content']) ? $arr['R']['content'] : '');
14178 list($lw, $cw, $rw) = $this->_shareHeaderFooterWidth($lContent, $cContent, $rContent);
14179 if ($hf == 'H') {
14180 $valign = 'bottom';
14181 $vpadding = '0 0 ' . $this->header_line_spacing . 'em 0';
14182 } else {
14183 $valign = 'top';
14184 $vpadding = '' . $this->footer_line_spacing . 'em 0 0 0';
14186 if ($this->directionality == 'rtl') { // table columns get reversed so need different text-alignment
14187 $talignL = 'right';
14188 $talignR = 'left';
14189 } else {
14190 $talignL = 'left';
14191 $talignR = 'right';
14193 $html = '<table width="100%" style="border-collapse: collapse; margin: 0; vertical-align: ' . $valign . '; color: #000000; ';
14194 if (isset($arr['line']) && $arr['line']) {
14195 $html .= ' border-' . $valign . ': 0.1mm solid #000000;';
14197 $html .= '">';
14198 $html .= '<tr>';
14199 $html .= '<td width="' . $lw . '%" style="padding: ' . $vpadding . '; text-align: ' . $talignL . '; ';
14200 if (isset($arr['L']['font-family'])) {
14201 $html .= ' font-family: ' . $arr['L']['font-family'] . ';';
14203 if (isset($arr['L']['color'])) {
14204 $html .= ' color: ' . $arr['L']['color'] . ';';
14206 if (isset($arr['L']['font-size'])) {
14207 $html .= ' font-size: ' . $arr['L']['font-size'] . 'pt;';
14209 if (isset($arr['L']['font-style'])) {
14210 if ($arr['L']['font-style'] == 'B' || $arr['L']['font-style'] == 'BI') {
14211 $html .= ' font-weight: bold;';
14213 if ($arr['L']['font-style'] == 'I' || $arr['L']['font-style'] == 'BI') {
14214 $html .= ' font-style: italic;';
14217 $html .= '">' . $lContent . '</td>';
14218 $html .= '<td width="' . $cw . '%" style="padding: ' . $vpadding . '; text-align: center; ';
14219 if (isset($arr['C']['font-family'])) {
14220 $html .= ' font-family: ' . $arr['C']['font-family'] . ';';
14222 if (isset($arr['C']['color'])) {
14223 $html .= ' color: ' . $arr['C']['color'] . ';';
14225 if (isset($arr['C']['font-size'])) {
14226 $html .= ' font-size: ' . $arr['L']['font-size'] . 'pt;';
14228 if (isset($arr['C']['font-style'])) {
14229 if ($arr['C']['font-style'] == 'B' || $arr['C']['font-style'] == 'BI') {
14230 $html .= ' font-weight: bold;';
14232 if ($arr['C']['font-style'] == 'I' || $arr['C']['font-style'] == 'BI') {
14233 $html .= ' font-style: italic;';
14236 $html .= '">' . $cContent . '</td>';
14237 $html .= '<td width="' . $rw . '%" style="padding: ' . $vpadding . '; text-align: ' . $talignR . '; ';
14238 if (isset($arr['R']['font-family'])) {
14239 $html .= ' font-family: ' . $arr['R']['font-family'] . ';';
14241 if (isset($arr['R']['color'])) {
14242 $html .= ' color: ' . $arr['R']['color'] . ';';
14244 if (isset($arr['R']['font-size'])) {
14245 $html .= ' font-size: ' . $arr['R']['font-size'] . 'pt;';
14247 if (isset($arr['R']['font-style'])) {
14248 if ($arr['R']['font-style'] == 'B' || $arr['R']['font-style'] == 'BI') {
14249 $html .= ' font-weight: bold;';
14251 if ($arr['R']['font-style'] == 'I' || $arr['R']['font-style'] == 'BI') {
14252 $html .= ' font-style: italic;';
14255 $html .= '">' . $rContent . '</td>';
14256 $html .= '</tr></table>';
14257 return $html;
14260 function DefHeaderByName($name, $arr)
14262 if (!$name) {
14263 $name = '_nonhtmldefault';
14265 $html = $this->_createHTMLheaderFooter($arr, 'H');
14267 $this->pageHTMLheaders[$name]['html'] = $html;
14268 $this->pageHTMLheaders[$name]['h'] = $this->_getHtmlHeight($html);
14271 function DefFooterByName($name, $arr)
14273 if (!$name) {
14274 $name = '_nonhtmldefault';
14276 $html = $this->_createHTMLheaderFooter($arr, 'F');
14278 $this->pageHTMLfooters[$name]['html'] = $html;
14279 $this->pageHTMLfooters[$name]['h'] = $this->_getHtmlHeight($html);
14282 function SetHeaderByName($name, $side = 'O', $write = false)
14284 if (!$name) {
14285 $name = '_nonhtmldefault';
14287 $this->SetHTMLHeader($this->pageHTMLheaders[$name], $side, $write);
14290 function SetFooterByName($name, $side = 'O')
14292 if (!$name) {
14293 $name = '_nonhtmldefault';
14295 $this->SetHTMLFooter($this->pageHTMLfooters[$name], $side);
14298 function DefHTMLHeaderByName($name, $html)
14300 if (!$name) {
14301 $name = '_default';
14304 $this->pageHTMLheaders[$name]['html'] = $html;
14305 $this->pageHTMLheaders[$name]['h'] = $this->_getHtmlHeight($html);
14308 function DefHTMLFooterByName($name, $html)
14310 if (!$name) {
14311 $name = '_default';
14314 $this->pageHTMLfooters[$name]['html'] = $html;
14315 $this->pageHTMLfooters[$name]['h'] = $this->_getHtmlHeight($html);
14318 function SetHTMLHeaderByName($name, $side = 'O', $write = false)
14320 if (!$name) {
14321 $name = '_default';
14323 $this->SetHTMLHeader($this->pageHTMLheaders[$name], $side, $write);
14326 function SetHTMLFooterByName($name, $side = 'O')
14328 if (!$name) {
14329 $name = '_default';
14331 $this->SetHTMLFooter($this->pageHTMLfooters[$name], $side);
14334 function SetHeader($Harray = [], $side = '', $write = false)
14336 $oddhtml = '';
14337 $evenhtml = '';
14338 if (is_string($Harray)) {
14339 if (strlen($Harray) == 0) {
14340 $oddhtml = '';
14341 $evenhtml = '';
14342 } elseif (strpos($Harray, '|') !== false) {
14343 $hdet = explode('|', $Harray);
14344 list($lw, $cw, $rw) = $this->_shareHeaderFooterWidth($hdet[0], $hdet[1], $hdet[2]);
14345 $oddhtml = '<table width="100%" style="border-collapse: collapse; margin: 0; vertical-align: bottom; color: #000000; ';
14346 if ($this->defaultheaderfontsize) {
14347 $oddhtml .= ' font-size: ' . $this->defaultheaderfontsize . 'pt;';
14349 if ($this->defaultheaderfontstyle) {
14350 if ($this->defaultheaderfontstyle == 'B' || $this->defaultheaderfontstyle == 'BI') {
14351 $oddhtml .= ' font-weight: bold;';
14353 if ($this->defaultheaderfontstyle == 'I' || $this->defaultheaderfontstyle == 'BI') {
14354 $oddhtml .= ' font-style: italic;';
14357 if ($this->defaultheaderline) {
14358 $oddhtml .= ' border-bottom: 0.1mm solid #000000;';
14360 $oddhtml .= '">';
14361 $oddhtml .= '<tr>';
14362 $oddhtml .= '<td width="' . $lw . '%" style="padding: 0 0 ' . $this->header_line_spacing . 'em 0; text-align: left; ">' . $hdet[0] . '</td>';
14363 $oddhtml .= '<td width="' . $cw . '%" style="padding: 0 0 ' . $this->header_line_spacing . 'em 0; text-align: center; ">' . $hdet[1] . '</td>';
14364 $oddhtml .= '<td width="' . $rw . '%" style="padding: 0 0 ' . $this->header_line_spacing . 'em 0; text-align: right; ">' . $hdet[2] . '</td>';
14365 $oddhtml .= '</tr></table>';
14367 $evenhtml = '<table width="100%" style="border-collapse: collapse; margin: 0; vertical-align: bottom; color: #000000; ';
14368 if ($this->defaultheaderfontsize) {
14369 $evenhtml .= ' font-size: ' . $this->defaultheaderfontsize . 'pt;';
14371 if ($this->defaultheaderfontstyle) {
14372 if ($this->defaultheaderfontstyle == 'B' || $this->defaultheaderfontstyle == 'BI') {
14373 $evenhtml .= ' font-weight: bold;';
14375 if ($this->defaultheaderfontstyle == 'I' || $this->defaultheaderfontstyle == 'BI') {
14376 $evenhtml .= ' font-style: italic;';
14379 if ($this->defaultheaderline) {
14380 $evenhtml .= ' border-bottom: 0.1mm solid #000000;';
14382 $evenhtml .= '">';
14383 $evenhtml .= '<tr>';
14384 $evenhtml .= '<td width="' . $rw . '%" style="padding: 0 0 ' . $this->header_line_spacing . 'em 0; text-align: left; ">' . $hdet[2] . '</td>';
14385 $evenhtml .= '<td width="' . $cw . '%" style="padding: 0 0 ' . $this->header_line_spacing . 'em 0; text-align: center; ">' . $hdet[1] . '</td>';
14386 $evenhtml .= '<td width="' . $lw . '%" style="padding: 0 0 ' . $this->header_line_spacing . 'em 0; text-align: right; ">' . $hdet[0] . '</td>';
14387 $evenhtml .= '</tr></table>';
14388 } else {
14389 $oddhtml = '<div style="margin: 0; color: #000000; ';
14390 if ($this->defaultheaderfontsize) {
14391 $oddhtml .= ' font-size: ' . $this->defaultheaderfontsize . 'pt;';
14393 if ($this->defaultheaderfontstyle) {
14394 if ($this->defaultheaderfontstyle == 'B' || $this->defaultheaderfontstyle == 'BI') {
14395 $oddhtml .= ' font-weight: bold;';
14397 if ($this->defaultheaderfontstyle == 'I' || $this->defaultheaderfontstyle == 'BI') {
14398 $oddhtml .= ' font-style: italic;';
14401 if ($this->defaultheaderline) {
14402 $oddhtml .= ' border-bottom: 0.1mm solid #000000;';
14404 $oddhtml .= 'text-align: right; ">' . $Harray . '</div>';
14406 $evenhtml = '<div style="margin: 0; color: #000000; ';
14407 if ($this->defaultheaderfontsize) {
14408 $evenhtml .= ' font-size: ' . $this->defaultheaderfontsize . 'pt;';
14410 if ($this->defaultheaderfontstyle) {
14411 if ($this->defaultheaderfontstyle == 'B' || $this->defaultheaderfontstyle == 'BI') {
14412 $evenhtml .= ' font-weight: bold;';
14414 if ($this->defaultheaderfontstyle == 'I' || $this->defaultheaderfontstyle == 'BI') {
14415 $evenhtml .= ' font-style: italic;';
14418 if ($this->defaultheaderline) {
14419 $evenhtml .= ' border-bottom: 0.1mm solid #000000;';
14421 $evenhtml .= 'text-align: left; ">' . $Harray . '</div>';
14423 } elseif (is_array($Harray) && !empty($Harray)) {
14424 if ($side == 'O') {
14425 $odd = $Harray;
14426 } elseif ($side == 'E') {
14427 $even = $Harray;
14428 } else {
14429 $odd = $Harray['odd'];
14430 $even = $Harray['even'];
14432 $oddhtml = $this->_createHTMLheaderFooter($odd, 'H');
14434 $evenhtml = $this->_createHTMLheaderFooter($even, 'H');
14437 if ($side == 'E') {
14438 $this->SetHTMLHeader($evenhtml, 'E', $write);
14439 } elseif ($side == 'O') {
14440 $this->SetHTMLHeader($oddhtml, 'O', $write);
14441 } else {
14442 $this->SetHTMLHeader($oddhtml, 'O', $write);
14443 $this->SetHTMLHeader($evenhtml, 'E', $write);
14447 function SetFooter($Farray = [], $side = '')
14449 $oddhtml = '';
14450 $evenhtml = '';
14451 if (is_string($Farray)) {
14452 if (strlen($Farray) == 0) {
14453 $oddhtml = '';
14454 $evenhtml = '';
14455 } elseif (strpos($Farray, '|') !== false) {
14456 $hdet = explode('|', $Farray);
14457 $oddhtml = '<table width="100%" style="border-collapse: collapse; margin: 0; vertical-align: top; color: #000000; ';
14458 if ($this->defaultfooterfontsize) {
14459 $oddhtml .= ' font-size: ' . $this->defaultfooterfontsize . 'pt;';
14461 if ($this->defaultfooterfontstyle) {
14462 if ($this->defaultfooterfontstyle == 'B' || $this->defaultfooterfontstyle == 'BI') {
14463 $oddhtml .= ' font-weight: bold;';
14465 if ($this->defaultfooterfontstyle == 'I' || $this->defaultfooterfontstyle == 'BI') {
14466 $oddhtml .= ' font-style: italic;';
14469 if ($this->defaultfooterline) {
14470 $oddhtml .= ' border-top: 0.1mm solid #000000;';
14472 $oddhtml .= '">';
14473 $oddhtml .= '<tr>';
14474 $oddhtml .= '<td width="33%" style="padding: ' . $this->footer_line_spacing . 'em 0 0 0; text-align: left; ">' . $hdet[0] . '</td>';
14475 $oddhtml .= '<td width="33%" style="padding: ' . $this->footer_line_spacing . 'em 0 0 0; text-align: center; ">' . $hdet[1] . '</td>';
14476 $oddhtml .= '<td width="33%" style="padding: ' . $this->footer_line_spacing . 'em 0 0 0; text-align: right; ">' . $hdet[2] . '</td>';
14477 $oddhtml .= '</tr></table>';
14479 $evenhtml = '<table width="100%" style="border-collapse: collapse; margin: 0; vertical-align: top; color: #000000; ';
14480 if ($this->defaultfooterfontsize) {
14481 $evenhtml .= ' font-size: ' . $this->defaultfooterfontsize . 'pt;';
14483 if ($this->defaultfooterfontstyle) {
14484 if ($this->defaultfooterfontstyle == 'B' || $this->defaultfooterfontstyle == 'BI') {
14485 $evenhtml .= ' font-weight: bold;';
14487 if ($this->defaultfooterfontstyle == 'I' || $this->defaultfooterfontstyle == 'BI') {
14488 $evenhtml .= ' font-style: italic;';
14491 if ($this->defaultfooterline) {
14492 $evenhtml .= ' border-top: 0.1mm solid #000000;';
14494 $evenhtml .= '">';
14495 $evenhtml .= '<tr>';
14496 $evenhtml .= '<td width="33%" style="padding: ' . $this->footer_line_spacing . 'em 0 0 0; text-align: left; ">' . $hdet[2] . '</td>';
14497 $evenhtml .= '<td width="33%" style="padding: ' . $this->footer_line_spacing . 'em 0 0 0; text-align: center; ">' . $hdet[1] . '</td>';
14498 $evenhtml .= '<td width="33%" style="padding: ' . $this->footer_line_spacing . 'em 0 0 0; text-align: right; ">' . $hdet[0] . '</td>';
14499 $evenhtml .= '</tr></table>';
14500 } else {
14501 $oddhtml = '<div style="margin: 0; color: #000000; ';
14502 if ($this->defaultfooterfontsize) {
14503 $oddhtml .= ' font-size: ' . $this->defaultfooterfontsize . 'pt;';
14505 if ($this->defaultfooterfontstyle) {
14506 if ($this->defaultfooterfontstyle == 'B' || $this->defaultfooterfontstyle == 'BI') {
14507 $oddhtml .= ' font-weight: bold;';
14509 if ($this->defaultfooterfontstyle == 'I' || $this->defaultfooterfontstyle == 'BI') {
14510 $oddhtml .= ' font-style: italic;';
14513 if ($this->defaultfooterline) {
14514 $oddhtml .= ' border-top: 0.1mm solid #000000;';
14516 $oddhtml .= 'text-align: right; ">' . $Farray . '</div>';
14518 $evenhtml = '<div style="margin: 0; color: #000000; ';
14519 if ($this->defaultfooterfontsize) {
14520 $evenhtml .= ' font-size: ' . $this->defaultfooterfontsize . 'pt;';
14522 if ($this->defaultfooterfontstyle) {
14523 if ($this->defaultfooterfontstyle == 'B' || $this->defaultfooterfontstyle == 'BI') {
14524 $evenhtml .= ' font-weight: bold;';
14526 if ($this->defaultfooterfontstyle == 'I' || $this->defaultfooterfontstyle == 'BI') {
14527 $evenhtml .= ' font-style: italic;';
14530 if ($this->defaultfooterline) {
14531 $evenhtml .= ' border-top: 0.1mm solid #000000;';
14533 $evenhtml .= 'text-align: left; ">' . $Farray . '</div>';
14535 } elseif (is_array($Farray)) {
14536 if ($side == 'O') {
14537 $odd = $Farray;
14538 } elseif ($side == 'E') {
14539 $even = $Farray;
14540 } else {
14541 if (isset($Farray['odd'])) {
14542 $odd = $Farray['odd'];
14544 if (isset($Farray['even'])) {
14545 $even = $Farray['even'];
14549 if (isset($odd)) {
14550 $oddhtml = $this->_createHTMLheaderFooter($odd, 'F');
14553 if (isset($even)) {
14554 $evenhtml = $this->_createHTMLheaderFooter($even, 'F');
14557 /* -- HTMLfooterS-FOOTERS -- */
14558 if ($side == 'E') {
14559 $this->SetHTMLFooter($evenhtml, 'E');
14560 } elseif ($side == 'O') {
14561 $this->SetHTMLFooter($oddhtml, 'O');
14562 } else {
14563 $this->SetHTMLFooter($oddhtml, 'O');
14564 $this->SetHTMLFooter($evenhtml, 'E');
14566 /* -- END HTMLfooterS-FOOTERS -- */
14569 /* -- WATERMARK -- */
14571 function SetWatermarkText($txt = '', $alpha = -1)
14573 if ($alpha >= 0) {
14574 $this->watermarkTextAlpha = $alpha;
14576 $this->watermarkText = $txt;
14579 function SetWatermarkImage($src, $alpha = -1, $size = 'D', $pos = 'F')
14581 if ($alpha >= 0) {
14582 $this->watermarkImageAlpha = $alpha;
14584 $this->watermarkImage = $src;
14585 $this->watermark_size = $size;
14586 $this->watermark_pos = $pos;
14589 /* -- END WATERMARK -- */
14591 // Page footer
14592 function Footer()
14594 /* -- CSS-PAGE -- */
14595 // PAGED MEDIA - CROP / CROSS MARKS from @PAGE
14596 if ($this->show_marks == 'CROP' || $this->show_marks == 'CROPCROSS') {
14597 // Show TICK MARKS
14598 $this->SetLineWidth(0.1); // = 0.1 mm
14599 $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
14600 $l = $this->cropMarkLength;
14601 $m = $this->cropMarkMargin; // Distance of crop mark from margin
14602 $b = $this->nonPrintMargin; // Non-printable border at edge of paper sheet
14603 $ax1 = $b;
14604 $bx = $this->page_box['outer_width_LR'] - $m;
14605 $ax = max($ax1, $bx - $l);
14606 $cx1 = $this->w - $b;
14607 $dx = $this->w - $this->page_box['outer_width_LR'] + $m;
14608 $cx = min($cx1, $dx + $l);
14609 $ay1 = $b;
14610 $by = $this->page_box['outer_width_TB'] - $m;
14611 $ay = max($ay1, $by - $l);
14612 $cy1 = $this->h - $b;
14613 $dy = $this->h - $this->page_box['outer_width_TB'] + $m;
14614 $cy = min($cy1, $dy + $l);
14616 $this->Line($ax, $this->page_box['outer_width_TB'], $bx, $this->page_box['outer_width_TB']);
14617 $this->Line($cx, $this->page_box['outer_width_TB'], $dx, $this->page_box['outer_width_TB']);
14618 $this->Line($ax, $this->h - $this->page_box['outer_width_TB'], $bx, $this->h - $this->page_box['outer_width_TB']);
14619 $this->Line($cx, $this->h - $this->page_box['outer_width_TB'], $dx, $this->h - $this->page_box['outer_width_TB']);
14620 $this->Line($this->page_box['outer_width_LR'], $ay, $this->page_box['outer_width_LR'], $by);
14621 $this->Line($this->page_box['outer_width_LR'], $cy, $this->page_box['outer_width_LR'], $dy);
14622 $this->Line($this->w - $this->page_box['outer_width_LR'], $ay, $this->w - $this->page_box['outer_width_LR'], $by);
14623 $this->Line($this->w - $this->page_box['outer_width_LR'], $cy, $this->w - $this->page_box['outer_width_LR'], $dy);
14625 if ($this->printers_info) {
14626 $hd = date('Y-m-d H:i') . ' Page ' . $this->page . ' of {nb}';
14627 $this->SetTColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
14628 $this->SetFont('arial', '', 7.5, true, true);
14629 $this->x = $this->page_box['outer_width_LR'] + 1.5;
14630 $this->y = 1;
14631 $this->Cell($headerpgwidth, $this->FontSize, $hd, 0, 0, 'L', 0, '', 0, 0, 0, 'M');
14632 $this->SetFont($this->default_font, '', $this->original_default_font_size);
14635 if ($this->show_marks == 'CROSS' || $this->show_marks == 'CROPCROSS') {
14636 $this->SetLineWidth(0.1); // = 0.1 mm
14637 $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
14638 $l = 14 / 2; // longer length of the cross line (half)
14639 $w = 6 / 2; // shorter width of the cross line (half)
14640 $r = 1.2; // radius of circle
14641 $m = $this->crossMarkMargin; // Distance of cross mark from margin
14642 $x1 = $this->page_box['outer_width_LR'] - $m;
14643 $x2 = $this->w - $this->page_box['outer_width_LR'] + $m;
14644 $y1 = $this->page_box['outer_width_TB'] - $m;
14645 $y2 = $this->h - $this->page_box['outer_width_TB'] + $m;
14646 // Left
14647 $this->Circle($x1, $this->h / 2, $r, 'S');
14648 $this->Line($x1 - $w, $this->h / 2, $x1 + $w, $this->h / 2);
14649 $this->Line($x1, $this->h / 2 - $l, $x1, $this->h / 2 + $l);
14650 // Right
14651 $this->Circle($x2, $this->h / 2, $r, 'S');
14652 $this->Line($x2 - $w, $this->h / 2, $x2 + $w, $this->h / 2);
14653 $this->Line($x2, $this->h / 2 - $l, $x2, $this->h / 2 + $l);
14654 // Top
14655 $this->Circle($this->w / 2, $y1, $r, 'S');
14656 $this->Line($this->w / 2, $y1 - $w, $this->w / 2, $y1 + $w);
14657 $this->Line($this->w / 2 - $l, $y1, $this->w / 2 + $l, $y1);
14658 // Bottom
14659 $this->Circle($this->w / 2, $y2, $r, 'S');
14660 $this->Line($this->w / 2, $y2 - $w, $this->w / 2, $y2 + $w);
14661 $this->Line($this->w / 2 - $l, $y2, $this->w / 2 + $l, $y2);
14664 /* -- END CSS-PAGE -- */
14666 // mPDF 6
14667 // If @page set non-HTML headers/footers named, they were not read until later in the HTML code - so now set them
14668 if ($this->page == 1) {
14669 if ($this->firstPageBoxHeader) {
14670 if (isset($this->pageHTMLheaders[$this->firstPageBoxHeader])) {
14671 $this->HTMLHeader = $this->pageHTMLheaders[$this->firstPageBoxHeader];
14673 $this->Header();
14675 if ($this->firstPageBoxFooter) {
14676 if (isset($this->pageHTMLfooters[$this->firstPageBoxFooter])) {
14677 $this->HTMLFooter = $this->pageHTMLfooters[$this->firstPageBoxFooter];
14680 $this->firstPageBoxHeader = '';
14681 $this->firstPageBoxFooter = '';
14685 if (($this->mirrorMargins && ($this->page % 2 == 0) && $this->HTMLFooterE) || ($this->mirrorMargins && ($this->page % 2 == 1) && $this->HTMLFooter) || (!$this->mirrorMargins && $this->HTMLFooter)) {
14686 $this->writeHTMLFooters();
14689 /* -- WATERMARK -- */
14690 if (($this->watermarkText) && ($this->showWatermarkText)) {
14691 $this->watermark($this->watermarkText, $this->watermarkAngle, 120, $this->watermarkTextAlpha); // Watermark text
14693 if (($this->watermarkImage) && ($this->showWatermarkImage)) {
14694 $this->watermarkImg($this->watermarkImage, $this->watermarkImageAlpha); // Watermark image
14696 /* -- END WATERMARK -- */
14699 /* -- HTML-CSS -- */
14702 * HTML parser
14704 * @param string $html
14705 * @param int $sub 0 = default;
14706 * 1 = headerCSS only
14707 * 2 = HTML body (parts) only;
14708 * 3 = HTML parses only
14709 * 4 = writes HTML headers/Fixed pos DIVs - stores in buffer - for single page only
14710 * @param bool $init Clears and sets buffers to Top level block etc.
14711 * @param bool $close If false leaves buffers etc. in current state, so that it can continue a block etc.
14713 function WriteHTML($html, $sub = 0, $init = true, $close = true)
14715 /* Check $html is an integer, float, string, boolean or class with __toString(), otherwise throw exception */
14716 if (is_scalar($html) === false) {
14717 if (!is_object($html) || ! method_exists($html, '__toString')) {
14718 throw new \Mpdf\MpdfException('WriteHTML() requires $html be an integer, float, string, boolean or an object with the __toString() magic method.');
14722 /* Cast $html as a string */
14723 $html = (string) $html;
14725 // @log Parsing CSS & Headers
14727 if ($init) {
14728 $this->headerbuffer = '';
14729 $this->textbuffer = [];
14730 $this->fixedPosBlockSave = [];
14732 if ($sub == 1) {
14733 $html = '<style> ' . $html . ' </style>';
14734 } // stylesheet only
14736 if ($this->allow_charset_conversion) {
14737 if ($sub < 1) {
14738 $this->ReadCharset($html);
14740 if ($this->charset_in && $sub != 4) {
14741 $success = iconv($this->charset_in, 'UTF-8//TRANSLIT', $html);
14742 if ($success) {
14743 $html = $success;
14748 $html = $this->purify_utf8($html, false);
14749 if ($init) {
14750 $this->blklvl = 0;
14751 $this->lastblocklevelchange = 0;
14752 $this->blk = [];
14753 $this->initialiseBlock($this->blk[0]);
14754 $this->blk[0]['width'] = & $this->pgwidth;
14755 $this->blk[0]['inner_width'] = & $this->pgwidth;
14756 $this->blk[0]['blockContext'] = $this->blockContext;
14759 $zproperties = [];
14760 if ($sub < 2) {
14761 $this->ReadMetaTags($html);
14763 if (preg_match('/<base[^>]*href=["\']([^"\'>]*)["\']/i', $html, $m)) {
14764 $this->SetBasePath($m[1]);
14766 $html = $this->cssManager->ReadCSS($html);
14768 if ($this->autoLangToFont && !$this->usingCoreFont && preg_match('/<html [^>]*lang=[\'\"](.*?)[\'\"]/ism', $html, $m)) {
14769 $html_lang = $m[1];
14772 if (preg_match('/<html [^>]*dir=[\'\"]\s*rtl\s*[\'\"]/ism', $html)) {
14773 $zproperties['DIRECTION'] = 'rtl';
14776 // allow in-line CSS for body tag to be parsed // Get <body> tag inline CSS
14777 if (preg_match('/<body([^>]*)>(.*?)<\/body>/ism', $html, $m) || preg_match('/<body([^>]*)>(.*)$/ism', $html, $m)) {
14778 $html = $m[2];
14779 // Changed to allow style="background: url('bg.jpg')"
14780 if (preg_match('/style=[\"](.*?)[\"]/ism', $m[1], $mm) || preg_match('/style=[\'](.*?)[\']/ism', $m[1], $mm)) {
14781 $zproperties = $this->cssManager->readInlineCSS($mm[1]);
14783 if (preg_match('/dir=[\'\"]\s*rtl\s*[\'\"]/ism', $m[1])) {
14784 $zproperties['DIRECTION'] = 'rtl';
14786 if (isset($html_lang) && $html_lang) {
14787 $zproperties['LANG'] = $html_lang;
14789 if ($this->autoLangToFont && !$this->onlyCoreFonts && preg_match('/lang=[\'\"](.*?)[\'\"]/ism', $m[1], $mm)) {
14790 $zproperties['LANG'] = $mm[1];
14794 $properties = $this->cssManager->MergeCSS('BLOCK', 'BODY', '');
14795 if ($zproperties) {
14796 $properties = $this->cssManager->array_merge_recursive_unique($properties, $zproperties);
14799 if (isset($properties['DIRECTION']) && $properties['DIRECTION']) {
14800 $this->cssManager->CSS['BODY']['DIRECTION'] = $properties['DIRECTION'];
14802 if (!isset($this->cssManager->CSS['BODY']['DIRECTION'])) {
14803 $this->cssManager->CSS['BODY']['DIRECTION'] = $this->directionality;
14804 } else {
14805 $this->SetDirectionality($this->cssManager->CSS['BODY']['DIRECTION']);
14808 $this->setCSS($properties, '', 'BODY');
14810 $this->blk[0]['InlineProperties'] = $this->saveInlineProperties();
14812 if ($sub == 1) {
14813 return '';
14815 if (!isset($this->cssManager->CSS['BODY'])) {
14816 $this->cssManager->CSS['BODY'] = [];
14819 /* -- BACKGROUNDS -- */
14820 if (isset($properties['BACKGROUND-GRADIENT'])) {
14821 $this->bodyBackgroundGradient = $properties['BACKGROUND-GRADIENT'];
14824 if (isset($properties['BACKGROUND-IMAGE']) && $properties['BACKGROUND-IMAGE']) {
14825 $ret = $this->SetBackground($properties, $this->pgwidth);
14826 if ($ret) {
14827 $this->bodyBackgroundImage = $ret;
14830 /* -- END BACKGROUNDS -- */
14832 /* -- CSS-PAGE -- */
14833 // If page-box is set
14834 if ($this->state == 0 && ((isset($this->cssManager->CSS['@PAGE']) && $this->cssManager->CSS['@PAGE']) || (isset($this->cssManager->CSS['@PAGE>>PSEUDO>>FIRST']) && $this->cssManager->CSS['@PAGE>>PSEUDO>>FIRST']))) { // mPDF 5.7.3
14835 $this->page_box['current'] = '';
14836 $this->page_box['using'] = true;
14837 list($pborientation, $pbmgl, $pbmgr, $pbmgt, $pbmgb, $pbmgh, $pbmgf, $hname, $fname, $bg, $resetpagenum, $pagenumstyle, $suppress, $marks, $newformat) = $this->SetPagedMediaCSS('', false, 'O');
14838 $this->DefOrientation = $this->CurOrientation = $pborientation;
14839 $this->orig_lMargin = $this->DeflMargin = $pbmgl;
14840 $this->orig_rMargin = $this->DefrMargin = $pbmgr;
14841 $this->orig_tMargin = $this->tMargin = $pbmgt;
14842 $this->orig_bMargin = $this->bMargin = $pbmgb;
14843 $this->orig_hMargin = $this->margin_header = $pbmgh;
14844 $this->orig_fMargin = $this->margin_footer = $pbmgf;
14845 list($pborientation, $pbmgl, $pbmgr, $pbmgt, $pbmgb, $pbmgh, $pbmgf, $hname, $fname, $bg, $resetpagenum, $pagenumstyle, $suppress, $marks, $newformat) = $this->SetPagedMediaCSS('', true, 'O'); // first page
14846 $this->show_marks = $marks;
14847 if ($hname) {
14848 $this->firstPageBoxHeader = $hname;
14850 if ($fname) {
14851 $this->firstPageBoxFooter = $fname;
14854 /* -- END CSS-PAGE -- */
14856 $parseonly = false;
14857 $this->bufferoutput = false;
14858 if ($sub == 3) {
14859 $parseonly = true;
14860 // Close any open block tags
14861 $arr = [];
14862 $ai = 0;
14863 for ($b = $this->blklvl; $b > 0; $b--) {
14864 $this->tag->CloseTag($this->blk[$b]['tag'], $arr, $ai);
14866 // Output any text left in buffer
14867 if (count($this->textbuffer)) {
14868 $this->printbuffer($this->textbuffer);
14870 $this->textbuffer = [];
14871 } elseif ($sub == 4) {
14872 // Close any open block tags
14873 $arr = [];
14874 $ai = 0;
14875 for ($b = $this->blklvl; $b > 0; $b--) {
14876 $this->tag->CloseTag($this->blk[$b]['tag'], $arr, $ai);
14878 // Output any text left in buffer
14879 if (count($this->textbuffer)) {
14880 $this->printbuffer($this->textbuffer);
14882 $this->bufferoutput = true;
14883 $this->textbuffer = [];
14884 $this->headerbuffer = '';
14885 $properties = $this->cssManager->MergeCSS('BLOCK', 'BODY', '');
14886 $this->setCSS($properties, '', 'BODY');
14889 mb_internal_encoding('UTF-8');
14891 $html = $this->AdjustHTML($html, $this->tabSpaces); // Try to make HTML look more like XHTML
14893 if ($this->autoScriptToLang) {
14894 $html = $this->markScriptToLang($html);
14897 preg_match_all('/<htmlpageheader([^>]*)>(.*?)<\/htmlpageheader>/si', $html, $h);
14898 for ($i = 0; $i < count($h[1]); $i++) {
14899 if (preg_match('/name=[\'|\"](.*?)[\'|\"]/', $h[1][$i], $n)) {
14900 $this->pageHTMLheaders[$n[1]]['html'] = $h[2][$i];
14901 $this->pageHTMLheaders[$n[1]]['h'] = $this->_getHtmlHeight($h[2][$i]);
14904 preg_match_all('/<htmlpagefooter([^>]*)>(.*?)<\/htmlpagefooter>/si', $html, $f);
14905 for ($i = 0; $i < count($f[1]); $i++) {
14906 if (preg_match('/name=[\'|\"](.*?)[\'|\"]/', $f[1][$i], $n)) {
14907 $this->pageHTMLfooters[$n[1]]['html'] = $f[2][$i];
14908 $this->pageHTMLfooters[$n[1]]['h'] = $this->_getHtmlHeight($f[2][$i]);
14912 $html = preg_replace('/<htmlpageheader.*?<\/htmlpageheader>/si', '', $html);
14913 $html = preg_replace('/<htmlpagefooter.*?<\/htmlpagefooter>/si', '', $html);
14915 if ($this->state == 0 && $sub != 1 && $sub != 3 && $sub != 4) {
14916 $this->AddPage($this->CurOrientation);
14920 if (isset($hname) && preg_match('/^html_(.*)$/i', $hname, $n)) {
14921 $this->SetHTMLHeader($this->pageHTMLheaders[$n[1]], 'O', true);
14923 if (isset($fname) && preg_match('/^html_(.*)$/i', $fname, $n)) {
14924 $this->SetHTMLFooter($this->pageHTMLfooters[$n[1]], 'O');
14929 $html = str_replace('<?', '< ', $html); // Fix '<?XML' bug from HTML code generated by MS Word
14931 $this->checkSIP = false;
14932 $this->checkSMP = false;
14933 $this->checkCJK = false;
14934 if ($this->onlyCoreFonts) {
14935 $html = $this->SubstituteChars($html);
14936 } else {
14937 if (preg_match("/([" . $this->pregRTLchars . "])/u", $html)) {
14938 $this->biDirectional = true;
14939 } // *OTL*
14940 if (preg_match("/([\x{20000}-\x{2FFFF}])/u", $html)) {
14941 $this->checkSIP = true;
14943 if (preg_match("/([\x{10000}-\x{1FFFF}])/u", $html)) {
14944 $this->checkSMP = true;
14946 /* -- CJK-FONTS -- */
14947 if (preg_match("/([" . $this->pregCJKchars . "])/u", $html)) {
14948 $this->checkCJK = true;
14950 /* -- END CJK-FONTS -- */
14953 // Don't allow non-breaking spaces that are converted to substituted chars or will break anyway and mess up table width calc.
14954 $html = str_replace('<tta>160</tta>', chr(32), $html);
14955 $html = str_replace('</tta><tta>', '|', $html);
14956 $html = str_replace('</tts><tts>', '|', $html);
14957 $html = str_replace('</ttz><ttz>', '|', $html);
14959 // Add new supported tags in the DisableTags function
14960 $html = strip_tags($html, $this->enabledtags); // remove all unsupported tags, but the ones inside the 'enabledtags' string
14961 // Explode the string in order to parse the HTML code
14962 $a = preg_split('/<(.*?)>/ms', $html, -1, PREG_SPLIT_DELIM_CAPTURE);
14963 // ? more accurate regexp that allows e.g. <a name="Silly <name>">
14964 // if changing - also change in fn.SubstituteChars()
14965 // $a = preg_split ('/<((?:[^<>]+(?:"[^"]*"|\'[^\']*\')?)+)>/ms', $html, -1, PREG_SPLIT_DELIM_CAPTURE);
14967 if ($this->mb_enc) {
14968 mb_internal_encoding($this->mb_enc);
14970 $pbc = 0;
14971 $this->subPos = -1;
14972 $cnt = count($a);
14973 for ($i = 0; $i < $cnt; $i++) {
14974 $e = $a[$i];
14975 if ($i % 2 == 0) {
14976 // TEXT
14977 if ($this->blk[$this->blklvl]['hide']) {
14978 continue;
14980 if ($this->inlineDisplayOff) {
14981 continue;
14983 if ($this->inMeter) {
14984 continue;
14987 if ($this->inFixedPosBlock) {
14988 $this->fixedPosBlock .= $e;
14989 continue;
14990 } // *CSS-POSITION*
14991 if (strlen($e) == 0) {
14992 continue;
14995 if ($this->ignorefollowingspaces && !$this->ispre) {
14996 if (strlen(ltrim($e)) == 0) {
14997 continue;
14999 if ($this->FontFamily != 'csymbol' && $this->FontFamily != 'czapfdingbats' && substr($e, 0, 1) == ' ') {
15000 $this->ignorefollowingspaces = false;
15001 $e = ltrim($e);
15005 $this->OTLdata = null; // mPDF 5.7.1
15007 $e = UtfString::strcode2utf($e);
15008 $e = $this->lesser_entity_decode($e);
15010 if ($this->usingCoreFont) {
15011 // If core font is selected in document which is not onlyCoreFonts - substitute with non-core font
15012 if ($this->useSubstitutions && !$this->onlyCoreFonts && $this->subPos < $i && !$this->specialcontent) {
15013 $cnt += $this->SubstituteCharsNonCore($a, $i, $e);
15015 // CONVERT ENCODING
15016 $e = mb_convert_encoding($e, $this->mb_enc, 'UTF-8');
15017 if ($this->textvar & TextVars::FT_UPPERCASE) {
15018 $e = mb_strtoupper($e, $this->mb_enc);
15019 } // mPDF 5.7.1
15020 elseif ($this->textvar & TextVars::FT_LOWERCASE) {
15021 $e = mb_strtolower($e, $this->mb_enc);
15022 } // mPDF 5.7.1
15023 elseif ($this->textvar & TextVars::FT_CAPITALIZE) {
15024 $e = mb_convert_case($e, MB_CASE_TITLE, "UTF-8");
15025 } // mPDF 5.7.1
15026 } else {
15027 if ($this->checkSIP && $this->CurrentFont['sipext'] && $this->subPos < $i && (!$this->specialcontent || !$this->useActiveForms)) {
15028 $cnt += $this->SubstituteCharsSIP($a, $i, $e);
15031 if ($this->useSubstitutions && !$this->onlyCoreFonts && $this->CurrentFont['type'] != 'Type0' && $this->subPos < $i && (!$this->specialcontent || !$this->useActiveForms)) {
15032 $cnt += $this->SubstituteCharsMB($a, $i, $e);
15035 if ($this->textvar & TextVars::FT_UPPERCASE) {
15036 $e = mb_strtoupper($e, $this->mb_enc);
15037 } elseif ($this->textvar & TextVars::FT_LOWERCASE) {
15038 $e = mb_strtolower($e, $this->mb_enc);
15039 } elseif ($this->textvar & TextVars::FT_CAPITALIZE) {
15040 $e = mb_convert_case($e, MB_CASE_TITLE, "UTF-8");
15043 /* -- OTL -- */
15044 // Use OTL OpenType Table Layout - GSUB & GPOS
15045 if (isset($this->CurrentFont['useOTL']) && $this->CurrentFont['useOTL'] && (!$this->specialcontent || !$this->useActiveForms)) {
15046 if (!$this->otl) {
15047 $this->otl = new Otl($this, $this->fontCache);
15049 $e = $this->otl->applyOTL($e, $this->CurrentFont['useOTL']);
15050 $this->OTLdata = $this->otl->OTLdata;
15051 $this->otl->removeChar($e, $this->OTLdata, "\xef\xbb\xbf"); // Remove ZWNBSP (also Byte order mark FEFF)
15052 } /* -- END OTL -- */
15053 else {
15054 // removes U+200E/U+200F LTR and RTL mark and U+200C/U+200D Zero-width Joiner and Non-joiner
15055 $e = preg_replace("/[\xe2\x80\x8c\xe2\x80\x8d\xe2\x80\x8e\xe2\x80\x8f]/u", '', $e);
15056 $e = preg_replace("/[\xef\xbb\xbf]/u", '', $e); // Remove ZWNBSP (also Byte order mark FEFF)
15060 if (($this->tts) || ($this->ttz) || ($this->tta)) {
15061 $es = explode('|', $e);
15062 $e = '';
15063 foreach ($es as $val) {
15064 $e .= chr($val);
15068 // FORM ELEMENTS
15069 if ($this->specialcontent) {
15070 /* -- FORMS -- */
15071 // SELECT tag (form element)
15072 if ($this->specialcontent == "type=select") {
15073 $e = ltrim($e);
15074 if (!empty($this->OTLdata)) {
15075 $this->otl->trimOTLdata($this->OTLdata, true, false);
15076 } // *OTL*
15077 $stringwidth = $this->GetStringWidth($e);
15078 if (!isset($this->selectoption['MAXWIDTH']) || $stringwidth > $this->selectoption['MAXWIDTH']) {
15079 $this->selectoption['MAXWIDTH'] = $stringwidth;
15081 if (!isset($this->selectoption['SELECTED']) || $this->selectoption['SELECTED'] == '') {
15082 $this->selectoption['SELECTED'] = $e;
15083 if (!empty($this->OTLdata)) {
15084 $this->selectoption['SELECTED-OTLDATA'] = $this->OTLdata;
15085 } // *OTL*
15087 // Active Forms
15088 if (isset($this->selectoption['ACTIVE']) && $this->selectoption['ACTIVE']) {
15089 $this->selectoption['ITEMS'][] = ['exportValue' => $this->selectoption['currentVAL'], 'content' => $e, 'selected' => $this->selectoption['currentSEL']];
15091 $this->OTLdata = [];
15092 } // TEXTAREA
15093 else {
15094 $objattr = unserialize($this->specialcontent);
15095 $objattr['text'] = $e;
15096 $objattr['OTLdata'] = $this->OTLdata;
15097 $this->OTLdata = [];
15098 $te = "\xbb\xa4\xactype=textarea,objattr=" . serialize($objattr) . "\xbb\xa4\xac";
15099 if ($this->tdbegin) {
15100 $this->_saveCellTextBuffer($te, $this->HREF);
15101 } else {
15102 $this->_saveTextBuffer($te, $this->HREF);
15105 /* -- END FORMS -- */
15106 } // TABLE
15107 elseif ($this->tableLevel) {
15108 /* -- TABLES -- */
15109 if ($this->tdbegin) {
15110 if (($this->ignorefollowingspaces) && !$this->ispre) {
15111 $e = ltrim($e);
15112 if (!empty($this->OTLdata)) {
15113 $this->otl->trimOTLdata($this->OTLdata, true, false);
15114 } // *OTL*
15116 if ($e || $e === '0') {
15117 if ($this->blockjustfinished && $this->cell[$this->row][$this->col]['s'] > 0) {
15118 $this->_saveCellTextBuffer("\n");
15119 if (!isset($this->cell[$this->row][$this->col]['maxs'])) {
15120 $this->cell[$this->row][$this->col]['maxs'] = $this->cell[$this->row][$this->col]['s'];
15121 } elseif ($this->cell[$this->row][$this->col]['maxs'] < $this->cell[$this->row][$this->col]['s']) {
15122 $this->cell[$this->row][$this->col]['maxs'] = $this->cell[$this->row][$this->col]['s'];
15124 $this->cell[$this->row][$this->col]['s'] = 0; // reset
15126 $this->blockjustfinished = false;
15128 if (!isset($this->cell[$this->row][$this->col]['R']) || !$this->cell[$this->row][$this->col]['R']) {
15129 if (isset($this->cell[$this->row][$this->col]['s'])) {
15130 $this->cell[$this->row][$this->col]['s'] += $this->GetStringWidth($e, false, $this->OTLdata, $this->textvar);
15131 } else {
15132 $this->cell[$this->row][$this->col]['s'] = $this->GetStringWidth($e, false, $this->OTLdata, $this->textvar);
15134 if (!empty($this->spanborddet)) {
15135 $this->cell[$this->row][$this->col]['s'] += (isset($this->spanborddet['L']['w']) ? $this->spanborddet['L']['w'] : 0) + (isset($this->spanborddet['R']['w']) ? $this->spanborddet['R']['w'] : 0);
15138 $this->_saveCellTextBuffer($e, $this->HREF);
15139 if (substr($this->cell[$this->row][$this->col]['a'], 0, 1) == 'D') {
15140 $dp = $this->decimal_align[substr($this->cell[$this->row][$this->col]['a'], 0, 2)];
15141 $s = preg_split('/' . preg_quote($dp, '/') . '/', $e, 2); // ? needs to be /u if not core
15142 $s0 = $this->GetStringWidth($s[0], false);
15143 if (isset($s[1]) && $s[1]) {
15144 $s1 = $this->GetStringWidth(($s[1] . $dp), false);
15145 } else {
15146 $s1 = 0;
15148 if (!isset($this->table[$this->tableLevel][$this->tbctr[$this->tableLevel]]['decimal_align'][$this->col]['maxs0'])) {
15149 $this->table[$this->tableLevel][$this->tbctr[$this->tableLevel]]['decimal_align'][$this->col]['maxs0'] = $s0;
15150 } else {
15151 $this->table[$this->tableLevel][$this->tbctr[$this->tableLevel]]['decimal_align'][$this->col]['maxs0'] = max($s0, $this->table[$this->tableLevel][$this->tbctr[$this->tableLevel]]['decimal_align'][$this->col]['maxs0']);
15153 if (!isset($this->table[$this->tableLevel][$this->tbctr[$this->tableLevel]]['decimal_align'][$this->col]['maxs1'])) {
15154 $this->table[$this->tableLevel][$this->tbctr[$this->tableLevel]]['decimal_align'][$this->col]['maxs1'] = $s1;
15155 } else {
15156 $this->table[$this->tableLevel][$this->tbctr[$this->tableLevel]]['decimal_align'][$this->col]['maxs1'] = max($s1, $this->table[$this->tableLevel][$this->tbctr[$this->tableLevel]]['decimal_align'][$this->col]['maxs1']);
15160 $this->nestedtablejustfinished = false;
15161 $this->linebreakjustfinished = false;
15164 /* -- END TABLES -- */
15165 } // ALL ELSE
15166 else {
15167 if ($this->ignorefollowingspaces && !$this->ispre) {
15168 $e = ltrim($e);
15169 if (!empty($this->OTLdata)) {
15170 $this->otl->trimOTLdata($this->OTLdata, true, false);
15171 } // *OTL*
15173 if ($e || $e === '0') {
15174 $this->_saveTextBuffer($e, $this->HREF);
15177 if ($e || $e === '0') {
15178 $this->ignorefollowingspaces = false; // mPDF 6
15180 if (substr($e, -1, 1) == ' ' && !$this->ispre && $this->FontFamily != 'csymbol' && $this->FontFamily != 'czapfdingbats') {
15181 $this->ignorefollowingspaces = true;
15183 } else { // TAG **
15184 if (isset($e[0]) && $e[0] == '/') {
15185 $endtag = trim(strtoupper(substr($e, 1)));
15187 /* -- CSS-POSITION -- */
15188 // mPDF 6
15189 if ($this->inFixedPosBlock) {
15190 if (in_array($endtag, $this->outerblocktags) || in_array($endtag, $this->innerblocktags)) {
15191 $this->fixedPosBlockDepth--;
15193 if ($this->fixedPosBlockDepth == 0) {
15194 $this->fixedPosBlockSave[] = [$this->fixedPosBlock, $this->fixedPosBlockBBox, $this->page];
15195 $this->fixedPosBlock = '';
15196 $this->inFixedPosBlock = false;
15197 continue;
15199 $this->fixedPosBlock .= '<' . $e . '>';
15200 continue;
15202 /* -- END CSS-POSITION -- */
15204 // mPDF 6
15205 // Correct for tags where HTML5 specifies optional end tags (see also OpenTag() )
15206 if ($this->allow_html_optional_endtags && !$parseonly) {
15207 if (isset($this->blk[$this->blklvl]['tag'])) {
15208 $closed = false;
15209 // li end tag may be omitted if there is no more content in the parent element
15210 if (!$closed && $this->blk[$this->blklvl]['tag'] == 'LI' && $endtag != 'LI' && (in_array($endtag, $this->outerblocktags) || in_array($endtag, $this->innerblocktags))) {
15211 $this->tag->CloseTag('LI', $a, $i);
15212 $closed = true;
15214 // dd end tag may be omitted if there is no more content in the parent element
15215 if (!$closed && $this->blk[$this->blklvl]['tag'] == 'DD' && $endtag != 'DD' && (in_array($endtag, $this->outerblocktags) || in_array($endtag, $this->innerblocktags))) {
15216 $this->tag->CloseTag('DD', $a, $i);
15217 $closed = true;
15219 // p end tag may be omitted if there is no more content in the parent element and the parent element is not an A element [??????]
15220 if (!$closed && $this->blk[$this->blklvl]['tag'] == 'P' && $endtag != 'P' && (in_array($endtag, $this->outerblocktags) || in_array($endtag, $this->innerblocktags))) {
15221 $this->tag->CloseTag('P', $a, $i);
15222 $closed = true;
15224 // option end tag may be omitted if there is no more content in the parent element
15225 if (!$closed && $this->blk[$this->blklvl]['tag'] == 'OPTION' && $endtag != 'OPTION' && (in_array($endtag, $this->outerblocktags) || in_array($endtag, $this->innerblocktags))) {
15226 $this->tag->CloseTag('OPTION', $a, $i);
15227 $closed = true;
15230 /* -- TABLES -- */
15231 // Check for Table tags where HTML specifies optional end tags,
15232 if ($endtag == 'TABLE') {
15233 if ($this->lastoptionaltag == 'THEAD' || $this->lastoptionaltag == 'TBODY' || $this->lastoptionaltag == 'TFOOT') {
15234 $this->tag->CloseTag($this->lastoptionaltag, $a, $i);
15236 if ($this->lastoptionaltag == 'TR') {
15237 $this->tag->CloseTag('TR', $a, $i);
15239 if ($this->lastoptionaltag == 'TD' || $this->lastoptionaltag == 'TH') {
15240 $this->tag->CloseTag($this->lastoptionaltag, $a, $i);
15241 $this->tag->CloseTag('TR', $a, $i);
15244 if ($endtag == 'THEAD' || $endtag == 'TBODY' || $endtag == 'TFOOT') {
15245 if ($this->lastoptionaltag == 'TR') {
15246 $this->tag->CloseTag('TR', $a, $i);
15248 if ($this->lastoptionaltag == 'TD' || $this->lastoptionaltag == 'TH') {
15249 $this->tag->CloseTag($this->lastoptionaltag, $a, $i);
15250 $this->tag->CloseTag('TR', $a, $i);
15253 if ($endtag == 'TR') {
15254 if ($this->lastoptionaltag == 'TD' || $this->lastoptionaltag == 'TH') {
15255 $this->tag->CloseTag($this->lastoptionaltag, $a, $i);
15258 /* -- END TABLES -- */
15262 // mPDF 6
15263 if ($this->blk[$this->blklvl]['hide']) {
15264 if (in_array($endtag, $this->outerblocktags) || in_array($endtag, $this->innerblocktags)) {
15265 unset($this->blk[$this->blklvl]);
15266 $this->blklvl--;
15268 continue;
15271 // mPDF 6
15272 $this->tag->CloseTag($endtag, $a, $i); // mPDF 6
15273 } else { // OPENING TAG
15274 if ($this->blk[$this->blklvl]['hide']) {
15275 if (strpos($e, ' ')) {
15276 $te = strtoupper(substr($e, 0, strpos($e, ' ')));
15277 } else {
15278 $te = strtoupper($e);
15280 // mPDF 6
15281 if ($te == 'THEAD' || $te == 'TBODY' || $te == 'TFOOT' || $te == 'TR' || $te == 'TD' || $te == 'TH') {
15282 $this->lastoptionaltag = $te;
15284 if (in_array($te, $this->outerblocktags) || in_array($te, $this->innerblocktags)) {
15285 $this->blklvl++;
15286 $this->blk[$this->blklvl]['hide'] = true;
15287 $this->blk[$this->blklvl]['tag'] = $te; // mPDF 6
15289 continue;
15292 /* -- CSS-POSITION -- */
15293 if ($this->inFixedPosBlock) {
15294 if (strpos($e, ' ')) {
15295 $te = strtoupper(substr($e, 0, strpos($e, ' ')));
15296 } else {
15297 $te = strtoupper($e);
15299 $this->fixedPosBlock .= '<' . $e . '>';
15300 if (in_array($te, $this->outerblocktags) || in_array($te, $this->innerblocktags)) {
15301 $this->fixedPosBlockDepth++;
15303 continue;
15305 /* -- END CSS-POSITION -- */
15306 $regexp = '|=\'(.*?)\'|s'; // eliminate single quotes, if any
15307 $e = preg_replace($regexp, "=\"\$1\"", $e);
15308 // changes anykey=anyvalue to anykey="anyvalue" (only do this inside [some] tags)
15309 if (substr($e, 0, 10) != 'pageheader' && substr($e, 0, 10) != 'pagefooter' && substr($e, 0, 12) != 'tocpagebreak' && substr($e, 0, 10) != 'indexentry' && substr($e, 0, 8) != 'tocentry') { // mPDF 6 (ZZZ99H)
15310 $regexp = '| (\\w+?)=([^\\s>"]+)|si';
15311 $e = preg_replace($regexp, " \$1=\"\$2\"", $e);
15314 $e = preg_replace('/ (\\S+?)\s*=\s*"/i', " \\1=\"", $e);
15316 // Fix path values, if needed
15317 $orig_srcpath = '';
15318 if ((stristr($e, "href=") !== false) or ( stristr($e, "src=") !== false)) {
15319 $regexp = '/ (href|src)\s*=\s*"(.*?)"/i';
15320 preg_match($regexp, $e, $auxiliararray);
15321 if (isset($auxiliararray[2])) {
15322 $path = $auxiliararray[2];
15323 } else {
15324 $path = '';
15326 if (trim($path) != '' && !(stristr($e, "src=") !== false && substr($path, 0, 4) == 'var:') && substr($path, 0, 1) != '@') {
15327 $path = htmlspecialchars_decode($path); // mPDF 5.7.4 URLs
15328 $orig_srcpath = $path;
15329 $this->GetFullPath($path);
15330 $regexp = '/ (href|src)="(.*?)"/i';
15331 $e = preg_replace($regexp, ' \\1="' . $path . '"', $e);
15333 }//END of Fix path values
15334 // Extract attributes
15335 $contents = [];
15336 $contents1 = [];
15337 $contents2 = [];
15338 // Changed to allow style="background: url('bg.jpg')"
15339 // Changed to improve performance; maximum length of \S (attribute) = 16
15340 // Increase allowed attribute name to 32 - cutting off "toc-even-header-name" etc.
15341 preg_match_all('/\\S{1,32}=["][^"]*["]/', $e, $contents1);
15342 preg_match_all('/\\S{1,32}=[\'][^\']*[\']/i', $e, $contents2);
15344 $contents = array_merge($contents1, $contents2);
15345 preg_match('/\\S+/', $e, $a2);
15346 $tag = (isset($a2[0]) ? strtoupper($a2[0]) : '');
15347 $attr = [];
15348 if ($orig_srcpath) {
15349 $attr['ORIG_SRC'] = $orig_srcpath;
15351 if (!empty($contents)) {
15352 foreach ($contents[0] as $v) {
15353 // Changed to allow style="background: url('bg.jpg')"
15354 if (preg_match('/^([^=]*)=["]?([^"]*)["]?$/', $v, $a3) || preg_match('/^([^=]*)=[\']?([^\']*)[\']?$/', $v, $a3)) {
15355 if (strtoupper($a3[1]) == 'ID' || strtoupper($a3[1]) == 'CLASS') { // 4.2.013 Omits STYLE
15356 $attr[strtoupper($a3[1])] = trim(strtoupper($a3[2]));
15357 } // includes header-style-right etc. used for <pageheader>
15358 elseif (preg_match('/^(HEADER|FOOTER)-STYLE/i', $a3[1])) {
15359 $attr[strtoupper($a3[1])] = trim(strtoupper($a3[2]));
15360 } else {
15361 $attr[strtoupper($a3[1])] = trim($a3[2]);
15366 $this->tag->OpenTag($tag, $attr, $a, $i); // mPDF 6
15367 /* -- CSS-POSITION -- */
15368 if ($this->inFixedPosBlock) {
15369 $this->fixedPosBlockBBox = [$tag, $attr, $this->x, $this->y];
15370 $this->fixedPosBlock = '';
15371 $this->fixedPosBlockDepth = 1;
15373 /* -- END CSS-POSITION -- */
15374 if (preg_match('/\/$/', $e)) {
15375 $this->tag->CloseTag($tag, $a, $i);
15378 } // end TAG
15379 } // end of foreach($a as $i=>$e)
15381 if ($close) {
15382 // Close any open block tags
15383 for ($b = $this->blklvl; $b > 0; $b--) {
15384 $this->tag->CloseTag($this->blk[$b]['tag'], $a, $i);
15387 // Output any text left in buffer
15388 if (count($this->textbuffer) && !$parseonly) {
15389 $this->printbuffer($this->textbuffer);
15391 if (!$parseonly) {
15392 $this->textbuffer = [];
15395 /* -- CSS-FLOAT -- */
15396 // If ended with a float, need to move to end page
15397 $currpos = $this->page * 1000 + $this->y;
15398 if (isset($this->blk[$this->blklvl]['float_endpos']) && $this->blk[$this->blklvl]['float_endpos'] > $currpos) {
15399 $old_page = $this->page;
15400 $new_page = intval($this->blk[$this->blklvl]['float_endpos'] / 1000);
15401 if ($old_page != $new_page) {
15402 $s = $this->PrintPageBackgrounds();
15403 // Writes after the marker so not overwritten later by page background etc.
15404 $this->pages[$this->page] = preg_replace('/(___BACKGROUND___PATTERNS' . $this->uniqstr . ')/', '\\1' . "\n" . $s . "\n", $this->pages[$this->page]);
15405 $this->pageBackgrounds = [];
15406 $this->page = $new_page;
15407 $this->ResetMargins();
15408 $this->Reset();
15409 $this->pageoutput[$this->page] = [];
15411 $this->y = (($this->blk[$this->blklvl]['float_endpos'] * 1000) % 1000000) / 1000; // mod changes operands to integers before processing
15413 /* -- END CSS-FLOAT -- */
15415 /* -- CSS-IMAGE-FLOAT -- */
15416 $this->printfloatbuffer();
15417 /* -- END CSS-IMAGE-FLOAT -- */
15419 // Create Internal Links, if needed
15420 if (!empty($this->internallink)) {
15421 foreach ($this->internallink as $k => $v) {
15422 if (strpos($k, "#") !== false) {
15423 continue;
15424 } // ignore
15425 $ypos = $v['Y'];
15426 $pagenum = $v['PAGE'];
15427 $sharp = "#";
15428 while (array_key_exists($sharp . $k, $this->internallink)) {
15429 $internallink = $this->internallink[$sharp . $k];
15430 $this->SetLink($internallink, $ypos, $pagenum);
15431 $sharp .= "#";
15436 $this->bufferoutput = false;
15438 /* -- CSS-POSITION -- */
15439 if (count($this->fixedPosBlockSave)) {
15440 foreach ($this->fixedPosBlockSave as $fpbs) {
15441 $old_page = $this->page;
15442 $this->page = $fpbs[2];
15443 $this->WriteFixedPosHTML($fpbs[0], 0, 0, 100, 100, 'auto', $fpbs[1]); // 0,0,10,10 are overwritten by bbox
15444 $this->page = $old_page;
15446 $this->fixedPosBlockSave = [];
15448 /* -- END CSS-POSITION -- */
15452 /* -- CSS-POSITION -- */
15454 function WriteFixedPosHTML($html, $x, $y, $w, $h, $overflow = 'visible', $bounding = [])
15456 // $overflow can be 'hidden', 'visible' or 'auto' - 'auto' causes autofit to size
15457 // Annotations disabled - enabled in mPDF 5.0
15458 // Links do work
15459 // Will always go on current page (or start Page 1 if required)
15460 // Probably INCOMPATIBLE WITH keep with table, columns etc.
15461 // Called externally or interally via <div style="position: [fixed|absolute]">
15462 // When used internally, $x $y $w $h and $overflow are all overridden by $bounding
15464 $overflow = strtolower($overflow);
15465 if ($this->state == 0) {
15466 $this->AddPage($this->CurOrientation);
15468 $save_y = $this->y;
15469 $save_x = $this->x;
15470 $this->fullImageHeight = $this->h;
15471 $save_cols = false;
15472 /* -- COLUMNS -- */
15473 if ($this->ColActive) {
15474 $save_cols = true;
15475 $save_nbcol = $this->NbCol; // other values of gap and vAlign will not change by setting Columns off
15476 $this->SetColumns(0);
15478 /* -- END COLUMNS -- */
15479 $save_annots = $this->title2annots; // *ANNOTATIONS*
15480 $this->writingHTMLheader = true; // a FIX to stop pagebreaks etc.
15481 $this->writingHTMLfooter = true;
15482 $this->InFooter = true; // suppresses autopagebreaks
15483 $save_bgs = $this->pageBackgrounds;
15484 $checkinnerhtml = preg_replace('/\s/', '', $html);
15485 $rotate = 0;
15487 if ($w > $this->w) {
15488 $x = 0;
15489 $w = $this->w;
15491 if ($h > $this->h) {
15492 $y = 0;
15493 $h = $this->h;
15495 if ($x > $this->w) {
15496 $x = $this->w - $w;
15498 if ($y > $this->h) {
15499 $y = $this->h - $h;
15502 if (!empty($bounding)) {
15503 // $cont_ containing block = full physical page (position: absolute) or page inside margins (position: fixed)
15504 // $bbox_ Bounding box is the <div> which is positioned absolutely/fixed
15505 // top/left/right/bottom/width/height/background*/border*/padding*/margin* are taken from bounding
15506 // font*[family/size/style/weight]/line-height/text*[align/decoration/transform/indent]/color are transferred to $inner
15507 // as an enclosing <div> (after having checked ID/CLASS)
15508 // $x, $y, $w, $h are inside of $bbox_ = containing box for $inner_
15509 // $inner_ InnerHTML is the contents of that block to be output
15510 $tag = $bounding[0];
15511 $attr = $bounding[1];
15512 $orig_x0 = $bounding[2];
15513 $orig_y0 = $bounding[3];
15515 // As in WriteHTML() initialising
15516 $this->blklvl = 0;
15517 $this->lastblocklevelchange = 0;
15518 $this->blk = [];
15519 $this->initialiseBlock($this->blk[0]);
15521 $this->blk[0]['width'] = & $this->pgwidth;
15522 $this->blk[0]['inner_width'] = & $this->pgwidth;
15524 $this->blk[0]['blockContext'] = $this->blockContext;
15526 $properties = $this->cssManager->MergeCSS('BLOCK', 'BODY', '');
15527 $this->setCSS($properties, '', 'BODY');
15528 $this->blklvl = 1;
15529 $this->initialiseBlock($this->blk[1]);
15530 $this->blk[1]['tag'] = $tag;
15531 $this->blk[1]['attr'] = $attr;
15532 $this->Reset();
15533 $p = $this->cssManager->MergeCSS('BLOCK', $tag, $attr);
15534 if (isset($p['ROTATE']) && ($p['ROTATE'] == 90 || $p['ROTATE'] == -90 || $p['ROTATE'] == 180)) {
15535 $rotate = $p['ROTATE'];
15536 } // mPDF 6
15537 if (isset($p['OVERFLOW'])) {
15538 $overflow = strtolower($p['OVERFLOW']);
15540 if (strtolower($p['POSITION']) == 'fixed') {
15541 $cont_w = $this->pgwidth; // $this->blk[0]['inner_width'];
15542 $cont_h = $this->h - $this->tMargin - $this->bMargin;
15543 $cont_x = $this->lMargin;
15544 $cont_y = $this->tMargin;
15545 } else {
15546 $cont_w = $this->w; // ABSOLUTE;
15547 $cont_h = $this->h;
15548 $cont_x = 0;
15549 $cont_y = 0;
15552 // Pass on in-line properties to the innerhtml
15553 $css = '';
15554 if (isset($p['TEXT-ALIGN'])) {
15555 $css .= 'text-align: ' . strtolower($p['TEXT-ALIGN']) . '; ';
15557 if (isset($p['TEXT-TRANSFORM'])) {
15558 $css .= 'text-transform: ' . strtolower($p['TEXT-TRANSFORM']) . '; ';
15560 if (isset($p['TEXT-INDENT'])) {
15561 $css .= 'text-indent: ' . strtolower($p['TEXT-INDENT']) . '; ';
15563 if (isset($p['TEXT-DECORATION'])) {
15564 $css .= 'text-decoration: ' . strtolower($p['TEXT-DECORATION']) . '; ';
15566 if (isset($p['FONT-FAMILY'])) {
15567 $css .= 'font-family: ' . strtolower($p['FONT-FAMILY']) . '; ';
15569 if (isset($p['FONT-STYLE'])) {
15570 $css .= 'font-style: ' . strtolower($p['FONT-STYLE']) . '; ';
15572 if (isset($p['FONT-WEIGHT'])) {
15573 $css .= 'font-weight: ' . strtolower($p['FONT-WEIGHT']) . '; ';
15575 if (isset($p['FONT-SIZE'])) {
15576 $css .= 'font-size: ' . strtolower($p['FONT-SIZE']) . '; ';
15578 if (isset($p['LINE-HEIGHT'])) {
15579 $css .= 'line-height: ' . strtolower($p['LINE-HEIGHT']) . '; ';
15581 if (isset($p['TEXT-SHADOW'])) {
15582 $css .= 'text-shadow: ' . strtolower($p['TEXT-SHADOW']) . '; ';
15584 if (isset($p['LETTER-SPACING'])) {
15585 $css .= 'letter-spacing: ' . strtolower($p['LETTER-SPACING']) . '; ';
15587 // mPDF 6
15588 if (isset($p['FONT-VARIANT-POSITION'])) {
15589 $css .= 'font-variant-position: ' . strtolower($p['FONT-VARIANT-POSITION']) . '; ';
15591 if (isset($p['FONT-VARIANT-CAPS'])) {
15592 $css .= 'font-variant-caps: ' . strtolower($p['FONT-VARIANT-CAPS']) . '; ';
15594 if (isset($p['FONT-VARIANT-LIGATURES'])) {
15595 $css .= 'font-variant-ligatures: ' . strtolower($p['FONT-VARIANT-LIGATURES']) . '; ';
15597 if (isset($p['FONT-VARIANT-NUMERIC'])) {
15598 $css .= 'font-variant-numeric: ' . strtolower($p['FONT-VARIANT-NUMERIC']) . '; ';
15600 if (isset($p['FONT-VARIANT-ALTERNATES'])) {
15601 $css .= 'font-variant-alternates: ' . strtolower($p['FONT-VARIANT-ALTERNATES']) . '; ';
15603 if (isset($p['FONT-FEATURE-SETTINGS'])) {
15604 $css .= 'font-feature-settings: ' . strtolower($p['FONT-FEATURE-SETTINGS']) . '; ';
15606 if (isset($p['FONT-LANGUAGE-OVERRIDE'])) {
15607 $css .= 'font-language-override: ' . strtolower($p['FONT-LANGUAGE-OVERRIDE']) . '; ';
15609 if (isset($p['FONT-KERNING'])) {
15610 $css .= 'font-kerning: ' . strtolower($p['FONT-KERNING']) . '; ';
15613 if (isset($p['COLOR'])) {
15614 $css .= 'color: ' . strtolower($p['COLOR']) . '; ';
15616 if (isset($p['Z-INDEX'])) {
15617 $css .= 'z-index: ' . $p['Z-INDEX'] . '; ';
15619 if ($css) {
15620 $html = '<div style="' . $css . '">' . $html . '</div>';
15622 // Copy over (only) the properties to set for border and background
15623 $pb = [];
15624 $pb['MARGIN-TOP'] = (isset($p['MARGIN-TOP']) ? $p['MARGIN-TOP'] : '');
15625 $pb['MARGIN-RIGHT'] = (isset($p['MARGIN-RIGHT']) ? $p['MARGIN-RIGHT'] : '');
15626 $pb['MARGIN-BOTTOM'] = (isset($p['MARGIN-BOTTOM']) ? $p['MARGIN-BOTTOM'] : '');
15627 $pb['MARGIN-LEFT'] = (isset($p['MARGIN-LEFT']) ? $p['MARGIN-LEFT'] : '');
15628 $pb['PADDING-TOP'] = (isset($p['PADDING-TOP']) ? $p['PADDING-TOP'] : '');
15629 $pb['PADDING-RIGHT'] = (isset($p['PADDING-RIGHT']) ? $p['PADDING-RIGHT'] : '');
15630 $pb['PADDING-BOTTOM'] = (isset($p['PADDING-BOTTOM']) ? $p['PADDING-BOTTOM'] : '');
15631 $pb['PADDING-LEFT'] = (isset($p['PADDING-LEFT']) ? $p['PADDING-LEFT'] : '');
15632 $pb['BORDER-TOP'] = (isset($p['BORDER-TOP']) ? $p['BORDER-TOP'] : '');
15633 $pb['BORDER-RIGHT'] = (isset($p['BORDER-RIGHT']) ? $p['BORDER-RIGHT'] : '');
15634 $pb['BORDER-BOTTOM'] = (isset($p['BORDER-BOTTOM']) ? $p['BORDER-BOTTOM'] : '');
15635 $pb['BORDER-LEFT'] = (isset($p['BORDER-LEFT']) ? $p['BORDER-LEFT'] : '');
15636 if (isset($p['BORDER-TOP-LEFT-RADIUS-H'])) {
15637 $pb['BORDER-TOP-LEFT-RADIUS-H'] = $p['BORDER-TOP-LEFT-RADIUS-H'];
15639 if (isset($p['BORDER-TOP-LEFT-RADIUS-V'])) {
15640 $pb['BORDER-TOP-LEFT-RADIUS-V'] = $p['BORDER-TOP-LEFT-RADIUS-V'];
15642 if (isset($p['BORDER-TOP-RIGHT-RADIUS-H'])) {
15643 $pb['BORDER-TOP-RIGHT-RADIUS-H'] = $p['BORDER-TOP-RIGHT-RADIUS-H'];
15645 if (isset($p['BORDER-TOP-RIGHT-RADIUS-V'])) {
15646 $pb['BORDER-TOP-RIGHT-RADIUS-V'] = $p['BORDER-TOP-RIGHT-RADIUS-V'];
15648 if (isset($p['BORDER-BOTTOM-LEFT-RADIUS-H'])) {
15649 $pb['BORDER-BOTTOM-LEFT-RADIUS-H'] = $p['BORDER-BOTTOM-LEFT-RADIUS-H'];
15651 if (isset($p['BORDER-BOTTOM-LEFT-RADIUS-V'])) {
15652 $pb['BORDER-BOTTOM-LEFT-RADIUS-V'] = $p['BORDER-BOTTOM-LEFT-RADIUS-V'];
15654 if (isset($p['BORDER-BOTTOM-RIGHT-RADIUS-H'])) {
15655 $pb['BORDER-BOTTOM-RIGHT-RADIUS-H'] = $p['BORDER-BOTTOM-RIGHT-RADIUS-H'];
15657 if (isset($p['BORDER-BOTTOM-RIGHT-RADIUS-V'])) {
15658 $pb['BORDER-BOTTOM-RIGHT-RADIUS-V'] = $p['BORDER-BOTTOM-RIGHT-RADIUS-V'];
15660 if (isset($p['BACKGROUND-COLOR'])) {
15661 $pb['BACKGROUND-COLOR'] = $p['BACKGROUND-COLOR'];
15663 if (isset($p['BOX-SHADOW'])) {
15664 $pb['BOX-SHADOW'] = $p['BOX-SHADOW'];
15666 /* -- BACKGROUNDS -- */
15667 if (isset($p['BACKGROUND-IMAGE'])) {
15668 $pb['BACKGROUND-IMAGE'] = $p['BACKGROUND-IMAGE'];
15670 if (isset($p['BACKGROUND-IMAGE-RESIZE'])) {
15671 $pb['BACKGROUND-IMAGE-RESIZE'] = $p['BACKGROUND-IMAGE-RESIZE'];
15673 if (isset($p['BACKGROUND-IMAGE-OPACITY'])) {
15674 $pb['BACKGROUND-IMAGE-OPACITY'] = $p['BACKGROUND-IMAGE-OPACITY'];
15676 if (isset($p['BACKGROUND-REPEAT'])) {
15677 $pb['BACKGROUND-REPEAT'] = $p['BACKGROUND-REPEAT'];
15679 if (isset($p['BACKGROUND-POSITION'])) {
15680 $pb['BACKGROUND-POSITION'] = $p['BACKGROUND-POSITION'];
15682 if (isset($p['BACKGROUND-GRADIENT'])) {
15683 $pb['BACKGROUND-GRADIENT'] = $p['BACKGROUND-GRADIENT'];
15685 if (isset($p['BACKGROUND-SIZE'])) {
15686 $pb['BACKGROUND-SIZE'] = $p['BACKGROUND-SIZE'];
15688 if (isset($p['BACKGROUND-ORIGIN'])) {
15689 $pb['BACKGROUND-ORIGIN'] = $p['BACKGROUND-ORIGIN'];
15691 if (isset($p['BACKGROUND-CLIP'])) {
15692 $pb['BACKGROUND-CLIP'] = $p['BACKGROUND-CLIP'];
15695 /* -- END BACKGROUNDS -- */
15697 $this->setCSS($pb, 'BLOCK', $tag);
15699 // ================================================================
15700 $bbox_br = $this->blk[1]['border_right']['w'];
15701 $bbox_bl = $this->blk[1]['border_left']['w'];
15702 $bbox_bt = $this->blk[1]['border_top']['w'];
15703 $bbox_bb = $this->blk[1]['border_bottom']['w'];
15704 $bbox_pr = $this->blk[1]['padding_right'];
15705 $bbox_pl = $this->blk[1]['padding_left'];
15706 $bbox_pt = $this->blk[1]['padding_top'];
15707 $bbox_pb = $this->blk[1]['padding_bottom'];
15708 $bbox_mr = $this->blk[1]['margin_right'];
15709 if (isset($p['MARGIN-RIGHT']) && strtolower($p['MARGIN-RIGHT']) == 'auto') {
15710 $bbox_mr = 'auto';
15712 $bbox_ml = $this->blk[1]['margin_left'];
15713 if (isset($p['MARGIN-LEFT']) && strtolower($p['MARGIN-LEFT']) == 'auto') {
15714 $bbox_ml = 'auto';
15716 $bbox_mt = $this->blk[1]['margin_top'];
15717 if (isset($p['MARGIN-TOP']) && strtolower($p['MARGIN-TOP']) == 'auto') {
15718 $bbox_mt = 'auto';
15720 $bbox_mb = $this->blk[1]['margin_bottom'];
15721 if (isset($p['MARGIN-BOTTOM']) && strtolower($p['MARGIN-BOTTOM']) == 'auto') {
15722 $bbox_mb = 'auto';
15724 if (isset($p['LEFT']) && strtolower($p['LEFT']) != 'auto') {
15725 $bbox_left = $this->sizeConverter->convert($p['LEFT'], $cont_w, $this->FontSize, false);
15726 } else {
15727 $bbox_left = 'auto';
15729 if (isset($p['TOP']) && strtolower($p['TOP']) != 'auto') {
15730 $bbox_top = $this->sizeConverter->convert($p['TOP'], $cont_h, $this->FontSize, false);
15731 } else {
15732 $bbox_top = 'auto';
15734 if (isset($p['RIGHT']) && strtolower($p['RIGHT']) != 'auto') {
15735 $bbox_right = $this->sizeConverter->convert($p['RIGHT'], $cont_w, $this->FontSize, false);
15736 } else {
15737 $bbox_right = 'auto';
15739 if (isset($p['BOTTOM']) && strtolower($p['BOTTOM']) != 'auto') {
15740 $bbox_bottom = $this->sizeConverter->convert($p['BOTTOM'], $cont_h, $this->FontSize, false);
15741 } else {
15742 $bbox_bottom = 'auto';
15744 if (isset($p['WIDTH']) && strtolower($p['WIDTH']) != 'auto') {
15745 $inner_w = $this->sizeConverter->convert($p['WIDTH'], $cont_w, $this->FontSize, false);
15746 } else {
15747 $inner_w = 'auto';
15749 if (isset($p['HEIGHT']) && strtolower($p['HEIGHT']) != 'auto') {
15750 $inner_h = $this->sizeConverter->convert($p['HEIGHT'], $cont_h, $this->FontSize, false);
15751 } else {
15752 $inner_h = 'auto';
15755 // If bottom or right pos are set and not left / top - save this to adjust rotated block later
15756 if ($rotate == 90 || $rotate == -90) { // mPDF 6
15757 if ($bbox_left === 'auto' && $bbox_right !== 'auto') {
15758 $rot_rpos = $bbox_right;
15759 } else {
15760 $rot_rpos = false;
15762 if ($bbox_top === 'auto' && $bbox_bottom !== 'auto') {
15763 $rot_bpos = $bbox_bottom;
15764 } else {
15765 $rot_bpos = false;
15769 // ================================================================
15770 if ($checkinnerhtml == '' && $inner_h === 'auto') {
15771 $inner_h = 0.0001;
15773 if ($checkinnerhtml == '' && $inner_w === 'auto') {
15774 $inner_w = 2 * $this->GetCharWidth('W', false);
15776 // ================================================================
15777 // Algorithm from CSS2.1 See http://www.w3.org/TR/CSS21/visudet.html#abs-non-replaced-height
15778 // mPD 5.3.14
15779 // Special case (not CSS) if all not specified, centre vertically on page
15780 $bbox_top_orig = '';
15781 if ($bbox_top === 'auto' && $inner_h === 'auto' && $bbox_bottom === 'auto' && $bbox_mt === 'auto' && $bbox_mb === 'auto') {
15782 $bbox_top_orig = $bbox_top;
15783 if ($bbox_mt === 'auto') {
15784 $bbox_mt = 0;
15786 if ($bbox_mb === 'auto') {
15787 $bbox_mb = 0;
15789 $bbox_top = $orig_y0 - $bbox_mt - $cont_y;
15790 // solve for $bbox_bottom when content_h known - $inner_h=='auto' && $bbox_bottom=='auto'
15791 } // mPD 5.3.14
15792 elseif ($bbox_top === 'auto' && $inner_h === 'auto' && $bbox_bottom === 'auto') {
15793 $bbox_top_orig = $bbox_top = $orig_y0 - $cont_y;
15794 if ($bbox_mt === 'auto') {
15795 $bbox_mt = 0;
15797 if ($bbox_mb === 'auto') {
15798 $bbox_mb = 0;
15800 // solve for $bbox_bottom when content_h known - $inner_h=='auto' && $bbox_bottom=='auto'
15801 } elseif ($bbox_top !== 'auto' && $inner_h !== 'auto' && $bbox_bottom !== 'auto') {
15802 if ($bbox_mt === 'auto' && $bbox_mb === 'auto') {
15803 $x = $cont_h - $bbox_top - $bbox_bt - $bbox_pt - $inner_h - $bbox_pb - $bbox_bb - $bbox_bottom;
15804 $bbox_mt = $bbox_mb = ($x / 2);
15805 } elseif ($bbox_mt === 'auto') {
15806 $bbox_mt = $cont_h - $bbox_top - $bbox_bt - $bbox_pt - $inner_h - $bbox_pb - $bbox_bb - $bbox_mb - $bbox_bottom;
15807 } elseif ($bbox_mb === 'auto') {
15808 $bbox_mb = $cont_h - $bbox_top - $bbox_mt - $bbox_bt - $bbox_pt - $inner_h - $bbox_pb - $bbox_bb - $bbox_bottom;
15809 } else {
15810 $bbox_bottom = $cont_h - $bbox_top - $bbox_mt - $bbox_bt - $bbox_pt - $inner_h - $bbox_pb - $bbox_bb - $bbox_mt;
15812 } else {
15813 if ($bbox_mt === 'auto') {
15814 $bbox_mt = 0;
15816 if ($bbox_mb === 'auto') {
15817 $bbox_mb = 0;
15819 if ($bbox_top === 'auto' && $inner_h === 'auto' && $bbox_bottom !== 'auto') {
15820 // solve for $bbox_top when content_h known - $inner_h=='auto' && $bbox_top =='auto'
15821 } elseif ($bbox_top === 'auto' && $bbox_bottom === 'auto' && $inner_h !== 'auto') {
15822 $bbox_top = $orig_y0 - $bbox_mt - $cont_y;
15823 $bbox_bottom = $cont_h - $bbox_top - $bbox_mt - $bbox_bt - $bbox_pt - $inner_h - $bbox_pb - $bbox_bb - $bbox_mt;
15824 } elseif ($inner_h === 'auto' && $bbox_bottom === 'auto' && $bbox_top !== 'auto') {
15825 // solve for $bbox_bottom when content_h known - $inner_h=='auto' && $bbox_bottom=='auto'
15826 } elseif ($bbox_top === 'auto' && $inner_h !== 'auto' && $bbox_bottom !== 'auto') {
15827 $bbox_top = $cont_h - $bbox_mt - $bbox_bt - $bbox_pt - $inner_h - $bbox_pb - $bbox_bb - $bbox_mt - $bbox_bottom;
15828 } elseif ($inner_h === 'auto' && $bbox_top !== 'auto' && $bbox_bottom !== 'auto') {
15829 $inner_h = $cont_h - $bbox_top - $bbox_mt - $bbox_bt - $bbox_pt - $bbox_pb - $bbox_bb - $bbox_mt - $bbox_bottom;
15830 } elseif ($bbox_bottom === 'auto' && $bbox_top !== 'auto' && $inner_h !== 'auto') {
15831 $bbox_bottom = $cont_h - $bbox_top - $bbox_mt - $bbox_bt - $bbox_pt - $inner_h - $bbox_pb - $bbox_bb - $bbox_mt;
15835 // THEN DO SAME FOR WIDTH
15836 // http://www.w3.org/TR/CSS21/visudet.html#abs-non-replaced-width
15837 if ($bbox_left === 'auto' && $inner_w === 'auto' && $bbox_right === 'auto') {
15838 if ($bbox_ml === 'auto') {
15839 $bbox_ml = 0;
15841 if ($bbox_mr === 'auto') {
15842 $bbox_mr = 0;
15844 // IF containing element RTL, should set $bbox_right
15845 $bbox_left = $orig_x0 - $bbox_ml - $cont_x;
15846 // solve for $bbox_right when content_w known - $inner_w=='auto' && $bbox_right=='auto'
15847 } elseif ($bbox_left !== 'auto' && $inner_w !== 'auto' && $bbox_right !== 'auto') {
15848 if ($bbox_ml === 'auto' && $bbox_mr === 'auto') {
15849 $x = $cont_w - $bbox_left - $bbox_bl - $bbox_pl - $inner_w - $bbox_pr - $bbox_br - $bbox_right;
15850 $bbox_ml = $bbox_mr = ($x / 2);
15851 } elseif ($bbox_ml === 'auto') {
15852 $bbox_ml = $cont_w - $bbox_left - $bbox_bl - $bbox_pl - $inner_w - $bbox_pr - $bbox_br - $bbox_mr - $bbox_right;
15853 } elseif ($bbox_mr === 'auto') {
15854 $bbox_mr = $cont_w - $bbox_left - $bbox_ml - $bbox_bl - $bbox_pl - $inner_w - $bbox_pr - $bbox_br - $bbox_right;
15855 } else {
15856 $bbox_right = $cont_w - $bbox_left - $bbox_ml - $bbox_bl - $bbox_pl - $inner_w - $bbox_pr - $bbox_br - $bbox_ml;
15858 } else {
15859 if ($bbox_ml === 'auto') {
15860 $bbox_ml = 0;
15862 if ($bbox_mr === 'auto') {
15863 $bbox_mr = 0;
15865 if ($bbox_left === 'auto' && $inner_w === 'auto' && $bbox_right !== 'auto') {
15866 // solve for $bbox_left when content_w known - $inner_w=='auto' && $bbox_left =='auto'
15867 } elseif ($bbox_left === 'auto' && $bbox_right === 'auto' && $inner_w !== 'auto') {
15868 // IF containing element RTL, should set $bbox_right
15869 $bbox_left = $orig_x0 - $bbox_ml - $cont_x;
15870 $bbox_right = $cont_w - $bbox_left - $bbox_ml - $bbox_bl - $bbox_pl - $inner_w - $bbox_pr - $bbox_br - $bbox_ml;
15871 } elseif ($inner_w === 'auto' && $bbox_right === 'auto' && $bbox_left !== 'auto') {
15872 // solve for $bbox_right when content_w known - $inner_w=='auto' && $bbox_right=='auto'
15873 } elseif ($bbox_left === 'auto' && $inner_w !== 'auto' && $bbox_right !== 'auto') {
15874 $bbox_left = $cont_w - $bbox_ml - $bbox_bl - $bbox_pl - $inner_w - $bbox_pr - $bbox_br - $bbox_ml - $bbox_right;
15875 } elseif ($inner_w === 'auto' && $bbox_left !== 'auto' && $bbox_right !== 'auto') {
15876 $inner_w = $cont_w - $bbox_left - $bbox_ml - $bbox_bl - $bbox_pl - $bbox_pr - $bbox_br - $bbox_ml - $bbox_right;
15877 } elseif ($bbox_right === 'auto' && $bbox_left !== 'auto' && $inner_w !== 'auto') {
15878 $bbox_right = $cont_w - $bbox_left - $bbox_ml - $bbox_bl - $bbox_pl - $inner_w - $bbox_pr - $bbox_br - $bbox_ml;
15882 // ================================================================
15883 // ================================================================
15884 /* -- BACKGROUNDS -- */
15885 if (isset($pb['BACKGROUND-IMAGE']) && $pb['BACKGROUND-IMAGE']) {
15886 $ret = $this->SetBackground($pb, $this->blk[1]['inner_width']);
15887 if ($ret) {
15888 $this->blk[1]['background-image'] = $ret;
15891 /* -- END BACKGROUNDS -- */
15893 $bbox_top_auto = $bbox_top === 'auto';
15894 $bbox_left_auto = $bbox_left === 'auto';
15895 $bbox_right_auto = $bbox_right === 'auto';
15896 $bbox_bottom_auto = $bbox_bottom === 'auto';
15898 $bbox_top = is_numeric($bbox_top) ? $bbox_top : 0;
15899 $bbox_left = is_numeric($bbox_left) ? $bbox_left : 0;
15900 $bbox_right = is_numeric($bbox_right) ? $bbox_right : 0;
15901 $bbox_bottom = is_numeric($bbox_bottom) ? $bbox_bottom : 0;
15903 $y = $cont_y + $bbox_top + $bbox_mt + $bbox_bt + $bbox_pt;
15904 $h = $cont_h - $bbox_top - $bbox_mt - $bbox_bt - $bbox_pt - $bbox_pb - $bbox_bb - $bbox_mb - $bbox_bottom;
15906 $x = $cont_x + $bbox_left + $bbox_ml + $bbox_bl + $bbox_pl;
15907 $w = $cont_w - $bbox_left - $bbox_ml - $bbox_bl - $bbox_pl - $bbox_pr - $bbox_br - $bbox_mr - $bbox_right;
15909 // Set (temporary) values for x y w h to do first paint, if values are auto
15910 if ($inner_h === 'auto' && $bbox_top_auto) {
15911 $y = $cont_y + $bbox_mt + $bbox_bt + $bbox_pt;
15912 $h = $cont_h - ($bbox_bottom + $bbox_mt + $bbox_mb + $bbox_bt + $bbox_bb + $bbox_pt + $bbox_pb);
15913 } elseif ($inner_h === 'auto' && $bbox_bottom_auto) {
15914 $y = $cont_y + $bbox_top + $bbox_mt + $bbox_bt + $bbox_pt;
15915 $h = $cont_h - ($bbox_top + $bbox_mt + $bbox_mb + $bbox_bt + $bbox_bb + $bbox_pt + $bbox_pb);
15917 if ($inner_w === 'auto' && $bbox_left_auto) {
15918 $x = $cont_x + $bbox_ml + $bbox_bl + $bbox_pl;
15919 $w = $cont_w - ($bbox_right + $bbox_ml + $bbox_mr + $bbox_bl + $bbox_br + $bbox_pl + $bbox_pr);
15920 } elseif ($inner_w === 'auto' && $bbox_right_auto) {
15921 $x = $cont_x + $bbox_left + $bbox_ml + $bbox_bl + $bbox_pl;
15922 $w = $cont_w - ($bbox_left + $bbox_ml + $bbox_mr + $bbox_bl + $bbox_br + $bbox_pl + $bbox_pr);
15925 $bbox_y = $cont_y + $bbox_top + $bbox_mt;
15926 $bbox_x = $cont_x + $bbox_left + $bbox_ml;
15928 $saved_block1 = $this->blk[1];
15930 unset($p);
15931 unset($pb);
15933 // ================================================================
15934 if ($inner_w === 'auto') { // do a first write
15935 $this->lMargin = $x;
15936 $this->rMargin = $this->w - $w - $x;
15938 // SET POSITION & FONT VALUES
15939 $this->pgwidth = $this->w - $this->lMargin - $this->rMargin;
15940 $this->pageoutput[$this->page] = [];
15941 $this->x = $x;
15942 $this->y = $y;
15943 $this->HTMLheaderPageLinks = [];
15944 $this->HTMLheaderPageAnnots = [];
15945 $this->HTMLheaderPageForms = [];
15946 $this->pageBackgrounds = [];
15947 $this->maxPosR = 0;
15948 $this->maxPosL = $this->w; // For RTL
15949 $this->WriteHTML($html, 4);
15950 $inner_w = $this->maxPosR - $this->lMargin;
15951 if ($bbox_right_auto) {
15952 $bbox_right = $cont_w - $bbox_left - $bbox_ml - $bbox_bl - $bbox_pl - $inner_w - $bbox_pr - $bbox_br - $bbox_ml;
15953 } elseif ($bbox_left_auto) {
15954 $bbox_left = $cont_w - $bbox_ml - $bbox_bl - $bbox_pl - $inner_w - $bbox_pr - $bbox_br - $bbox_ml - $bbox_right;
15955 $bbox_x = $cont_x + $bbox_left + $bbox_ml;
15956 $inner_x = $bbox_x + $bbox_bl + $bbox_pl;
15957 $x = $inner_x;
15960 $w = $inner_w;
15961 $bbox_y = $cont_y + $bbox_top + $bbox_mt;
15962 $bbox_x = $cont_x + $bbox_left + $bbox_ml;
15965 if ($inner_h === 'auto') { // do a first write
15967 $this->lMargin = $x;
15968 $this->rMargin = $this->w - $w - $x;
15970 // SET POSITION & FONT VALUES
15971 $this->pgwidth = $this->w - $this->lMargin - $this->rMargin;
15972 $this->pageoutput[$this->page] = [];
15973 $this->x = $x;
15974 $this->y = $y;
15975 $this->HTMLheaderPageLinks = [];
15976 $this->HTMLheaderPageAnnots = [];
15977 $this->HTMLheaderPageForms = [];
15978 $this->pageBackgrounds = [];
15979 $this->WriteHTML($html, 4);
15980 $inner_h = $this->y - $y;
15982 if ($overflow != 'hidden' && $overflow != 'visible') { // constrained
15983 if (($this->y + $bbox_pb + $bbox_bb) > ($cont_y + $cont_h)) {
15984 $adj = ($this->y + $bbox_pb + $bbox_bb) - ($cont_y + $cont_h);
15985 $inner_h -= $adj;
15988 if ($bbox_bottom_auto && $bbox_top_orig === 'auto') {
15989 $bbox_bottom = $bbox_top = ($cont_h - $bbox_mt - $bbox_bt - $bbox_pt - $inner_h - $bbox_pb - $bbox_bb - $bbox_mb) / 2;
15990 if ($overflow != 'hidden' && $overflow != 'visible') { // constrained
15991 if ($bbox_top < 0) {
15992 $bbox_top = 0;
15993 $inner_h = $cont_h - $bbox_top - $bbox_mt - $bbox_bt - $bbox_pt - $bbox_pb - $bbox_bb - $bbox_mb - $bbox_bottom;
15996 $bbox_y = $cont_y + $bbox_top + $bbox_mt;
15997 $inner_y = $bbox_y + $bbox_bt + $bbox_pt;
15998 $y = $inner_y;
15999 } elseif ($bbox_bottom_auto) {
16000 $bbox_bottom = $cont_h - $bbox_top - $bbox_mt - $bbox_bt - $bbox_pt - $inner_h - $bbox_pb - $bbox_bb - $bbox_mb;
16001 } elseif ($bbox_top_auto) {
16002 $bbox_top = $cont_h - $bbox_mt - $bbox_bt - $bbox_pt - $inner_h - $bbox_pb - $bbox_bb - $bbox_mb - $bbox_bottom;
16003 if ($overflow != 'hidden' && $overflow != 'visible') { // constrained
16004 if ($bbox_top < 0) {
16005 $bbox_top = 0;
16006 $inner_h = $cont_h - $bbox_top - $bbox_mt - $bbox_bt - $bbox_pt - $bbox_pb - $bbox_bb - $bbox_mb - $bbox_bottom;
16009 $bbox_y = $cont_y + $bbox_top + $bbox_mt;
16010 $inner_y = $bbox_y + $bbox_bt + $bbox_pt;
16011 $y = $inner_y;
16013 $h = $inner_h;
16014 $bbox_y = $cont_y + $bbox_top + $bbox_mt;
16015 $bbox_x = $cont_x + $bbox_left + $bbox_ml;
16018 $inner_w = $w;
16019 $inner_h = $h;
16022 $this->lMargin = $x;
16023 $this->rMargin = $this->w - $w - $x;
16025 // SET POSITION & FONT VALUES
16026 $this->pgwidth = $this->w - $this->lMargin - $this->rMargin;
16027 $this->pageoutput[$this->page] = [];
16029 $this->x = $x;
16030 $this->y = $y;
16032 $this->HTMLheaderPageLinks = [];
16033 $this->HTMLheaderPageAnnots = [];
16034 $this->HTMLheaderPageForms = [];
16036 $this->pageBackgrounds = [];
16038 $this->WriteHTML($html, 4); // parameter 4 saves output to $this->headerbuffer
16040 $actual_h = $this->y - $y;
16041 $use_w = $w;
16042 $use_h = $h;
16043 $ratio = $actual_h / $use_w;
16045 if ($overflow != 'hidden' && $overflow != 'visible') {
16046 $target = $h / $w;
16047 if (($ratio / $target ) > 1) {
16048 $nl = ceil($actual_h / $this->lineheight);
16049 $l = $use_w * $nl;
16050 $est_w = sqrt(($l * $this->lineheight) / $target) * 0.8;
16051 $use_w += ($est_w - $use_w) - ($w / 100);
16053 $bpcstart = ($ratio / $target);
16054 $bpcctr = 1;
16056 while (($ratio / $target ) > 1) {
16057 // @log 'Auto-sizing fixed-position block $bpcctr++
16059 $this->x = $x;
16060 $this->y = $y;
16062 if (($ratio / $target) > 1.5 || ($ratio / $target) < 0.6) {
16063 $use_w += ($w / $this->incrementFPR1);
16064 } elseif (($ratio / $target) > 1.2 || ($ratio / $target) < 0.85) {
16065 $use_w += ($w / $this->incrementFPR2);
16066 } elseif (($ratio / $target) > 1.1 || ($ratio / $target) < 0.91) {
16067 $use_w += ($w / $this->incrementFPR3);
16068 } else {
16069 $use_w += ($w / $this->incrementFPR4);
16072 $use_h = $use_w * $target;
16073 $this->rMargin = $this->w - $use_w - $x;
16074 $this->pgwidth = $this->w - $this->lMargin - $this->rMargin;
16075 $this->HTMLheaderPageLinks = [];
16076 $this->HTMLheaderPageAnnots = [];
16077 $this->HTMLheaderPageForms = [];
16078 $this->pageBackgrounds = [];
16079 $this->WriteHTML($html, 4); // parameter 4 saves output to $this->headerbuffer
16080 $actual_h = $this->y - $y;
16081 $ratio = $actual_h / $use_w;
16085 $shrink_f = $w / $use_w;
16087 // ================================================================
16089 $this->pages[$this->page] .= '___BEFORE_BORDERS___';
16090 $block_s = $this->PrintPageBackgrounds(); // Save to print later inside clipping path
16091 $this->pageBackgrounds = [];
16093 // ================================================================
16095 if ($rotate == 90 || $rotate == -90) { // mPDF 6
16096 $prerotw = $bbox_bl + $bbox_pl + $inner_w + $bbox_pr + $bbox_br;
16097 $preroth = $bbox_bt + $bbox_pt + $inner_h + $bbox_pb + $bbox_bb;
16098 $rot_start = " q\n";
16099 if ($rotate == 90) {
16100 if ($rot_rpos !== false) {
16101 $adjw = $prerotw;
16102 } // width before rotation
16103 else {
16104 $adjw = $preroth;
16105 } // height before rotation
16106 if ($rot_bpos !== false) {
16107 $adjh = -$prerotw + $preroth;
16108 } else {
16109 $adjh = 0;
16111 } else {
16112 if ($rot_rpos !== false) {
16113 $adjw = $prerotw - $preroth;
16114 } else {
16115 $adjw = 0;
16117 if ($rot_bpos !== false) {
16118 $adjh = $preroth;
16119 } // height before rotation
16120 else {
16121 $adjh = $prerotw;
16122 } // width before rotation
16124 $rot_start .= $this->transformTranslate($adjw, $adjh, true) . "\n";
16125 $rot_start .= $this->transformRotate($rotate, $bbox_x, $bbox_y, true) . "\n";
16126 $rot_end = " Q\n";
16127 } elseif ($rotate == 180) { // mPDF 6
16128 $rot_start = " q\n";
16129 $rot_start .= $this->transformTranslate($bbox_bl + $bbox_pl + $inner_w + $bbox_pr + $bbox_br, $bbox_bt + $bbox_pt + $inner_h + $bbox_pb + $bbox_bb, true) . "\n";
16130 $rot_start .= $this->transformRotate(180, $bbox_x, $bbox_y, true) . "\n";
16131 $rot_end = " Q\n";
16132 } else {
16133 $rot_start = '';
16134 $rot_end = '';
16137 // ================================================================
16138 if (!empty($bounding)) {
16139 // WHEN HEIGHT // BOTTOM EDGE IS KNOWN and $this->y is set to the bottom
16140 // Re-instate saved $this->blk[1]
16141 $this->blk[1] = $saved_block1;
16143 // These are only needed when painting border/background
16144 $this->blk[1]['width'] = $bbox_w = $cont_w - $bbox_left - $bbox_ml - $bbox_mr - $bbox_right;
16145 $this->blk[1]['x0'] = $bbox_x;
16146 $this->blk[1]['y0'] = $bbox_y;
16147 $this->blk[1]['startpage'] = $this->page;
16148 $this->blk[1]['y1'] = $bbox_y + $bbox_bt + $bbox_pt + $inner_h + $bbox_pb + $bbox_bb;
16149 $this->_out($rot_start);
16150 $this->PaintDivBB('', 0, 1); // Prints borders and sets backgrounds in $this->pageBackgrounds
16151 $this->_out($rot_end);
16154 $s = $this->PrintPageBackgrounds();
16155 $s = $rot_start . $s . $rot_end;
16156 $this->pages[$this->page] = preg_replace('/___BEFORE_BORDERS___/', "\n" . $s . "\n", $this->pages[$this->page]);
16157 $this->pageBackgrounds = [];
16159 $this->_out($rot_start);
16161 // Clipping Output
16162 if ($overflow == 'hidden') {
16163 // Bounding rectangle to clip
16164 $clip_y1 = $this->y;
16165 if (!empty($bounding) && ($this->y + $bbox_pb + $bbox_bb) > ($bbox_y + $bbox_bt + $bbox_pt + $inner_h + $bbox_pb + $bbox_bb )) {
16166 $clip_y1 = ($bbox_y + $bbox_bt + $bbox_pt + $inner_h + $bbox_pb + $bbox_bb ) - ($bbox_pb + $bbox_bb);
16168 // $op = 'W* n'; // Clipping
16169 $op = 'W n'; // Clipping alternative mode
16170 $this->_out("q");
16171 $ch = $clip_y1 - $y;
16172 $this->_out(sprintf('%.3F %.3F %.3F %.3F re %s', $x * Mpdf::SCALE, ($this->h - $y) * Mpdf::SCALE, $w * Mpdf::SCALE, -$ch * Mpdf::SCALE, $op));
16173 if (!empty($block_s)) {
16174 $tmp = "q\n" . sprintf('%.3F %.3F %.3F %.3F re %s', $x * Mpdf::SCALE, ($this->h - $y) * Mpdf::SCALE, $w * Mpdf::SCALE, -$ch * Mpdf::SCALE, $op);
16175 $tmp .= "\n" . $block_s . "\nQ";
16176 $block_s = $tmp;
16181 if (!empty($block_s)) {
16182 if ($shrink_f != 1) { // i.e. autofit has resized the box
16183 $tmp = "q\n" . $this->transformScale(($shrink_f * 100), ($shrink_f * 100), $x, $y, true);
16184 $tmp .= "\n" . $block_s . "\nQ";
16185 $block_s = $tmp;
16187 $this->_out($block_s);
16192 if ($shrink_f != 1) { // i.e. autofit has resized the box
16193 $this->StartTransform();
16194 $this->transformScale(($shrink_f * 100), ($shrink_f * 100), $x, $y);
16197 $this->_out($this->headerbuffer);
16199 if ($shrink_f != 1) { // i.e. autofit has resized the box
16200 $this->StopTransform();
16203 if ($overflow == 'hidden') {
16204 // End clipping
16205 $this->_out("Q");
16208 $this->_out($rot_end);
16211 // Page Links
16212 foreach ($this->HTMLheaderPageLinks as $lk) {
16213 if ($rotate) {
16214 $tmp = $lk[2]; // Switch h - w
16215 $lk[2] = $lk[3];
16216 $lk[3] = $tmp;
16218 $lx1 = (($lk[0] / Mpdf::SCALE));
16219 $ly1 = (($this->h - ($lk[1] / Mpdf::SCALE)));
16220 if ($rotate == 90) {
16221 $adjx = -($lx1 - $bbox_x) + ($preroth - ($ly1 - $bbox_y));
16222 $adjy = -($ly1 - $bbox_y) + ($lx1 - $bbox_x);
16223 $lk[2] = -$lk[2];
16224 } elseif ($rotate == -90) {
16225 $adjx = -($lx1 - $bbox_x) + ($ly1 - $bbox_y);
16226 $adjy = -($ly1 - $bbox_y) - ($lx1 - $bbox_x) + $prerotw;
16227 $lk[3] = -$lk[3];
16229 if ($rot_rpos !== false) {
16230 $adjx += $prerotw - $preroth;
16232 if ($rot_bpos !== false) {
16233 $adjy += $preroth - $prerotw;
16235 $lx1 += $adjx;
16236 $ly1 += $adjy;
16238 $lk[0] = $lx1 * Mpdf::SCALE;
16239 $lk[1] = ($this->h - $ly1) * Mpdf::SCALE;
16241 if ($shrink_f != 1) { // i.e. autofit has resized the box
16242 $lx1 = (($lk[0] / Mpdf::SCALE) - $x);
16243 $lx2 = $x + ($lx1 * $shrink_f);
16244 $lk[0] = $lx2 * Mpdf::SCALE;
16245 $ly1 = (($this->h - ($lk[1] / Mpdf::SCALE)) - $y);
16246 $ly2 = $y + ($ly1 * $shrink_f);
16247 $lk[1] = ($this->h - $ly2) * Mpdf::SCALE;
16248 $lk[2] *= $shrink_f; // width
16249 $lk[3] *= $shrink_f; // height
16251 $this->PageLinks[$this->page][] = $lk;
16254 foreach ($this->HTMLheaderPageForms as $n => $f) {
16255 if ($shrink_f != 1) { // i.e. autofit has resized the box
16256 $f['x'] = $x + (($f['x'] - $x) * $shrink_f);
16257 $f['y'] = $y + (($f['y'] - $y) * $shrink_f);
16258 $f['w'] *= $shrink_f;
16259 $f['h'] *= $shrink_f;
16260 $f['style']['fontsize'] *= $shrink_f;
16262 $this->form->forms[$f['n']] = $f;
16264 // Page Annotations
16265 foreach ($this->HTMLheaderPageAnnots as $lk) {
16266 if ($rotate) {
16267 if ($rotate == 90) {
16268 $adjx = -($lk['x'] - $bbox_x) + ($preroth - ($lk['y'] - $bbox_y));
16269 $adjy = -($lk['y'] - $bbox_y) + ($lk['x'] - $bbox_x);
16270 } elseif ($rotate == -90) {
16271 $adjx = -($lk['x'] - $bbox_x) + ($lk['y'] - $bbox_y);
16272 $adjy = -($lk['y'] - $bbox_y) - ($lk['x'] - $bbox_x) + $prerotw;
16274 if ($rot_rpos !== false) {
16275 $adjx += $prerotw - $preroth;
16277 if ($rot_bpos !== false) {
16278 $adjy += $preroth - $prerotw;
16280 $lk['x'] += $adjx;
16281 $lk['y'] += $adjy;
16283 if ($shrink_f != 1) { // i.e. autofit has resized the box
16284 $lk['x'] = $x + (($lk['x'] - $x) * $shrink_f);
16285 $lk['y'] = $y + (($lk['y'] - $y) * $shrink_f);
16287 $this->PageAnnots[$this->page][] = $lk;
16290 // Restore
16291 $this->headerbuffer = '';
16292 $this->HTMLheaderPageLinks = [];
16293 $this->HTMLheaderPageAnnots = [];
16294 $this->HTMLheaderPageForms = [];
16295 $this->pageBackgrounds = $save_bgs;
16296 $this->writingHTMLheader = false;
16298 $this->writingHTMLfooter = false;
16299 $this->fullImageHeight = false;
16300 $this->ResetMargins();
16301 $this->pgwidth = $this->w - $this->lMargin - $this->rMargin;
16302 $this->SetXY($save_x, $save_y);
16303 $this->title2annots = $save_annots; // *ANNOTATIONS*
16304 $this->InFooter = false; // turns back on autopagebreaks
16305 $this->pageoutput[$this->page] = [];
16306 $this->pageoutput[$this->page]['Font'] = '';
16307 /* -- COLUMNS -- */
16308 if ($save_cols) {
16309 $this->SetColumns($save_nbcol, $this->colvAlign, $this->ColGap);
16311 /* -- END COLUMNS -- */
16314 /* -- END CSS-POSITION -- */
16316 function initialiseBlock(&$blk)
16318 $blk['margin_top'] = 0;
16319 $blk['margin_left'] = 0;
16320 $blk['margin_bottom'] = 0;
16321 $blk['margin_right'] = 0;
16322 $blk['padding_top'] = 0;
16323 $blk['padding_left'] = 0;
16324 $blk['padding_bottom'] = 0;
16325 $blk['padding_right'] = 0;
16326 $blk['border_top']['w'] = 0;
16327 $blk['border_left']['w'] = 0;
16328 $blk['border_bottom']['w'] = 0;
16329 $blk['border_right']['w'] = 0;
16330 $blk['direction'] = 'ltr';
16331 $blk['hide'] = false;
16332 $blk['outer_left_margin'] = 0;
16333 $blk['outer_right_margin'] = 0;
16334 $blk['cascadeCSS'] = [];
16335 $blk['block-align'] = false;
16336 $blk['bgcolor'] = false;
16337 $blk['page_break_after_avoid'] = false;
16338 $blk['keep_block_together'] = false;
16339 $blk['float'] = false;
16340 $blk['line_height'] = '';
16341 $blk['margin_collapse'] = false;
16344 function border_details($bd)
16346 $prop = preg_split('/\s+/', trim($bd));
16348 if (isset($this->blk[$this->blklvl]['inner_width'])) {
16349 $refw = $this->blk[$this->blklvl]['inner_width'];
16350 } elseif (isset($this->blk[$this->blklvl - 1]['inner_width'])) {
16351 $refw = $this->blk[$this->blklvl - 1]['inner_width'];
16352 } else {
16353 $refw = $this->w;
16355 if (count($prop) == 1) {
16356 $bsize = $this->sizeConverter->convert($prop[0], $refw, $this->FontSize, false);
16357 if ($bsize > 0) {
16358 return ['s' => 1, 'w' => $bsize, 'c' => $this->colorConverter->convert(0, $this->PDFAXwarnings), 'style' => 'solid'];
16359 } else {
16360 return ['w' => 0, 's' => 0];
16362 } elseif (count($prop) == 2) {
16363 // 1px solid
16364 if (in_array($prop[1], $this->borderstyles) || $prop[1] == 'none' || $prop[1] == 'hidden') {
16365 $prop[2] = '';
16366 } // solid #000000
16367 elseif (in_array($prop[0], $this->borderstyles) || $prop[0] == 'none' || $prop[0] == 'hidden') {
16368 $prop[0] = '';
16369 $prop[1] = $prop[0];
16370 $prop[2] = $prop[1];
16371 } // 1px #000000
16372 else {
16373 $prop[1] = '';
16374 $prop[2] = $prop[1];
16376 } elseif (count($prop) == 3) {
16377 // Change #000000 1px solid to 1px solid #000000 (proper)
16378 if (substr($prop[0], 0, 1) == '#') {
16379 $tmp = $prop[0];
16380 $prop[0] = $prop[1];
16381 $prop[1] = $prop[2];
16382 $prop[2] = $tmp;
16383 } // Change solid #000000 1px to 1px solid #000000 (proper)
16384 elseif (substr($prop[0], 1, 1) == '#') {
16385 $tmp = $prop[1];
16386 $prop[0] = $prop[2];
16387 $prop[1] = $prop[0];
16388 $prop[2] = $tmp;
16389 } // Change solid 1px #000000 to 1px solid #000000 (proper)
16390 elseif (in_array($prop[0], $this->borderstyles) || $prop[0] == 'none' || $prop[0] == 'hidden') {
16391 $tmp = $prop[0];
16392 $prop[0] = $prop[1];
16393 $prop[1] = $tmp;
16395 } else {
16396 return [];
16398 // Size
16399 $bsize = $this->sizeConverter->convert($prop[0], $refw, $this->FontSize, false);
16400 // color
16401 $coul = $this->colorConverter->convert($prop[2], $this->PDFAXwarnings); // returns array
16402 // Style
16403 $prop[1] = strtolower($prop[1]);
16404 if (in_array($prop[1], $this->borderstyles) && $bsize > 0) {
16405 $on = 1;
16406 } elseif ($prop[1] == 'hidden') {
16407 $on = 1;
16408 $bsize = 0;
16409 $coul = '';
16410 } elseif ($prop[1] == 'none') {
16411 $on = 0;
16412 $bsize = 0;
16413 $coul = '';
16414 } else {
16415 $on = 0;
16416 $bsize = 0;
16417 $coul = '';
16418 $prop[1] = '';
16420 return ['s' => $on, 'w' => $bsize, 'c' => $coul, 'style' => $prop[1], 'dom' => 0];
16423 /* -- END HTML-CSS -- */
16426 /* -- BORDER-RADIUS -- */
16428 function _borderPadding($a, $b, &$px, &$py)
16430 // $px and py are padding long axis (x) and short axis (y)
16431 $added = 0; // extra padding
16433 $x = $a - $px;
16434 $y = $b - $py;
16435 // Check if Falls within ellipse of border radius
16436 if (( (($x + $added) * ($x + $added)) / ($a * $a) + (($y + $added) * ($y + $added)) / ($b * $b) ) <= 1) {
16437 return false;
16440 $t = atan2($y, $x);
16442 $newx = $b / sqrt((($b * $b) / ($a * $a)) + ( tan($t) * tan($t) ));
16443 $newy = $a / sqrt((($a * $a) / ($b * $b)) + ( (1 / tan($t)) * (1 / tan($t)) ));
16444 $px = max($px, $a - $newx + $added);
16445 $py = max($py, $b - $newy + $added);
16448 /* -- END BORDER-RADIUS -- */
16449 /* -- HTML-CSS -- */
16450 /* -- CSS-PAGE -- */
16452 function SetPagedMediaCSS($name, $first, $oddEven)
16454 if ($oddEven == 'E') {
16455 if ($this->directionality == 'rtl') {
16456 $side = 'R';
16457 } else {
16458 $side = 'L';
16460 } else {
16461 if ($this->directionality == 'rtl') {
16462 $side = 'L';
16463 } else {
16464 $side = 'R';
16467 $name = strtoupper($name);
16468 $p = [];
16469 $p['SIZE'] = 'AUTO';
16471 // Uses mPDF original margins as default
16472 $p['MARGIN-RIGHT'] = strval($this->orig_rMargin) . 'mm';
16473 $p['MARGIN-LEFT'] = strval($this->orig_lMargin) . 'mm';
16474 $p['MARGIN-TOP'] = strval($this->orig_tMargin) . 'mm';
16475 $p['MARGIN-BOTTOM'] = strval($this->orig_bMargin) . 'mm';
16476 $p['MARGIN-HEADER'] = strval($this->orig_hMargin) . 'mm';
16477 $p['MARGIN-FOOTER'] = strval($this->orig_fMargin) . 'mm';
16479 // Basic page + selector
16480 if (isset($this->cssManager->CSS['@PAGE'])) {
16481 $zp = $this->cssManager->CSS['@PAGE'];
16482 } else {
16483 $zp = [];
16485 if (is_array($zp) && !empty($zp)) {
16486 $p = array_merge($p, $zp);
16489 if (isset($p['EVEN-HEADER-NAME']) && $oddEven == 'E') {
16490 $p['HEADER'] = $p['EVEN-HEADER-NAME'];
16491 unset($p['EVEN-HEADER-NAME']);
16493 if (isset($p['ODD-HEADER-NAME']) && $oddEven != 'E') {
16494 $p['HEADER'] = $p['ODD-HEADER-NAME'];
16495 unset($p['ODD-HEADER-NAME']);
16497 if (isset($p['EVEN-FOOTER-NAME']) && $oddEven == 'E') {
16498 $p['FOOTER'] = $p['EVEN-FOOTER-NAME'];
16499 unset($p['EVEN-FOOTER-NAME']);
16501 if (isset($p['ODD-FOOTER-NAME']) && $oddEven != 'E') {
16502 $p['FOOTER'] = $p['ODD-FOOTER-NAME'];
16503 unset($p['ODD-FOOTER-NAME']);
16506 // If right/Odd page
16507 if (isset($this->cssManager->CSS['@PAGE>>PSEUDO>>RIGHT']) && $side == 'R') {
16508 $zp = $this->cssManager->CSS['@PAGE>>PSEUDO>>RIGHT'];
16509 } else {
16510 $zp = [];
16512 if (isset($zp['SIZE'])) {
16513 unset($zp['SIZE']);
16515 if (isset($zp['SHEET-SIZE'])) {
16516 unset($zp['SHEET-SIZE']);
16518 // Disallow margin-left or -right on :LEFT or :RIGHT
16519 if (isset($zp['MARGIN-LEFT'])) {
16520 unset($zp['MARGIN-LEFT']);
16522 if (isset($zp['MARGIN-RIGHT'])) {
16523 unset($zp['MARGIN-RIGHT']);
16525 if (is_array($zp) && !empty($zp)) {
16526 $p = array_merge($p, $zp);
16529 // If left/Even page
16530 if (isset($this->cssManager->CSS['@PAGE>>PSEUDO>>LEFT']) && $side == 'L') {
16531 $zp = $this->cssManager->CSS['@PAGE>>PSEUDO>>LEFT'];
16532 } else {
16533 $zp = [];
16535 if (isset($zp['SIZE'])) {
16536 unset($zp['SIZE']);
16538 if (isset($zp['SHEET-SIZE'])) {
16539 unset($zp['SHEET-SIZE']);
16541 // Disallow margin-left or -right on :LEFT or :RIGHT
16542 if (isset($zp['MARGIN-LEFT'])) {
16543 unset($zp['MARGIN-LEFT']);
16545 if (isset($zp['MARGIN-RIGHT'])) {
16546 unset($zp['MARGIN-RIGHT']);
16548 if (is_array($zp) && !empty($zp)) {
16549 $p = array_merge($p, $zp);
16552 // If first page
16553 if (isset($this->cssManager->CSS['@PAGE>>PSEUDO>>FIRST']) && $first) {
16554 $zp = $this->cssManager->CSS['@PAGE>>PSEUDO>>FIRST'];
16555 } else {
16556 $zp = [];
16558 if (isset($zp['SIZE'])) {
16559 unset($zp['SIZE']);
16561 if (isset($zp['SHEET-SIZE'])) {
16562 unset($zp['SHEET-SIZE']);
16564 // Disallow margin-left or -right on :FIRST // mPDF 5.7.3
16565 if (isset($zp['MARGIN-LEFT'])) {
16566 unset($zp['MARGIN-LEFT']);
16568 if (isset($zp['MARGIN-RIGHT'])) {
16569 unset($zp['MARGIN-RIGHT']);
16571 if (is_array($zp) && !empty($zp)) {
16572 $p = array_merge($p, $zp);
16575 // If named page
16576 if ($name) {
16577 if (isset($this->cssManager->CSS['@PAGE>>NAMED>>' . $name])) {
16578 $zp = $this->cssManager->CSS['@PAGE>>NAMED>>' . $name];
16579 } else {
16580 $zp = [];
16582 if (is_array($zp) && !empty($zp)) {
16583 $p = array_merge($p, $zp);
16586 if (isset($p['EVEN-HEADER-NAME']) && $oddEven == 'E') {
16587 $p['HEADER'] = $p['EVEN-HEADER-NAME'];
16588 unset($p['EVEN-HEADER-NAME']);
16590 if (isset($p['ODD-HEADER-NAME']) && $oddEven != 'E') {
16591 $p['HEADER'] = $p['ODD-HEADER-NAME'];
16592 unset($p['ODD-HEADER-NAME']);
16594 if (isset($p['EVEN-FOOTER-NAME']) && $oddEven == 'E') {
16595 $p['FOOTER'] = $p['EVEN-FOOTER-NAME'];
16596 unset($p['EVEN-FOOTER-NAME']);
16598 if (isset($p['ODD-FOOTER-NAME']) && $oddEven != 'E') {
16599 $p['FOOTER'] = $p['ODD-FOOTER-NAME'];
16600 unset($p['ODD-FOOTER-NAME']);
16603 // If named right/Odd page
16604 if (isset($this->cssManager->CSS['@PAGE>>NAMED>>' . $name . '>>PSEUDO>>RIGHT']) && $side == 'R') {
16605 $zp = $this->cssManager->CSS['@PAGE>>NAMED>>' . $name . '>>PSEUDO>>RIGHT'];
16606 } else {
16607 $zp = [];
16609 if (isset($zp['SIZE'])) {
16610 unset($zp['SIZE']);
16612 if (isset($zp['SHEET-SIZE'])) {
16613 unset($zp['SHEET-SIZE']);
16615 // Disallow margin-left or -right on :LEFT or :RIGHT
16616 if (isset($zp['MARGIN-LEFT'])) {
16617 unset($zp['MARGIN-LEFT']);
16619 if (isset($zp['MARGIN-RIGHT'])) {
16620 unset($zp['MARGIN-RIGHT']);
16622 if (is_array($zp) && !empty($zp)) {
16623 $p = array_merge($p, $zp);
16626 // If named left/Even page
16627 if (isset($this->cssManager->CSS['@PAGE>>NAMED>>' . $name . '>>PSEUDO>>LEFT']) && $side == 'L') {
16628 $zp = $this->cssManager->CSS['@PAGE>>NAMED>>' . $name . '>>PSEUDO>>LEFT'];
16629 } else {
16630 $zp = [];
16632 if (isset($zp['SIZE'])) {
16633 unset($zp['SIZE']);
16635 if (isset($zp['SHEET-SIZE'])) {
16636 unset($zp['SHEET-SIZE']);
16638 // Disallow margin-left or -right on :LEFT or :RIGHT
16639 if (isset($zp['MARGIN-LEFT'])) {
16640 unset($zp['MARGIN-LEFT']);
16642 if (isset($zp['MARGIN-RIGHT'])) {
16643 unset($zp['MARGIN-RIGHT']);
16645 if (is_array($zp) && !empty($zp)) {
16646 $p = array_merge($p, $zp);
16649 // If named first page
16650 if (isset($this->cssManager->CSS['@PAGE>>NAMED>>' . $name . '>>PSEUDO>>FIRST']) && $first) {
16651 $zp = $this->cssManager->CSS['@PAGE>>NAMED>>' . $name . '>>PSEUDO>>FIRST'];
16652 } else {
16653 $zp = [];
16655 if (isset($zp['SIZE'])) {
16656 unset($zp['SIZE']);
16658 if (isset($zp['SHEET-SIZE'])) {
16659 unset($zp['SHEET-SIZE']);
16661 // Disallow margin-left or -right on :FIRST // mPDF 5.7.3
16662 if (isset($zp['MARGIN-LEFT'])) {
16663 unset($zp['MARGIN-LEFT']);
16665 if (isset($zp['MARGIN-RIGHT'])) {
16666 unset($zp['MARGIN-RIGHT']);
16668 if (is_array($zp) && !empty($zp)) {
16669 $p = array_merge($p, $zp);
16673 $orientation = $mgl = $mgr = $mgt = $mgb = $mgh = $mgf = '';
16674 $header = $footer = '';
16675 $resetpagenum = $pagenumstyle = $suppress = '';
16676 $marks = '';
16677 $bg = [];
16679 $newformat = '';
16682 if (isset($p['SHEET-SIZE']) && is_array($p['SHEET-SIZE'])) {
16683 $newformat = $p['SHEET-SIZE'];
16684 if ($newformat[0] > $newformat[1]) { // landscape
16685 $newformat = array_reverse($newformat);
16686 $p['ORIENTATION'] = 'L';
16687 } else {
16688 $p['ORIENTATION'] = 'P';
16690 $this->_setPageSize($newformat, $p['ORIENTATION']);
16693 if (isset($p['SIZE']) && is_array($p['SIZE']) && !$newformat) {
16694 if ($p['SIZE']['W'] > $p['SIZE']['H']) {
16695 $p['ORIENTATION'] = 'L';
16696 } else {
16697 $p['ORIENTATION'] = 'P';
16700 if (is_array($p['SIZE'])) {
16701 if ($p['SIZE']['W'] > $this->fw) {
16702 $p['SIZE']['W'] = $this->fw;
16703 } // mPD 4.2 use fw not fPt
16704 if ($p['SIZE']['H'] > $this->fh) {
16705 $p['SIZE']['H'] = $this->fh;
16707 if (($p['ORIENTATION'] == $this->DefOrientation && !$newformat) || ($newformat && $p['ORIENTATION'] == 'P')) {
16708 $outer_width_LR = ($this->fw - $p['SIZE']['W']) / 2;
16709 $outer_width_TB = ($this->fh - $p['SIZE']['H']) / 2;
16710 } else {
16711 $outer_width_LR = ($this->fh - $p['SIZE']['W']) / 2;
16712 $outer_width_TB = ($this->fw - $p['SIZE']['H']) / 2;
16714 $pgw = $p['SIZE']['W'];
16715 $pgh = $p['SIZE']['H'];
16716 } else { // AUTO LANDSCAPE PORTRAIT
16717 $outer_width_LR = 0;
16718 $outer_width_TB = 0;
16719 if (!$newformat) {
16720 if (strtoupper($p['SIZE']) == 'AUTO') {
16721 $p['ORIENTATION'] = $this->DefOrientation;
16722 } elseif (strtoupper($p['SIZE']) == 'LANDSCAPE') {
16723 $p['ORIENTATION'] = 'L';
16724 } else {
16725 $p['ORIENTATION'] = 'P';
16728 if (($p['ORIENTATION'] == $this->DefOrientation && !$newformat) || ($newformat && $p['ORIENTATION'] == 'P')) {
16729 $pgw = $this->fw;
16730 $pgh = $this->fh;
16731 } else {
16732 $pgw = $this->fh;
16733 $pgh = $this->fw;
16737 if (isset($p['HEADER']) && $p['HEADER']) {
16738 $header = $p['HEADER'];
16740 if (isset($p['FOOTER']) && $p['FOOTER']) {
16741 $footer = $p['FOOTER'];
16743 if (isset($p['RESETPAGENUM']) && $p['RESETPAGENUM']) {
16744 $resetpagenum = $p['RESETPAGENUM'];
16746 if (isset($p['PAGENUMSTYLE']) && $p['PAGENUMSTYLE']) {
16747 $pagenumstyle = $p['PAGENUMSTYLE'];
16749 if (isset($p['SUPPRESS']) && $p['SUPPRESS']) {
16750 $suppress = $p['SUPPRESS'];
16753 if (isset($p['MARKS'])) {
16754 if (preg_match('/cross/i', $p['MARKS']) && preg_match('/crop/i', $p['MARKS'])) {
16755 $marks = 'CROPCROSS';
16756 } elseif (strtoupper($p['MARKS']) == 'CROP') {
16757 $marks = 'CROP';
16758 } elseif (strtoupper($p['MARKS']) == 'CROSS') {
16759 $marks = 'CROSS';
16763 if (isset($p['BACKGROUND-COLOR']) && $p['BACKGROUND-COLOR']) {
16764 $bg['BACKGROUND-COLOR'] = $p['BACKGROUND-COLOR'];
16766 /* -- BACKGROUNDS -- */
16767 if (isset($p['BACKGROUND-GRADIENT']) && $p['BACKGROUND-GRADIENT']) {
16768 $bg['BACKGROUND-GRADIENT'] = $p['BACKGROUND-GRADIENT'];
16770 if (isset($p['BACKGROUND-IMAGE']) && $p['BACKGROUND-IMAGE']) {
16771 $bg['BACKGROUND-IMAGE'] = $p['BACKGROUND-IMAGE'];
16773 if (isset($p['BACKGROUND-REPEAT']) && $p['BACKGROUND-REPEAT']) {
16774 $bg['BACKGROUND-REPEAT'] = $p['BACKGROUND-REPEAT'];
16776 if (isset($p['BACKGROUND-POSITION']) && $p['BACKGROUND-POSITION']) {
16777 $bg['BACKGROUND-POSITION'] = $p['BACKGROUND-POSITION'];
16779 if (isset($p['BACKGROUND-IMAGE-RESIZE']) && $p['BACKGROUND-IMAGE-RESIZE']) {
16780 $bg['BACKGROUND-IMAGE-RESIZE'] = $p['BACKGROUND-IMAGE-RESIZE'];
16782 if (isset($p['BACKGROUND-IMAGE-OPACITY'])) {
16783 $bg['BACKGROUND-IMAGE-OPACITY'] = $p['BACKGROUND-IMAGE-OPACITY'];
16785 /* -- END BACKGROUNDS -- */
16787 if (isset($p['MARGIN-LEFT'])) {
16788 $mgl = $this->sizeConverter->convert($p['MARGIN-LEFT'], $pgw) + $outer_width_LR;
16790 if (isset($p['MARGIN-RIGHT'])) {
16791 $mgr = $this->sizeConverter->convert($p['MARGIN-RIGHT'], $pgw) + $outer_width_LR;
16793 if (isset($p['MARGIN-BOTTOM'])) {
16794 $mgb = $this->sizeConverter->convert($p['MARGIN-BOTTOM'], $pgh) + $outer_width_TB;
16796 if (isset($p['MARGIN-TOP'])) {
16797 $mgt = $this->sizeConverter->convert($p['MARGIN-TOP'], $pgh) + $outer_width_TB;
16799 if (isset($p['MARGIN-HEADER'])) {
16800 $mgh = $this->sizeConverter->convert($p['MARGIN-HEADER'], $pgh) + $outer_width_TB;
16802 if (isset($p['MARGIN-FOOTER'])) {
16803 $mgf = $this->sizeConverter->convert($p['MARGIN-FOOTER'], $pgh) + $outer_width_TB;
16806 if (isset($p['ORIENTATION']) && $p['ORIENTATION']) {
16807 $orientation = $p['ORIENTATION'];
16809 $this->page_box['outer_width_LR'] = $outer_width_LR; // Used in MARKS:crop etc.
16810 $this->page_box['outer_width_TB'] = $outer_width_TB;
16812 return [$orientation, $mgl, $mgr, $mgt, $mgb, $mgh, $mgf, $header, $footer, $bg, $resetpagenum, $pagenumstyle, $suppress, $marks, $newformat];
16815 /* -- END CSS-PAGE -- */
16819 /* -- CSS-FLOAT -- */
16821 // Added mPDF 3.0 Float DIV - CLEAR
16822 function ClearFloats($clear, $blklvl = 0)
16824 list($l_exists, $r_exists, $l_max, $r_max, $l_width, $r_width) = $this->GetFloatDivInfo($blklvl, true);
16825 $end = $currpos = ($this->page * 1000 + $this->y);
16826 if ($clear == 'BOTH' && ($l_exists || $r_exists)) {
16827 $this->pageoutput[$this->page] = [];
16828 $end = max($l_max, $r_max, $currpos);
16829 } elseif ($clear == 'RIGHT' && $r_exists) {
16830 $this->pageoutput[$this->page] = [];
16831 $end = max($r_max, $currpos);
16832 } elseif ($clear == 'LEFT' && $l_exists) {
16833 $this->pageoutput[$this->page] = [];
16834 $end = max($l_max, $currpos);
16835 } else {
16836 return;
16838 $old_page = $this->page;
16839 $new_page = intval($end / 1000);
16840 if ($old_page != $new_page) {
16841 $s = $this->PrintPageBackgrounds();
16842 // Writes after the marker so not overwritten later by page background etc.
16843 $this->pages[$this->page] = preg_replace('/(___BACKGROUND___PATTERNS' . $this->uniqstr . ')/', '\\1' . "\n" . $s . "\n", $this->pages[$this->page]);
16844 $this->pageBackgrounds = [];
16845 $this->page = $new_page;
16847 $this->ResetMargins();
16848 $this->pageoutput[$this->page] = [];
16849 $this->y = (($end * 1000) % 1000000) / 1000; // mod changes operands to integers before processing
16852 // Added mPDF 3.0 Float DIV
16853 function GetFloatDivInfo($blklvl = 0, $clear = false)
16855 // If blklvl specified, only returns floats at that level - for ClearFloats
16856 $l_exists = false;
16857 $r_exists = false;
16858 $l_max = 0;
16859 $r_max = 0;
16860 $l_width = 0;
16861 $r_width = 0;
16862 if (count($this->floatDivs)) {
16863 $currpos = ($this->page * 1000 + $this->y);
16864 foreach ($this->floatDivs as $f) {
16865 if (($clear && $f['blockContext'] == $this->blk[$blklvl]['blockContext']) || (!$clear && $currpos >= $f['startpos'] && $currpos < ($f['endpos'] - 0.001) && $f['blklvl'] > $blklvl && $f['blockContext'] == $this->blk[$blklvl]['blockContext'])) {
16866 if ($f['side'] == 'L') {
16867 $l_exists = true;
16868 $l_max = max($l_max, $f['endpos']);
16869 $l_width = max($l_width, $f['w']);
16871 if ($f['side'] == 'R') {
16872 $r_exists = true;
16873 $r_max = max($r_max, $f['endpos']);
16874 $r_width = max($r_width, $f['w']);
16879 return [$l_exists, $r_exists, $l_max, $r_max, $l_width, $r_width];
16882 /* -- END CSS-FLOAT -- */
16884 // LIST MARKERS // mPDF 6 Lists
16885 function _setListMarker($listitemtype, $listitemimage, $listitemposition)
16887 // if position:inside (and NOT table) - output now as a textbuffer; (so if next is block, will move to new line)
16888 // elseif position:outside (and NOT table) - output in front of first textbuffer output by setting listitem (cf. _saveTextBuffer)
16889 $e = '';
16890 $this->listitem = '';
16891 $spacer = ' ';
16892 // IMAGE
16893 if ($listitemimage && $listitemimage != 'none') {
16894 $listitemimage = trim(preg_replace('/url\(["\']*(.*?)["\']*\)/', '\\1', $listitemimage));
16896 // ? Restrict maximum height/width of list marker??
16897 $maxWidth = 100;
16898 $maxHeight = 100;
16900 $objattr = [];
16901 $objattr['margin_top'] = 0;
16902 $objattr['margin_bottom'] = 0;
16903 $objattr['margin_left'] = 0;
16904 $objattr['margin_right'] = 0;
16905 $objattr['padding_top'] = 0;
16906 $objattr['padding_bottom'] = 0;
16907 $objattr['padding_left'] = 0;
16908 $objattr['padding_right'] = 0;
16909 $objattr['width'] = 0;
16910 $objattr['height'] = 0;
16911 $objattr['border_top']['w'] = 0;
16912 $objattr['border_bottom']['w'] = 0;
16913 $objattr['border_left']['w'] = 0;
16914 $objattr['border_right']['w'] = 0;
16915 $objattr['visibility'] = 'visible';
16916 $srcpath = $listitemimage;
16917 $orig_srcpath = $listitemimage;
16919 $objattr['vertical-align'] = 'BS'; // vertical alignment of marker (baseline)
16920 $w = 0;
16921 $h = 0;
16923 // Image file
16924 $info = $this->imageProcessor->getImage($srcpath, true, true, $orig_srcpath);
16925 if (!$info) {
16926 return;
16929 if ($info['w'] == 0 && $info['h'] == 0) {
16930 $info['h'] = $this->sizeConverter->convert('1em', $this->blk[$this->blklvl]['inner_width'], $this->FontSize, false);
16933 $objattr['file'] = $srcpath;
16935 // Default width and height calculation if needed
16936 if ($w == 0 and $h == 0) {
16937 /* -- IMAGES-WMF -- */
16938 if ($info['type'] == 'wmf') {
16939 // WMF units are twips (1/20pt)
16940 // divide by 20 to get points
16941 // divide by k to get user units
16942 $w = abs($info['w']) / (20 * Mpdf::SCALE);
16943 $h = abs($info['h']) / (20 * Mpdf::SCALE);
16944 } else { /* -- END IMAGES-WMF -- */
16945 if ($info['type'] == 'svg') {
16946 // SVG units are pixels
16947 $w = abs($info['w']) / Mpdf::SCALE;
16948 $h = abs($info['h']) / Mpdf::SCALE;
16949 } else {
16950 // Put image at default image dpi
16951 $w = ($info['w'] / Mpdf::SCALE) * (72 / $this->img_dpi);
16952 $h = ($info['h'] / Mpdf::SCALE) * (72 / $this->img_dpi);
16956 // IF WIDTH OR HEIGHT SPECIFIED
16957 if ($w == 0) {
16958 $w = abs($h * $info['w'] / $info['h']);
16960 if ($h == 0) {
16961 $h = abs($w * $info['h'] / $info['w']);
16964 if ($w > $maxWidth) {
16965 $w = $maxWidth;
16966 $h = abs($w * $info['h'] / $info['w']);
16969 if ($h > $maxHeight) {
16970 $h = $maxHeight;
16971 $w = abs($h * $info['w'] / $info['h']);
16974 $objattr['type'] = 'image';
16975 $objattr['itype'] = $info['type'];
16977 $objattr['orig_h'] = $info['h'];
16978 $objattr['orig_w'] = $info['w'];
16980 /* -- IMAGES-WMF -- */
16981 if ($info['type'] == 'wmf') {
16982 $objattr['wmf_x'] = $info['x'];
16983 $objattr['wmf_y'] = $info['y'];
16984 } else { /* -- END IMAGES-WMF -- */
16985 if ($info['type'] == 'svg') {
16986 $objattr['wmf_x'] = $info['x'];
16987 $objattr['wmf_y'] = $info['y'];
16991 $objattr['height'] = $h;
16992 $objattr['width'] = $w;
16993 $objattr['image_height'] = $h;
16994 $objattr['image_width'] = $w;
16996 $objattr['dir'] = (isset($this->blk[$this->blklvl]['direction']) ? $this->blk[$this->blklvl]['direction'] : 'ltr');
16997 $objattr['listmarker'] = true;
16999 $objattr['listmarkerposition'] = $listitemposition;
17001 $e = "\xbb\xa4\xactype=image,objattr=" . serialize($objattr) . "\xbb\xa4\xac";
17002 $this->_saveTextBuffer($e);
17004 if ($listitemposition == 'inside') {
17005 $e = $spacer;
17006 $this->_saveTextBuffer($e);
17008 } elseif ($listitemtype == 'disc' || $listitemtype == 'circle' || $listitemtype == 'square') { // SYMBOL (needs new font)
17009 $objattr = [];
17010 $objattr['type'] = 'listmarker';
17011 $objattr['listmarkerposition'] = $listitemposition;
17012 $objattr['width'] = 0;
17013 $size = $this->sizeConverter->convert($this->list_symbol_size, $this->FontSize);
17014 $objattr['size'] = $size;
17015 $objattr['offset'] = $this->sizeConverter->convert($this->list_marker_offset, $this->FontSize);
17017 if ($listitemposition == 'inside') {
17018 $objattr['width'] = $size + $objattr['offset'];
17021 $objattr['height'] = $this->FontSize;
17022 $objattr['vertical-align'] = 'T';
17023 $objattr['text'] = '';
17024 $objattr['dir'] = (isset($this->blk[$this->blklvl]['direction']) ? $this->blk[$this->blklvl]['direction'] : 'ltr');
17025 $objattr['bullet'] = $listitemtype;
17026 $objattr['colorarray'] = $this->colorarray;
17027 $objattr['fontfamily'] = $this->FontFamily;
17028 $objattr['fontsize'] = $this->FontSize;
17029 $objattr['fontsizept'] = $this->FontSizePt;
17030 $objattr['fontstyle'] = $this->FontStyle;
17032 $e = "\xbb\xa4\xactype=listmarker,objattr=" . serialize($objattr) . "\xbb\xa4\xac";
17033 $this->listitem = $this->_saveTextBuffer($e, '', '', true); // true returns array
17035 } elseif (preg_match('/U\+([a-fA-F0-9]+)/i', $listitemtype, $m)) { // SYMBOL 2 (needs new font)
17037 if ($this->_charDefined($this->CurrentFont['cw'], hexdec($m[1]))) {
17038 $list_item_marker = UtfString::codeHex2utf($m[1]);
17039 } else {
17040 $list_item_marker = '-';
17042 if (preg_match('/rgb\(.*?\)/', $listitemtype, $m)) {
17043 $list_item_color = $this->colorConverter->convert($m[0], $this->PDFAXwarnings);
17044 } else {
17045 $list_item_color = '';
17048 // SAVE then SET COLR
17049 $save_colorarray = $this->colorarray;
17050 if ($list_item_color) {
17051 $this->colorarray = $list_item_color;
17054 if ($listitemposition == 'inside') {
17055 $e = $list_item_marker . $spacer;
17056 $this->_saveTextBuffer($e);
17057 } else {
17058 $objattr = [];
17059 $objattr['type'] = 'listmarker';
17060 $objattr['width'] = 0;
17061 $objattr['height'] = $this->FontSize;
17062 $objattr['vertical-align'] = 'T';
17063 $objattr['text'] = $list_item_marker;
17064 $objattr['dir'] = (isset($this->blk[$this->blklvl]['direction']) ? $this->blk[$this->blklvl]['direction'] : 'ltr');
17065 $objattr['colorarray'] = $this->colorarray;
17066 $objattr['fontfamily'] = $this->FontFamily;
17067 $objattr['fontsize'] = $this->FontSize;
17068 $objattr['fontsizept'] = $this->FontSizePt;
17069 $objattr['fontstyle'] = $this->FontStyle;
17070 $e = "\xbb\xa4\xactype=listmarker,objattr=" . serialize($objattr) . "\xbb\xa4\xac";
17071 $this->listitem = $this->_saveTextBuffer($e, '', '', true); // true returns array
17074 // RESET COLOR
17075 $this->colorarray = $save_colorarray;
17077 } else { // TEXT
17078 $counter = $this->listcounter[$this->listlvl];
17080 if ($listitemtype == 'none') {
17081 return;
17084 $num = $this->_getStyledNumber($counter, $listitemtype, true);
17086 if ($listitemposition == 'inside') {
17087 $e = $num . $this->list_number_suffix . $spacer;
17088 $this->_saveTextBuffer($e);
17089 } else {
17090 if (isset($this->blk[$this->blklvl]['direction']) && $this->blk[$this->blklvl]['direction'] == 'rtl') {
17091 // REPLACE MIRRORED RTL $this->list_number_suffix e.g. ) -> ( (NB could use Ucdn::$mirror_pairs)
17092 $m = strtr($this->list_number_suffix, ")]}", "([{") . $num;
17093 } else {
17094 $m = $num . $this->list_number_suffix;
17097 $objattr = [];
17098 $objattr['type'] = 'listmarker';
17099 $objattr['width'] = 0;
17100 $objattr['height'] = $this->FontSize;
17101 $objattr['vertical-align'] = 'T';
17102 $objattr['text'] = $m;
17103 $objattr['dir'] = (isset($this->blk[$this->blklvl]['direction']) ? $this->blk[$this->blklvl]['direction'] : 'ltr');
17104 $objattr['colorarray'] = $this->colorarray;
17105 $objattr['fontfamily'] = $this->FontFamily;
17106 $objattr['fontsize'] = $this->FontSize;
17107 $objattr['fontsizept'] = $this->FontSizePt;
17108 $objattr['fontstyle'] = $this->FontStyle;
17109 $e = "\xbb\xa4\xactype=listmarker,objattr=" . serialize($objattr) . "\xbb\xa4\xac";
17111 $this->listitem = $this->_saveTextBuffer($e, '', '', true); // true returns array
17116 // mPDF Lists
17117 function _getListMarkerWidth(&$currblk, &$a, &$i)
17119 $blt_width = 0;
17121 $markeroffset = $this->sizeConverter->convert($this->list_marker_offset, $this->FontSize);
17123 // Get Maximum number in the list
17124 $maxnum = $this->listcounter[$this->listlvl];
17125 if ($currblk['list_style_type'] != 'disc' && $currblk['list_style_type'] != 'circle' && $currblk['list_style_type'] != 'square') {
17126 $lvl = 1;
17127 for ($j = $i + 2; $j < count($a); $j+=2) {
17128 $e = $a[$j];
17129 if (!$e) {
17130 continue;
17132 if ($e[0] == '/') { // end tag
17133 $e = strtoupper(substr($e, 1));
17134 if ($e == 'OL' || $e == 'UL') {
17135 if ($lvl == 1) {
17136 break;
17138 $lvl--;
17140 } else { // opening tag
17141 if (strpos($e, ' ')) {
17142 $e = substr($e, 0, strpos($e, ' '));
17144 $e = strtoupper($e);
17145 if ($e == 'LI') {
17146 if ($lvl == 1) {
17147 $maxnum++;
17149 } elseif ($e == 'OL' || $e == 'UL') {
17150 $lvl++;
17156 $decToAlpha = new Conversion\DecToAlpha();
17157 $decToRoman = new Conversion\DecToRoman();
17158 $decToOther = new Conversion\DecToOther($this);
17160 switch ($currblk['list_style_type']) {
17161 case 'decimal':
17162 case '1':
17163 $blt_width = $this->GetStringWidth(str_repeat('5', strlen($maxnum)) . $this->list_number_suffix);
17164 break;
17165 case 'none':
17166 $blt_width = 0;
17167 break;
17168 case 'upper-alpha':
17169 case 'upper-latin':
17170 case 'A':
17171 $maxnumA = $decToAlpha->convert($maxnum, true);
17172 if ($maxnum < 13) {
17173 $blt_width = $this->GetStringWidth('D' . $this->list_number_suffix);
17174 } else {
17175 $blt_width = $this->GetStringWidth(str_repeat('W', strlen($maxnumA)) . $this->list_number_suffix);
17177 break;
17178 case 'lower-alpha':
17179 case 'lower-latin':
17180 case 'a':
17181 $maxnuma = $decToAlpha->convert($maxnum, false);
17182 if ($maxnum < 13) {
17183 $blt_width = $this->GetStringWidth('b' . $this->list_number_suffix);
17184 } else {
17185 $blt_width = $this->GetStringWidth(str_repeat('m', strlen($maxnuma)) . $this->list_number_suffix);
17187 break;
17188 case 'upper-roman':
17189 case 'I':
17190 if ($maxnum > 87) {
17191 $bbit = 87;
17192 } elseif ($maxnum > 86) {
17193 $bbit = 86;
17194 } elseif ($maxnum > 37) {
17195 $bbit = 38;
17196 } elseif ($maxnum > 36) {
17197 $bbit = 37;
17198 } elseif ($maxnum > 27) {
17199 $bbit = 28;
17200 } elseif ($maxnum > 26) {
17201 $bbit = 27;
17202 } elseif ($maxnum > 17) {
17203 $bbit = 18;
17204 } elseif ($maxnum > 16) {
17205 $bbit = 17;
17206 } elseif ($maxnum > 7) {
17207 $bbit = 8;
17208 } elseif ($maxnum > 6) {
17209 $bbit = 7;
17210 } elseif ($maxnum > 3) {
17211 $bbit = 4;
17212 } else {
17213 $bbit = $maxnum;
17216 $maxlnum = $decToRoman->convert($bbit, true);
17217 $blt_width = $this->GetStringWidth($maxlnum . $this->list_number_suffix);
17219 break;
17220 case 'lower-roman':
17221 case 'i':
17222 if ($maxnum > 87) {
17223 $bbit = 87;
17224 } elseif ($maxnum > 86) {
17225 $bbit = 86;
17226 } elseif ($maxnum > 37) {
17227 $bbit = 38;
17228 } elseif ($maxnum > 36) {
17229 $bbit = 37;
17230 } elseif ($maxnum > 27) {
17231 $bbit = 28;
17232 } elseif ($maxnum > 26) {
17233 $bbit = 27;
17234 } elseif ($maxnum > 17) {
17235 $bbit = 18;
17236 } elseif ($maxnum > 16) {
17237 $bbit = 17;
17238 } elseif ($maxnum > 7) {
17239 $bbit = 8;
17240 } elseif ($maxnum > 6) {
17241 $bbit = 7;
17242 } elseif ($maxnum > 3) {
17243 $bbit = 4;
17244 } else {
17245 $bbit = $maxnum;
17247 $maxlnum = $decToRoman->convert($bbit, false);
17248 $blt_width = $this->GetStringWidth($maxlnum . $this->list_number_suffix);
17249 break;
17251 case 'disc':
17252 case 'circle':
17253 case 'square':
17254 $size = $this->sizeConverter->convert($this->list_symbol_size, $this->FontSize);
17255 $offset = $this->sizeConverter->convert($this->list_marker_offset, $this->FontSize);
17256 $blt_width = $size + $offset;
17257 break;
17259 case 'arabic-indic':
17260 $blt_width = $this->GetStringWidth(str_repeat($decToOther->convert(3, 0x0660), strlen($maxnum)) . $this->list_number_suffix);
17261 break;
17262 case 'persian':
17263 case 'urdu':
17264 $blt_width = $this->GetStringWidth(str_repeat($decToOther->convert(3, 0x06F0), strlen($maxnum)) . $this->list_number_suffix);
17265 break;
17266 case 'bengali':
17267 $blt_width = $this->GetStringWidth(str_repeat($decToOther->convert(3, 0x09E6), strlen($maxnum)) . $this->list_number_suffix);
17268 break;
17269 case 'devanagari':
17270 $blt_width = $this->GetStringWidth(str_repeat($decToOther->convert(3, 0x0966), strlen($maxnum)) . $this->list_number_suffix);
17271 break;
17272 case 'gujarati':
17273 $blt_width = $this->GetStringWidth(str_repeat($decToOther->convert(3, 0x0AE6), strlen($maxnum)) . $this->list_number_suffix);
17274 break;
17275 case 'gurmukhi':
17276 $blt_width = $this->GetStringWidth(str_repeat($decToOther->convert(3, 0x0A66), strlen($maxnum)) . $this->list_number_suffix);
17277 break;
17278 case 'kannada':
17279 $blt_width = $this->GetStringWidth(str_repeat($decToOther->convert(3, 0x0CE6), strlen($maxnum)) . $this->list_number_suffix);
17280 break;
17281 case 'malayalam':
17282 $blt_width = $this->GetStringWidth(str_repeat($decToOther->convert(6, 0x0D66), strlen($maxnum)) . $this->list_number_suffix);
17283 break;
17284 case 'oriya':
17285 $blt_width = $this->GetStringWidth(str_repeat($decToOther->convert(3, 0x0B66), strlen($maxnum)) . $this->list_number_suffix);
17286 break;
17287 case 'telugu':
17288 $blt_width = $this->GetStringWidth(str_repeat($decToOther->convert(3, 0x0C66), strlen($maxnum)) . $this->list_number_suffix);
17289 break;
17290 case 'tamil':
17291 $blt_width = $this->GetStringWidth(str_repeat($decToOther->convert(9, 0x0BE6), strlen($maxnum)) . $this->list_number_suffix);
17292 break;
17293 case 'thai':
17294 $blt_width = $this->GetStringWidth(str_repeat($decToOther->convert(5, 0x0E50), strlen($maxnum)) . $this->list_number_suffix);
17295 break;
17296 default:
17297 $blt_width = $this->GetStringWidth(str_repeat('5', strlen($maxnum)) . $this->list_number_suffix);
17298 break;
17301 return ($blt_width + $markeroffset);
17304 /* -- TABLES -- */
17306 // This function determines the shrink factor when resizing tables
17307 // val is the table_height / page_height_available
17308 // returns a scaling factor used as $shrin_k to resize the table
17309 // Overcompensating will be quicker but may unnecessarily shrink table too much
17310 // Undercompensating means it will reiterate more times (taking more processing time)
17311 function tbsqrt($val, $iteration = 3)
17313 $k = 4; // Alters number of iterations until it returns $val itself - Must be > 2
17314 // Probably best guess and most accurate
17315 if ($iteration == 1) {
17316 return sqrt($val);
17318 // Faster than using sqrt (because it won't undercompensate), and gives reasonable results
17319 // return 1+(($val-1)/2);
17320 $x = 2 - (($iteration - 2) / ($k - 2));
17321 if ($x == 0) {
17322 $ret = $val + 0.00001;
17323 } elseif ($x < 0) {
17324 $ret = 1 + ( pow(2, ($iteration - 2 - $k)) / 1000 );
17325 } else {
17326 $ret = 1 + (($val - 1) / $x);
17328 return $ret;
17331 /* -- END TABLES -- */
17333 function _saveTextBuffer($t, $link = '', $intlink = '', $return = false)
17335 // mPDF 6 Lists
17336 $arr = [];
17337 $arr[0] = $t;
17338 if (isset($link) && $link) {
17339 $arr[1] = $link;
17341 $arr[2] = $this->currentfontstyle;
17342 if (isset($this->colorarray) && $this->colorarray) {
17343 $arr[3] = $this->colorarray;
17345 $arr[4] = $this->currentfontfamily;
17346 $arr[5] = $this->currentLang; // mPDF 6
17347 if (isset($intlink) && $intlink) {
17348 $arr[7] = $intlink;
17350 // mPDF 6
17351 // If Kerning set for OTL, and useOTL has positive value, but has not set for this particular script,
17352 // set for kerning via kern table
17353 // e.g. Latin script when useOTL set as 0x80
17354 if (isset($this->OTLtags['Plus']) && strpos($this->OTLtags['Plus'], 'kern') !== false && empty($this->OTLdata['GPOSinfo'])) {
17355 $this->textvar = ($this->textvar | TextVars::FC_KERNING);
17357 $arr[8] = $this->textvar; // mPDF 5.7.1
17358 if (isset($this->textparam) && $this->textparam) {
17359 $arr[9] = $this->textparam;
17361 if (isset($this->spanbgcolorarray) && $this->spanbgcolorarray) {
17362 $arr[10] = $this->spanbgcolorarray;
17364 $arr[11] = $this->currentfontsize;
17365 if (isset($this->ReqFontStyle) && $this->ReqFontStyle) {
17366 $arr[12] = $this->ReqFontStyle;
17368 if (isset($this->lSpacingCSS) && $this->lSpacingCSS) {
17369 $arr[14] = $this->lSpacingCSS;
17371 if (isset($this->wSpacingCSS) && $this->wSpacingCSS) {
17372 $arr[15] = $this->wSpacingCSS;
17374 if (isset($this->spanborddet) && $this->spanborddet) {
17375 $arr[16] = $this->spanborddet;
17377 if (isset($this->textshadow) && $this->textshadow) {
17378 $arr[17] = $this->textshadow;
17380 if (isset($this->OTLdata) && $this->OTLdata) {
17381 $arr[18] = $this->OTLdata;
17382 $this->OTLdata = [];
17383 } // mPDF 5.7.1
17384 else {
17385 $arr[18] = null;
17387 // mPDF 6 Lists
17388 if ($return) {
17389 return ($arr);
17391 if ($this->listitem) {
17392 $this->textbuffer[] = $this->listitem;
17393 $this->listitem = [];
17395 $this->textbuffer[] = $arr;
17398 function _saveCellTextBuffer($t, $link = '', $intlink = '')
17400 $arr = [];
17401 $arr[0] = $t;
17402 if (isset($link) && $link) {
17403 $arr[1] = $link;
17405 $arr[2] = $this->currentfontstyle;
17406 if (isset($this->colorarray) && $this->colorarray) {
17407 $arr[3] = $this->colorarray;
17409 $arr[4] = $this->currentfontfamily;
17410 if (isset($intlink) && $intlink) {
17411 $arr[7] = $intlink;
17413 // mPDF 6
17414 // If Kerning set for OTL, and useOTL has positive value, but has not set for this particular script,
17415 // set for kerning via kern table
17416 // e.g. Latin script when useOTL set as 0x80
17417 if (isset($this->OTLtags['Plus']) && strpos($this->OTLtags['Plus'], 'kern') !== false && empty($this->OTLdata['GPOSinfo'])) {
17418 $this->textvar = ($this->textvar | TextVars::FC_KERNING);
17420 $arr[8] = $this->textvar; // mPDF 5.7.1
17421 if (isset($this->textparam) && $this->textparam) {
17422 $arr[9] = $this->textparam;
17424 if (isset($this->spanbgcolorarray) && $this->spanbgcolorarray) {
17425 $arr[10] = $this->spanbgcolorarray;
17427 $arr[11] = $this->currentfontsize;
17428 if (isset($this->ReqFontStyle) && $this->ReqFontStyle) {
17429 $arr[12] = $this->ReqFontStyle;
17431 if (isset($this->lSpacingCSS) && $this->lSpacingCSS) {
17432 $arr[14] = $this->lSpacingCSS;
17434 if (isset($this->wSpacingCSS) && $this->wSpacingCSS) {
17435 $arr[15] = $this->wSpacingCSS;
17437 if (isset($this->spanborddet) && $this->spanborddet) {
17438 $arr[16] = $this->spanborddet;
17440 if (isset($this->textshadow) && $this->textshadow) {
17441 $arr[17] = $this->textshadow;
17443 if (isset($this->OTLdata) && $this->OTLdata) {
17444 $arr[18] = $this->OTLdata;
17445 $this->OTLdata = [];
17446 } // mPDF 5.7.1
17447 else {
17448 $arr[18] = null;
17450 $this->cell[$this->row][$this->col]['textbuffer'][] = $arr;
17453 function printbuffer($arrayaux, $blockstate = 0, $is_table = false, $table_draft = false, $cell_dir = '')
17455 // $blockstate = 0; // NO margins/padding
17456 // $blockstate = 1; // Top margins/padding only
17457 // $blockstate = 2; // Bottom margins/padding only
17458 // $blockstate = 3; // Top & bottom margins/padding
17459 $this->spanbgcolorarray = '';
17460 $this->spanbgcolor = false;
17461 $this->spanborder = false;
17462 $this->spanborddet = [];
17463 $paint_ht_corr = 0;
17464 /* -- CSS-FLOAT -- */
17465 if (count($this->floatDivs)) {
17466 list($l_exists, $r_exists, $l_max, $r_max, $l_width, $r_width) = $this->GetFloatDivInfo($this->blklvl);
17467 if (($this->blk[$this->blklvl]['inner_width'] - $l_width - $r_width) < (2 * $this->GetCharWidth('W', false))) {
17468 // Too narrow to fit - try to move down past L or R float
17469 if ($l_max < $r_max && ($this->blk[$this->blklvl]['inner_width'] - $r_width) > (2 * $this->GetCharWidth('W', false))) {
17470 $this->ClearFloats('LEFT', $this->blklvl);
17471 } elseif ($r_max < $l_max && ($this->blk[$this->blklvl]['inner_width'] - $l_width) > (2 * $this->GetCharWidth('W', false))) {
17472 $this->ClearFloats('RIGHT', $this->blklvl);
17473 } else {
17474 $this->ClearFloats('BOTH', $this->blklvl);
17478 /* -- END CSS-FLOAT -- */
17479 $bak_y = $this->y;
17480 $bak_x = $this->x;
17481 $align = '';
17482 if (!$is_table) {
17483 if (isset($this->blk[$this->blklvl]['align']) && $this->blk[$this->blklvl]['align']) {
17484 $align = $this->blk[$this->blklvl]['align'];
17486 // Block-align is set by e.g. <.. align="center"> Takes priority for this block but not inherited
17487 if (isset($this->blk[$this->blklvl]['block-align']) && $this->blk[$this->blklvl]['block-align']) {
17488 $align = $this->blk[$this->blklvl]['block-align'];
17490 if (isset($this->blk[$this->blklvl]['direction'])) {
17491 $blockdir = $this->blk[$this->blklvl]['direction'];
17492 } else {
17493 $blockdir = "";
17495 $this->divwidth = $this->blk[$this->blklvl]['width'];
17496 } else {
17497 $align = $this->cellTextAlign;
17498 $blockdir = $cell_dir;
17500 $oldpage = $this->page;
17502 // ADDED for Out of Block now done as Flowing Block
17503 if ($this->divwidth == 0) {
17504 $this->divwidth = $this->pgwidth;
17507 if (!$is_table) {
17508 $this->SetLineHeight($this->FontSizePt, $this->blk[$this->blklvl]['line_height']);
17510 $this->divheight = $this->lineheight;
17511 $old_height = $this->divheight;
17513 // As a failsafe - if font has been set but not output to page
17514 if (!$table_draft) {
17515 $this->SetFont($this->default_font, '', $this->default_font_size, true, true); // force output to page
17518 $this->newFlowingBlock($this->divwidth, $this->divheight, $align, $is_table, $blockstate, true, $blockdir, $table_draft);
17520 $array_size = count($arrayaux);
17522 // Added - Otherwise <div><div><p> did not output top margins/padding for 1st/2nd div
17523 if ($array_size == 0) {
17524 $this->finishFlowingBlock(true);
17525 } // true = END of flowing block
17526 // mPDF 6
17527 // ALL the chunks of textbuffer need to have at least basic OTLdata set
17528 // First make sure each element/chunk has the OTLdata for Bidi set.
17529 for ($i = 0; $i < $array_size; $i++) {
17530 if (empty($arrayaux[$i][18])) {
17531 if (substr($arrayaux[$i][0], 0, 3) == "\xbb\xa4\xac") { // object identifier has been identified!
17532 $unicode = [0xFFFC]; // Object replacement character
17533 } else {
17534 $unicode = $this->UTF8StringToArray($arrayaux[$i][0], false);
17536 $is_strong = false;
17537 $this->getBasicOTLdata($arrayaux[$i][18], $unicode, $is_strong);
17539 // Gets messed up if try and use core fonts inside a paragraph of text which needs to be BiDi re-ordered or OTLdata set
17540 if (($blockdir == 'rtl' || $this->biDirectional) && isset($arrayaux[$i][4]) && in_array($arrayaux[$i][4], ['ccourier', 'ctimes', 'chelvetica', 'csymbol', 'czapfdingbats'])) {
17541 throw new \Mpdf\MpdfException("You cannot use core fonts in a document which contains RTL text.");
17544 // mPDF 6
17545 // Process bidirectional text ready for bidi-re-ordering (which is done after line-breaks are established in WriteFlowingBlock etc.)
17546 if (($blockdir == 'rtl' || $this->biDirectional) && !$table_draft) {
17547 if (empty($this->otl)) {
17548 $this->otl = new Otl($this, $this->fontCache);
17550 $this->otl->bidiPrepare($arrayaux, $blockdir);
17551 $array_size = count($arrayaux);
17555 // Remove empty items // mPDF 6
17556 for ($i = $array_size - 1; $i > 0; $i--) {
17557 if (empty($arrayaux[$i][0]) && (isset($arrayaux[$i][16]) && $arrayaux[$i][16] !== '0') && empty($arrayaux[$i][7])) {
17558 unset($arrayaux[$i]);
17562 // Correct adjoining borders for inline elements
17563 if (isset($arrayaux[0][16])) {
17564 $lastspanborder = $arrayaux[0][16];
17565 } else {
17566 $lastspanborder = false;
17568 for ($i = 1; $i < $array_size; $i++) {
17569 if (isset($arrayaux[$i][16]) && $arrayaux[$i][16] == $lastspanborder &&
17570 ((!isset($arrayaux[$i][9]['bord-decoration']) && !isset($arrayaux[$i - 1][9]['bord-decoration'])) ||
17571 (isset($arrayaux[$i][9]['bord-decoration']) && isset($arrayaux[$i - 1][9]['bord-decoration']) && $arrayaux[$i][9]['bord-decoration'] == $arrayaux[$i - 1][9]['bord-decoration'])
17574 if (isset($arrayaux[$i][16]['R'])) {
17575 $lastspanborder = $arrayaux[$i][16];
17576 } else {
17577 $lastspanborder = false;
17579 $arrayaux[$i][16]['L']['s'] = 0;
17580 $arrayaux[$i][16]['L']['w'] = 0;
17581 $arrayaux[$i - 1][16]['R']['s'] = 0;
17582 $arrayaux[$i - 1][16]['R']['w'] = 0;
17583 } else {
17584 if (isset($arrayaux[$i][16]['R'])) {
17585 $lastspanborder = $arrayaux[$i][16];
17586 } else {
17587 $lastspanborder = false;
17592 for ($i = 0; $i < $array_size; $i++) {
17593 // COLS
17594 $oldcolumn = $this->CurrCol;
17595 $vetor = isset($arrayaux[$i]) ? $arrayaux[$i] : null;
17596 if ($i == 0 && $vetor[0] != "\n" && ! $this->ispre) {
17597 $vetor[0] = ltrim($vetor[0]);
17598 if (!empty($vetor[18])) {
17599 $this->otl->trimOTLdata($vetor[18], true, false);
17600 } // *OTL*
17603 // FIXED TO ALLOW IT TO SHOW '0'
17604 if (empty($vetor[0]) && !($vetor[0] === '0') && empty($vetor[7])) { // Ignore empty text and not carrying an internal link
17605 // Check if it is the last element. If so then finish printing the block
17606 if ($i == ($array_size - 1)) {
17607 $this->finishFlowingBlock(true);
17608 } // true = END of flowing block
17609 continue;
17613 // Activating buffer properties
17614 if (isset($vetor[11]) && $vetor[11] != '') { // Font Size
17615 if ($is_table && $this->shrin_k) {
17616 $this->SetFontSize($vetor[11] / $this->shrin_k, false);
17617 } else {
17618 $this->SetFontSize($vetor[11], false);
17622 if (isset($vetor[17]) && !empty($vetor[17])) { // TextShadow
17623 $this->textshadow = $vetor[17];
17625 if (isset($vetor[16]) && !empty($vetor[16])) { // Border
17626 $this->spanborddet = $vetor[16];
17627 $this->spanborder = true;
17630 if (isset($vetor[15])) { // Word spacing
17631 $this->wSpacingCSS = $vetor[15];
17632 if ($this->wSpacingCSS && strtoupper($this->wSpacingCSS) != 'NORMAL') {
17633 $this->minwSpacing = $this->sizeConverter->convert($this->wSpacingCSS, $this->FontSize) / $this->shrin_k; // mPDF 5.7.3
17636 if (isset($vetor[14])) { // Letter spacing
17637 $this->lSpacingCSS = $vetor[14];
17638 if (($this->lSpacingCSS || $this->lSpacingCSS === '0') && strtoupper($this->lSpacingCSS) != 'NORMAL') {
17639 $this->fixedlSpacing = $this->sizeConverter->convert($this->lSpacingCSS, $this->FontSize) / $this->shrin_k; // mPDF 5.7.3
17644 if (isset($vetor[10]) and ! empty($vetor[10])) { // Background color
17645 $this->spanbgcolorarray = $vetor[10];
17646 $this->spanbgcolor = true;
17648 if (isset($vetor[9]) and ! empty($vetor[9])) { // Text parameters - Outline + hyphens
17649 $this->textparam = $vetor[9];
17650 $this->SetTextOutline($this->textparam);
17651 // mPDF 5.7.3 inline text-decoration parameters
17652 if ($is_table && $this->shrin_k) {
17653 if (isset($this->textparam['text-baseline'])) {
17654 $this->textparam['text-baseline'] /= $this->shrin_k;
17656 if (isset($this->textparam['decoration-baseline'])) {
17657 $this->textparam['decoration-baseline'] /= $this->shrin_k;
17659 if (isset($this->textparam['decoration-fontsize'])) {
17660 $this->textparam['decoration-fontsize'] /= $this->shrin_k;
17664 if (isset($vetor[8])) { // mPDF 5.7.1
17665 $this->textvar = $vetor[8];
17667 if (isset($vetor[7]) and $vetor[7] != '') { // internal target: <a name="anyvalue">
17668 $ily = $this->y;
17669 if ($this->table_rotate) {
17670 $this->internallink[$vetor[7]] = ["Y" => $ily, "PAGE" => $this->page, "tbrot" => true];
17671 } elseif ($this->kwt) {
17672 $this->internallink[$vetor[7]] = ["Y" => $ily, "PAGE" => $this->page, "kwt" => true];
17673 } elseif ($this->ColActive) {
17674 $this->internallink[$vetor[7]] = ["Y" => $ily, "PAGE" => $this->page, "col" => $this->CurrCol];
17675 } elseif (!$this->keep_block_together) {
17676 $this->internallink[$vetor[7]] = ["Y" => $ily, "PAGE" => $this->page];
17678 if (empty($vetor[0])) { // Ignore empty text
17679 // Check if it is the last element. If so then finish printing the block
17680 if ($i == ($array_size - 1)) {
17681 $this->finishFlowingBlock(true);
17682 } // true = END of flowing block
17683 continue;
17686 if (isset($vetor[5]) and $vetor[5] != '') { // Language // mPDF 6
17687 $this->currentLang = $vetor[5];
17689 if (isset($vetor[4]) and $vetor[4] != '') { // Font Family
17690 $font = $this->SetFont($vetor[4], $this->FontStyle, 0, false);
17692 if (!empty($vetor[3])) { // Font Color
17693 $cor = $vetor[3];
17694 $this->SetTColor($cor);
17696 if (isset($vetor[2]) and $vetor[2] != '') { // Bold,Italic styles
17697 $this->SetStyles($vetor[2]);
17700 if (isset($vetor[12]) and $vetor[12] != '') { // Requested Bold,Italic
17701 $this->ReqFontStyle = $vetor[12];
17703 if (isset($vetor[1]) and $vetor[1] != '') { // LINK
17704 if (strpos($vetor[1], ".") === false && strpos($vetor[1], "@") !== 0) { // assuming every external link has a dot indicating extension (e.g: .html .txt .zip www.somewhere.com etc.)
17705 // Repeated reference to same anchor?
17706 while (array_key_exists($vetor[1], $this->internallink)) {
17707 $vetor[1] = "#" . $vetor[1];
17709 $this->internallink[$vetor[1]] = $this->AddLink();
17710 $vetor[1] = $this->internallink[$vetor[1]];
17712 $this->HREF = $vetor[1]; // HREF link style set here ******
17715 // SPECIAL CONTENT - IMAGES & FORM OBJECTS
17716 // Print-out special content
17718 if (substr($vetor[0], 0, 3) == "\xbb\xa4\xac") { // identifier has been identified!
17719 $objattr = $this->_getObjAttr($vetor[0]);
17721 /* -- TABLES -- */
17722 if ($objattr['type'] == 'nestedtable') {
17723 if ($objattr['nestedcontent']) {
17724 $level = $objattr['level'];
17725 $table = &$this->table[$level][$objattr['table']];
17727 if ($table_draft) {
17728 $this->y += $this->table[($level + 1)][$objattr['nestedcontent']]['h']; // nested table height
17729 $this->finishFlowingBlock(false, 'nestedtable');
17730 } else {
17731 $cell = &$table['cells'][$objattr['row']][$objattr['col']];
17732 $this->finishFlowingBlock(false, 'nestedtable');
17733 $save_dw = $this->divwidth;
17734 $save_buffer = $this->cellBorderBuffer;
17735 $this->cellBorderBuffer = [];
17736 $ncx = $this->x;
17737 list($dummyx, $w) = $this->_tableGetWidth($table, $objattr['row'], $objattr['col']);
17738 $ntw = $this->table[($level + 1)][$objattr['nestedcontent']]['w']; // nested table width
17739 if (!$this->simpleTables) {
17740 if ($this->packTableData) {
17741 list($bt, $br, $bb, $bl) = $this->_getBorderWidths($cell['borderbin']);
17742 } else {
17743 $br = $cell['border_details']['R']['w'];
17744 $bl = $cell['border_details']['L']['w'];
17746 if ($table['borders_separate']) {
17747 $innerw = $w - $bl - $br - $cell['padding']['L'] - $cell['padding']['R'] - $table['border_spacing_H'];
17748 } else {
17749 $innerw = $w - $bl / 2 - $br / 2 - $cell['padding']['L'] - $cell['padding']['R'];
17751 } elseif ($this->simpleTables) {
17752 if ($table['borders_separate']) {
17753 $innerw = $w - $table['simple']['border_details']['L']['w'] - $table['simple']['border_details']['R']['w'] - $cell['padding']['L'] - $cell['padding']['R'] - $table['border_spacing_H'];
17754 } else {
17755 $innerw = $w - $table['simple']['border_details']['L']['w'] / 2 - $table['simple']['border_details']['R']['w'] / 2 - $cell['padding']['L'] - $cell['padding']['R'];
17758 if ($cell['a'] == 'C' || $this->table[($level + 1)][$objattr['nestedcontent']]['a'] == 'C') {
17759 $ncx += ($innerw - $ntw) / 2;
17760 } elseif ($cell['a'] == 'R' || $this->table[($level + 1)][$objattr['nestedcontent']]['a'] == 'R') {
17761 $ncx += $innerw - $ntw;
17763 $this->x = $ncx;
17765 $this->_tableWrite($this->table[($level + 1)][$objattr['nestedcontent']]);
17766 $this->cellBorderBuffer = $save_buffer;
17767 $this->x = $bak_x;
17768 $this->divwidth = $save_dw;
17771 $this->newFlowingBlock($this->divwidth, $this->divheight, $align, $is_table, $blockstate, false, $blockdir, $table_draft);
17773 } else {
17774 /* -- END TABLES -- */
17775 if ($is_table) { // *TABLES*
17776 $maxWidth = $this->divwidth; // *TABLES*
17777 } // *TABLES*
17778 else { // *TABLES*
17779 $maxWidth = $this->divwidth - ($this->blk[$this->blklvl]['padding_left'] + $this->blk[$this->blklvl]['border_left']['w'] + $this->blk[$this->blklvl]['padding_right'] + $this->blk[$this->blklvl]['border_right']['w']);
17780 } // *TABLES*
17782 /* -- CSS-IMAGE-FLOAT -- */
17783 // If float (already) exists at this level
17784 if (isset($this->floatmargins['R']) && $this->y <= $this->floatmargins['R']['y1'] && $this->y >= $this->floatmargins['R']['y0']) {
17785 $maxWidth -= $this->floatmargins['R']['w'];
17787 if (isset($this->floatmargins['L']) && $this->y <= $this->floatmargins['L']['y1'] && $this->y >= $this->floatmargins['L']['y0']) {
17788 $maxWidth -= $this->floatmargins['L']['w'];
17790 /* -- END CSS-IMAGE-FLOAT -- */
17792 list($skipln) = $this->inlineObject($objattr['type'], '', $this->y, $objattr, $this->lMargin, ($this->flowingBlockAttr['contentWidth'] / Mpdf::SCALE), $maxWidth, $this->flowingBlockAttr['height'], false, $is_table);
17793 // 1 -> New line needed because of width
17794 // -1 -> Will fit width on line but NEW PAGE REQUIRED because of height
17795 // -2 -> Will not fit on line therefore needs new line but thus NEW PAGE REQUIRED
17796 $iby = $this->y;
17797 $oldpage = $this->page;
17798 $oldcol = $this->CurrCol;
17799 if (($skipln == 1 || $skipln == -2) && !isset($objattr['float'])) {
17800 $this->finishFlowingBlock(false, $objattr['type']);
17801 $this->newFlowingBlock($this->divwidth, $this->divheight, $align, $is_table, $blockstate, false, $blockdir, $table_draft);
17804 if (!$table_draft) {
17805 $thispage = $this->page;
17806 if ($this->CurrCol != $oldcol) {
17807 $changedcol = true;
17808 } else {
17809 $changedcol = false;
17812 // the previous lines can already have triggered page break or column change
17813 if (!$changedcol && $skipln < 0 && $this->AcceptPageBreak() && $thispage == $oldpage) {
17814 $this->AddPage($this->CurOrientation);
17816 // Added to correct Images already set on line before page advanced
17817 // i.e. if second inline image on line is higher than first and forces new page
17818 if (count($this->objectbuffer)) {
17819 $yadj = $iby - $this->y;
17820 foreach ($this->objectbuffer as $ib => $val) {
17821 if ($this->objectbuffer[$ib]['OUTER-Y']) {
17822 $this->objectbuffer[$ib]['OUTER-Y'] -= $yadj;
17824 if ($this->objectbuffer[$ib]['BORDER-Y']) {
17825 $this->objectbuffer[$ib]['BORDER-Y'] -= $yadj;
17827 if ($this->objectbuffer[$ib]['INNER-Y']) {
17828 $this->objectbuffer[$ib]['INNER-Y'] -= $yadj;
17834 // Added to correct for OddEven Margins
17835 if ($this->page != $oldpage) {
17836 if (($this->page - $oldpage) % 2 == 1) {
17837 $bak_x += $this->MarginCorrection;
17839 $oldpage = $this->page;
17840 $y = $this->tMargin - $paint_ht_corr;
17841 $this->oldy = $this->tMargin - $paint_ht_corr;
17842 $old_height = 0;
17844 $this->x = $bak_x;
17845 /* -- COLUMNS -- */
17846 // COLS
17847 // OR COLUMN CHANGE
17848 if ($this->CurrCol != $oldcolumn) {
17849 if ($this->directionality == 'rtl') { // *OTL*
17850 $bak_x -= ($this->CurrCol - $oldcolumn) * ($this->ColWidth + $this->ColGap); // *OTL*
17851 } // *OTL*
17852 else { // *OTL*
17853 $bak_x += ($this->CurrCol - $oldcolumn) * ($this->ColWidth + $this->ColGap);
17854 } // *OTL*
17855 $this->x = $bak_x;
17856 $oldcolumn = $this->CurrCol;
17857 $y = $this->y0 - $paint_ht_corr;
17858 $this->oldy = $this->y0 - $paint_ht_corr;
17859 $old_height = 0;
17861 /* -- END COLUMNS -- */
17864 /* -- CSS-IMAGE-FLOAT -- */
17865 if ($objattr['type'] == 'image' && isset($objattr['float'])) {
17866 $fy = $this->y;
17868 // DIV TOP MARGIN/BORDER/PADDING
17869 if ($this->flowingBlockAttr['newblock'] && ($this->flowingBlockAttr['blockstate'] == 1 || $this->flowingBlockAttr['blockstate'] == 3) && $this->flowingBlockAttr['lineCount'] == 0) {
17870 $fy += $this->blk[$this->blklvl]['margin_top'] + $this->blk[$this->blklvl]['padding_top'] + $this->blk[$this->blklvl]['border_top']['w'];
17873 if ($objattr['float'] == 'R') {
17874 $fx = $this->w - $this->rMargin - $objattr['width'] - ($this->blk[$this->blklvl]['outer_right_margin'] + $this->blk[$this->blklvl]['border_right']['w'] + $this->blk[$this->blklvl]['padding_right']);
17875 } elseif ($objattr['float'] == 'L') {
17876 $fx = $this->lMargin + ($this->blk[$this->blklvl]['outer_left_margin'] + $this->blk[$this->blklvl]['border_left']['w'] + $this->blk[$this->blklvl]['padding_left']);
17878 $w = $objattr['width'];
17879 $h = abs($objattr['height']);
17881 $widthLeft = $maxWidth - ($this->flowingBlockAttr['contentWidth'] / Mpdf::SCALE);
17882 $maxHeight = $this->h - ($this->tMargin + $this->margin_header + $this->bMargin + 10);
17883 // For Images
17884 $extraWidth = ($objattr['border_left']['w'] + $objattr['border_right']['w'] + $objattr['margin_left'] + $objattr['margin_right']);
17885 $extraHeight = ($objattr['border_top']['w'] + $objattr['border_bottom']['w'] + $objattr['margin_top'] + $objattr['margin_bottom']);
17887 if ($objattr['itype'] == 'wmf' || $objattr['itype'] == 'svg') {
17888 $file = $objattr['file'];
17889 $info = $this->formobjects[$file];
17890 } else {
17891 $file = $objattr['file'];
17892 $info = $this->images[$file];
17894 $img_w = $w - $extraWidth;
17895 $img_h = $h - $extraHeight;
17896 if ($objattr['border_left']['w']) {
17897 $objattr['BORDER-WIDTH'] = $img_w + (($objattr['border_left']['w'] + $objattr['border_right']['w']) / 2);
17898 $objattr['BORDER-HEIGHT'] = $img_h + (($objattr['border_top']['w'] + $objattr['border_bottom']['w']) / 2);
17899 $objattr['BORDER-X'] = $fx + $objattr['margin_left'] + (($objattr['border_left']['w']) / 2);
17900 $objattr['BORDER-Y'] = $fy + $objattr['margin_top'] + (($objattr['border_top']['w']) / 2);
17902 $objattr['INNER-WIDTH'] = $img_w;
17903 $objattr['INNER-HEIGHT'] = $img_h;
17904 $objattr['INNER-X'] = $fx + $objattr['margin_left'] + ($objattr['border_left']['w']);
17905 $objattr['INNER-Y'] = $fy + $objattr['margin_top'] + ($objattr['border_top']['w']);
17906 $objattr['ID'] = $info['i'];
17907 $objattr['OUTER-WIDTH'] = $w;
17908 $objattr['OUTER-HEIGHT'] = $h;
17909 $objattr['OUTER-X'] = $fx;
17910 $objattr['OUTER-Y'] = $fy;
17911 if ($objattr['float'] == 'R') {
17912 // If R float already exists at this level
17913 $this->floatmargins['R']['skipline'] = false;
17914 if (isset($this->floatmargins['R']['y1']) && $this->floatmargins['R']['y1'] > 0 && $fy < $this->floatmargins['R']['y1']) {
17915 $this->WriteFlowingBlock($vetor[0], $vetor[18]); // mPDF 5.7.1
17916 } // If L float already exists at this level
17917 elseif (isset($this->floatmargins['L']['y1']) && $this->floatmargins['L']['y1'] > 0 && $fy < $this->floatmargins['L']['y1']) {
17918 // Final check distance between floats is not now too narrow to fit text
17919 $mw = 2 * $this->GetCharWidth('W', false);
17920 if (($this->blk[$this->blklvl]['inner_width'] - $w - $this->floatmargins['L']['w']) < $mw) {
17921 $this->WriteFlowingBlock($vetor[0], $vetor[18]); // mPDF 5.7.1
17922 } else {
17923 $this->floatmargins['R']['x'] = $fx;
17924 $this->floatmargins['R']['w'] = $w;
17925 $this->floatmargins['R']['y0'] = $fy;
17926 $this->floatmargins['R']['y1'] = $fy + $h;
17927 if ($skipln == 1) {
17928 $this->floatmargins['R']['skipline'] = true;
17929 $this->floatmargins['R']['id'] = count($this->floatbuffer) + 0;
17930 $objattr['skipline'] = true;
17932 $this->floatbuffer[] = $objattr;
17934 } else {
17935 $this->floatmargins['R']['x'] = $fx;
17936 $this->floatmargins['R']['w'] = $w;
17937 $this->floatmargins['R']['y0'] = $fy;
17938 $this->floatmargins['R']['y1'] = $fy + $h;
17939 if ($skipln == 1) {
17940 $this->floatmargins['R']['skipline'] = true;
17941 $this->floatmargins['R']['id'] = count($this->floatbuffer) + 0;
17942 $objattr['skipline'] = true;
17944 $this->floatbuffer[] = $objattr;
17946 } elseif ($objattr['float'] == 'L') {
17947 // If L float already exists at this level
17948 $this->floatmargins['L']['skipline'] = false;
17949 if (isset($this->floatmargins['L']['y1']) && $this->floatmargins['L']['y1'] > 0 && $fy < $this->floatmargins['L']['y1']) {
17950 $this->floatmargins['L']['skipline'] = false;
17951 $this->WriteFlowingBlock($vetor[0], $vetor[18]); // mPDF 5.7.1
17952 } // If R float already exists at this level
17953 elseif (isset($this->floatmargins['R']['y1']) && $this->floatmargins['R']['y1'] > 0 && $fy < $this->floatmargins['R']['y1']) {
17954 // Final check distance between floats is not now too narrow to fit text
17955 $mw = 2 * $this->GetCharWidth('W', false);
17956 if (($this->blk[$this->blklvl]['inner_width'] - $w - $this->floatmargins['R']['w']) < $mw) {
17957 $this->WriteFlowingBlock($vetor[0], $vetor[18]); // mPDF 5.7.1
17958 } else {
17959 $this->floatmargins['L']['x'] = $fx + $w;
17960 $this->floatmargins['L']['w'] = $w;
17961 $this->floatmargins['L']['y0'] = $fy;
17962 $this->floatmargins['L']['y1'] = $fy + $h;
17963 if ($skipln == 1) {
17964 $this->floatmargins['L']['skipline'] = true;
17965 $this->floatmargins['L']['id'] = count($this->floatbuffer) + 0;
17966 $objattr['skipline'] = true;
17968 $this->floatbuffer[] = $objattr;
17970 } else {
17971 $this->floatmargins['L']['x'] = $fx + $w;
17972 $this->floatmargins['L']['w'] = $w;
17973 $this->floatmargins['L']['y0'] = $fy;
17974 $this->floatmargins['L']['y1'] = $fy + $h;
17975 if ($skipln == 1) {
17976 $this->floatmargins['L']['skipline'] = true;
17977 $this->floatmargins['L']['id'] = count($this->floatbuffer) + 0;
17978 $objattr['skipline'] = true;
17980 $this->floatbuffer[] = $objattr;
17983 } else {
17984 /* -- END CSS-IMAGE-FLOAT -- */
17985 $this->WriteFlowingBlock($vetor[0], (isset($vetor[18]) ? $vetor[18] : null)); // mPDF 5.7.1
17986 /* -- CSS-IMAGE-FLOAT -- */
17988 /* -- END CSS-IMAGE-FLOAT -- */
17989 } // *TABLES*
17990 } // END If special content
17991 else { // THE text
17992 if ($this->tableLevel) {
17993 $paint_ht_corr = 0;
17994 } // To move the y up when new column/page started if div border needed
17995 else {
17996 $paint_ht_corr = $this->blk[$this->blklvl]['border_top']['w'];
17999 if ($vetor[0] == "\n") { // We are reading a <BR> now turned into newline ("\n")
18000 if ($this->flowingBlockAttr['content']) {
18001 $this->finishFlowingBlock(false, 'br');
18002 } elseif ($is_table) {
18003 $this->y+= $this->_computeLineheight($this->cellLineHeight);
18004 } elseif (!$is_table) {
18005 $this->DivLn($this->lineheight);
18006 if ($this->ColActive) {
18007 $this->breakpoints[$this->CurrCol][] = $this->y;
18008 } // *COLUMNS*
18010 // Added to correct for OddEven Margins
18011 if ($this->page != $oldpage) {
18012 if (($this->page - $oldpage) % 2 == 1) {
18013 $bak_x += $this->MarginCorrection;
18015 $oldpage = $this->page;
18016 $y = $this->tMargin - $paint_ht_corr;
18017 $this->oldy = $this->tMargin - $paint_ht_corr;
18018 $old_height = 0;
18020 $this->x = $bak_x;
18021 /* -- COLUMNS -- */
18022 // COLS
18023 // OR COLUMN CHANGE
18024 if ($this->CurrCol != $oldcolumn) {
18025 if ($this->directionality == 'rtl') { // *OTL*
18026 $bak_x -= ($this->CurrCol - $oldcolumn) * ($this->ColWidth + $this->ColGap); // *OTL*
18027 } // *OTL*
18028 else { // *OTL*
18029 $bak_x += ($this->CurrCol - $oldcolumn) * ($this->ColWidth + $this->ColGap);
18030 } // *OTL*
18031 $this->x = $bak_x;
18032 $oldcolumn = $this->CurrCol;
18033 $y = $this->y0 - $paint_ht_corr;
18034 $this->oldy = $this->y0 - $paint_ht_corr;
18035 $old_height = 0;
18037 /* -- END COLUMNS -- */
18038 $this->newFlowingBlock($this->divwidth, $this->divheight, $align, $is_table, $blockstate, false, $blockdir, $table_draft);
18039 } else {
18040 $this->WriteFlowingBlock($vetor[0], $vetor[18]); // mPDF 5.7.1
18041 // Added to correct for OddEven Margins
18042 if ($this->page != $oldpage) {
18043 if (($this->page - $oldpage) % 2 == 1) {
18044 $bak_x += $this->MarginCorrection;
18045 $this->x = $bak_x;
18047 $oldpage = $this->page;
18048 $y = $this->tMargin - $paint_ht_corr;
18049 $this->oldy = $this->tMargin - $paint_ht_corr;
18050 $old_height = 0;
18052 /* -- COLUMNS -- */
18053 // COLS
18054 // OR COLUMN CHANGE
18055 if ($this->CurrCol != $oldcolumn) {
18056 if ($this->directionality == 'rtl') { // *OTL*
18057 $bak_x -= ($this->CurrCol - $oldcolumn) * ($this->ColWidth + $this->ColGap); // *OTL*
18058 } // *OTL*
18059 else { // *OTL*
18060 $bak_x += ($this->CurrCol - $oldcolumn) * ($this->ColWidth + $this->ColGap);
18061 } // *OTL*
18062 $this->x = $bak_x;
18063 $oldcolumn = $this->CurrCol;
18064 $y = $this->y0 - $paint_ht_corr;
18065 $this->oldy = $this->y0 - $paint_ht_corr;
18066 $old_height = 0;
18068 /* -- END COLUMNS -- */
18072 // Check if it is the last element. If so then finish printing the block
18073 if ($i == ($array_size - 1)) {
18074 $this->finishFlowingBlock(true); // true = END of flowing block
18075 // Added to correct for OddEven Margins
18076 if ($this->page != $oldpage) {
18077 if (($this->page - $oldpage) % 2 == 1) {
18078 $bak_x += $this->MarginCorrection;
18079 $this->x = $bak_x;
18081 $oldpage = $this->page;
18082 $y = $this->tMargin - $paint_ht_corr;
18083 $this->oldy = $this->tMargin - $paint_ht_corr;
18084 $old_height = 0;
18087 /* -- COLUMNS -- */
18088 // COLS
18089 // OR COLUMN CHANGE
18090 if ($this->CurrCol != $oldcolumn) {
18091 if ($this->directionality == 'rtl') { // *OTL*
18092 $bak_x -= ($this->CurrCol - $oldcolumn) * ($this->ColWidth + $this->ColGap); // *OTL*
18093 } // *OTL*
18094 else { // *OTL*
18095 $bak_x += ($this->CurrCol - $oldcolumn) * ($this->ColWidth + $this->ColGap);
18096 } // *OTL*
18097 $this->x = $bak_x;
18098 $oldcolumn = $this->CurrCol;
18099 $y = $this->y0 - $paint_ht_corr;
18100 $this->oldy = $this->y0 - $paint_ht_corr;
18101 $old_height = 0;
18103 /* -- END COLUMNS -- */
18106 // RESETTING VALUES
18107 $this->SetTColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
18108 $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
18109 $this->SetFColor($this->colorConverter->convert(255, $this->PDFAXwarnings));
18110 $this->colorarray = '';
18111 $this->spanbgcolorarray = '';
18112 $this->spanbgcolor = false;
18113 $this->spanborder = false;
18114 $this->spanborddet = [];
18115 $this->HREF = '';
18116 $this->textparam = [];
18117 $this->SetTextOutline();
18119 $this->textvar = 0x00; // mPDF 5.7.1
18120 $this->OTLtags = [];
18121 $this->textshadow = '';
18123 $this->currentfontfamily = '';
18124 $this->currentfontsize = '';
18125 $this->currentfontstyle = '';
18126 $this->currentLang = $this->default_lang; // mPDF 6
18127 $this->RestrictUnicodeFonts($this->default_available_fonts); // mPDF 6
18128 /* -- TABLES -- */
18129 if ($this->tableLevel) {
18130 $this->SetLineHeight('', $this->table[1][1]['cellLineHeight']); // *TABLES*
18131 } else { /* -- END TABLES -- */
18132 if (isset($this->blk[$this->blklvl]['line_height']) && $this->blk[$this->blklvl]['line_height']) {
18133 $this->SetLineHeight('', $this->blk[$this->blklvl]['line_height']); // sets default line height
18136 $this->ResetStyles();
18137 $this->lSpacingCSS = '';
18138 $this->wSpacingCSS = '';
18139 $this->fixedlSpacing = false;
18140 $this->minwSpacing = 0;
18141 $this->SetDash();
18142 $this->dash_on = false;
18143 $this->dotted_on = false;
18144 }//end of for(i=0;i<arraysize;i++)
18146 $this->Reset(); // mPDF 6
18147 // PAINT DIV BORDER // DISABLED IN COLUMNS AS DOESN'T WORK WHEN BROKEN ACROSS COLS??
18148 if ((isset($this->blk[$this->blklvl]['border']) || isset($this->blk[$this->blklvl]['bgcolor']) || isset($this->blk[$this->blklvl]['box_shadow'])) && $blockstate && ($this->y != $this->oldy)) {
18149 $bottom_y = $this->y; // Does not include Bottom Margin
18150 if (isset($this->blk[$this->blklvl]['startpage']) && $this->blk[$this->blklvl]['startpage'] != $this->page && $blockstate != 1) {
18151 $this->PaintDivBB('pagetop', $blockstate);
18152 } elseif ($blockstate != 1) {
18153 $this->PaintDivBB('', $blockstate);
18155 $this->y = $bottom_y;
18156 $this->x = $bak_x;
18159 // Reset Font
18160 $this->SetFontSize($this->default_font_size, false);
18161 if ($table_draft) {
18162 $ch = $this->y - $bak_y;
18163 $this->y = $bak_y;
18164 $this->x = $bak_x;
18165 return $ch;
18169 function _setDashBorder($style, $div, $cp, $side)
18171 if ($style == 'dashed' && (($side == 'L' || $side == 'R') || ($side == 'T' && $div != 'pagetop' && !$cp) || ($side == 'B' && $div != 'pagebottom') )) {
18172 $dashsize = 2; // final dash will be this + 1*linewidth
18173 $dashsizek = 1.5; // ratio of Dash/Blank
18174 $this->SetDash($dashsize, ($dashsize / $dashsizek) + ($this->LineWidth * 2));
18175 } elseif ($style == 'dotted' || ($side == 'T' && ($div == 'pagetop' || $cp)) || ($side == 'B' && $div == 'pagebottom')) {
18176 // Round join and cap
18177 $this->SetLineJoin(1);
18178 $this->SetLineCap(1);
18179 $this->SetDash(0.001, ($this->LineWidth * 3));
18183 function _setBorderLine($b, $k = 1)
18185 $this->SetLineWidth($b['w'] / $k);
18186 $this->SetDColor($b['c']);
18187 if ($b['c'][0] == 5) { // RGBa
18188 $this->SetAlpha(ord($b['c'][4]) / 100, 'Normal', false, 'S'); // mPDF 5.7.2
18189 } elseif ($b['c'][0] == 6) { // CMYKa
18190 $this->SetAlpha(ord($b['c'][5]) / 100, 'Normal', false, 'S'); // mPDF 5.7.2
18194 function PaintDivBB($divider = '', $blockstate = 0, $blvl = 0)
18196 // Borders & backgrounds are done elsewhere for columns - messes up the repositioning in printcolumnbuffer
18197 if ($this->ColActive) {
18198 return;
18199 } // *COLUMNS*
18200 if ($this->keep_block_together) {
18201 return;
18202 } // mPDF 6
18203 $save_y = $this->y;
18204 if (!$blvl) {
18205 $blvl = $this->blklvl;
18207 $x0 = $x1 = $y0 = $y1 = 0;
18209 // Added mPDF 3.0 Float DIV
18210 if (isset($this->blk[$blvl]['bb_painted'][$this->page]) && $this->blk[$blvl]['bb_painted'][$this->page]) {
18211 return;
18212 } // *CSS-FLOAT*
18214 if (isset($this->blk[$blvl]['x0'])) {
18215 $x0 = $this->blk[$blvl]['x0'];
18216 } // left
18217 if (isset($this->blk[$blvl]['y1'])) {
18218 $y1 = $this->blk[$blvl]['y1'];
18219 } // bottom
18220 // Added mPDF 3.0 Float DIV - ensures backgrounds/borders are drawn to bottom of page
18221 if ($y1 == 0) {
18222 if ($divider == 'pagebottom') {
18223 $y1 = $this->h - $this->bMargin;
18224 } else {
18225 $y1 = $this->y;
18229 if (isset($this->blk[$blvl]['startpage']) && $this->blk[$blvl]['startpage'] != $this->page) {
18230 $continuingpage = true;
18231 } else {
18232 $continuingpage = false;
18235 if (isset($this->blk[$blvl]['y0'])) {
18236 $y0 = $this->blk[$blvl]['y0'];
18238 $h = $y1 - $y0;
18239 $w = $this->blk[$blvl]['width'];
18240 $x1 = $x0 + $w;
18242 // Set border-widths as used here
18243 $border_top = $this->blk[$blvl]['border_top']['w'];
18244 $border_bottom = $this->blk[$blvl]['border_bottom']['w'];
18245 $border_left = $this->blk[$blvl]['border_left']['w'];
18246 $border_right = $this->blk[$blvl]['border_right']['w'];
18247 if (!$this->blk[$blvl]['border_top'] || $divider == 'pagetop' || $continuingpage) {
18248 $border_top = 0;
18250 if (!$this->blk[$blvl]['border_bottom'] || $blockstate == 1 || $divider == 'pagebottom') {
18251 $border_bottom = 0;
18254 $brTL_H = 0;
18255 $brTL_V = 0;
18256 $brTR_H = 0;
18257 $brTR_V = 0;
18258 $brBL_H = 0;
18259 $brBL_V = 0;
18260 $brBR_H = 0;
18261 $brBR_V = 0;
18263 $brset = false;
18264 /* -- BORDER-RADIUS -- */
18265 if (isset($this->blk[$blvl]['border_radius_TL_H'])) {
18266 $brTL_H = $this->blk[$blvl]['border_radius_TL_H'];
18267 $brset = true;
18269 if (isset($this->blk[$blvl]['border_radius_TL_V'])) {
18270 $brTL_V = $this->blk[$blvl]['border_radius_TL_V'];
18271 $brset = true;
18273 if (isset($this->blk[$blvl]['border_radius_TR_H'])) {
18274 $brTR_H = $this->blk[$blvl]['border_radius_TR_H'];
18275 $brset = true;
18277 if (isset($this->blk[$blvl]['border_radius_TR_V'])) {
18278 $brTR_V = $this->blk[$blvl]['border_radius_TR_V'];
18279 $brset = true;
18281 if (isset($this->blk[$blvl]['border_radius_BR_H'])) {
18282 $brBR_H = $this->blk[$blvl]['border_radius_BR_H'];
18283 $brset = true;
18285 if (isset($this->blk[$blvl]['border_radius_BR_V'])) {
18286 $brBR_V = $this->blk[$blvl]['border_radius_BR_V'];
18287 $brset = true;
18289 if (isset($this->blk[$blvl]['border_radius_BL_H'])) {
18290 $brBL_H = $this->blk[$blvl]['border_radius_BL_H'];
18291 $brset = true;
18293 if (isset($this->blk[$blvl]['border_radius_BL_V'])) {
18294 $brBL_V = $this->blk[$blvl]['border_radius_BL_V'];
18295 $brset = true;
18298 if (!$this->blk[$blvl]['border_top'] || $divider == 'pagetop' || $continuingpage) {
18299 $brTL_H = 0;
18300 $brTL_V = 0;
18301 $brTR_H = 0;
18302 $brTR_V = 0;
18304 if (!$this->blk[$blvl]['border_bottom'] || $blockstate == 1 || $divider == 'pagebottom') {
18305 $brBL_H = 0;
18306 $brBL_V = 0;
18307 $brBR_H = 0;
18308 $brBR_V = 0;
18311 // Disallow border-radius if it is smaller than the border width.
18312 if ($brTL_H < min($border_left, $border_top)) {
18313 $brTL_H = $brTL_V = 0;
18315 if ($brTL_V < min($border_left, $border_top)) {
18316 $brTL_V = $brTL_H = 0;
18318 if ($brTR_H < min($border_right, $border_top)) {
18319 $brTR_H = $brTR_V = 0;
18321 if ($brTR_V < min($border_right, $border_top)) {
18322 $brTR_V = $brTR_H = 0;
18324 if ($brBL_H < min($border_left, $border_bottom)) {
18325 $brBL_H = $brBL_V = 0;
18327 if ($brBL_V < min($border_left, $border_bottom)) {
18328 $brBL_V = $brBL_H = 0;
18330 if ($brBR_H < min($border_right, $border_bottom)) {
18331 $brBR_H = $brBR_V = 0;
18333 if ($brBR_V < min($border_right, $border_bottom)) {
18334 $brBR_V = $brBR_H = 0;
18337 // CHECK FOR radii that sum to > width or height of div ********
18338 $f = min($h / ($brTL_V + $brBL_V + 0.001), $h / ($brTR_V + $brBR_V + 0.001), $w / ($brTL_H + $brTR_H + 0.001), $w / ($brBL_H + $brBR_H + 0.001));
18339 if ($f < 1) {
18340 $brTL_H *= $f;
18341 $brTL_V *= $f;
18342 $brTR_H *= $f;
18343 $brTR_V *= $f;
18344 $brBL_H *= $f;
18345 $brBL_V *= $f;
18346 $brBR_H *= $f;
18347 $brBR_V *= $f;
18349 /* -- END BORDER-RADIUS -- */
18351 $tbcol = $this->colorConverter->convert(255, $this->PDFAXwarnings);
18352 for ($l = 0; $l <= $blvl; $l++) {
18353 if ($this->blk[$l]['bgcolor']) {
18354 $tbcol = $this->blk[$l]['bgcolorarray'];
18358 // BORDERS
18359 if (isset($this->blk[$blvl]['y0']) && $this->blk[$blvl]['y0']) {
18360 $y0 = $this->blk[$blvl]['y0'];
18362 $h = $y1 - $y0;
18363 $w = $this->blk[$blvl]['width'];
18365 if ($this->blk[$blvl]['border_top'] && $divider != 'pagetop' && !$continuingpage) {
18366 $tbd = $this->blk[$blvl]['border_top'];
18368 $legend = '';
18369 $legbreakL = 0;
18370 $legbreakR = 0;
18371 // BORDER LEGEND
18372 if (isset($this->blk[$blvl]['border_legend']) && $this->blk[$blvl]['border_legend']) {
18373 $legend = $this->blk[$blvl]['border_legend']; // Same structure array as textbuffer
18374 $txt = $legend[0] = ltrim($legend[0]);
18375 if (!empty($legend[18])) {
18376 $this->otl->trimOTLdata($legend[18], true, false);
18377 } // *OTL*
18378 // Set font, size, style, color
18379 $this->SetFont($legend[4], $legend[2], $legend[11]);
18380 if (isset($legend[3]) && $legend[3]) {
18381 $cor = $legend[3];
18382 $this->SetTColor($cor);
18384 $stringWidth = $this->GetStringWidth($txt, true, $legend[18], $legend[8]);
18385 $save_x = $this->x;
18386 $save_y = $this->y;
18387 $save_currentfontfamily = $this->FontFamily;
18388 $save_currentfontsize = $this->FontSizePt;
18389 $save_currentfontstyle = $this->FontStyle;
18390 $this->y = $y0 - $this->FontSize / 2 + $this->blk[$blvl]['border_top']['w'] / 2;
18391 $this->x = $x0 + $this->blk[$blvl]['padding_left'] + $this->blk[$blvl]['border_left']['w'];
18393 // Set the distance from the border line to the text ? make configurable variable
18394 $gap = 0.2 * $this->FontSize;
18395 $legbreakL = $this->x - $gap;
18396 $legbreakR = $this->x + $stringWidth + $gap;
18397 $this->magic_reverse_dir($txt, $this->blk[$blvl]['direction'], $legend[18]);
18398 $fill = '';
18399 $this->Cell($stringWidth, $this->FontSize, $txt, '', 0, 'C', $fill, '', 0, 0, 0, 'M', $fill, false, $legend[18], $legend[8]);
18400 // Reset
18401 $this->x = $save_x;
18402 $this->y = $save_y;
18403 $this->SetFont($save_currentfontfamily, $save_currentfontstyle, $save_currentfontsize);
18404 $this->SetTColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
18407 if (isset($tbd['s']) && $tbd['s']) {
18408 if (!$brset && $tbd['style'] != 'dotted' && $tbd['style'] != 'dashed') {
18409 $this->_out('q');
18410 $this->SetLineWidth(0);
18411 $this->_out(sprintf('%.3F %.3F m ', ($x0) * Mpdf::SCALE, ($this->h - ($y0)) * Mpdf::SCALE));
18412 $this->_out(sprintf('%.3F %.3F l ', ($x0 + $border_left) * Mpdf::SCALE, ($this->h - ($y0 + $border_top)) * Mpdf::SCALE));
18413 $this->_out(sprintf('%.3F %.3F l ', ($x0 + $w - $border_right) * Mpdf::SCALE, ($this->h - ($y0 + $border_top)) * Mpdf::SCALE));
18414 $this->_out(sprintf('%.3F %.3F l ', ($x0 + $w) * Mpdf::SCALE, ($this->h - ($y0)) * Mpdf::SCALE));
18415 $this->_out(' h W n '); // Ends path no-op & Sets the clipping path
18418 $this->_setBorderLine($tbd);
18419 if ($tbd['style'] == 'dotted' || $tbd['style'] == 'dashed') {
18420 $legbreakL -= $border_top / 2; // because line cap different
18421 $legbreakR += $border_top / 2;
18422 $this->_setDashBorder($tbd['style'], $divider, $continuingpage, 'T');
18423 } /* -- BORDER-RADIUS -- */ elseif (($brTL_V && $brTL_H) || ($brTR_V && $brTR_H) || $tbd['style'] == 'solid' || $tbd['style'] == 'double') {
18424 $this->SetLineJoin(0);
18425 $this->SetLineCap(0);
18427 $s = '';
18428 if ($brTR_H && $brTR_V) {
18429 $s .= ($this->_EllipseArc($x0 + $w - $brTR_H, $y0 + $brTR_V, $brTR_H - $border_top / 2, $brTR_V - $border_top / 2, 1, 2, true)) . "\n";
18430 } else { /* -- END BORDER-RADIUS -- */
18431 if ($tbd['style'] == 'solid' || $tbd['style'] == 'double') {
18432 $s .= (sprintf('%.3F %.3F m ', ($x0 + $w) * Mpdf::SCALE, ($this->h - ($y0 + ($border_top / 2))) * Mpdf::SCALE)) . "\n";
18433 } else {
18434 $s .= (sprintf('%.3F %.3F m ', ($x0 + $w - ($border_top / 2)) * Mpdf::SCALE, ($this->h - ($y0 + ($border_top / 2))) * Mpdf::SCALE)) . "\n";
18437 /* -- BORDER-RADIUS -- */
18438 if ($brTL_H && $brTL_V) {
18439 if ($legend) {
18440 if ($legbreakR < ($x0 + $w - $brTR_H)) {
18441 $s .= (sprintf('%.3F %.3F l ', $legbreakR * Mpdf::SCALE, ($this->h - ($y0 + ($border_top / 2))) * Mpdf::SCALE)) . "\n";
18443 if ($legbreakL > ($x0 + $brTL_H )) {
18444 $s .= (sprintf('%.3F %.3F m ', $legbreakL * Mpdf::SCALE, ($this->h - ($y0 + ($border_top / 2))) * Mpdf::SCALE)) . "\n";
18445 $s .= (sprintf('%.3F %.3F l ', ($x0 + $brTL_H ) * Mpdf::SCALE, ($this->h - ($y0 + ($border_top / 2))) * Mpdf::SCALE) . "\n");
18446 } else {
18447 $s .= (sprintf('%.3F %.3F m ', ($x0 + $brTL_H ) * Mpdf::SCALE, ($this->h - ($y0 + ($border_top / 2))) * Mpdf::SCALE)) . "\n";
18449 } else {
18450 $s .= (sprintf('%.3F %.3F l ', ($x0 + $brTL_H ) * Mpdf::SCALE, ($this->h - ($y0 + ($border_top / 2))) * Mpdf::SCALE)) . "\n";
18452 $s .= ($this->_EllipseArc($x0 + $brTL_H, $y0 + $brTL_V, $brTL_H - $border_top / 2, $brTL_V - $border_top / 2, 2, 1)) . "\n";
18453 } else {
18454 /* -- END BORDER-RADIUS -- */
18455 if ($legend) {
18456 if ($legbreakR < ($x0 + $w)) {
18457 $s .= (sprintf('%.3F %.3F l ', $legbreakR * Mpdf::SCALE, ($this->h - ($y0 + ($border_top / 2))) * Mpdf::SCALE)) . "\n";
18459 if ($legbreakL > ($x0)) {
18460 $s .= (sprintf('%.3F %.3F m ', $legbreakL * Mpdf::SCALE, ($this->h - ($y0 + ($border_top / 2))) * Mpdf::SCALE)) . "\n";
18461 if ($tbd['style'] == 'solid' || $tbd['style'] == 'double') {
18462 $s .= (sprintf('%.3F %.3F l ', ($x0) * Mpdf::SCALE, ($this->h - ($y0 + ($border_top / 2))) * Mpdf::SCALE)) . "\n";
18463 } else {
18464 $s .= (sprintf('%.3F %.3F l ', ($x0 + ($border_top / 2)) * Mpdf::SCALE, ($this->h - ($y0 + ($border_top / 2))) * Mpdf::SCALE)) . "\n";
18466 } elseif ($tbd['style'] == 'solid' || $tbd['style'] == 'double') {
18467 $s .= (sprintf('%.3F %.3F m ', ($x0) * Mpdf::SCALE, ($this->h - ($y0 + ($border_top / 2))) * Mpdf::SCALE)) . "\n";
18468 } else {
18469 $s .= (sprintf('%.3F %.3F m ', ($x0 + $border_top / 2) * Mpdf::SCALE, ($this->h - ($y0 + ($border_top / 2))) * Mpdf::SCALE)) . "\n";
18471 } elseif ($tbd['style'] == 'solid' || $tbd['style'] == 'double') {
18472 $s .= (sprintf('%.3F %.3F l ', ($x0) * Mpdf::SCALE, ($this->h - ($y0 + ($border_top / 2))) * Mpdf::SCALE)) . "\n";
18473 } else {
18474 $s .= (sprintf('%.3F %.3F l ', ($x0 + ($border_top / 2)) * Mpdf::SCALE, ($this->h - ($y0 + ($border_top / 2))) * Mpdf::SCALE)) . "\n";
18476 /* -- BORDER-RADIUS -- */
18478 /* -- END BORDER-RADIUS -- */
18479 $s .= 'S' . "\n";
18480 $this->_out($s);
18482 if ($tbd['style'] == 'double') {
18483 $this->SetLineWidth($tbd['w'] / 3);
18484 $this->SetDColor($tbcol);
18485 $this->_out($s);
18487 if (!$brset && $tbd['style'] != 'dotted' && $tbd['style'] != 'dashed') {
18488 $this->_out('Q');
18491 // Reset Corners and Dash off
18492 $this->SetLineWidth(0.1);
18493 $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
18494 $this->SetLineJoin(2);
18495 $this->SetLineCap(2);
18496 $this->SetDash();
18499 // Reinstate line above for dotted line divider when block border crosses a page
18500 // elseif ($divider == 'pagetop' || $continuingpage) {
18502 if ($this->blk[$blvl]['border_bottom'] && $blockstate != 1 && $divider != 'pagebottom') {
18503 $tbd = $this->blk[$blvl]['border_bottom'];
18504 if (isset($tbd['s']) && $tbd['s']) {
18505 if (!$brset && $tbd['style'] != 'dotted' && $tbd['style'] != 'dashed') {
18506 $this->_out('q');
18507 $this->SetLineWidth(0);
18508 $this->_out(sprintf('%.3F %.3F m ', ($x0) * Mpdf::SCALE, ($this->h - ($y0 + $h)) * Mpdf::SCALE));
18509 $this->_out(sprintf('%.3F %.3F l ', ($x0 + $border_left) * Mpdf::SCALE, ($this->h - ($y0 + $h - $border_bottom)) * Mpdf::SCALE));
18510 $this->_out(sprintf('%.3F %.3F l ', ($x0 + $w - $border_right) * Mpdf::SCALE, ($this->h - ($y0 + $h - $border_bottom)) * Mpdf::SCALE));
18511 $this->_out(sprintf('%.3F %.3F l ', ($x0 + $w) * Mpdf::SCALE, ($this->h - ($y0 + $h)) * Mpdf::SCALE));
18512 $this->_out(' h W n '); // Ends path no-op & Sets the clipping path
18515 $this->_setBorderLine($tbd);
18516 if ($tbd['style'] == 'dotted' || $tbd['style'] == 'dashed') {
18517 $this->_setDashBorder($tbd['style'], $divider, $continuingpage, 'B');
18518 } /* -- BORDER-RADIUS -- */ elseif (($brBL_V && $brBL_H) || ($brBR_V && $brBR_H) || $tbd['style'] == 'solid' || $tbd['style'] == 'double') {
18519 $this->SetLineJoin(0);
18520 $this->SetLineCap(0);
18522 $s = '';
18523 if ($brBL_H && $brBL_V) {
18524 $s .= ($this->_EllipseArc($x0 + $brBL_H, $y0 + $h - $brBL_V, $brBL_H - $border_bottom / 2, $brBL_V - $border_bottom / 2, 3, 2, true)) . "\n";
18525 } else { /* -- END BORDER-RADIUS -- */
18526 if ($tbd['style'] == 'solid' || $tbd['style'] == 'double') {
18527 $s .= (sprintf('%.3F %.3F m ', ($x0) * Mpdf::SCALE, ($this->h - ($y0 + $h - ($border_bottom / 2))) * Mpdf::SCALE)) . "\n";
18528 } else {
18529 $s .= (sprintf('%.3F %.3F m ', ($x0 + ($border_bottom / 2)) * Mpdf::SCALE, ($this->h - ($y0 + $h - ($border_bottom / 2))) * Mpdf::SCALE)) . "\n";
18532 /* -- BORDER-RADIUS -- */
18533 if ($brBR_H && $brBR_V) {
18534 $s .= (sprintf('%.3F %.3F l ', ($x0 + $w - ($border_bottom / 2) - $brBR_H ) * Mpdf::SCALE, ($this->h - ($y0 + $h - ($border_bottom / 2))) * Mpdf::SCALE)) . "\n";
18535 $s .= ($this->_EllipseArc($x0 + $w - $brBR_H, $y0 + $h - $brBR_V, $brBR_H - $border_bottom / 2, $brBR_V - $border_bottom / 2, 4, 1)) . "\n";
18536 } else { /* -- END BORDER-RADIUS -- */
18537 if ($tbd['style'] == 'solid' || $tbd['style'] == 'double') {
18538 $s .= (sprintf('%.3F %.3F l ', ($x0 + $w) * Mpdf::SCALE, ($this->h - ($y0 + $h - ($border_bottom / 2))) * Mpdf::SCALE)) . "\n";
18539 } else {
18540 $s .= (sprintf('%.3F %.3F l ', ($x0 + $w - ($border_bottom / 2)) * Mpdf::SCALE, ($this->h - ($y0 + $h - ($border_bottom / 2))) * Mpdf::SCALE)) . "\n";
18543 $s .= 'S' . "\n";
18544 $this->_out($s);
18546 if ($tbd['style'] == 'double') {
18547 $this->SetLineWidth($tbd['w'] / 3);
18548 $this->SetDColor($tbcol);
18549 $this->_out($s);
18551 if (!$brset && $tbd['style'] != 'dotted' && $tbd['style'] != 'dashed') {
18552 $this->_out('Q');
18555 // Reset Corners and Dash off
18556 $this->SetLineWidth(0.1);
18557 $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
18558 $this->SetLineJoin(2);
18559 $this->SetLineCap(2);
18560 $this->SetDash();
18563 // Reinstate line below for dotted line divider when block border crosses a page
18564 // elseif ($blockstate == 1 || $divider == 'pagebottom') {
18566 if ($this->blk[$blvl]['border_left']) {
18567 $tbd = $this->blk[$blvl]['border_left'];
18568 if (isset($tbd['s']) && $tbd['s']) {
18569 if (!$brset && $tbd['style'] != 'dotted' && $tbd['style'] != 'dashed') {
18570 $this->_out('q');
18571 $this->SetLineWidth(0);
18572 $this->_out(sprintf('%.3F %.3F m ', ($x0) * Mpdf::SCALE, ($this->h - ($y0)) * Mpdf::SCALE));
18573 $this->_out(sprintf('%.3F %.3F l ', ($x0 + $border_left) * Mpdf::SCALE, ($this->h - ($y0 + $border_top)) * Mpdf::SCALE));
18574 $this->_out(sprintf('%.3F %.3F l ', ($x0 + $border_left) * Mpdf::SCALE, ($this->h - ($y0 + $h - $border_bottom)) * Mpdf::SCALE));
18575 $this->_out(sprintf('%.3F %.3F l ', ($x0) * Mpdf::SCALE, ($this->h - ($y0 + $h)) * Mpdf::SCALE));
18576 $this->_out(' h W n '); // Ends path no-op & Sets the clipping path
18579 $this->_setBorderLine($tbd);
18580 if ($tbd['style'] == 'dotted' || $tbd['style'] == 'dashed') {
18581 $this->_setDashBorder($tbd['style'], $divider, $continuingpage, 'L');
18582 } /* -- BORDER-RADIUS -- */ elseif (($brTL_V && $brTL_H) || ($brBL_V && $brBL_H) || $tbd['style'] == 'solid' || $tbd['style'] == 'double') {
18583 $this->SetLineJoin(0);
18584 $this->SetLineCap(0);
18586 $s = '';
18587 if ($brTL_V && $brTL_H) {
18588 $s .= ($this->_EllipseArc($x0 + $brTL_H, $y0 + $brTL_V, $brTL_H - $border_left / 2, $brTL_V - $border_left / 2, 2, 2, true)) . "\n";
18589 } else { /* -- END BORDER-RADIUS -- */
18590 if ($tbd['style'] == 'solid' || $tbd['style'] == 'double') {
18591 $s .= (sprintf('%.3F %.3F m ', ($x0 + ($border_left / 2)) * Mpdf::SCALE, ($this->h - ($y0)) * Mpdf::SCALE)) . "\n";
18592 } else {
18593 $s .= (sprintf('%.3F %.3F m ', ($x0 + ($border_left / 2)) * Mpdf::SCALE, ($this->h - ($y0 + ($border_left / 2))) * Mpdf::SCALE)) . "\n";
18596 /* -- BORDER-RADIUS -- */
18597 if ($brBL_V && $brBL_H) {
18598 $s .= (sprintf('%.3F %.3F l ', ($x0 + ($border_left / 2)) * Mpdf::SCALE, ($this->h - ($y0 + $h - ($border_left / 2) - $brBL_V) ) * Mpdf::SCALE)) . "\n";
18599 $s .= ($this->_EllipseArc($x0 + $brBL_H, $y0 + $h - $brBL_V, $brBL_H - $border_left / 2, $brBL_V - $border_left / 2, 3, 1)) . "\n";
18600 } else { /* -- END BORDER-RADIUS -- */
18601 if ($tbd['style'] == 'solid' || $tbd['style'] == 'double') {
18602 $s .= (sprintf('%.3F %.3F l ', ($x0 + ($border_left / 2)) * Mpdf::SCALE, ($this->h - ($y0 + $h) ) * Mpdf::SCALE)) . "\n";
18603 } else {
18604 $s .= (sprintf('%.3F %.3F l ', ($x0 + ($border_left / 2)) * Mpdf::SCALE, ($this->h - ($y0 + $h - ($border_left / 2)) ) * Mpdf::SCALE)) . "\n";
18607 $s .= 'S' . "\n";
18608 $this->_out($s);
18610 if ($tbd['style'] == 'double') {
18611 $this->SetLineWidth($tbd['w'] / 3);
18612 $this->SetDColor($tbcol);
18613 $this->_out($s);
18615 if (!$brset && $tbd['style'] != 'dotted' && $tbd['style'] != 'dashed') {
18616 $this->_out('Q');
18619 // Reset Corners and Dash off
18620 $this->SetLineWidth(0.1);
18621 $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
18622 $this->SetLineJoin(2);
18623 $this->SetLineCap(2);
18624 $this->SetDash();
18627 if ($this->blk[$blvl]['border_right']) {
18628 $tbd = $this->blk[$blvl]['border_right'];
18629 if (isset($tbd['s']) && $tbd['s']) {
18630 if (!$brset && $tbd['style'] != 'dotted' && $tbd['style'] != 'dashed') {
18631 $this->_out('q');
18632 $this->SetLineWidth(0);
18633 $this->_out(sprintf('%.3F %.3F m ', ($x0 + $w) * Mpdf::SCALE, ($this->h - ($y0)) * Mpdf::SCALE));
18634 $this->_out(sprintf('%.3F %.3F l ', ($x0 + $w - $border_right) * Mpdf::SCALE, ($this->h - ($y0 + $border_top)) * Mpdf::SCALE));
18635 $this->_out(sprintf('%.3F %.3F l ', ($x0 + $w - $border_right) * Mpdf::SCALE, ($this->h - ($y0 + $h - $border_bottom)) * Mpdf::SCALE));
18636 $this->_out(sprintf('%.3F %.3F l ', ($x0 + $w) * Mpdf::SCALE, ($this->h - ($y0 + $h)) * Mpdf::SCALE));
18637 $this->_out(' h W n '); // Ends path no-op & Sets the clipping path
18640 $this->_setBorderLine($tbd);
18641 if ($tbd['style'] == 'dotted' || $tbd['style'] == 'dashed') {
18642 $this->_setDashBorder($tbd['style'], $divider, $continuingpage, 'R');
18643 } /* -- BORDER-RADIUS -- */ elseif (($brTR_V && $brTR_H) || ($brBR_V && $brBR_H) || $tbd['style'] == 'solid' || $tbd['style'] == 'double') {
18644 $this->SetLineJoin(0);
18645 $this->SetLineCap(0);
18647 $s = '';
18648 if ($brBR_V && $brBR_H) {
18649 $s .= ($this->_EllipseArc($x0 + $w - $brBR_H, $y0 + $h - $brBR_V, $brBR_H - $border_right / 2, $brBR_V - $border_right / 2, 4, 2, true)) . "\n";
18650 } else { /* -- END BORDER-RADIUS -- */
18651 if ($tbd['style'] == 'solid' || $tbd['style'] == 'double') {
18652 $s .= (sprintf('%.3F %.3F m ', ($x0 + $w - ($border_right / 2)) * Mpdf::SCALE, ($this->h - ($y0 + $h)) * Mpdf::SCALE)) . "\n";
18653 } else {
18654 $s .= (sprintf('%.3F %.3F m ', ($x0 + $w - ($border_right / 2)) * Mpdf::SCALE, ($this->h - ($y0 + $h - ($border_right / 2))) * Mpdf::SCALE)) . "\n";
18657 /* -- BORDER-RADIUS -- */
18658 if ($brTR_V && $brTR_H) {
18659 $s .= (sprintf('%.3F %.3F l ', ($x0 + $w - ($border_right / 2)) * Mpdf::SCALE, ($this->h - ($y0 + ($border_right / 2) + $brTR_V) ) * Mpdf::SCALE)) . "\n";
18660 $s .= ($this->_EllipseArc($x0 + $w - $brTR_H, $y0 + $brTR_V, $brTR_H - $border_right / 2, $brTR_V - $border_right / 2, 1, 1)) . "\n";
18661 } else { /* -- END BORDER-RADIUS -- */
18662 if ($tbd['style'] == 'solid' || $tbd['style'] == 'double') {
18663 $s .= (sprintf('%.3F %.3F l ', ($x0 + $w - ($border_right / 2)) * Mpdf::SCALE, ($this->h - ($y0) ) * Mpdf::SCALE)) . "\n";
18664 } else {
18665 $s .= (sprintf('%.3F %.3F l ', ($x0 + $w - ($border_right / 2)) * Mpdf::SCALE, ($this->h - ($y0 + ($border_right / 2)) ) * Mpdf::SCALE)) . "\n";
18668 $s .= 'S' . "\n";
18669 $this->_out($s);
18671 if ($tbd['style'] == 'double') {
18672 $this->SetLineWidth($tbd['w'] / 3);
18673 $this->SetDColor($tbcol);
18674 $this->_out($s);
18676 if (!$brset && $tbd['style'] != 'dotted' && $tbd['style'] != 'dashed') {
18677 $this->_out('Q');
18680 // Reset Corners and Dash off
18681 $this->SetLineWidth(0.1);
18682 $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
18683 $this->SetLineJoin(2);
18684 $this->SetLineCap(2);
18685 $this->SetDash();
18690 $this->SetDash();
18691 $this->y = $save_y;
18694 // BACKGROUNDS are disabled in columns/kbt/headers - messes up the repositioning in printcolumnbuffer
18695 if ($this->ColActive || $this->kwt || $this->keep_block_together) {
18696 return;
18700 $bgx0 = $x0;
18701 $bgx1 = $x1;
18702 $bgy0 = $y0;
18703 $bgy1 = $y1;
18705 // Defined br values represent the radius of the outer curve - need to take border-width/2 from each radius for drawing the borders
18706 if (isset($this->blk[$blvl]['background_clip']) && $this->blk[$blvl]['background_clip'] == 'padding-box') {
18707 $brbgTL_H = max(0, $brTL_H - $this->blk[$blvl]['border_left']['w']);
18708 $brbgTL_V = max(0, $brTL_V - $this->blk[$blvl]['border_top']['w']);
18709 $brbgTR_H = max(0, $brTR_H - $this->blk[$blvl]['border_right']['w']);
18710 $brbgTR_V = max(0, $brTR_V - $this->blk[$blvl]['border_top']['w']);
18711 $brbgBL_H = max(0, $brBL_H - $this->blk[$blvl]['border_left']['w']);
18712 $brbgBL_V = max(0, $brBL_V - $this->blk[$blvl]['border_bottom']['w']);
18713 $brbgBR_H = max(0, $brBR_H - $this->blk[$blvl]['border_right']['w']);
18714 $brbgBR_V = max(0, $brBR_V - $this->blk[$blvl]['border_bottom']['w']);
18715 $bgx0 += $this->blk[$blvl]['border_left']['w'];
18716 $bgx1 -= $this->blk[$blvl]['border_right']['w'];
18717 if ($this->blk[$blvl]['border_top'] && $divider != 'pagetop' && !$continuingpage) {
18718 $bgy0 += $this->blk[$blvl]['border_top']['w'];
18720 if ($this->blk[$blvl]['border_bottom'] && $blockstate != 1 && $divider != 'pagebottom') {
18721 $bgy1 -= $this->blk[$blvl]['border_bottom']['w'];
18723 } elseif (isset($this->blk[$blvl]['background_clip']) && $this->blk[$blvl]['background_clip'] == 'content-box') {
18724 $brbgTL_H = max(0, $brTL_H - $this->blk[$blvl]['border_left']['w'] - $this->blk[$blvl]['padding_left']);
18725 $brbgTL_V = max(0, $brTL_V - $this->blk[$blvl]['border_top']['w'] - $this->blk[$blvl]['padding_top']);
18726 $brbgTR_H = max(0, $brTR_H - $this->blk[$blvl]['border_right']['w'] - $this->blk[$blvl]['padding_right']);
18727 $brbgTR_V = max(0, $brTR_V - $this->blk[$blvl]['border_top']['w'] - $this->blk[$blvl]['padding_top']);
18728 $brbgBL_H = max(0, $brBL_H - $this->blk[$blvl]['border_left']['w'] - $this->blk[$blvl]['padding_left']);
18729 $brbgBL_V = max(0, $brBL_V - $this->blk[$blvl]['border_bottom']['w'] - $this->blk[$blvl]['padding_bottom']);
18730 $brbgBR_H = max(0, $brBR_H - $this->blk[$blvl]['border_right']['w'] - $this->blk[$blvl]['padding_right']);
18731 $brbgBR_V = max(0, $brBR_V - $this->blk[$blvl]['border_bottom']['w'] - $this->blk[$blvl]['padding_bottom']);
18732 $bgx0 += $this->blk[$blvl]['border_left']['w'] + $this->blk[$blvl]['padding_left'];
18733 $bgx1 -= $this->blk[$blvl]['border_right']['w'] + $this->blk[$blvl]['padding_right'];
18734 if (($this->blk[$blvl]['border_top']['w'] || $this->blk[$blvl]['padding_top']) && $divider != 'pagetop' && !$continuingpage) {
18735 $bgy0 += $this->blk[$blvl]['border_top']['w'] + $this->blk[$blvl]['padding_top'];
18737 if (($this->blk[$blvl]['border_bottom']['w'] || $this->blk[$blvl]['padding_bottom']) && $blockstate != 1 && $divider != 'pagebottom') {
18738 $bgy1 -= $this->blk[$blvl]['border_bottom']['w'] + $this->blk[$blvl]['padding_bottom'];
18740 } else {
18741 $brbgTL_H = $brTL_H;
18742 $brbgTL_V = $brTL_V;
18743 $brbgTR_H = $brTR_H;
18744 $brbgTR_V = $brTR_V;
18745 $brbgBL_H = $brBL_H;
18746 $brbgBL_V = $brBL_V;
18747 $brbgBR_H = $brBR_H;
18748 $brbgBR_V = $brBR_V;
18751 // Set clipping path
18752 $s = ' q 0 w '; // Line width=0
18753 $s .= sprintf('%.3F %.3F m ', ($bgx0 + $brbgTL_H ) * Mpdf::SCALE, ($this->h - $bgy0) * Mpdf::SCALE); // start point TL before the arc
18754 /* -- BORDER-RADIUS -- */
18755 if ($brbgTL_H || $brbgTL_V) {
18756 $s .= $this->_EllipseArc($bgx0 + $brbgTL_H, $bgy0 + $brbgTL_V, $brbgTL_H, $brbgTL_V, 2); // segment 2 TL
18758 /* -- END BORDER-RADIUS -- */
18759 $s .= sprintf('%.3F %.3F l ', ($bgx0) * Mpdf::SCALE, ($this->h - ($bgy1 - $brbgBL_V )) * Mpdf::SCALE); // line to BL
18760 /* -- BORDER-RADIUS -- */
18761 if ($brbgBL_H || $brbgBL_V) {
18762 $s .= $this->_EllipseArc($bgx0 + $brbgBL_H, $bgy1 - $brbgBL_V, $brbgBL_H, $brbgBL_V, 3); // segment 3 BL
18764 /* -- END BORDER-RADIUS -- */
18765 $s .= sprintf('%.3F %.3F l ', ($bgx1 - $brbgBR_H ) * Mpdf::SCALE, ($this->h - ($bgy1)) * Mpdf::SCALE); // line to BR
18766 /* -- BORDER-RADIUS -- */
18767 if ($brbgBR_H || $brbgBR_V) {
18768 $s .= $this->_EllipseArc($bgx1 - $brbgBR_H, $bgy1 - $brbgBR_V, $brbgBR_H, $brbgBR_V, 4); // segment 4 BR
18770 /* -- END BORDER-RADIUS -- */
18771 $s .= sprintf('%.3F %.3F l ', ($bgx1) * Mpdf::SCALE, ($this->h - ($bgy0 + $brbgTR_V)) * Mpdf::SCALE); // line to TR
18772 /* -- BORDER-RADIUS -- */
18773 if ($brbgTR_H || $brbgTR_V) {
18774 $s .= $this->_EllipseArc($bgx1 - $brbgTR_H, $bgy0 + $brbgTR_V, $brbgTR_H, $brbgTR_V, 1); // segment 1 TR
18776 /* -- END BORDER-RADIUS -- */
18777 $s .= sprintf('%.3F %.3F l ', ($bgx0 + $brbgTL_H ) * Mpdf::SCALE, ($this->h - $bgy0) * Mpdf::SCALE); // line to TL
18778 // Box Shadow
18779 $shadow = '';
18780 if (isset($this->blk[$blvl]['box_shadow']) && $this->blk[$blvl]['box_shadow'] && $h > 0) {
18781 foreach ($this->blk[$blvl]['box_shadow'] as $sh) {
18782 // Colors
18783 if ($sh['col']{0} == 1) {
18784 $colspace = 'Gray';
18785 if ($sh['col']{2} == 1) {
18786 $col1 = '1' . $sh['col'][1] . '1' . $sh['col'][3];
18787 } else {
18788 $col1 = '1' . $sh['col'][1] . '1' . chr(100);
18790 $col2 = '1' . $sh['col'][1] . '1' . chr(0);
18791 } elseif ($sh['col']{0} == 4) { // CMYK
18792 $colspace = 'CMYK';
18793 $col1 = '6' . $sh['col'][1] . $sh['col'][2] . $sh['col'][3] . $sh['col'][4] . chr(100);
18794 $col2 = '6' . $sh['col'][1] . $sh['col'][2] . $sh['col'][3] . $sh['col'][4] . chr(0);
18795 } elseif ($sh['col']{0} == 5) { // RGBa
18796 $colspace = 'RGB';
18797 $col1 = '5' . $sh['col'][1] . $sh['col'][2] . $sh['col'][3] . $sh['col'][4];
18798 $col2 = '5' . $sh['col'][1] . $sh['col'][2] . $sh['col'][3] . chr(0);
18799 } elseif ($sh['col']{0} == 6) { // CMYKa
18800 $colspace = 'CMYK';
18801 $col1 = '6' . $sh['col'][1] . $sh['col'][2] . $sh['col'][3] . $sh['col'][4] . $sh['col'][5];
18802 $col2 = '6' . $sh['col'][1] . $sh['col'][2] . $sh['col'][3] . $sh['col'][4] . chr(0);
18803 } else {
18804 $colspace = 'RGB';
18805 $col1 = '5' . $sh['col'][1] . $sh['col'][2] . $sh['col'][3] . chr(100);
18806 $col2 = '5' . $sh['col'][1] . $sh['col'][2] . $sh['col'][3] . chr(0);
18809 // Use clipping path as set above (and rectangle around page) to clip area outside box
18810 $shadow .= $s; // Use the clipping path with W*
18811 $shadow .= sprintf('0 %.3F m %.3F %.3F l ', $this->h * Mpdf::SCALE, $this->w * Mpdf::SCALE, $this->h * Mpdf::SCALE);
18812 $shadow .= sprintf('%.3F 0 l 0 0 l 0 %.3F l ', $this->w * Mpdf::SCALE, $this->h * Mpdf::SCALE);
18813 $shadow .= 'W n' . "\n";
18815 $sh['blur'] = abs($sh['blur']); // cannot have negative blur value
18816 // Ensure spread/blur do not make effective shadow width/height < 0
18817 // Could do more complex things but this just adjusts spread value
18818 if (-$sh['spread'] + $sh['blur'] / 2 > min($w / 2, $h / 2)) {
18819 $sh['spread'] = $sh['blur'] / 2 - min($w / 2, $h / 2) + 0.01;
18821 // Shadow Offset
18822 if ($sh['x'] || $sh['y']) {
18823 $shadow .= sprintf(' q 1 0 0 1 %.4F %.4F cm', $sh['x'] * Mpdf::SCALE, -$sh['y'] * Mpdf::SCALE) . "\n";
18826 // Set path for INNER shadow
18827 $shadow .= ' q 0 w ';
18828 $shadow .= $this->SetFColor($col1, true) . "\n";
18829 if ($col1{0} == 5 && ord($col1{4}) < 100) { // RGBa
18830 $shadow .= $this->SetAlpha(ord($col1{4}) / 100, 'Normal', true, 'F') . "\n";
18831 } elseif ($col1{0} == 6 && ord($col1{5}) < 100) { // CMYKa
18832 $shadow .= $this->SetAlpha(ord($col1{5}) / 100, 'Normal', true, 'F') . "\n";
18833 } elseif ($col1{0} == 1 && $col1{2} == 1 && ord($col1{3}) < 100) { // Gray
18834 $shadow .= $this->SetAlpha(ord($col1{3}) / 100, 'Normal', true, 'F') . "\n";
18837 // Blur edges
18838 $mag = 0.551784; // Bezier Control magic number for 4-part spline for circle/ellipse
18839 $mag2 = 0.551784; // Bezier Control magic number to fill in edge of blurred rectangle
18840 $d1 = $sh['spread'] + $sh['blur'] / 2;
18841 $d2 = $sh['spread'] - $sh['blur'] / 2;
18842 $bl = $sh['blur'];
18843 $x00 = $x0 - $d1;
18844 $y00 = $y0 - $d1;
18845 $w00 = $w + $d1 * 2;
18846 $h00 = $h + $d1 * 2;
18848 // If any border-radius is greater width-negative spread(inner edge), ignore radii for shadow or screws up
18849 $flatten = false;
18850 if (max($brbgTR_H, $brbgTL_H, $brbgBR_H, $brbgBL_H) >= $w + $d2) {
18851 $flatten = true;
18853 if (max($brbgTR_V, $brbgTL_V, $brbgBR_V, $brbgBL_V) >= $h + $d2) {
18854 $flatten = true;
18858 // TOP RIGHT corner
18859 $p1x = $x00 + $w00 - $d1 - $brbgTR_H;
18860 $p1c2x = $p1x + ($d2 + $brbgTR_H) * $mag;
18861 $p1y = $y00 + $bl;
18862 $p2x = $x00 + $w00 - $d1 - $brbgTR_H;
18863 $p2c2x = $p2x + ($d1 + $brbgTR_H) * $mag;
18864 $p2y = $y00;
18865 $p2c1y = $p2y + $bl / 2;
18866 $p3x = $x00 + $w00;
18867 $p3c2x = $p3x - $bl / 2;
18868 $p3y = $y00 + $d1 + $brbgTR_V;
18869 $p3c1y = $p3y - ($d1 + $brbgTR_V) * $mag;
18870 $p4x = $x00 + $w00 - $bl;
18871 $p4y = $y00 + $d1 + $brbgTR_V;
18872 $p4c2y = $p4y - ($d2 + $brbgTR_V) * $mag;
18873 if (-$d2 > min($brbgTR_H, $brbgTR_V) || $flatten) {
18874 $p1x = $x00 + $w00 - $bl;
18875 $p1c2x = $p1x;
18876 $p2x = $x00 + $w00 - $bl;
18877 $p2c2x = $p2x + $bl * $mag2;
18878 $p3y = $y00 + $bl;
18879 $p3c1y = $p3y - $bl * $mag2;
18880 $p4y = $y00 + $bl;
18881 $p4c2y = $p4y;
18884 $shadow .= sprintf('%.3F %.3F m ', ($p1x ) * Mpdf::SCALE, ($this->h - ($p1y )) * Mpdf::SCALE);
18885 $shadow .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', ($p1c2x) * Mpdf::SCALE, ($this->h - ($p1y)) * Mpdf::SCALE, ($p4x) * Mpdf::SCALE, ($this->h - ($p4c2y)) * Mpdf::SCALE, ($p4x) * Mpdf::SCALE, ($this->h - ($p4y)) * Mpdf::SCALE);
18886 $patch_array[0]['f'] = 0;
18887 $patch_array[0]['points'] = [$p1x, $p1y, $p1x, $p1y,
18888 $p2x, $p2c1y, $p2x, $p2y, $p2c2x, $p2y,
18889 $p3x, $p3c1y, $p3x, $p3y, $p3c2x, $p3y,
18890 $p4x, $p4y, $p4x, $p4y, $p4x, $p4c2y,
18891 $p1c2x, $p1y];
18892 $patch_array[0]['colors'] = [$col1, $col2, $col2, $col1];
18895 // RIGHT
18896 $p1x = $x00 + $w00; // control point only matches p3 preceding
18897 $p1y = $y00 + $d1 + $brbgTR_V;
18898 $p2x = $x00 + $w00 - $bl; // control point only matches p4 preceding
18899 $p2y = $y00 + $d1 + $brbgTR_V;
18900 $p3x = $x00 + $w00 - $bl;
18901 $p3y = $y00 + $h00 - $d1 - $brbgBR_V;
18902 $p4x = $x00 + $w00;
18903 $p4c1x = $p4x - $bl / 2;
18904 $p4y = $y00 + $h00 - $d1 - $brbgBR_V;
18905 if (-$d2 > min($brbgTR_H, $brbgTR_V) || $flatten) {
18906 $p1y = $y00 + $bl;
18907 $p2y = $y00 + $bl;
18909 if (-$d2 > min($brbgBR_H, $brbgBR_V) || $flatten) {
18910 $p3y = $y00 + $h00 - $bl;
18911 $p4y = $y00 + $h00 - $bl;
18914 $shadow .= sprintf('%.3F %.3F l ', ($p3x ) * Mpdf::SCALE, ($this->h - ($p3y )) * Mpdf::SCALE);
18915 $patch_array[1]['f'] = 2;
18916 $patch_array[1]['points'] = [$p2x, $p2y,
18917 $p3x, $p3y, $p3x, $p3y, $p3x, $p3y,
18918 $p4c1x, $p4y, $p4x, $p4y, $p4x, $p4y,
18919 $p1x, $p1y];
18920 $patch_array[1]['colors'] = [$col1, $col2];
18923 // BOTTOM RIGHT corner
18924 $p1x = $x00 + $w00 - $bl; // control points only matches p3 preceding
18925 $p1y = $y00 + $h00 - $d1 - $brbgBR_V;
18926 $p1c2y = $p1y + ($d2 + $brbgBR_V) * $mag;
18927 $p2x = $x00 + $w00; // control point only matches p4 preceding
18928 $p2y = $y00 + $h00 - $d1 - $brbgBR_V;
18929 $p2c2y = $p2y + ($d1 + $brbgBR_V) * $mag;
18930 $p3x = $x00 + $w00 - $d1 - $brbgBR_H;
18931 $p3c1x = $p3x + ($d1 + $brbgBR_H) * $mag;
18932 $p3y = $y00 + $h00;
18933 $p3c2y = $p3y - $bl / 2;
18934 $p4x = $x00 + $w00 - $d1 - $brbgBR_H;
18935 $p4c2x = $p4x + ($d2 + $brbgBR_H) * $mag;
18936 $p4y = $y00 + $h00 - $bl;
18938 if (-$d2 > min($brbgBR_H, $brbgBR_V) || $flatten) {
18939 $p1y = $y00 + $h00 - $bl;
18940 $p1c2y = $p1y;
18941 $p2y = $y00 + $h00 - $bl;
18942 $p2c2y = $p2y + $bl * $mag2;
18943 $p3x = $x00 + $w00 - $bl;
18944 $p3c1x = $p3x + $bl * $mag2;
18945 $p4x = $x00 + $w00 - $bl;
18946 $p4c2x = $p4x;
18949 $shadow .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', ($p1x) * Mpdf::SCALE, ($this->h - ($p1c2y)) * Mpdf::SCALE, ($p4c2x) * Mpdf::SCALE, ($this->h - ($p4y)) * Mpdf::SCALE, ($p4x) * Mpdf::SCALE, ($this->h - ($p4y)) * Mpdf::SCALE);
18950 $patch_array[2]['f'] = 2;
18951 $patch_array[2]['points'] = [$p2x, $p2c2y,
18952 $p3c1x, $p3y, $p3x, $p3y, $p3x, $p3c2y,
18953 $p4x, $p4y, $p4x, $p4y, $p4c2x, $p4y,
18954 $p1x, $p1c2y];
18955 $patch_array[2]['colors'] = [$col2, $col1];
18959 // BOTTOM
18960 $p1x = $x00 + $w00 - $d1 - $brbgBR_H; // control point only matches p3 preceding
18961 $p1y = $y00 + $h00;
18962 $p2x = $x00 + $w00 - $d1 - $brbgBR_H; // control point only matches p4 preceding
18963 $p2y = $y00 + $h00 - $bl;
18964 $p3x = $x00 + $d1 + $brbgBL_H;
18965 $p3y = $y00 + $h00 - $bl;
18966 $p4x = $x00 + $d1 + $brbgBL_H;
18967 $p4y = $y00 + $h00;
18968 $p4c1y = $p4y - $bl / 2;
18970 if (-$d2 > min($brbgBR_H, $brbgBR_V) || $flatten) {
18971 $p1x = $x00 + $w00 - $bl;
18972 $p2x = $x00 + $w00 - $bl;
18974 if (-$d2 > min($brbgBL_H, $brbgBL_V) || $flatten) {
18975 $p3x = $x00 + $bl;
18976 $p4x = $x00 + $bl;
18979 $shadow .= sprintf('%.3F %.3F l ', ($p3x ) * Mpdf::SCALE, ($this->h - ($p3y )) * Mpdf::SCALE);
18980 $patch_array[3]['f'] = 2;
18981 $patch_array[3]['points'] = [$p2x, $p2y,
18982 $p3x, $p3y, $p3x, $p3y, $p3x, $p3y,
18983 $p4x, $p4c1y, $p4x, $p4y, $p4x, $p4y,
18984 $p1x, $p1y];
18985 $patch_array[3]['colors'] = [$col1, $col2];
18987 // BOTTOM LEFT corner
18988 $p1x = $x00 + $d1 + $brbgBL_H;
18989 $p1c2x = $p1x - ($d2 + $brbgBL_H) * $mag; // control points only matches p3 preceding
18990 $p1y = $y00 + $h00 - $bl;
18991 $p2x = $x00 + $d1 + $brbgBL_H;
18992 $p2c2x = $p2x - ($d1 + $brbgBL_H) * $mag; // control point only matches p4 preceding
18993 $p2y = $y00 + $h00;
18994 $p3x = $x00;
18995 $p3c2x = $p3x + $bl / 2;
18996 $p3y = $y00 + $h00 - $d1 - $brbgBL_V;
18997 $p3c1y = $p3y + ($d1 + $brbgBL_V) * $mag;
18998 $p4x = $x00 + $bl;
18999 $p4y = $y00 + $h00 - $d1 - $brbgBL_V;
19000 $p4c2y = $p4y + ($d2 + $brbgBL_V) * $mag;
19001 if (-$d2 > min($brbgBL_H, $brbgBL_V) || $flatten) {
19002 $p1x = $x00 + $bl;
19003 $p1c2x = $p1x;
19004 $p2x = $x00 + $bl;
19005 $p2c2x = $p2x - $bl * $mag2;
19006 $p3y = $y00 + $h00 - $bl;
19007 $p3c1y = $p3y + $bl * $mag2;
19008 $p4y = $y00 + $h00 - $bl;
19009 $p4c2y = $p4y;
19012 $shadow .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', ($p1c2x) * Mpdf::SCALE, ($this->h - ($p1y)) * Mpdf::SCALE, ($p4x) * Mpdf::SCALE, ($this->h - ($p4c2y)) * Mpdf::SCALE, ($p4x) * Mpdf::SCALE, ($this->h - ($p4y)) * Mpdf::SCALE);
19013 $patch_array[4]['f'] = 2;
19014 $patch_array[4]['points'] = [$p2c2x, $p2y,
19015 $p3x, $p3c1y, $p3x, $p3y, $p3c2x, $p3y,
19016 $p4x, $p4y, $p4x, $p4y, $p4x, $p4c2y,
19017 $p1c2x, $p1y];
19018 $patch_array[4]['colors'] = [$col2, $col1];
19021 // LEFT - joins on the right (C3-C4 of previous): f = 2
19022 $p1x = $x00; // control point only matches p3 preceding
19023 $p1y = $y00 + $h00 - $d1 - $brbgBL_V;
19024 $p2x = $x00 + $bl; // control point only matches p4 preceding
19025 $p2y = $y00 + $h00 - $d1 - $brbgBL_V;
19026 $p3x = $x00 + $bl;
19027 $p3y = $y00 + $d1 + $brbgTL_V;
19028 $p4x = $x00;
19029 $p4c1x = $p4x + $bl / 2;
19030 $p4y = $y00 + $d1 + $brbgTL_V;
19031 if (-$d2 > min($brbgBL_H, $brbgBL_V) || $flatten) {
19032 $p1y = $y00 + $h00 - $bl;
19033 $p2y = $y00 + $h00 - $bl;
19035 if (-$d2 > min($brbgTL_H, $brbgTL_V) || $flatten) {
19036 $p3y = $y00 + $bl;
19037 $p4y = $y00 + $bl;
19040 $shadow .= sprintf('%.3F %.3F l ', ($p3x ) * Mpdf::SCALE, ($this->h - ($p3y )) * Mpdf::SCALE);
19041 $patch_array[5]['f'] = 2;
19042 $patch_array[5]['points'] = [$p2x, $p2y,
19043 $p3x, $p3y, $p3x, $p3y, $p3x, $p3y,
19044 $p4c1x, $p4y, $p4x, $p4y, $p4x, $p4y,
19045 $p1x, $p1y];
19046 $patch_array[5]['colors'] = [$col1, $col2];
19048 // TOP LEFT corner
19049 $p1x = $x00 + $bl; // control points only matches p3 preceding
19050 $p1y = $y00 + $d1 + $brbgTL_V;
19051 $p1c2y = $p1y - ($d2 + $brbgTL_V) * $mag;
19052 $p2x = $x00; // control point only matches p4 preceding
19053 $p2y = $y00 + $d1 + $brbgTL_V;
19054 $p2c2y = $p2y - ($d1 + $brbgTL_V) * $mag;
19055 $p3x = $x00 + $d1 + $brbgTL_H;
19056 $p3c1x = $p3x - ($d1 + $brbgTL_H) * $mag;
19057 $p3y = $y00;
19058 $p3c2y = $p3y + $bl / 2;
19059 $p4x = $x00 + $d1 + $brbgTL_H;
19060 $p4c2x = $p4x - ($d2 + $brbgTL_H) * $mag;
19061 $p4y = $y00 + $bl;
19063 if (-$d2 > min($brbgTL_H, $brbgTL_V) || $flatten) {
19064 $p1y = $y00 + $bl;
19065 $p1c2y = $p1y;
19066 $p2y = $y00 + $bl;
19067 $p2c2y = $p2y - $bl * $mag2;
19068 $p3x = $x00 + $bl;
19069 $p3c1x = $p3x - $bl * $mag2;
19070 $p4x = $x00 + $bl;
19071 $p4c2x = $p4x;
19074 $shadow .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', ($p1x) * Mpdf::SCALE, ($this->h - ($p1c2y)) * Mpdf::SCALE, ($p4c2x) * Mpdf::SCALE, ($this->h - ($p4y)) * Mpdf::SCALE, ($p4x) * Mpdf::SCALE, ($this->h - ($p4y)) * Mpdf::SCALE);
19075 $patch_array[6]['f'] = 2;
19076 $patch_array[6]['points'] = [$p2x, $p2c2y,
19077 $p3c1x, $p3y, $p3x, $p3y, $p3x, $p3c2y,
19078 $p4x, $p4y, $p4x, $p4y, $p4c2x, $p4y,
19079 $p1x, $p1c2y];
19080 $patch_array[6]['colors'] = [$col2, $col1];
19083 // TOP - joins on the right (C3-C4 of previous): f = 2
19084 $p1x = $x00 + $d1 + $brbgTL_H; // control point only matches p3 preceding
19085 $p1y = $y00;
19086 $p2x = $x00 + $d1 + $brbgTL_H; // control point only matches p4 preceding
19087 $p2y = $y00 + $bl;
19088 $p3x = $x00 + $w00 - $d1 - $brbgTR_H;
19089 $p3y = $y00 + $bl;
19090 $p4x = $x00 + $w00 - $d1 - $brbgTR_H;
19091 $p4y = $y00;
19092 $p4c1y = $p4y + $bl / 2;
19093 if (-$d2 > min($brbgTL_H, $brbgTL_V) || $flatten) {
19094 $p1x = $x00 + $bl;
19095 $p2x = $x00 + $bl;
19097 if (-$d2 > min($brbgTR_H, $brbgTR_V) || $flatten) {
19098 $p3x = $x00 + $w00 - $bl;
19099 $p4x = $x00 + $w00 - $bl;
19102 $shadow .= sprintf('%.3F %.3F l ', ($p3x ) * Mpdf::SCALE, ($this->h - ($p3y )) * Mpdf::SCALE);
19103 $patch_array[7]['f'] = 2;
19104 $patch_array[7]['points'] = [$p2x, $p2y,
19105 $p3x, $p3y, $p3x, $p3y, $p3x, $p3y,
19106 $p4x, $p4c1y, $p4x, $p4y, $p4x, $p4y,
19107 $p1x, $p1y];
19108 $patch_array[7]['colors'] = [$col1, $col2];
19110 $shadow .= ' h f Q ' . "\n"; // Close path and Fill the inner solid shadow
19112 if ($bl) {
19113 $shadow .= $this->gradient->CoonsPatchMesh($x00, $y00, $w00, $h00, $patch_array, $x00, $x00 + $w00, $y00, $y00 + $h00, $colspace, true);
19116 if ($sh['x'] || $sh['y']) {
19117 $shadow .= ' Q' . "\n"; // Shadow Offset
19119 $shadow .= ' Q' . "\n"; // Ends path no-op & Sets the clipping path
19123 $s .= ' W n '; // Ends path no-op & Sets the clipping path
19125 if ($this->blk[$blvl]['bgcolor']) {
19126 $this->pageBackgrounds[$blvl][] = ['x' => $x0, 'y' => $y0, 'w' => $w, 'h' => $h, 'col' => $this->blk[$blvl]['bgcolorarray'], 'clippath' => $s, 'visibility' => $this->visibility, 'shadow' => $shadow, 'z-index' => $this->current_layer];
19127 } elseif ($shadow) {
19128 $this->pageBackgrounds[$blvl][] = ['shadowonly' => true, 'col' => '', 'clippath' => '', 'visibility' => $this->visibility, 'shadow' => $shadow, 'z-index' => $this->current_layer];
19131 /* -- BACKGROUNDS -- */
19132 if (isset($this->blk[$blvl]['gradient'])) {
19133 $g = $this->gradient->parseBackgroundGradient($this->blk[$blvl]['gradient']);
19134 if ($g) {
19135 $gx = $x0;
19136 $gy = $y0;
19137 $this->pageBackgrounds[$blvl][] = ['gradient' => true, 'x' => $gx, 'y' => $gy, 'w' => $w, 'h' => $h, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => $s, 'visibility' => $this->visibility, 'z-index' => $this->current_layer];
19140 if (isset($this->blk[$blvl]['background-image'])) {
19141 if (isset($this->blk[$blvl]['background-image']['gradient']) && $this->blk[$blvl]['background-image']['gradient'] && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $this->blk[$blvl]['background-image']['gradient'])) {
19142 $g = $this->gradient->parseMozGradient($this->blk[$blvl]['background-image']['gradient']);
19143 if ($g) {
19144 $gx = $x0;
19145 $gy = $y0;
19146 // origin specifies the background-positioning-area (bpa)
19147 if ($this->blk[$blvl]['background-image']['origin'] == 'padding-box') {
19148 $gx += $this->blk[$blvl]['border_left']['w'];
19149 $w -= ($this->blk[$blvl]['border_left']['w'] + $this->blk[$blvl]['border_right']['w']);
19150 if ($this->blk[$blvl]['border_top'] && $divider != 'pagetop' && !$continuingpage) {
19151 $gy += $this->blk[$blvl]['border_top']['w'];
19153 if ($this->blk[$blvl]['border_bottom'] && $blockstate != 1 && $divider != 'pagebottom') {
19154 $gy1 = $y1 - $this->blk[$blvl]['border_bottom']['w'];
19155 } else {
19156 $gy1 = $y1;
19158 $h = $gy1 - $gy;
19159 } elseif ($this->blk[$blvl]['background-image']['origin'] == 'content-box') {
19160 $gx += $this->blk[$blvl]['border_left']['w'] + $this->blk[$blvl]['padding_left'];
19161 $w -= ($this->blk[$blvl]['border_left']['w'] + $this->blk[$blvl]['padding_left'] + $this->blk[$blvl]['border_right']['w'] + $this->blk[$blvl]['padding_right']);
19162 if ($this->blk[$blvl]['border_top'] && $divider != 'pagetop' && !$continuingpage) {
19163 $gy += $this->blk[$blvl]['border_top']['w'] + $this->blk[$blvl]['padding_top'];
19165 if ($this->blk[$blvl]['border_bottom'] && $blockstate != 1 && $divider != 'pagebottom') {
19166 $gy1 = $y1 - ($this->blk[$blvl]['border_bottom']['w'] + $this->blk[$blvl]['padding_bottom']);
19167 } else {
19168 $gy1 = $y1 - $this->blk[$blvl]['padding_bottom'];
19170 $h = $gy1 - $gy;
19173 if (isset($this->blk[$blvl]['background-image']['size']['w']) && $this->blk[$blvl]['background-image']['size']['w']) {
19174 $size = $this->blk[$blvl]['background-image']['size'];
19175 if ($size['w'] != 'contain' && $size['w'] != 'cover') {
19176 if (stristr($size['w'], '%')) {
19177 $size['w'] = (float) $size['w'];
19178 $size['w'] /= 100;
19179 $w *= $size['w'];
19180 } elseif ($size['w'] != 'auto') {
19181 $w = $size['w'];
19183 if (stristr($size['h'], '%')) {
19184 $size['h'] = (float) $size['h'];
19185 $size['h'] /= 100;
19186 $h *= $size['h'];
19187 } elseif ($size['h'] != 'auto') {
19188 $h = $size['h'];
19192 $this->pageBackgrounds[$blvl][] = ['gradient' => true, 'x' => $gx, 'y' => $gy, 'w' => $w, 'h' => $h, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => $s, 'visibility' => $this->visibility, 'z-index' => $this->current_layer];
19194 } else {
19195 $image_id = $this->blk[$blvl]['background-image']['image_id'];
19196 $orig_w = $this->blk[$blvl]['background-image']['orig_w'];
19197 $orig_h = $this->blk[$blvl]['background-image']['orig_h'];
19198 $x_pos = $this->blk[$blvl]['background-image']['x_pos'];
19199 $y_pos = $this->blk[$blvl]['background-image']['y_pos'];
19200 $x_repeat = $this->blk[$blvl]['background-image']['x_repeat'];
19201 $y_repeat = $this->blk[$blvl]['background-image']['y_repeat'];
19202 $resize = $this->blk[$blvl]['background-image']['resize'];
19203 $opacity = $this->blk[$blvl]['background-image']['opacity'];
19204 $itype = $this->blk[$blvl]['background-image']['itype'];
19205 $size = $this->blk[$blvl]['background-image']['size'];
19206 // origin specifies the background-positioning-area (bpa)
19207 $bpa = ['x' => $x0, 'y' => $y0, 'w' => $w, 'h' => $h];
19208 if ($this->blk[$blvl]['background-image']['origin'] == 'padding-box') {
19209 $bpa['x'] = $x0 + $this->blk[$blvl]['border_left']['w'];
19210 $bpa['w'] = $w - ($this->blk[$blvl]['border_left']['w'] + $this->blk[$blvl]['border_right']['w']);
19211 if ($this->blk[$blvl]['border_top'] && $divider != 'pagetop' && !$continuingpage) {
19212 $bpa['y'] = $y0 + $this->blk[$blvl]['border_top']['w'];
19213 } else {
19214 $bpa['y'] = $y0;
19216 if ($this->blk[$blvl]['border_bottom'] && $blockstate != 1 && $divider != 'pagebottom') {
19217 $bpay = $y1 - $this->blk[$blvl]['border_bottom']['w'];
19218 } else {
19219 $bpay = $y1;
19221 $bpa['h'] = $bpay - $bpa['y'];
19222 } elseif ($this->blk[$blvl]['background-image']['origin'] == 'content-box') {
19223 $bpa['x'] = $x0 + $this->blk[$blvl]['border_left']['w'] + $this->blk[$blvl]['padding_left'];
19224 $bpa['w'] = $w - ($this->blk[$blvl]['border_left']['w'] + $this->blk[$blvl]['padding_left'] + $this->blk[$blvl]['border_right']['w'] + $this->blk[$blvl]['padding_right']);
19225 if ($this->blk[$blvl]['border_top'] && $divider != 'pagetop' && !$continuingpage) {
19226 $bpa['y'] = $y0 + $this->blk[$blvl]['border_top']['w'] + $this->blk[$blvl]['padding_top'];
19227 } else {
19228 $bpa['y'] = $y0 + $this->blk[$blvl]['padding_top'];
19230 if ($this->blk[$blvl]['border_bottom'] && $blockstate != 1 && $divider != 'pagebottom') {
19231 $bpay = $y1 - ($this->blk[$blvl]['border_bottom']['w'] + $this->blk[$blvl]['padding_bottom']);
19232 } else {
19233 $bpay = $y1 - $this->blk[$blvl]['padding_bottom'];
19235 $bpa['h'] = $bpay - $bpa['y'];
19237 $this->pageBackgrounds[$blvl][] = ['x' => $x0, 'y' => $y0, 'w' => $w, 'h' => $h, 'image_id' => $image_id, 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $x_pos, 'y_pos' => $y_pos, 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'clippath' => $s, 'resize' => $resize, 'opacity' => $opacity, 'itype' => $itype, 'visibility' => $this->visibility, 'z-index' => $this->current_layer, 'size' => $size, 'bpa' => $bpa];
19240 /* -- END BACKGROUNDS -- */
19242 // Float DIV
19243 $this->blk[$blvl]['bb_painted'][$this->page] = true;
19246 /* -- BORDER-RADIUS -- */
19248 function _EllipseArc($x0, $y0, $rx, $ry, $seg = 1, $part = false, $start = false)
19250 // Anticlockwise segment 1-4 TR-TL-BL-BR (part=1 or 2)
19251 $s = '';
19252 if ($rx < 0) {
19253 $rx = 0;
19255 if ($ry < 0) {
19256 $ry = 0;
19258 $rx *= Mpdf::SCALE;
19259 $ry *= Mpdf::SCALE;
19260 $astart = 0;
19261 if ($seg == 1) { // Top Right
19262 $afinish = 90;
19263 $nSeg = 4;
19264 } elseif ($seg == 2) { // Top Left
19265 $afinish = 180;
19266 $nSeg = 8;
19267 } elseif ($seg == 3) { // Bottom Left
19268 $afinish = 270;
19269 $nSeg = 12;
19270 } else { // Bottom Right
19271 $afinish = 360;
19272 $nSeg = 16;
19274 $astart = deg2rad((float) $astart);
19275 $afinish = deg2rad((float) $afinish);
19276 $totalAngle = $afinish - $astart;
19277 $dt = $totalAngle / $nSeg; // segment angle
19278 $dtm = $dt / 3;
19279 $x0 *= Mpdf::SCALE;
19280 $y0 = ($this->h - $y0) * Mpdf::SCALE;
19281 $t1 = $astart;
19282 $a0 = $x0 + ($rx * cos($t1));
19283 $b0 = $y0 + ($ry * sin($t1));
19284 $c0 = -$rx * sin($t1);
19285 $d0 = $ry * cos($t1);
19286 $op = false;
19287 for ($i = 1; $i <= $nSeg; $i++) {
19288 // Draw this bit of the total curve
19289 $t1 = ($i * $dt) + $astart;
19290 $a1 = $x0 + ($rx * cos($t1));
19291 $b1 = $y0 + ($ry * sin($t1));
19292 $c1 = -$rx * sin($t1);
19293 $d1 = $ry * cos($t1);
19294 if ($i > ($nSeg - 4) && (!$part || ($part == 1 && $i <= $nSeg - 2) || ($part == 2 && $i > $nSeg - 2))) {
19295 if ($start && !$op) {
19296 $s .= sprintf('%.3F %.3F m ', $a0, $b0);
19298 $s .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', ($a0 + ($c0 * $dtm)), ($b0 + ($d0 * $dtm)), ($a1 - ($c1 * $dtm)), ($b1 - ($d1 * $dtm)), $a1, $b1);
19299 $op = true;
19301 $a0 = $a1;
19302 $b0 = $b1;
19303 $c0 = $c1;
19304 $d0 = $d1;
19306 return $s;
19309 /* -- END BORDER-RADIUS -- */
19311 function PaintDivLnBorder($state = 0, $blvl = 0, $h = 0)
19313 // $state = 0 normal; 1 top; 2 bottom; 3 top and bottom
19314 $this->ColDetails[$this->CurrCol]['bottom_margin'] = $this->y + $h;
19316 $save_y = $this->y;
19318 $w = $this->blk[$blvl]['width'];
19319 $x0 = $this->x; // left
19320 $y0 = $this->y; // top
19321 $x1 = $this->x + $w; // bottom
19322 $y1 = $this->y + $h; // bottom
19324 if ($this->blk[$blvl]['border_top'] && ($state == 1 || $state == 3)) {
19325 $tbd = $this->blk[$blvl]['border_top'];
19326 if (isset($tbd['s']) && $tbd['s']) {
19327 $this->_setBorderLine($tbd);
19328 $this->y = $y0 + ($tbd['w'] / 2);
19329 if ($tbd['style'] == 'dotted' || $tbd['style'] == 'dashed') {
19330 $this->_setDashBorder($tbd['style'], '', $continuingpage, 'T');
19331 $this->Line($x0 + ($tbd['w'] / 2), $this->y, $x0 + $w - ($tbd['w'] / 2), $this->y);
19332 } else {
19333 $this->SetLineJoin(0);
19334 $this->SetLineCap(0);
19335 $this->Line($x0, $this->y, $x0 + $w, $this->y);
19337 $this->y += $tbd['w'];
19338 // Reset Corners and Dash off
19339 $this->SetLineJoin(2);
19340 $this->SetLineCap(2);
19341 $this->SetDash();
19344 if ($this->blk[$blvl]['border_left']) {
19345 $tbd = $this->blk[$blvl]['border_left'];
19346 if (isset($tbd['s']) && $tbd['s']) {
19347 $this->_setBorderLine($tbd);
19348 if ($tbd['style'] == 'dotted' || $tbd['style'] == 'dashed') {
19349 $this->y = $y0 + ($tbd['w'] / 2);
19350 $this->_setDashBorder($tbd['style'], '', $continuingpage, 'L');
19351 $this->Line($x0 + ($tbd['w'] / 2), $this->y, $x0 + ($tbd['w'] / 2), $y0 + $h - ($tbd['w'] / 2));
19352 } else {
19353 $this->y = $y0;
19354 $this->SetLineJoin(0);
19355 $this->SetLineCap(0);
19356 $this->Line($x0 + ($tbd['w'] / 2), $this->y, $x0 + ($tbd['w'] / 2), $y0 + $h);
19358 $this->y += $tbd['w'];
19359 // Reset Corners and Dash off
19360 $this->SetLineJoin(2);
19361 $this->SetLineCap(2);
19362 $this->SetDash();
19365 if ($this->blk[$blvl]['border_right']) {
19366 $tbd = $this->blk[$blvl]['border_right'];
19367 if (isset($tbd['s']) && $tbd['s']) {
19368 $this->_setBorderLine($tbd);
19369 if ($tbd['style'] == 'dotted' || $tbd['style'] == 'dashed') {
19370 $this->y = $y0 + ($tbd['w'] / 2);
19371 $this->_setDashBorder($tbd['style'], '', $continuingpage, 'R');
19372 $this->Line($x0 + $w - ($tbd['w'] / 2), $this->y, $x0 + $w - ($tbd['w'] / 2), $y0 + $h - ($tbd['w'] / 2));
19373 } else {
19374 $this->y = $y0;
19375 $this->SetLineJoin(0);
19376 $this->SetLineCap(0);
19377 $this->Line($x0 + $w - ($tbd['w'] / 2), $this->y, $x0 + $w - ($tbd['w'] / 2), $y0 + $h);
19379 $this->y += $tbd['w'];
19380 // Reset Corners and Dash off
19381 $this->SetLineJoin(2);
19382 $this->SetLineCap(2);
19383 $this->SetDash();
19386 if ($this->blk[$blvl]['border_bottom'] && $state > 1) {
19387 $tbd = $this->blk[$blvl]['border_bottom'];
19388 if (isset($tbd['s']) && $tbd['s']) {
19389 $this->_setBorderLine($tbd);
19390 $this->y = $y0 + $h - ($tbd['w'] / 2);
19391 if ($tbd['style'] == 'dotted' || $tbd['style'] == 'dashed') {
19392 $this->_setDashBorder($tbd['style'], '', $continuingpage, 'B');
19393 $this->Line($x0 + ($tbd['w'] / 2), $this->y, $x0 + $w - ($tbd['w'] / 2), $this->y);
19394 } else {
19395 $this->SetLineJoin(0);
19396 $this->SetLineCap(0);
19397 $this->Line($x0, $this->y, $x0 + $w, $this->y);
19399 $this->y += $tbd['w'];
19400 // Reset Corners and Dash off
19401 $this->SetLineJoin(2);
19402 $this->SetLineCap(2);
19403 $this->SetDash();
19406 $this->SetDash();
19407 $this->y = $save_y;
19410 function PaintImgBorder($objattr, $is_table)
19412 // Borders are disabled in columns - messes up the repositioning in printcolumnbuffer
19413 if ($this->ColActive) {
19414 return;
19415 } // *COLUMNS*
19416 if ($is_table) {
19417 $k = $this->shrin_k;
19418 } else {
19419 $k = 1;
19421 $h = (isset($objattr['BORDER-HEIGHT']) ? $objattr['BORDER-HEIGHT'] : 0);
19422 $w = (isset($objattr['BORDER-WIDTH']) ? $objattr['BORDER-WIDTH'] : 0);
19423 $x0 = (isset($objattr['BORDER-X']) ? $objattr['BORDER-X'] : 0);
19424 $y0 = (isset($objattr['BORDER-Y']) ? $objattr['BORDER-Y'] : 0);
19426 // BORDERS
19427 if ($objattr['border_top']) {
19428 $tbd = $objattr['border_top'];
19429 if (!empty($tbd['s'])) {
19430 $this->_setBorderLine($tbd, $k);
19431 if ($tbd['style'] == 'dotted' || $tbd['style'] == 'dashed') {
19432 $this->_setDashBorder($tbd['style'], '', '', 'T');
19434 $this->Line($x0, $y0, $x0 + $w, $y0);
19435 // Reset Corners and Dash off
19436 $this->SetLineJoin(2);
19437 $this->SetLineCap(2);
19438 $this->SetDash();
19441 if ($objattr['border_left']) {
19442 $tbd = $objattr['border_left'];
19443 if (!empty($tbd['s'])) {
19444 $this->_setBorderLine($tbd, $k);
19445 if ($tbd['style'] == 'dotted' || $tbd['style'] == 'dashed') {
19446 $this->_setDashBorder($tbd['style'], '', '', 'L');
19448 $this->Line($x0, $y0, $x0, $y0 + $h);
19449 // Reset Corners and Dash off
19450 $this->SetLineJoin(2);
19451 $this->SetLineCap(2);
19452 $this->SetDash();
19455 if ($objattr['border_right']) {
19456 $tbd = $objattr['border_right'];
19457 if (!empty($tbd['s'])) {
19458 $this->_setBorderLine($tbd, $k);
19459 if ($tbd['style'] == 'dotted' || $tbd['style'] == 'dashed') {
19460 $this->_setDashBorder($tbd['style'], '', '', 'R');
19462 $this->Line($x0 + $w, $y0, $x0 + $w, $y0 + $h);
19463 // Reset Corners and Dash off
19464 $this->SetLineJoin(2);
19465 $this->SetLineCap(2);
19466 $this->SetDash();
19469 if ($objattr['border_bottom']) {
19470 $tbd = $objattr['border_bottom'];
19471 if (!empty($tbd['s'])) {
19472 $this->_setBorderLine($tbd, $k);
19473 if ($tbd['style'] == 'dotted' || $tbd['style'] == 'dashed') {
19474 $this->_setDashBorder($tbd['style'], '', '', 'B');
19476 $this->Line($x0, $y0 + $h, $x0 + $w, $y0 + $h);
19477 // Reset Corners and Dash off
19478 $this->SetLineJoin(2);
19479 $this->SetLineCap(2);
19480 $this->SetDash();
19483 $this->SetDash();
19484 $this->SetAlpha(1);
19487 /* -- END HTML-CSS -- */
19489 function Reset()
19491 $this->SetTColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
19492 $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
19493 $this->SetFColor($this->colorConverter->convert(255, $this->PDFAXwarnings));
19494 $this->SetAlpha(1);
19495 $this->colorarray = '';
19497 $this->spanbgcolorarray = '';
19498 $this->spanbgcolor = false;
19499 $this->spanborder = false;
19500 $this->spanborddet = [];
19502 $this->ResetStyles();
19504 $this->HREF = '';
19505 $this->textparam = [];
19506 $this->SetTextOutline();
19508 $this->textvar = 0x00; // mPDF 5.7.1
19509 $this->OTLtags = [];
19510 $this->textshadow = '';
19512 $this->currentLang = $this->default_lang; // mPDF 6
19513 $this->RestrictUnicodeFonts($this->default_available_fonts); // mPDF 6
19514 $this->SetFont($this->default_font, '', 0, false);
19515 $this->SetFontSize($this->default_font_size, false);
19517 $this->currentfontfamily = '';
19518 $this->currentfontsize = '';
19519 $this->currentfontstyle = '';
19521 /* -- TABLES -- */
19522 if ($this->tableLevel && isset($this->table[1][1]['cellLineHeight'])) {
19523 $this->SetLineHeight('', $this->table[1][1]['cellLineHeight']); // *TABLES*
19524 } else { /* -- END TABLES -- */
19525 if (isset($this->blk[$this->blklvl]['line_height']) && $this->blk[$this->blklvl]['line_height']) {
19526 $this->SetLineHeight('', $this->blk[$this->blklvl]['line_height']); // sets default line height
19530 $this->lSpacingCSS = '';
19531 $this->wSpacingCSS = '';
19532 $this->fixedlSpacing = false;
19533 $this->minwSpacing = 0;
19534 $this->SetDash(); // restore to no dash
19535 $this->dash_on = false;
19536 $this->dotted_on = false;
19537 $this->divwidth = 0;
19538 $this->divheight = 0;
19539 $this->cellTextAlign = '';
19540 $this->cellLineHeight = '';
19541 $this->cellLineStackingStrategy = '';
19542 $this->cellLineStackingShift = '';
19543 $this->oldy = -1;
19545 $bodystyle = [];
19546 if (isset($this->cssManager->CSS['BODY']['FONT-STYLE'])) {
19547 $bodystyle['FONT-STYLE'] = $this->cssManager->CSS['BODY']['FONT-STYLE'];
19549 if (isset($this->cssManager->CSS['BODY']['FONT-WEIGHT'])) {
19550 $bodystyle['FONT-WEIGHT'] = $this->cssManager->CSS['BODY']['FONT-WEIGHT'];
19552 if (isset($this->cssManager->CSS['BODY']['COLOR'])) {
19553 $bodystyle['COLOR'] = $this->cssManager->CSS['BODY']['COLOR'];
19555 if (isset($bodystyle)) {
19556 $this->setCSS($bodystyle, 'BLOCK', 'BODY');
19560 /* -- HTML-CSS -- */
19562 function ReadMetaTags($html)
19564 // changes anykey=anyvalue to anykey="anyvalue" (only do this when this happens inside tags)
19565 $regexp = '/ (\\w+?)=([^\\s>"]+)/si';
19566 $html = preg_replace($regexp, " \$1=\"\$2\"", $html);
19567 if (preg_match('/<title>(.*?)<\/title>/si', $html, $m)) {
19568 $this->SetTitle($m[1]);
19570 preg_match_all('/<meta [^>]*?(name|content)="([^>]*?)" [^>]*?(name|content)="([^>]*?)".*?>/si', $html, $aux);
19571 $firstattr = $aux[1];
19572 $secondattr = $aux[3];
19573 for ($i = 0; $i < count($aux[0]); $i++) {
19574 $name = ( strtoupper($firstattr[$i]) == "NAME" ) ? strtoupper($aux[2][$i]) : strtoupper($aux[4][$i]);
19575 $content = ( strtoupper($firstattr[$i]) == "CONTENT" ) ? $aux[2][$i] : $aux[4][$i];
19576 switch ($name) {
19577 case "KEYWORDS":
19578 $this->SetKeywords($content);
19579 break;
19580 case "AUTHOR":
19581 $this->SetAuthor($content);
19582 break;
19583 case "DESCRIPTION":
19584 $this->SetSubject($content);
19585 break;
19590 function ReadCharset($html)
19592 // Charset conversion
19593 if ($this->allow_charset_conversion) {
19594 if (preg_match('/<head.*charset=([^\'\"\s]*).*<\/head>/si', $html, $m)) {
19595 if (strtoupper($m[1]) != 'UTF-8') {
19596 $this->charset_in = strtoupper($m[1]);
19602 function setCSS($arrayaux, $type = '', $tag = '')
19604 // type= INLINE | BLOCK | TABLECELL // tag= BODY
19605 if (!is_array($arrayaux)) {
19606 return; // Removes PHP Warning
19609 // mPDF 5.7.3 inline text-decoration parameters
19610 $preceeding_fontkey = $this->FontFamily . $this->FontStyle;
19611 $preceeding_fontsize = $this->FontSize;
19612 $spanbordset = false;
19613 $spanbgset = false;
19614 // mPDF 6
19615 $prevlevel = (($this->blklvl == 0) ? 0 : $this->blklvl - 1);
19617 // Set font size first so that e.g. MARGIN 0.83em works on font size for this element
19618 if (isset($arrayaux['FONT-SIZE'])) {
19619 $v = $arrayaux['FONT-SIZE'];
19620 if (is_numeric($v[0])) {
19621 if ($type == 'BLOCK' && $this->blklvl > 0 && isset($this->blk[$this->blklvl - 1]['InlineProperties']) && isset($this->blk[$this->blklvl - 1]['InlineProperties']['size'])) {
19622 $mmsize = $this->sizeConverter->convert($v, $this->blk[$this->blklvl - 1]['InlineProperties']['size']);
19623 } elseif ($type == 'TABLECELL') {
19624 $mmsize = $this->sizeConverter->convert($v, $this->default_font_size / Mpdf::SCALE);
19625 } else {
19626 $mmsize = $this->sizeConverter->convert($v, $this->FontSize);
19628 $this->SetFontSize($mmsize * (Mpdf::SCALE), false); // Get size in points (pt)
19629 } else {
19630 $v = strtoupper($v);
19631 if (isset($this->fontsizes[$v])) {
19632 $this->SetFontSize($this->fontsizes[$v] * $this->default_font_size, false);
19635 if ($tag == 'BODY') {
19636 $this->SetDefaultFontSize($this->FontSizePt);
19640 // mPDF 6
19641 if (isset($arrayaux['LANG']) && $arrayaux['LANG']) {
19642 if ($this->autoLangToFont && !$this->usingCoreFont) {
19643 if ($arrayaux['LANG'] != $this->default_lang && $arrayaux['LANG'] != 'UTF-8') {
19644 list ($coreSuitable, $mpdf_pdf_unifont) = $this->languageToFont->getLanguageOptions($arrayaux['LANG'], $this->useAdobeCJK);
19645 if ($mpdf_pdf_unifont) {
19646 $arrayaux['FONT-FAMILY'] = $mpdf_pdf_unifont;
19648 if ($tag == 'BODY') {
19649 $this->default_lang = $arrayaux['LANG'];
19653 $this->currentLang = $arrayaux['LANG'];
19656 // FOR INLINE and BLOCK OR 'BODY'
19657 if (isset($arrayaux['FONT-FAMILY'])) {
19658 $v = $arrayaux['FONT-FAMILY'];
19659 // If it is a font list, get all font types
19660 $aux_fontlist = explode(",", $v);
19661 $found = 0;
19662 foreach ($aux_fontlist as $f) {
19663 $fonttype = trim($f);
19664 $fonttype = preg_replace('/["\']*(.*?)["\']*/', '\\1', $fonttype);
19665 $fonttype = preg_replace('/ /', '', $fonttype);
19666 $v = strtolower(trim($fonttype));
19667 if (isset($this->fonttrans[$v]) && $this->fonttrans[$v]) {
19668 $v = $this->fonttrans[$v];
19670 if ((!$this->onlyCoreFonts && in_array($v, $this->available_unifonts)) ||
19671 in_array($v, ['ccourier', 'ctimes', 'chelvetica']) ||
19672 ($this->onlyCoreFonts && in_array($v, ['courier', 'times', 'helvetica', 'arial'])) ||
19673 in_array($v, ['sjis', 'uhc', 'big5', 'gb'])) {
19674 $fonttype = $v;
19675 $found = 1;
19676 break;
19679 if (!$found) {
19680 foreach ($aux_fontlist as $f) {
19681 $fonttype = trim($f);
19682 $fonttype = preg_replace('/["\']*(.*?)["\']*/', '\\1', $fonttype);
19683 $fonttype = preg_replace('/ /', '', $fonttype);
19684 $v = strtolower(trim($fonttype));
19685 if (isset($this->fonttrans[$v]) && $this->fonttrans[$v]) {
19686 $v = $this->fonttrans[$v];
19688 if (in_array($v, $this->sans_fonts) || in_array($v, $this->serif_fonts) || in_array($v, $this->mono_fonts)) {
19689 $fonttype = $v;
19690 break;
19695 if ($tag == 'BODY') {
19696 $this->SetDefaultFont($fonttype);
19698 $this->SetFont($fonttype, $this->currentfontstyle, 0, false);
19699 } else {
19700 $this->SetFont($this->currentfontfamily, $this->currentfontstyle, 0, false);
19703 foreach ($arrayaux as $k => $v) {
19704 if ($type != 'INLINE' && $tag != 'BODY' && $type != 'TABLECELL') {
19705 switch ($k) {
19706 // BORDERS
19707 case 'BORDER-TOP':
19708 $this->blk[$this->blklvl]['border_top'] = $this->border_details($v);
19709 if ($this->blk[$this->blklvl]['border_top']['s']) {
19710 $this->blk[$this->blklvl]['border'] = 1;
19712 break;
19713 case 'BORDER-BOTTOM':
19714 $this->blk[$this->blklvl]['border_bottom'] = $this->border_details($v);
19715 if ($this->blk[$this->blklvl]['border_bottom']['s']) {
19716 $this->blk[$this->blklvl]['border'] = 1;
19718 break;
19719 case 'BORDER-LEFT':
19720 $this->blk[$this->blklvl]['border_left'] = $this->border_details($v);
19721 if ($this->blk[$this->blklvl]['border_left']['s']) {
19722 $this->blk[$this->blklvl]['border'] = 1;
19724 break;
19725 case 'BORDER-RIGHT':
19726 $this->blk[$this->blklvl]['border_right'] = $this->border_details($v);
19727 if ($this->blk[$this->blklvl]['border_right']['s']) {
19728 $this->blk[$this->blklvl]['border'] = 1;
19730 break;
19732 // PADDING
19733 case 'PADDING-TOP':
19734 $this->blk[$this->blklvl]['padding_top'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false);
19735 break;
19736 case 'PADDING-BOTTOM':
19737 $this->blk[$this->blklvl]['padding_bottom'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false);
19738 break;
19739 case 'PADDING-LEFT':
19740 if (($tag == 'UL' || $tag == 'OL') && $v == 'auto') {
19741 $this->blk[$this->blklvl]['padding_left'] = 'auto';
19742 break;
19744 $this->blk[$this->blklvl]['padding_left'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false);
19745 break;
19746 case 'PADDING-RIGHT':
19747 if (($tag == 'UL' || $tag == 'OL') && $v == 'auto') {
19748 $this->blk[$this->blklvl]['padding_right'] = 'auto';
19749 break;
19751 $this->blk[$this->blklvl]['padding_right'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false);
19752 break;
19754 // MARGINS
19755 case 'MARGIN-TOP':
19756 $tmp = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false);
19757 if (isset($this->blk[$this->blklvl]['lastbottommargin'])) {
19758 if ($tmp > $this->blk[$this->blklvl]['lastbottommargin']) {
19759 $tmp -= $this->blk[$this->blklvl]['lastbottommargin'];
19760 } else {
19761 $tmp = 0;
19764 $this->blk[$this->blklvl]['margin_top'] = $tmp;
19765 break;
19766 case 'MARGIN-BOTTOM':
19767 $this->blk[$this->blklvl]['margin_bottom'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false);
19768 break;
19769 case 'MARGIN-LEFT':
19770 $this->blk[$this->blklvl]['margin_left'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false);
19771 break;
19772 case 'MARGIN-RIGHT':
19773 $this->blk[$this->blklvl]['margin_right'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false);
19774 break;
19776 /* -- BORDER-RADIUS -- */
19777 case 'BORDER-TOP-LEFT-RADIUS-H':
19778 $this->blk[$this->blklvl]['border_radius_TL_H'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false);
19779 break;
19780 case 'BORDER-TOP-LEFT-RADIUS-V':
19781 $this->blk[$this->blklvl]['border_radius_TL_V'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false);
19782 break;
19783 case 'BORDER-TOP-RIGHT-RADIUS-H':
19784 $this->blk[$this->blklvl]['border_radius_TR_H'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false);
19785 break;
19786 case 'BORDER-TOP-RIGHT-RADIUS-V':
19787 $this->blk[$this->blklvl]['border_radius_TR_V'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false);
19788 break;
19789 case 'BORDER-BOTTOM-LEFT-RADIUS-H':
19790 $this->blk[$this->blklvl]['border_radius_BL_H'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false);
19791 break;
19792 case 'BORDER-BOTTOM-LEFT-RADIUS-V':
19793 $this->blk[$this->blklvl]['border_radius_BL_V'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false);
19794 break;
19795 case 'BORDER-BOTTOM-RIGHT-RADIUS-H':
19796 $this->blk[$this->blklvl]['border_radius_BR_H'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false);
19797 break;
19798 case 'BORDER-BOTTOM-RIGHT-RADIUS-V':
19799 $this->blk[$this->blklvl]['border_radius_BR_V'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false);
19800 break;
19801 /* -- END BORDER-RADIUS -- */
19803 case 'BOX-SHADOW':
19804 $bs = $this->cssManager->setCSSboxshadow($v);
19805 if ($bs) {
19806 $this->blk[$this->blklvl]['box_shadow'] = $bs;
19808 break;
19810 case 'BACKGROUND-CLIP':
19811 if (strtoupper($v) == 'PADDING-BOX') {
19812 $this->blk[$this->blklvl]['background_clip'] = 'padding-box';
19813 } elseif (strtoupper($v) == 'CONTENT-BOX') {
19814 $this->blk[$this->blklvl]['background_clip'] = 'content-box';
19816 break;
19818 case 'PAGE-BREAK-AFTER':
19819 if (strtoupper($v) == 'AVOID') {
19820 $this->blk[$this->blklvl]['page_break_after_avoid'] = true;
19821 } elseif (strtoupper($v) == 'ALWAYS' || strtoupper($v) == 'LEFT' || strtoupper($v) == 'RIGHT') {
19822 $this->blk[$this->blklvl]['page_break_after'] = strtoupper($v);
19824 break;
19826 // mPDF 6 pagebreaktype
19827 case 'BOX-DECORATION-BREAK':
19828 if (strtoupper($v) == 'CLONE') {
19829 $this->blk[$this->blklvl]['box_decoration_break'] = 'clone';
19830 } elseif (strtoupper($v) == 'SLICE') {
19831 $this->blk[$this->blklvl]['box_decoration_break'] = 'slice';
19833 break;
19835 case 'WIDTH':
19836 if (strtoupper($v) != 'AUTO') {
19837 $this->blk[$this->blklvl]['css_set_width'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false);
19839 break;
19841 // mPDF 6 Lists
19842 // LISTS
19843 case 'LIST-STYLE-TYPE':
19844 $this->blk[$this->blklvl]['list_style_type'] = strtolower($v);
19845 break;
19846 case 'LIST-STYLE-IMAGE':
19847 $this->blk[$this->blklvl]['list_style_image'] = strtolower($v);
19848 break;
19849 case 'LIST-STYLE-POSITION':
19850 $this->blk[$this->blklvl]['list_style_position'] = strtolower($v);
19851 break;
19852 }//end of switch($k)
19856 if ($type != 'INLINE' && $type != 'TABLECELL') { // All block-level, including BODY tag
19857 switch ($k) {
19858 case 'TEXT-INDENT':
19859 // Computed value - to inherit
19860 $this->blk[$this->blklvl]['text_indent'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false) . 'mm';
19861 break;
19863 case 'MARGIN-COLLAPSE': // Custom tag to collapse margins at top and bottom of page
19864 if (strtoupper($v) == 'COLLAPSE') {
19865 $this->blk[$this->blklvl]['margin_collapse'] = true;
19867 break;
19869 case 'LINE-HEIGHT':
19870 $this->blk[$this->blklvl]['line_height'] = $this->fixLineheight($v);
19871 if (!$this->blk[$this->blklvl]['line_height']) {
19872 $this->blk[$this->blklvl]['line_height'] = 'N';
19873 } // mPDF 6
19874 break;
19876 // mPDF 6
19877 case 'LINE-STACKING-STRATEGY':
19878 $this->blk[$this->blklvl]['line_stacking_strategy'] = strtolower($v);
19879 break;
19881 case 'LINE-STACKING-SHIFT':
19882 $this->blk[$this->blklvl]['line_stacking_shift'] = strtolower($v);
19883 break;
19885 case 'TEXT-ALIGN': // left right center justify
19886 switch (strtoupper($v)) {
19887 case 'LEFT':
19888 $this->blk[$this->blklvl]['align'] = "L";
19889 break;
19890 case 'CENTER':
19891 $this->blk[$this->blklvl]['align'] = "C";
19892 break;
19893 case 'RIGHT':
19894 $this->blk[$this->blklvl]['align'] = "R";
19895 break;
19896 case 'JUSTIFY':
19897 $this->blk[$this->blklvl]['align'] = "J";
19898 break;
19900 break;
19902 /* -- BACKGROUNDS -- */
19903 case 'BACKGROUND-GRADIENT':
19904 if ($type == 'BLOCK') {
19905 $this->blk[$this->blklvl]['gradient'] = $v;
19907 break;
19908 /* -- END BACKGROUNDS -- */
19910 case 'DIRECTION':
19911 if ($v) {
19912 $this->blk[$this->blklvl]['direction'] = strtolower($v);
19914 break;
19918 // FOR INLINE ONLY
19919 if ($type == 'INLINE') {
19920 switch ($k) {
19921 case 'DISPLAY':
19922 if (strtoupper($v) == 'NONE') {
19923 $this->inlineDisplayOff = true;
19925 break;
19926 case 'DIRECTION':
19927 break;
19930 // FOR INLINE ONLY
19931 if ($type == 'INLINE') {
19932 switch ($k) {
19933 // BORDERS
19934 case 'BORDER-TOP':
19935 $this->spanborddet['T'] = $this->border_details($v);
19936 $this->spanborder = true;
19937 $spanbordset = true;
19938 break;
19939 case 'BORDER-BOTTOM':
19940 $this->spanborddet['B'] = $this->border_details($v);
19941 $this->spanborder = true;
19942 $spanbordset = true;
19943 break;
19944 case 'BORDER-LEFT':
19945 $this->spanborddet['L'] = $this->border_details($v);
19946 $this->spanborder = true;
19947 $spanbordset = true;
19948 break;
19949 case 'BORDER-RIGHT':
19950 $this->spanborddet['R'] = $this->border_details($v);
19951 $this->spanborder = true;
19952 $spanbordset = true;
19953 break;
19954 case 'VISIBILITY': // block is set in OpenTag
19955 $v = strtolower($v);
19956 if ($v == 'visible' || $v == 'hidden' || $v == 'printonly' || $v == 'screenonly') {
19957 $this->textparam['visibility'] = $v;
19959 break;
19960 }//end of switch($k)
19963 if ($type != 'TABLECELL') {
19964 // FOR INLINE and BLOCK
19965 switch ($k) {
19966 case 'TEXT-ALIGN': // left right center justify
19967 if (strtoupper($v) == 'NOJUSTIFY' && $this->blk[$this->blklvl]['align'] == "J") {
19968 $this->blk[$this->blklvl]['align'] = "";
19970 break;
19971 // bgcolor only - to stay consistent with original html2fpdf
19972 case 'BACKGROUND':
19973 case 'BACKGROUND-COLOR':
19974 $cor = $this->colorConverter->convert($v, $this->PDFAXwarnings);
19975 if ($cor) {
19976 if ($tag == 'BODY') {
19977 $this->bodyBackgroundColor = $cor;
19978 } elseif ($type == 'INLINE') {
19979 $this->spanbgcolorarray = $cor;
19980 $this->spanbgcolor = true;
19981 $spanbgset = true;
19982 } else {
19983 $this->blk[$this->blklvl]['bgcolorarray'] = $cor;
19984 $this->blk[$this->blklvl]['bgcolor'] = true;
19986 } elseif ($type != 'INLINE') {
19987 if ($this->ColActive) {
19988 $this->blk[$this->blklvl]['bgcolorarray'] = $this->blk[$prevlevel]['bgcolorarray'];
19989 $this->blk[$this->blklvl]['bgcolor'] = $this->blk[$prevlevel]['bgcolor'];
19992 break;
19994 case 'VERTICAL-ALIGN': // super and sub only dealt with here e.g. <SUB> and <SUP>
19995 switch (strtoupper($v)) {
19996 case 'SUPER':
19997 $this->textvar = ($this->textvar | TextVars::FA_SUPERSCRIPT); // mPDF 5.7.1
19998 $this->textvar = ($this->textvar & ~TextVars::FA_SUBSCRIPT);
19999 // mPDF 5.7.3 inline text-decoration parameters
20000 if (isset($this->textparam['text-baseline'])) {
20001 $this->textparam['text-baseline'] += ($this->baselineSup) * $preceeding_fontsize;
20002 } else {
20003 $this->textparam['text-baseline'] = ($this->baselineSup) * $preceeding_fontsize;
20005 break;
20006 case 'SUB':
20007 $this->textvar = ($this->textvar | TextVars::FA_SUBSCRIPT);
20008 $this->textvar = ($this->textvar & ~TextVars::FA_SUPERSCRIPT);
20009 // mPDF 5.7.3 inline text-decoration parameters
20010 if (isset($this->textparam['text-baseline'])) {
20011 $this->textparam['text-baseline'] += ($this->baselineSub) * $preceeding_fontsize;
20012 } else {
20013 $this->textparam['text-baseline'] = ($this->baselineSub) * $preceeding_fontsize;
20015 break;
20016 case 'BASELINE':
20017 $this->textvar = ($this->textvar & ~TextVars::FA_SUBSCRIPT);
20018 $this->textvar = ($this->textvar & ~TextVars::FA_SUPERSCRIPT);
20019 // mPDF 5.7.3 inline text-decoration parameters
20020 if (isset($this->textparam['text-baseline'])) {
20021 unset($this->textparam['text-baseline']);
20023 break;
20024 // mPDF 5.7.3 inline text-decoration parameters
20025 default:
20026 $lh = $this->_computeLineheight($this->blk[$this->blklvl]['line_height']);
20027 $sz = $this->sizeConverter->convert($v, $lh, $this->FontSize, false);
20028 $this->textvar = ($this->textvar & ~TextVars::FA_SUBSCRIPT);
20029 $this->textvar = ($this->textvar & ~TextVars::FA_SUPERSCRIPT);
20030 if ($sz) {
20031 if ($sz > 0) {
20032 $this->textvar = ($this->textvar | TextVars::FA_SUPERSCRIPT);
20033 } else {
20034 $this->textvar = ($this->textvar | TextVars::FA_SUBSCRIPT);
20036 if (isset($this->textparam['text-baseline'])) {
20037 $this->textparam['text-baseline'] += $sz;
20038 } else {
20039 $this->textparam['text-baseline'] = $sz;
20043 break;
20044 }//end of switch($k)
20048 // FOR ALL
20049 switch ($k) {
20050 case 'LETTER-SPACING':
20051 $this->lSpacingCSS = $v;
20052 if (($this->lSpacingCSS || $this->lSpacingCSS === '0') && strtoupper($this->lSpacingCSS) != 'NORMAL') {
20053 $this->fixedlSpacing = $this->sizeConverter->convert($this->lSpacingCSS, $this->FontSize);
20055 break;
20057 case 'WORD-SPACING':
20058 $this->wSpacingCSS = $v;
20059 if ($this->wSpacingCSS && strtoupper($this->wSpacingCSS) != 'NORMAL') {
20060 $this->minwSpacing = $this->sizeConverter->convert($this->wSpacingCSS, $this->FontSize);
20062 break;
20064 case 'FONT-STYLE': // italic normal oblique
20065 switch (strtoupper($v)) {
20066 case 'ITALIC':
20067 case 'OBLIQUE':
20068 $this->SetStyle('I', true);
20069 break;
20070 case 'NORMAL':
20071 $this->SetStyle('I', false);
20072 break;
20074 break;
20076 case 'FONT-WEIGHT': // normal bold // Does not support: bolder, lighter, 100..900(step value=100)
20077 switch (strtoupper($v)) {
20078 case 'BOLD':
20079 $this->SetStyle('B', true);
20080 break;
20081 case 'NORMAL':
20082 $this->SetStyle('B', false);
20083 break;
20085 break;
20087 case 'FONT-KERNING':
20088 if (strtoupper($v) == 'NORMAL' || (strtoupper($v) == 'AUTO' && $this->useKerning)) {
20089 /* -- OTL -- */
20090 if ($this->CurrentFont['haskernGPOS']) {
20091 if (isset($this->OTLtags['Plus'])) {
20092 $this->OTLtags['Plus'] .= ' kern';
20093 } else {
20094 $this->OTLtags['Plus'] = ' kern';
20096 } /* -- END OTL -- */ else { // *OTL*
20097 $this->textvar = ($this->textvar | TextVars::FC_KERNING);
20098 } // *OTL*
20099 } elseif (strtoupper($v) == 'NONE' || (strtoupper($v) == 'AUTO' && !$this->useKerning)) {
20100 if (isset($this->OTLtags['Plus'])) {
20101 $this->OTLtags['Plus'] = str_replace('kern', '', $this->OTLtags['Plus']); // *OTL*
20103 if (isset($this->OTLtags['FFPlus'])) {
20104 $this->OTLtags['FFPlus'] = preg_replace('/kern[\d]*/', '', $this->OTLtags['FFPlus']);
20106 $this->textvar = ($this->textvar & ~TextVars::FC_KERNING);
20108 break;
20110 /* -- OTL -- */
20111 case 'FONT-LANGUAGE-OVERRIDE':
20112 $v = strtoupper($v);
20113 if (strpos($v, 'NORMAL') !== false) {
20114 $this->fontLanguageOverride = '';
20115 } else {
20116 $this->fontLanguageOverride = trim($v);
20118 break;
20121 case 'FONT-VARIANT-POSITION':
20122 if (isset($this->OTLtags['Plus'])) {
20123 $this->OTLtags['Plus'] = str_replace(['sups', 'subs'], '', $this->OTLtags['Plus']);
20125 switch (strtoupper($v)) {
20126 case 'SUPER':
20127 $this->OTLtags['Plus'] .= ' sups';
20128 break;
20129 case 'SUB':
20130 $this->OTLtags['Plus'] .= ' subs';
20131 break;
20132 case 'NORMAL':
20133 break;
20135 break;
20137 case 'FONT-VARIANT-CAPS':
20138 $v = strtoupper($v);
20139 if (!isset($this->OTLtags['Plus'])) {
20140 $this->OTLtags['Plus'] = '';
20142 $this->OTLtags['Plus'] = str_replace(['c2sc', 'smcp', 'c2pc', 'pcap', 'unic', 'titl'], '', $this->OTLtags['Plus']);
20143 $this->textvar = ($this->textvar & ~TextVars::FC_SMALLCAPS); // ?????????????? <small-caps>
20144 if (strpos($v, 'ALL-SMALL-CAPS') !== false) {
20145 $this->OTLtags['Plus'] .= ' c2sc smcp';
20146 } elseif (strpos($v, 'SMALL-CAPS') !== false) {
20147 if (isset($this->CurrentFont['hassmallcapsGSUB']) && $this->CurrentFont['hassmallcapsGSUB']) {
20148 $this->OTLtags['Plus'] .= ' smcp';
20149 } else {
20150 $this->textvar = ($this->textvar | TextVars::FC_SMALLCAPS);
20152 } elseif (strpos($v, 'ALL-PETITE-CAPS') !== false) {
20153 $this->OTLtags['Plus'] .= ' c2pc pcap';
20154 } elseif (strpos($v, 'PETITE-CAPS') !== false) {
20155 $this->OTLtags['Plus'] .= ' pcap';
20156 } elseif (strpos($v, 'UNICASE') !== false) {
20157 $this->OTLtags['Plus'] .= ' unic';
20158 } elseif (strpos($v, 'TITLING-CAPS') !== false) {
20159 $this->OTLtags['Plus'] .= ' titl';
20161 break;
20163 case 'FONT-VARIANT-LIGATURES':
20164 $v = strtoupper($v);
20165 if (!isset($this->OTLtags['Plus'])) {
20166 $this->OTLtags['Plus'] = '';
20168 if (!isset($this->OTLtags['Minus'])) {
20169 $this->OTLtags['Minus'] = '';
20171 if (strpos($v, 'NORMAL') !== false) {
20172 $this->OTLtags['Minus'] = str_replace(['liga', 'clig', 'calt'], '', $this->OTLtags['Minus']);
20173 $this->OTLtags['Plus'] = str_replace(['dlig', 'hlig'], '', $this->OTLtags['Plus']);
20174 } elseif (strpos($v, 'NONE') !== false) {
20175 $this->OTLtags['Minus'] .= ' liga clig calt';
20176 $this->OTLtags['Plus'] = str_replace(['dlig', 'hlig'], '', $this->OTLtags['Plus']);
20178 if (strpos($v, 'NO-COMMON-LIGATURES') !== false) {
20179 $this->OTLtags['Minus'] .= ' liga clig';
20180 } elseif (strpos($v, 'COMMON-LIGATURES') !== false) {
20181 $this->OTLtags['Minus'] = str_replace(['liga', 'clig'], '', $this->OTLtags['Minus']);
20183 if (strpos($v, 'NO-CONTEXTUAL') !== false) {
20184 $this->OTLtags['Minus'] .= ' calt';
20185 } elseif (strpos($v, 'CONTEXTUAL') !== false) {
20186 $this->OTLtags['Minus'] = str_replace('calt', '', $this->OTLtags['Minus']);
20188 if (strpos($v, 'NO-DISCRETIONARY-LIGATURES') !== false) {
20189 $this->OTLtags['Plus'] = str_replace('dlig', '', $this->OTLtags['Plus']);
20190 } elseif (strpos($v, 'DISCRETIONARY-LIGATURES') !== false) {
20191 $this->OTLtags['Plus'] .= ' dlig';
20193 if (strpos($v, 'NO-HISTORICAL-LIGATURES') !== false) {
20194 $this->OTLtags['Plus'] = str_replace('hlig', '', $this->OTLtags['Plus']);
20195 } elseif (strpos($v, 'HISTORICAL-LIGATURES') !== false) {
20196 $this->OTLtags['Plus'] .= ' hlig';
20199 break;
20201 case 'FONT-VARIANT-NUMERIC':
20202 $v = strtoupper($v);
20203 if (!isset($this->OTLtags['Plus'])) {
20204 $this->OTLtags['Plus'] = '';
20206 if (strpos($v, 'NORMAL') !== false) {
20207 $this->OTLtags['Plus'] = str_replace(['ordn', 'zero', 'lnum', 'onum', 'pnum', 'tnum', 'frac', 'afrc'], '', $this->OTLtags['Plus']);
20209 if (strpos($v, 'ORDINAL') !== false) {
20210 $this->OTLtags['Plus'] .= ' ordn';
20212 if (strpos($v, 'SLASHED-ZERO') !== false) {
20213 $this->OTLtags['Plus'] .= ' zero';
20215 if (strpos($v, 'LINING-NUMS') !== false) {
20216 $this->OTLtags['Plus'] .= ' lnum';
20217 $this->OTLtags['Plus'] = str_replace('onum', '', $this->OTLtags['Plus']);
20218 } elseif (strpos($v, 'OLDSTYLE-NUMS') !== false) {
20219 $this->OTLtags['Plus'] .= ' onum';
20220 $this->OTLtags['Plus'] = str_replace('lnum', '', $this->OTLtags['Plus']);
20222 if (strpos($v, 'PROPORTIONAL-NUMS') !== false) {
20223 $this->OTLtags['Plus'] .= ' pnum';
20224 $this->OTLtags['Plus'] = str_replace('tnum', '', $this->OTLtags['Plus']);
20225 } elseif (strpos($v, 'TABULAR-NUMS') !== false) {
20226 $this->OTLtags['Plus'] .= ' tnum';
20227 $this->OTLtags['Plus'] = str_replace('pnum', '', $this->OTLtags['Plus']);
20229 if (strpos($v, 'DIAGONAL-FRACTIONS') !== false) {
20230 $this->OTLtags['Plus'] .= ' frac';
20231 $this->OTLtags['Plus'] = str_replace('afrc', '', $this->OTLtags['Plus']);
20232 } elseif (strpos($v, 'STACKED-FRACTIONS') !== false) {
20233 $this->OTLtags['Plus'] .= ' afrc';
20234 $this->OTLtags['Plus'] = str_replace('frac', '', $this->OTLtags['Plus']);
20236 break;
20238 case 'FONT-VARIANT-ALTERNATES': // Only supports historical-forms
20239 $v = strtoupper($v);
20240 if (!isset($this->OTLtags['Plus'])) {
20241 $this->OTLtags['Plus'] = '';
20243 if (strpos($v, 'NORMAL') !== false) {
20244 $this->OTLtags['Plus'] = str_replace('hist', '', $this->OTLtags['Plus']);
20246 if (strpos($v, 'HISTORICAL-FORMS') !== false) {
20247 $this->OTLtags['Plus'] .= ' hist';
20249 break;
20252 case 'FONT-FEATURE-SETTINGS':
20253 $v = strtolower($v);
20254 if (strpos($v, 'normal') !== false) {
20255 $this->OTLtags['FFMinus'] = '';
20256 $this->OTLtags['FFPlus'] = '';
20257 } else {
20258 if (!isset($this->OTLtags['FFPlus'])) {
20259 $this->OTLtags['FFPlus'] = '';
20261 if (!isset($this->OTLtags['FFMinus'])) {
20262 $this->OTLtags['FFMinus'] = '';
20264 $tags = preg_split('/[,]/', $v);
20265 foreach ($tags as $t) {
20266 if (preg_match('/[\"\']([a-zA-Z0-9]{4})[\"\']\s*(on|off|\d*){0,1}/', $t, $m)) {
20267 if ($m[2] == 'off' || $m[2] === '0') {
20268 if (strpos($this->OTLtags['FFMinus'], $m[1]) === false) {
20269 $this->OTLtags['FFMinus'] .= ' ' . $m[1];
20271 $this->OTLtags['FFPlus'] = preg_replace('/' . $m[1] . '[\d]*/', '', $this->OTLtags['FFPlus']);
20272 } else {
20273 if ($m[2] == 'on') {
20274 $m[2] = '1';
20276 if (strpos($this->OTLtags['FFPlus'], $m[1]) === false) {
20277 $this->OTLtags['FFPlus'] .= ' ' . $m[1] . $m[2];
20279 $this->OTLtags['FFMinus'] = str_replace($m[1], '', $this->OTLtags['FFMinus']);
20284 break;
20285 /* -- END OTL -- */
20288 case 'TEXT-TRANSFORM': // none uppercase lowercase // Does support: capitalize
20289 switch (strtoupper($v)) { // Not working 100%
20290 case 'CAPITALIZE':
20291 $this->textvar = ($this->textvar | TextVars::FT_CAPITALIZE); // mPDF 5.7.1
20292 $this->textvar = ($this->textvar & ~TextVars::FT_UPPERCASE); // mPDF 5.7.1
20293 $this->textvar = ($this->textvar & ~TextVars::FT_LOWERCASE); // mPDF 5.7.1
20294 break;
20295 case 'UPPERCASE':
20296 $this->textvar = ($this->textvar | TextVars::FT_UPPERCASE); // mPDF 5.7.1
20297 $this->textvar = ($this->textvar & ~TextVars::FT_LOWERCASE); // mPDF 5.7.1
20298 $this->textvar = ($this->textvar & ~TextVars::FT_CAPITALIZE); // mPDF 5.7.1
20299 break;
20300 case 'LOWERCASE':
20301 $this->textvar = ($this->textvar | TextVars::FT_LOWERCASE); // mPDF 5.7.1
20302 $this->textvar = ($this->textvar & ~TextVars::FT_UPPERCASE); // mPDF 5.7.1
20303 $this->textvar = ($this->textvar & ~TextVars::FT_CAPITALIZE); // mPDF 5.7.1
20304 break;
20305 case 'NONE':
20306 break;
20307 $this->textvar = ($this->textvar & ~TextVars::FT_UPPERCASE); // mPDF 5.7.1
20308 $this->textvar = ($this->textvar & ~TextVars::FT_LOWERCASE); // mPDF 5.7.1
20309 $this->textvar = ($this->textvar & ~TextVars::FT_CAPITALIZE); // mPDF 5.7.1
20311 break;
20313 case 'TEXT-SHADOW':
20314 $ts = $this->cssManager->setCSStextshadow($v);
20315 if ($ts) {
20316 $this->textshadow = $ts;
20318 break;
20320 case 'HYPHENS':
20321 if (strtoupper($v) == 'NONE') {
20322 $this->textparam['hyphens'] = 2;
20323 } elseif (strtoupper($v) == 'AUTO') {
20324 $this->textparam['hyphens'] = 1;
20325 } elseif (strtoupper($v) == 'MANUAL') {
20326 $this->textparam['hyphens'] = 0;
20328 break;
20330 case 'TEXT-OUTLINE':
20331 if (strtoupper($v) == 'NONE') {
20332 $this->textparam['outline-s'] = false;
20334 break;
20336 case 'TEXT-OUTLINE-WIDTH':
20337 case 'OUTLINE-WIDTH':
20338 switch (strtoupper($v)) {
20339 case 'THIN':
20340 $v = '0.03em';
20341 break;
20342 case 'MEDIUM':
20343 $v = '0.05em';
20344 break;
20345 case 'THICK':
20346 $v = '0.07em';
20347 break;
20349 $w = $this->sizeConverter->convert($v, $this->FontSize, $this->FontSize);
20350 if ($w) {
20351 $this->textparam['outline-WIDTH'] = $w;
20352 $this->textparam['outline-s'] = true;
20353 } else {
20354 $this->textparam['outline-s'] = false;
20356 break;
20358 case 'TEXT-OUTLINE-COLOR':
20359 case 'OUTLINE-COLOR':
20360 if (strtoupper($v) == 'INVERT') {
20361 if ($this->colorarray) {
20362 $cor = $this->colorarray;
20363 $this->textparam['outline-COLOR'] = $this->colorConverter->invert($cor);
20364 } else {
20365 $this->textparam['outline-COLOR'] = $this->colorConverter->convert(255, $this->PDFAXwarnings);
20367 } else {
20368 $cor = $this->colorConverter->convert($v, $this->PDFAXwarnings);
20369 if ($cor) {
20370 $this->textparam['outline-COLOR'] = $cor;
20373 break;
20375 case 'COLOR': // font color
20376 $cor = $this->colorConverter->convert($v, $this->PDFAXwarnings);
20377 if ($cor) {
20378 $this->colorarray = $cor;
20379 $this->SetTColor($cor);
20381 break;
20382 }//end of switch($k)
20383 }//end of foreach
20384 // mPDF 5.7.3 inline text-decoration parameters
20385 // Needs to be set at the end - after vertical-align = super/sub, so that textparam['text-baseline'] is set
20386 if (isset($arrayaux['TEXT-DECORATION'])) {
20387 $v = $arrayaux['TEXT-DECORATION']; // none underline line-through (strikeout) // Does not support: blink
20388 if (stristr($v, 'LINE-THROUGH')) {
20389 $this->textvar = ($this->textvar | TextVars::FD_LINETHROUGH);
20390 // mPDF 5.7.3 inline text-decoration parameters
20391 if (isset($this->textparam['text-baseline'])) {
20392 $this->textparam['s-decoration']['baseline'] = $this->textparam['text-baseline'];
20393 } else {
20394 $this->textparam['s-decoration']['baseline'] = 0;
20396 $this->textparam['s-decoration']['fontkey'] = $this->FontFamily . $this->FontStyle;
20397 $this->textparam['s-decoration']['fontsize'] = $this->FontSize;
20398 $this->textparam['s-decoration']['color'] = strtoupper($this->TextColor); // change 0 0 0 rg to 0 0 0 RG
20400 if (stristr($v, 'UNDERLINE')) {
20401 $this->textvar = ($this->textvar | TextVars::FD_UNDERLINE);
20402 // mPDF 5.7.3 inline text-decoration parameters
20403 if (isset($this->textparam['text-baseline'])) {
20404 $this->textparam['u-decoration']['baseline'] = $this->textparam['text-baseline'];
20405 } else {
20406 $this->textparam['u-decoration']['baseline'] = 0;
20408 $this->textparam['u-decoration']['fontkey'] = $this->FontFamily . $this->FontStyle;
20409 $this->textparam['u-decoration']['fontsize'] = $this->FontSize;
20410 $this->textparam['u-decoration']['color'] = strtoupper($this->TextColor); // change 0 0 0 rg to 0 0 0 RG
20412 if (stristr($v, 'OVERLINE')) {
20413 $this->textvar = ($this->textvar | TextVars::FD_OVERLINE);
20414 // mPDF 5.7.3 inline text-decoration parameters
20415 if (isset($this->textparam['text-baseline'])) {
20416 $this->textparam['o-decoration']['baseline'] = $this->textparam['text-baseline'];
20417 } else {
20418 $this->textparam['o-decoration']['baseline'] = 0;
20420 $this->textparam['o-decoration']['fontkey'] = $this->FontFamily . $this->FontStyle;
20421 $this->textparam['o-decoration']['fontsize'] = $this->FontSize;
20422 $this->textparam['o-decoration']['color'] = strtoupper($this->TextColor); // change 0 0 0 rg to 0 0 0 RG
20424 if (stristr($v, 'NONE')) {
20425 $this->textvar = ($this->textvar & ~TextVars::FD_UNDERLINE);
20426 $this->textvar = ($this->textvar & ~TextVars::FD_LINETHROUGH);
20427 $this->textvar = ($this->textvar & ~TextVars::FD_OVERLINE);
20428 // mPDF 5.7.3 inline text-decoration parameters
20429 if (isset($this->textparam['u-decoration'])) {
20430 unset($this->textparam['u-decoration']);
20432 if (isset($this->textparam['s-decoration'])) {
20433 unset($this->textparam['s-decoration']);
20435 if (isset($this->textparam['o-decoration'])) {
20436 unset($this->textparam['o-decoration']);
20440 // mPDF 6
20441 if ($spanbordset) { // BORDER has been set on this INLINE element
20442 if (isset($this->textparam['text-baseline'])) {
20443 $this->textparam['bord-decoration']['baseline'] = $this->textparam['text-baseline'];
20444 } else {
20445 $this->textparam['bord-decoration']['baseline'] = 0;
20447 $this->textparam['bord-decoration']['fontkey'] = $this->FontFamily . $this->FontStyle;
20448 $this->textparam['bord-decoration']['fontsize'] = $this->FontSize;
20450 if ($spanbgset) { // BACKGROUND[-COLOR] has been set on this INLINE element
20451 if (isset($this->textparam['text-baseline'])) {
20452 $this->textparam['bg-decoration']['baseline'] = $this->textparam['text-baseline'];
20453 } else {
20454 $this->textparam['bg-decoration']['baseline'] = 0;
20456 $this->textparam['bg-decoration']['fontkey'] = $this->FontFamily . $this->FontStyle;
20457 $this->textparam['bg-decoration']['fontsize'] = $this->FontSize;
20461 /* -- END HTML-CSS -- */
20463 function SetStyle($tag, $enable)
20465 $this->$tag = $enable;
20466 $style = '';
20467 foreach (['B', 'I'] as $s) {
20468 if ($this->$s) {
20469 $style.=$s;
20472 $this->currentfontstyle = $style;
20473 $this->SetFont('', $style, 0, false);
20476 // Set multiple styles at one time
20477 function SetStylesArray($arr)
20479 $style = '';
20480 foreach (['B', 'I'] as $s) {
20481 if (isset($arr[$s])) {
20482 if ($arr[$s]) {
20483 $this->$s = true;
20484 $style.=$s;
20485 } else {
20486 $this->$s = false;
20488 } elseif ($this->$s) {
20489 $style.=$s;
20492 $this->currentfontstyle = $style;
20493 $this->SetFont('', $style, 0, false);
20496 // Set multiple styles at one $str e.g. "BI"
20497 function SetStyles($str)
20499 $style = '';
20500 foreach (['B', 'I'] as $s) {
20501 if (strpos($str, $s) !== false) {
20502 $this->$s = true;
20503 $style.=$s;
20504 } else {
20505 $this->$s = false;
20508 $this->currentfontstyle = $style;
20509 $this->SetFont('', $style, 0, false);
20512 function ResetStyles()
20514 foreach (['B', 'I'] as $s) {
20515 $this->$s = false;
20517 $this->currentfontstyle = '';
20518 $this->SetFont('', '', 0, false);
20521 function DisableTags($str = '')
20523 if ($str == '') { // enable all tags
20524 // Insert new supported tags in the long string below.
20525 $this->enabledtags = "<a><acronym><address><article><aside><b><bdi><bdo><big><blockquote><br><caption><center><cite><code><del><details><dd><div><dl><dt><em><fieldset><figcaption><figure><font><form><h1><h2><h3><h4><h5><h6><hgroup><hr><i><img><input><ins><kbd><legend><li><main><mark><meter><nav><ol><option><p><pre><progress><q><s><samp><section><select><small><span><strike><strong><sub><summary><sup><table><tbody><td><template><textarea><tfoot><th><thead><time><tr><tt><u><ul><var><footer><header><annotation><bookmark><textcircle><barcode><dottab><indexentry><indexinsert><watermarktext><watermarkimage><tts><ttz><tta><column_break><columnbreak><newcolumn><newpage><page_break><pagebreak><formfeed><columns><toc><tocentry><tocpagebreak><pageheader><pagefooter><setpageheader><setpagefooter><sethtmlpageheader><sethtmlpagefooter>";
20526 } else {
20527 $str = explode(",", $str);
20528 foreach ($str as $v) {
20529 $this->enabledtags = str_replace(trim($v), '', $this->enabledtags);
20534 /* -- TABLES -- */
20536 function TableCheckMinWidth($maxwidth, $forcewrap = 0, $textbuffer = [], $checkletter = false)
20538 // mPDF 6
20539 $acclength = 0; // mPDF 6 (accumulated length across > 1 chunk)
20540 $acclongest = 0; // mPDF 6 (accumulated length max across > 1 chunk)
20541 $biggestword = 0;
20542 $toonarrow = false;
20543 if ((count($textbuffer) == 0) or ( (count($textbuffer) == 1) && ($textbuffer[0][0] == ''))) {
20544 return 0;
20547 foreach ($textbuffer as $chunk) {
20548 $line = $chunk[0];
20549 $OTLdata = (isset($chunk[18]) ? $chunk[18] : null);
20551 // mPDF ITERATION
20552 if ($this->iterationCounter) {
20553 $line = preg_replace('/{iteration ([a-zA-Z0-9_]+)}/', '\\1', $line);
20556 // IMAGES & FORM ELEMENTS
20557 if (substr($line, 0, 3) == "\xbb\xa4\xac") { // inline object - FORM element or IMAGE!
20558 $objattr = $this->_getObjAttr($line);
20559 if ($objattr['type'] != 'hr' && isset($objattr['width']) && ($objattr['width'] / $this->shrin_k) > ($maxwidth + 0.0001)) {
20560 if (($objattr['width'] / $this->shrin_k) > $biggestword) {
20561 $biggestword = ($objattr['width'] / $this->shrin_k);
20563 $toonarrow = true;
20565 continue;
20568 if ($line == "\n") {
20569 $acclength = 0; // mPDF 6 (accumulated length across > 1 chunk)
20570 continue;
20572 $line = trim($line);
20573 if (!empty($OTLdata)) {
20574 $this->otl->trimOTLdata($OTLdata, true, true);
20575 } // *OTL*
20576 // SET FONT SIZE/STYLE from $chunk[n]
20577 // FONTSIZE
20578 if (isset($chunk[11]) and $chunk[11] != '') {
20579 if ($this->shrin_k) {
20580 $this->SetFontSize($chunk[11] / $this->shrin_k, false);
20581 } else {
20582 $this->SetFontSize($chunk[11], false);
20585 // FONTFAMILY
20586 if (isset($chunk[4]) and $chunk[4] != '') {
20587 $font = $this->SetFont($chunk[4], $this->FontStyle, 0, false);
20589 // B I
20590 if (isset($chunk[2]) and $chunk[2] != '') {
20591 $this->SetStyles($chunk[2]);
20594 $lbw = $rbw = 0; // Border widths
20595 if (isset($chunk[16]) && !empty($chunk[16])) { // Border
20596 $this->spanborddet = $chunk[16];
20597 $lbw = (isset($this->spanborddet['L']['w']) ? $this->spanborddet['L']['w'] : 0);
20598 $rbw = (isset($this->spanborddet['R']['w']) ? $this->spanborddet['R']['w'] : 0);
20600 if (isset($chunk[15])) { // Word spacing
20601 $this->wSpacingCSS = $chunk[15];
20602 if ($this->wSpacingCSS && strtoupper($this->wSpacingCSS) != 'NORMAL') {
20603 $this->minwSpacing = $this->sizeConverter->convert($this->wSpacingCSS, $this->FontSize) / $this->shrin_k; // mPDF 5.7.3
20606 if (isset($chunk[14])) { // Letter spacing
20607 $this->lSpacingCSS = $chunk[14];
20608 if (($this->lSpacingCSS || $this->lSpacingCSS === '0') && strtoupper($this->lSpacingCSS) != 'NORMAL') {
20609 $this->fixedlSpacing = $this->sizeConverter->convert($this->lSpacingCSS, $this->FontSize) / $this->shrin_k; // mPDF 5.7.3
20612 if (isset($chunk[8])) { // mPDF 5.7.1
20613 $this->textvar = $chunk[8];
20616 // mPDF 6
20617 // If overflow==wrap ($checkletter) OR (No word breaks and contains CJK)
20618 if ($checkletter || (!preg_match('/(\xe2\x80\x8b| )/', trim($line)) && preg_match("/([" . $this->pregCJKchars . "])/u", $line) )) {
20619 if (preg_match("/([" . $this->pregCJKchars . "])/u", $line)) {
20620 $checkCJK = true;
20621 } else {
20622 $checkCJK = false;
20625 $letters = preg_split('//u', $line);
20626 foreach ($letters as $k => $letter) {
20627 // mPDF 6
20628 if ($checkCJK) {
20629 if (preg_match("/[" . $this->CJKleading . "]/u", $letter) && $k > 0) {
20630 $letter = $letters[$k - 1] . $letter;
20632 if (preg_match("/[" . $this->CJKfollowing . "]/u", $letter) && $k < (count($letters) - 1)) {
20633 $letter = $letter . $letters[$k + 1];
20637 $letterwidth = $this->GetStringWidth($letter, false, false, $chunk[8]); // Pass $textvar ($chunk[8]), but do OTLdata here
20638 // so don't have to split OTLdata for each word
20639 if ($k == 0) {
20640 $letterwidth += $lbw;
20642 if ($k == (count($letters) - 1)) {
20643 $letterwidth += $rbw;
20646 // Warn user that maxwidth is insufficient
20647 if ($letterwidth > $maxwidth + 0.0001) {
20648 if ($letterwidth > $biggestword) {
20649 $biggestword = $letterwidth;
20651 $toonarrow = true;
20654 } else {
20655 // mPDF 6
20656 // Need to account for any XAdvance in GPOSinfo (OTLdata = $chunk[18])
20657 $wordXAdvance = [];
20658 if (isset($chunk[18]) && $chunk[18]) {
20659 preg_match_all('/(\xe2\x80\x8b| )/', $line, $spaces, PREG_OFFSET_CAPTURE); // U+200B Zero Width word boundary, or space
20660 $lastoffset = 0;
20661 $k = -1; // Added so that if no spaces found, "last word" later is calculated for the one and only word
20662 foreach ($spaces[0] as $k => $m) {
20663 $offset = $m[1];
20664 // ...TableCheckMinWidth...
20665 // At this point, BIDI not applied, Writing direction is not set, and XAdvanceL balances XAdvanceR
20666 for ($n = $lastoffset; $n < $offset; $n++) {
20667 if (isset($chunk[18]['GPOSinfo'][$n]['XAdvanceL'])) {
20668 if (isset($wordXAdvance[$k])) {
20669 $wordXAdvance[$k] += $chunk[18]['GPOSinfo'][$n]['XAdvanceL'];
20670 } else {
20671 $wordXAdvance[$k] = $chunk[18]['GPOSinfo'][$n]['XAdvanceL'];
20675 $lastoffset = $offset + 1;
20678 $k++; // last word
20679 foreach ($chunk[18]['GPOSinfo'] as $n => $gpos) {
20680 if ($n >= $lastoffset && isset($chunk[18]['GPOSinfo'][$n]['XAdvanceL'])) {
20681 if (isset($wordXAdvance[$k])) {
20682 $wordXAdvance[$k] += $chunk[18]['GPOSinfo'][$n]['XAdvanceL'];
20683 } else {
20684 $wordXAdvance[$k] = $chunk[18]['GPOSinfo'][$n]['XAdvanceL'];
20690 $words = preg_split('/(\xe2\x80\x8b| )/', $line); // U+200B Zero Width word boundary, or space
20691 foreach ($words as $k => $word) {
20692 $word = trim($word);
20693 $wordwidth = $this->GetStringWidth($word, false, false, $chunk[8]); // Pass $textvar ($chunk[8]), but do OTLdata here
20694 // so don't have to split OTLdata for each word
20695 if (isset($wordXAdvance[$k])) {
20696 $wordwidth += ($wordXAdvance[$k] * 1000 / $this->CurrentFont['unitsPerEm']) * ($this->FontSize / 1000);
20698 if ($k == 0) {
20699 $wordwidth += $lbw;
20701 if ($k == (count($words) - 1)) {
20702 $wordwidth += $rbw;
20705 // mPDF 6
20706 if (count($words) == 1 && substr($chunk[0], 0, 1) != ' ') {
20707 $acclength += $wordwidth;
20708 } elseif (count($words) > 1 && $k == 0 && substr($chunk[0], 0, 1) != ' ') {
20709 $acclength += $wordwidth;
20710 } else {
20711 $acclength = $wordwidth;
20713 $acclongest = max($acclongest, $acclength);
20714 if (count($words) == 1 && substr($chunk[0], -1, 1) == ' ') {
20715 $acclength = 0;
20716 } elseif (count($words) > 1 && ($k != (count($words) - 1) || substr($chunk[0], -1, 1) == ' ')) {
20717 $acclength = 0;
20720 // Warn user that maxwidth is insufficient
20721 if ($wordwidth > $maxwidth + 0.0001) {
20722 if ($wordwidth > $biggestword) {
20723 $biggestword = $wordwidth;
20725 $toonarrow = true;
20730 // mPDF 6 Accumulated length of biggest word - across multiple chunks
20731 if ($acclongest > $maxwidth + 0.0001) {
20732 if ($acclongest > $biggestword) {
20733 $biggestword = $acclongest;
20735 $toonarrow = true;
20738 // RESET FONT SIZE/STYLE
20739 // RESETTING VALUES
20740 // Now we must deactivate what we have used
20741 if (isset($chunk[2]) and $chunk[2] != '') {
20742 $this->ResetStyles();
20744 if (isset($chunk[4]) and $chunk[4] != '') {
20745 $this->SetFont($this->default_font, $this->FontStyle, 0, false);
20747 if (isset($chunk[11]) and $chunk[11] != '') {
20748 $this->SetFontSize($this->default_font_size, false);
20750 $this->spanborddet = [];
20751 $this->textvar = 0x00; // mPDF 5.7.1
20752 $this->OTLtags = [];
20753 $this->lSpacingCSS = '';
20754 $this->wSpacingCSS = '';
20755 $this->fixedlSpacing = false;
20756 $this->minwSpacing = 0;
20759 // Return -(wordsize) if word is bigger than maxwidth
20760 // ADDED
20761 if (($toonarrow) && ($this->table_error_report)) {
20762 throw new \Mpdf\MpdfException("Word is too long to fit in table - " . $this->table_error_report_param);
20764 if ($toonarrow) {
20765 return -$biggestword;
20766 } else {
20767 return 1;
20771 function shrinkTable(&$table, $k)
20773 $table['border_spacing_H'] /= $k;
20774 $table['border_spacing_V'] /= $k;
20776 $table['padding']['T'] /= $k;
20777 $table['padding']['R'] /= $k;
20778 $table['padding']['B'] /= $k;
20779 $table['padding']['L'] /= $k;
20781 $table['margin']['T'] /= $k;
20782 $table['margin']['R'] /= $k;
20783 $table['margin']['B'] /= $k;
20784 $table['margin']['L'] /= $k;
20786 $table['border_details']['T']['w'] /= $k;
20787 $table['border_details']['R']['w'] /= $k;
20788 $table['border_details']['B']['w'] /= $k;
20789 $table['border_details']['L']['w'] /= $k;
20791 if (isset($table['max_cell_border_width']['T'])) {
20792 $table['max_cell_border_width']['T'] /= $k;
20794 if (isset($table['max_cell_border_width']['R'])) {
20795 $table['max_cell_border_width']['R'] /= $k;
20797 if (isset($table['max_cell_border_width']['B'])) {
20798 $table['max_cell_border_width']['B'] /= $k;
20800 if (isset($table['max_cell_border_width']['L'])) {
20801 $table['max_cell_border_width']['L'] /= $k;
20804 if ($this->simpleTables) {
20805 $table['simple']['border_details']['T']['w'] /= $k;
20806 $table['simple']['border_details']['R']['w'] /= $k;
20807 $table['simple']['border_details']['B']['w'] /= $k;
20808 $table['simple']['border_details']['L']['w'] /= $k;
20811 $table['miw'] /= $k;
20812 $table['maw'] /= $k;
20814 for ($j = 0; $j < $table['nc']; $j++) { // columns
20816 $table['wc'][$j]['miw'] = isset($table['wc'][$j]['miw']) ? $table['wc'][$j]['miw'] : 0;
20817 $table['wc'][$j]['maw'] = isset($table['wc'][$j]['maw']) ? $table['wc'][$j]['maw'] : 0;
20819 $table['wc'][$j]['miw'] /= $k;
20820 $table['wc'][$j]['maw'] /= $k;
20822 if (isset($table['decimal_align'][$j]['maxs0']) && $table['decimal_align'][$j]['maxs0']) {
20823 $table['decimal_align'][$j]['maxs0'] /= $k;
20826 if (isset($table['decimal_align'][$j]['maxs1']) && $table['decimal_align'][$j]['maxs1']) {
20827 $table['decimal_align'][$j]['maxs1'] /= $k;
20830 if (isset($table['wc'][$j]['absmiw']) && $table['wc'][$j]['absmiw']) {
20831 $table['wc'][$j]['absmiw'] /= $k;
20834 for ($i = 0; $i < $table['nr']; $i++) { // rows
20836 $c = &$table['cells'][$i][$j];
20838 if (isset($c) && $c) {
20840 if (!$this->simpleTables) {
20842 if ($this->packTableData) {
20844 $cell = $this->_unpackCellBorder($c['borderbin']);
20846 $cell['border_details']['T']['w'] /= $k;
20847 $cell['border_details']['R']['w'] /= $k;
20848 $cell['border_details']['B']['w'] /= $k;
20849 $cell['border_details']['L']['w'] /= $k;
20850 $cell['border_details']['mbw']['TL'] /= $k;
20851 $cell['border_details']['mbw']['TR'] /= $k;
20852 $cell['border_details']['mbw']['BL'] /= $k;
20853 $cell['border_details']['mbw']['BR'] /= $k;
20854 $cell['border_details']['mbw']['LT'] /= $k;
20855 $cell['border_details']['mbw']['LB'] /= $k;
20856 $cell['border_details']['mbw']['RT'] /= $k;
20857 $cell['border_details']['mbw']['RB'] /= $k;
20859 $c['borderbin'] = $this->_packCellBorder($cell);
20861 } else {
20863 $c['border_details']['T']['w'] /= $k;
20864 $c['border_details']['R']['w'] /= $k;
20865 $c['border_details']['B']['w'] /= $k;
20866 $c['border_details']['L']['w'] /= $k;
20867 $c['border_details']['mbw']['TL'] /= $k;
20868 $c['border_details']['mbw']['TR'] /= $k;
20869 $c['border_details']['mbw']['BL'] /= $k;
20870 $c['border_details']['mbw']['BR'] /= $k;
20871 $c['border_details']['mbw']['LT'] /= $k;
20872 $c['border_details']['mbw']['LB'] /= $k;
20873 $c['border_details']['mbw']['RT'] /= $k;
20874 $c['border_details']['mbw']['RB'] /= $k;
20878 $c['padding']['T'] /= $k;
20879 $c['padding']['R'] /= $k;
20880 $c['padding']['B'] /= $k;
20881 $c['padding']['L'] /= $k;
20883 $c['maxs'] = isset($c['maxs']) ? $c['maxs'] /= $k : 0;
20884 $c['w'] = isset($c['w']) ? $c['w'] /= $k : 0;
20886 $c['s'] = isset($c['s']) ? $c['s'] /= $k : 0;
20887 $c['h'] = isset($c['h']) ? $c['h'] /= $k : 0;
20889 $c['miw'] = isset($c['miw']) ? $c['miw'] /= $k : 0;
20890 $c['maw'] = isset($c['maw']) ? $c['maw'] /= $k : 0;
20892 $c['absmiw'] = isset($c['absmiw']) ? $c['absmiw'] /= $k : 0;
20894 $c['nestedmaw'] = isset($c['nestedmaw']) ? $c['nestedmaw'] /= $k : 0;
20895 $c['nestedmiw'] = isset($c['nestedmiw']) ? $c['nestedmiw'] /= $k : 0;
20897 if (isset($c['textbuffer'])) {
20898 foreach ($c['textbuffer'] as $n => $tb) {
20899 if (!empty($tb[16])) {
20900 !isset($c['textbuffer'][$n][16]['T']) || $c['textbuffer'][$n][16]['T']['w'] /= $k;
20901 !isset($c['textbuffer'][$n][16]['B']) || $c['textbuffer'][$n][16]['B']['w'] /= $k;
20902 !isset($c['textbuffer'][$n][16]['L']) || $c['textbuffer'][$n][16]['L']['w'] /= $k;
20903 !isset($c['textbuffer'][$n][16]['R']) || $c['textbuffer'][$n][16]['R']['w'] /= $k;
20908 unset($c);
20911 } // rows
20912 } // columns
20915 function read_short(&$fh)
20917 $s = fread($fh, 2);
20918 $a = (ord($s[0]) << 8) + ord($s[1]);
20919 if ($a & (1 << 15)) {
20920 $a = ($a - (1 << 16));
20922 return $a;
20925 function _packCellBorder($cell)
20927 if (!is_array($cell) || !isset($cell)) {
20928 return '';
20931 if (!$this->packTableData) {
20932 return $cell;
20934 // = 186 bytes
20935 $bindata = pack("nnda6A10nnda6A10nnda6A10nnda6A10nd9", $cell['border'], $cell['border_details']['R']['s'], $cell['border_details']['R']['w'], $cell['border_details']['R']['c'], $cell['border_details']['R']['style'], $cell['border_details']['R']['dom'], $cell['border_details']['L']['s'], $cell['border_details']['L']['w'], $cell['border_details']['L']['c'], $cell['border_details']['L']['style'], $cell['border_details']['L']['dom'], $cell['border_details']['T']['s'], $cell['border_details']['T']['w'], $cell['border_details']['T']['c'], $cell['border_details']['T']['style'], $cell['border_details']['T']['dom'], $cell['border_details']['B']['s'], $cell['border_details']['B']['w'], $cell['border_details']['B']['c'], $cell['border_details']['B']['style'], $cell['border_details']['B']['dom'], $cell['border_details']['mbw']['BL'], $cell['border_details']['mbw']['BR'], $cell['border_details']['mbw']['RT'], $cell['border_details']['mbw']['RB'], $cell['border_details']['mbw']['TL'], $cell['border_details']['mbw']['TR'], $cell['border_details']['mbw']['LT'], $cell['border_details']['mbw']['LB'], (isset($cell['border_details']['cellposdom']) ? $cell['border_details']['cellposdom'] : 0));
20936 return $bindata;
20939 function _getBorderWidths($bindata)
20941 if (!$bindata) {
20942 return [0, 0, 0, 0];
20944 if (!$this->packTableData) {
20945 return [$bindata['border_details']['T']['w'], $bindata['border_details']['R']['w'], $bindata['border_details']['B']['w'], $bindata['border_details']['L']['w']];
20948 $bd = unpack("nbord/nrs/drw/a6rca/A10rst/nrd/nls/dlw/a6lca/A10lst/nld/nts/dtw/a6tca/A10tst/ntd/nbs/dbw/a6bca/A10bst/nbd/dmbl/dmbr/dmrt/dmrb/dmtl/dmtr/dmlt/dmlb/dcpd", $bindata);
20949 $cell['border_details']['R']['w'] = $bd['rw'];
20950 $cell['border_details']['L']['w'] = $bd['lw'];
20951 $cell['border_details']['T']['w'] = $bd['tw'];
20952 $cell['border_details']['B']['w'] = $bd['bw'];
20953 return [$bd['tw'], $bd['rw'], $bd['bw'], $bd['lw']];
20956 function _unpackCellBorder($bindata)
20958 if (!$bindata) {
20959 return [];
20961 if (!$this->packTableData) {
20962 return $bindata;
20965 $bd = unpack("nbord/nrs/drw/a6rca/A10rst/nrd/nls/dlw/a6lca/A10lst/nld/nts/dtw/a6tca/A10tst/ntd/nbs/dbw/a6bca/A10bst/nbd/dmbl/dmbr/dmrt/dmrb/dmtl/dmtr/dmlt/dmlb/dcpd", $bindata);
20967 $cell['border'] = $bd['bord'];
20968 $cell['border_details']['R']['s'] = $bd['rs'];
20969 $cell['border_details']['R']['w'] = $bd['rw'];
20970 $cell['border_details']['R']['c'] = str_pad($bd['rca'], 6, "\x00");
20971 $cell['border_details']['R']['style'] = trim($bd['rst']);
20972 $cell['border_details']['R']['dom'] = $bd['rd'];
20974 $cell['border_details']['L']['s'] = $bd['ls'];
20975 $cell['border_details']['L']['w'] = $bd['lw'];
20976 $cell['border_details']['L']['c'] = str_pad($bd['lca'], 6, "\x00");
20977 $cell['border_details']['L']['style'] = trim($bd['lst']);
20978 $cell['border_details']['L']['dom'] = $bd['ld'];
20980 $cell['border_details']['T']['s'] = $bd['ts'];
20981 $cell['border_details']['T']['w'] = $bd['tw'];
20982 $cell['border_details']['T']['c'] = str_pad($bd['tca'], 6, "\x00");
20983 $cell['border_details']['T']['style'] = trim($bd['tst']);
20984 $cell['border_details']['T']['dom'] = $bd['td'];
20986 $cell['border_details']['B']['s'] = $bd['bs'];
20987 $cell['border_details']['B']['w'] = $bd['bw'];
20988 $cell['border_details']['B']['c'] = str_pad($bd['bca'], 6, "\x00");
20989 $cell['border_details']['B']['style'] = trim($bd['bst']);
20990 $cell['border_details']['B']['dom'] = $bd['bd'];
20992 $cell['border_details']['mbw']['BL'] = $bd['mbl'];
20993 $cell['border_details']['mbw']['BR'] = $bd['mbr'];
20994 $cell['border_details']['mbw']['RT'] = $bd['mrt'];
20995 $cell['border_details']['mbw']['RB'] = $bd['mrb'];
20996 $cell['border_details']['mbw']['TL'] = $bd['mtl'];
20997 $cell['border_details']['mbw']['TR'] = $bd['mtr'];
20998 $cell['border_details']['mbw']['LT'] = $bd['mlt'];
20999 $cell['border_details']['mbw']['LB'] = $bd['mlb'];
21000 $cell['border_details']['cellposdom'] = $bd['cpd'];
21003 return($cell);
21006 ////////////////////////TABLE CODE (from PDFTable)/////////////////////////////////////
21007 ////////////////////////TABLE CODE (from PDFTable)/////////////////////////////////////
21008 ////////////////////////TABLE CODE (from PDFTable)/////////////////////////////////////
21009 // table Array of (w, h, bc, nr, wc, hr, cells)
21010 // w Width of table
21011 // h Height of table
21012 // nc Number column
21013 // nr Number row
21014 // hr List of height of each row
21015 // wc List of width of each column
21016 // cells List of cells of each rows, cells[i][j] is a cell in the table
21017 function _tableColumnWidth(&$table, $firstpass = false)
21019 $cs = &$table['cells'];
21021 $nc = $table['nc'];
21022 $nr = $table['nr'];
21023 $listspan = [];
21025 if ($table['borders_separate']) {
21026 $tblbw = $table['border_details']['L']['w'] + $table['border_details']['R']['w'] + $table['margin']['L'] + $table['margin']['R'] + $table['padding']['L'] + $table['padding']['R'] + $table['border_spacing_H'];
21027 } else {
21028 $tblbw = $table['max_cell_border_width']['L'] / 2 + $table['max_cell_border_width']['R'] / 2 + $table['margin']['L'] + $table['margin']['R'];
21031 // ADDED table['l'][colno]
21032 // = total length of text approx (using $c['s']) in that column - used to approximately distribute col widths in _tableWidth
21034 for ($j = 0; $j < $nc; $j++) { // columns
21035 $wc = &$table['wc'][$j];
21036 for ($i = 0; $i < $nr; $i++) { // rows
21037 if (isset($cs[$i][$j]) && $cs[$i][$j]) {
21038 $c = &$cs[$i][$j];
21040 if ($this->simpleTables) {
21041 if ($table['borders_separate']) { // NB twice border width
21042 $extrcw = $table['simple']['border_details']['L']['w'] + $table['simple']['border_details']['R']['w'] + $c['padding']['L'] + $c['padding']['R'] + $table['border_spacing_H'];
21043 } else {
21044 $extrcw = $table['simple']['border_details']['L']['w'] / 2 + $table['simple']['border_details']['R']['w'] / 2 + $c['padding']['L'] + $c['padding']['R'];
21046 } else {
21047 if ($this->packTableData) {
21048 list($bt, $br, $bb, $bl) = $this->_getBorderWidths($c['borderbin']);
21049 } else {
21050 $br = $c['border_details']['R']['w'];
21051 $bl = $c['border_details']['L']['w'];
21053 if ($table['borders_separate']) { // NB twice border width
21054 $extrcw = $bl + $br + $c['padding']['L'] + $c['padding']['R'] + $table['border_spacing_H'];
21055 } else {
21056 $extrcw = $bl / 2 + $br / 2 + $c['padding']['L'] + $c['padding']['R'];
21060 // $mw = $this->GetStringWidth('W') + $extrcw ;
21061 $mw = $extrcw; // mPDF 6
21062 if (substr($c['a'], 0, 1) == 'D') {
21063 $mw = $table['decimal_align'][$j]['maxs0'] + $table['decimal_align'][$j]['maxs1'] + $extrcw;
21066 $c['absmiw'] = $mw;
21068 if (isset($c['R']) && $c['R']) {
21069 $c['maw'] = $c['miw'] = $this->FontSize + $extrcw;
21070 if (isset($c['w'])) { // If cell width is specified
21071 if ($c['miw'] < $c['w']) {
21072 $c['miw'] = $c['w'];
21075 if (!isset($c['colspan'])) {
21076 if ($wc['miw'] < $c['miw']) {
21077 $wc['miw'] = $c['miw'];
21079 if ($wc['maw'] < $c['maw']) {
21080 $wc['maw'] = $c['maw'];
21083 if ($firstpass) {
21084 if (isset($table['l'][$j])) {
21085 $table['l'][$j] += $c['miw'];
21086 } else {
21087 $table['l'][$j] = $c['miw'];
21091 if ($c['miw'] > $wc['miw']) {
21092 $wc['miw'] = $c['miw'];
21094 if ($wc['miw'] > $wc['maw']) {
21095 $wc['maw'] = $wc['miw'];
21097 continue;
21100 if ($firstpass) {
21101 if (isset($c['s'])) {
21102 $c['s'] += $extrcw;
21104 if (isset($c['maxs'])) {
21105 $c['maxs'] += $extrcw;
21107 if (isset($c['nestedmiw'])) {
21108 $c['nestedmiw'] += $extrcw;
21110 if (isset($c['nestedmaw'])) {
21111 $c['nestedmaw'] += $extrcw;
21116 // If minimum width has already been set by a nested table or inline object (image/form), use it
21117 if (isset($c['nestedmiw']) && (!isset($this->table[1][1]['overflow']) || $this->table[1][1]['overflow'] != 'visible')) {
21118 $miw = $c['nestedmiw'];
21119 } else {
21120 $miw = $mw;
21123 if (isset($c['maxs']) && $c['maxs'] != '') {
21124 $c['s'] = $c['maxs'];
21127 // If maximum width has already been set by a nested table, use it
21128 if (isset($c['nestedmaw'])) {
21129 $c['maw'] = $c['nestedmaw'];
21130 } else {
21131 $c['maw'] = $c['s'];
21134 if (isset($table['overflow']) && $table['overflow'] == 'visible' && $table['level'] == 1) {
21135 if (($c['maw'] + $tblbw) > $this->blk[$this->blklvl]['inner_width']) {
21136 $c['maw'] = $this->blk[$this->blklvl]['inner_width'] - $tblbw;
21140 if (isset($c['nowrap']) && $c['nowrap']) {
21141 $miw = $c['maw'];
21144 if (isset($c['wpercent']) && $firstpass) {
21145 if (isset($c['colspan'])) { // Not perfect - but % set on colspan is shared equally on cols.
21146 for ($k = 0; $k < $c['colspan']; $k++) {
21147 $table['wc'][($j + $k)]['wpercent'] = $c['wpercent'] / $c['colspan'];
21149 } else {
21150 if (isset($table['w']) && $table['w']) {
21151 $c['w'] = $c['wpercent'] / 100 * ($table['w'] - $tblbw );
21153 $wc['wpercent'] = $c['wpercent'];
21157 if (isset($table['overflow']) && $table['overflow'] == 'visible' && $table['level'] == 1) {
21158 if (isset($c['w']) && ($c['w'] + $tblbw) > $this->blk[$this->blklvl]['inner_width']) {
21159 $c['w'] = $this->blk[$this->blklvl]['inner_width'] - $tblbw;
21164 if (isset($c['w'])) { // If cell width is specified
21165 if ($miw < $c['w']) {
21166 $c['miw'] = $c['w'];
21167 } // Cell min width = that specified
21168 if ($miw > $c['w']) {
21169 $c['miw'] = $c['w'] = $miw;
21170 } // If width specified is less than minimum allowed (W) increase it
21171 // mPDF 5.7.4 Do not set column width in colspan
21172 // cf. http://www.mpdf1.com/forum/discussion/2221/colspan-bug
21173 if (!isset($c['colspan'])) {
21174 if (!isset($wc['w'])) {
21175 $wc['w'] = 1;
21176 } // If the Col width is not specified = set it to 1
21178 // mPDF 5.7.3 cf. http://www.mpdf1.com/forum/discussion/1648/nested-table-bug-
21179 $c['maw'] = $c['w'];
21180 } else {
21181 $c['miw'] = $miw;
21182 } // If cell width not specified -> set Cell min width it to minimum allowed (W)
21184 if (isset($c['miw']) && $c['maw'] < $c['miw']) {
21185 $c['maw'] = $c['miw'];
21186 } // If Cell max width < Minwidth - increase it to =
21187 if (!isset($c['colspan'])) {
21188 if (isset($c['miw']) && $wc['miw'] < $c['miw']) {
21189 $wc['miw'] = $c['miw'];
21190 } // Update Col Minimum and maximum widths
21191 if ($wc['maw'] < $c['maw']) {
21192 $wc['maw'] = $c['maw'];
21194 if ((isset($wc['absmiw']) && $wc['absmiw'] < $c['absmiw']) || !isset($wc['absmiw'])) {
21195 $wc['absmiw'] = $c['absmiw'];
21196 } // Update Col Minimum and maximum widths
21198 if (isset($table['l'][$j])) {
21199 $table['l'][$j] += $c['s'];
21200 } else {
21201 $table['l'][$j] = $c['s'];
21203 } else {
21204 $listspan[] = [$i, $j];
21207 // Check if minimum width of the whole column is big enough for largest word to fit
21208 // mPDF 6
21209 if (isset($c['textbuffer'])) {
21210 if (isset($table['overflow']) && $table['overflow'] == 'wrap') {
21211 $letter = true;
21212 } // check for maximum width of letters
21213 else {
21214 $letter = false;
21216 $minwidth = $this->TableCheckMinWidth($wc['miw'] - $extrcw, 0, $c['textbuffer'], $letter);
21217 } else {
21218 $minwidth = 0;
21220 if ($minwidth < 0) {
21221 // increase minimum width
21222 if (!isset($c['colspan'])) {
21223 $wc['miw'] = max($wc['miw'], ((-$minwidth) + $extrcw));
21224 } else {
21225 $c['miw'] = max($c['miw'], ((-$minwidth) + $extrcw));
21228 if (!isset($c['colspan'])) {
21229 if ($wc['miw'] > $wc['maw']) {
21230 $wc['maw'] = $wc['miw'];
21231 } // update maximum width, if needed
21234 unset($c);
21235 }//rows
21236 }//columns
21237 // COLUMN SPANS
21238 $wc = &$table['wc'];
21239 foreach ($listspan as $span) {
21240 list($i, $j) = $span;
21241 $c = &$cs[$i][$j];
21242 $lc = $j + $c['colspan'];
21243 if ($lc > $nc) {
21244 $lc = $nc;
21246 $wis = $wisa = 0;
21247 $was = $wasa = 0;
21248 $list = [];
21249 for ($k = $j; $k < $lc; $k++) {
21250 if (isset($table['l'][$k])) {
21251 if ($c['R']) {
21252 $table['l'][$k] += $c['miw'] / $c['colspan'];
21253 } else {
21254 $table['l'][$k] += $c['s'] / $c['colspan'];
21256 } else {
21257 if ($c['R']) {
21258 $table['l'][$k] = $c['miw'] / $c['colspan'];
21259 } else {
21260 $table['l'][$k] = $c['s'] / $c['colspan'];
21263 $wis += $wc[$k]['miw']; // $wis is the sum of the column miw in the colspan
21264 $was += $wc[$k]['maw']; // $was is the sum of the column maw in the colspan
21265 if (!isset($c['w'])) {
21266 $list[] = $k;
21267 $wisa += $wc[$k]['miw']; // $wisa is the sum of the column miw in cells with no width specified in the colspan
21268 $wasa += $wc[$k]['maw']; // $wasa is the sum of the column maw in cells with no width specified in the colspan
21271 if ($c['miw'] > $wis) {
21272 if (!$wis) {
21273 for ($k = $j; $k < $lc; $k++) {
21274 $wc[$k]['miw'] = $c['miw'] / $c['colspan'];
21276 } elseif (!count($list)) {
21277 $wi = $c['miw'] - $wis;
21278 for ($k = $j; $k < $lc; $k++) {
21279 $wc[$k]['miw'] += ($wc[$k]['miw'] / $wis) * $wi;
21281 } else {
21282 $wi = $c['miw'] - $wis;
21283 // mPDF 5.7.2 Extra min width distributed proportionately to all cells in colspan without a specified width
21284 // cf. http://www.mpdf1.com/forum/discussion/1607#Item_4
21285 foreach ($list as $k) {
21286 if (!isset($wc[$k]['w']) || !$wc[$k]['w']) {
21287 $wc[$k]['miw'] += ($wc[$k]['miw'] / $wisa) * $wi;
21289 } // mPDF 5.7.2
21292 if ($c['maw'] > $was) {
21293 if (!$wis) {
21294 for ($k = $j; $k < $lc; $k++) {
21295 $wc[$k]['maw'] = $c['maw'] / $c['colspan'];
21297 } elseif (!count($list)) {
21298 $wi = $c['maw'] - $was;
21299 for ($k = $j; $k < $lc; $k++) {
21300 $wc[$k]['maw'] += ($wc[$k]['maw'] / $was) * $wi;
21302 } else {
21303 $wi = $c['maw'] - $was;
21304 // mPDF 5.7.4 Extra max width distributed evenly to all cells in colspan without a specified width
21305 // cf. http://www.mpdf1.com/forum/discussion/2221/colspan-bug
21306 foreach ($list as $k) {
21307 $wc[$k]['maw'] += $wi / count($list);
21311 unset($c);
21314 $checkminwidth = 0;
21315 $checkmaxwidth = 0;
21316 $totallength = 0;
21318 for ($i = 0; $i < $nc; $i++) {
21319 $checkminwidth += $table['wc'][$i]['miw'];
21320 $checkmaxwidth += $table['wc'][$i]['maw'];
21321 $totallength += isset($table['l']) ? $table['l'][$i] : 0;
21324 if (!isset($table['w']) && $firstpass) {
21325 $sumpc = 0;
21326 $notset = 0;
21327 for ($i = 0; $i < $nc; $i++) {
21328 if (isset($table['wc'][$i]['wpercent']) && $table['wc'][$i]['wpercent']) {
21329 $sumpc += $table['wc'][$i]['wpercent'];
21330 } else {
21331 $notset++;
21335 // If sum of widths as % >= 100% and not all columns are set
21336 // Set a nominal width of 1% for unset columns
21337 if ($sumpc >= 100 && $notset) {
21338 for ($i = 0; $i < $nc; $i++) {
21339 if ((!isset($table['wc'][$i]['wpercent']) || !$table['wc'][$i]['wpercent']) &&
21340 (!isset($table['wc'][$i]['w']) || !$table['wc'][$i]['w'])) {
21341 $table['wc'][$i]['wpercent'] = 1;
21347 if ($sumpc) { // if any percents are set
21348 $sumnonpc = (100 - $sumpc);
21349 $sumpc = max($sumpc, 100);
21350 $miwleft = 0;
21351 $miwleftcount = 0;
21352 $miwsurplusnonpc = 0;
21353 $maxcalcmiw = 0;
21354 $mawleft = 0;
21355 $mawleftcount = 0;
21356 $mawsurplusnonpc = 0;
21357 $maxcalcmaw = 0;
21358 $mawnon = 0;
21359 $miwnon = 0;
21360 for ($i = 0; $i < $nc; $i++) {
21361 if (isset($table['wc'][$i]['wpercent'])) {
21362 $maxcalcmiw = max($maxcalcmiw, ($table['wc'][$i]['miw'] * $sumpc / $table['wc'][$i]['wpercent']));
21363 $maxcalcmaw = max($maxcalcmaw, ($table['wc'][$i]['maw'] * $sumpc / $table['wc'][$i]['wpercent']));
21364 } else {
21365 $miwleft += $table['wc'][$i]['miw'];
21366 $mawleft += $table['wc'][$i]['maw'];
21367 if (!isset($table['wc'][$i]['w'])) {
21368 $miwleftcount++;
21369 $mawleftcount++;
21373 if ($miwleft && $sumnonpc > 0) {
21374 $miwnon = $miwleft * 100 / $sumnonpc;
21376 if ($mawleft && $sumnonpc > 0) {
21377 $mawnon = $mawleft * 100 / $sumnonpc;
21379 if (($miwnon > $checkminwidth || $maxcalcmiw > $checkminwidth) && $this->keep_table_proportions) {
21380 if ($miwnon > $maxcalcmiw) {
21381 $miwsurplusnonpc = round((($miwnon * $sumnonpc / 100) - $miwleft), 3);
21382 $checkminwidth = $miwnon;
21383 } else {
21384 $checkminwidth = $maxcalcmiw;
21386 for ($i = 0; $i < $nc; $i++) {
21387 if (isset($table['wc'][$i]['wpercent'])) {
21388 $newmiw = $checkminwidth * $table['wc'][$i]['wpercent'] / 100;
21389 if ($table['wc'][$i]['miw'] < $newmiw) {
21390 $table['wc'][$i]['miw'] = $newmiw;
21392 $table['wc'][$i]['w'] = 1;
21393 } elseif ($miwsurplusnonpc && !$table['wc'][$i]['w']) {
21394 $table['wc'][$i]['miw'] += $miwsurplusnonpc / $miwleftcount;
21398 if (($mawnon > $checkmaxwidth || $maxcalcmaw > $checkmaxwidth)) {
21399 if ($mawnon > $maxcalcmaw) {
21400 $mawsurplusnonpc = round((($mawnon * $sumnonpc / 100) - $mawleft), 3);
21401 $checkmaxwidth = $mawnon;
21402 } else {
21403 $checkmaxwidth = $maxcalcmaw;
21405 for ($i = 0; $i < $nc; $i++) {
21406 if (isset($table['wc'][$i]['wpercent'])) {
21407 $newmaw = $checkmaxwidth * $table['wc'][$i]['wpercent'] / 100;
21408 if ($table['wc'][$i]['maw'] < $newmaw) {
21409 $table['wc'][$i]['maw'] = $newmaw;
21411 $table['wc'][$i]['w'] = 1;
21412 } elseif ($mawsurplusnonpc && !$table['wc'][$i]['w']) {
21413 $table['wc'][$i]['maw'] += $mawsurplusnonpc / $mawleftcount;
21415 if ($table['wc'][$i]['maw'] < $table['wc'][$i]['miw']) {
21416 $table['wc'][$i]['maw'] = $table['wc'][$i]['miw'];
21420 if ($checkminwidth > $checkmaxwidth) {
21421 $checkmaxwidth = $checkminwidth;
21426 if (isset($table['wpercent']) && $table['wpercent']) {
21427 $checkminwidth *= (100 / $table['wpercent']);
21428 $checkmaxwidth *= (100 / $table['wpercent']);
21432 $checkminwidth += $tblbw;
21433 $checkmaxwidth += $tblbw;
21435 // Table['miw'] set by percent in first pass may be larger than sum of column miw
21436 if ((isset($table['miw']) && $checkminwidth > $table['miw']) || !isset($table['miw'])) {
21437 $table['miw'] = $checkminwidth;
21439 if ((isset($table['maw']) && $checkmaxwidth > $table['maw']) || !isset($table['maw'])) {
21440 $table['maw'] = $checkmaxwidth;
21442 $table['tl'] = $totallength;
21444 // mPDF 6
21445 if ($this->table_rotate) {
21446 $mxw = $this->tbrot_maxw;
21447 } else {
21448 $mxw = $this->blk[$this->blklvl]['inner_width'];
21451 if (!isset($table['overflow'])) {
21452 $table['overflow'] = null;
21455 if ($table['overflow'] == 'visible') {
21456 return [0, 0];
21457 } elseif ($table['overflow'] == 'hidden' && !$this->table_rotate && !$this->ColActive && $checkminwidth > $mxw) {
21458 $table['w'] = $table['miw'];
21459 return [0, 0];
21461 // elseif ($table['overflow']=='wrap') { return array(0,0); } // mPDF 6
21463 if (isset($table['w']) && $table['w']) {
21465 if ($table['w'] >= $checkminwidth && $table['w'] <= $mxw) {
21466 $table['maw'] = $mxw = $table['w'];
21467 } elseif ($table['w'] >= $checkminwidth && $table['w'] > $mxw && $this->keep_table_proportions) {
21468 $checkminwidth = $table['w'];
21469 } elseif ($table['w'] < $checkminwidth && $checkminwidth < $mxw && $this->keep_table_proportions) {
21470 $table['maw'] = $table['w'] = $checkminwidth;
21471 } else {
21472 unset($table['w']);
21476 $ratio = $checkminwidth / $mxw;
21478 if ($checkminwidth > $mxw) {
21479 return [($ratio + 0.001), $checkminwidth]; // 0.001 to allow for rounded numbers when resizing
21482 unset($cs);
21484 return [0, 0];
21487 function _tableWidth(&$table)
21489 $widthcols = &$table['wc'];
21490 $numcols = $table['nc'];
21491 $tablewidth = 0;
21493 if ($table['borders_separate']) {
21494 $tblbw = $table['border_details']['L']['w'] + $table['border_details']['R']['w'] + $table['margin']['L'] + $table['margin']['R'] + $table['padding']['L'] + $table['padding']['R'] + $table['border_spacing_H'];
21495 } else {
21496 $tblbw = $table['max_cell_border_width']['L'] / 2 + $table['max_cell_border_width']['R'] / 2 + $table['margin']['L'] + $table['margin']['R'];
21499 if ($table['level'] > 1 && isset($table['w'])) {
21501 if (isset($table['wpercent']) && $table['wpercent']) {
21502 $table['w'] = $temppgwidth = (($table['w'] - $tblbw) * $table['wpercent'] / 100) + $tblbw;
21503 } else {
21504 $temppgwidth = $table['w'];
21507 } elseif ($this->table_rotate) {
21509 $temppgwidth = $this->tbrot_maxw;
21511 // If it is less than 1/20th of the remaining page height to finish the DIV (i.e. DIV padding + table bottom margin) then allow for this
21512 $enddiv = $this->blk[$this->blklvl]['padding_bottom'] + $this->blk[$this->blklvl]['border_bottom']['w'];
21514 if ($enddiv / $temppgwidth < 0.05) {
21515 $temppgwidth -= $enddiv;
21518 } else {
21520 if (isset($table['w']) && $table['w'] < $this->blk[$this->blklvl]['inner_width']) {
21521 $notfullwidth = 1;
21522 $temppgwidth = $table['w'];
21523 } elseif ($table['overflow'] == 'visible' && $table['level'] == 1) {
21524 $temppgwidth = null;
21525 } elseif ($table['overflow'] == 'hidden' && !$this->ColActive && isset($table['w']) && $table['w'] > $this->blk[$this->blklvl]['inner_width'] && $table['w'] == $table) {
21526 // $temppgwidth = $this->blk[$this->blklvl]['inner_width'];
21527 $temppgwidth = $table['w'];
21528 } else {
21529 $temppgwidth = $this->blk[$this->blklvl]['inner_width'];
21534 $totaltextlength = 0; // Added - to sum $table['l'][colno]
21535 $totalatextlength = 0; // Added - to sum $table['l'][colno] for those columns where width not set
21536 $percentages_set = 0;
21538 for ($i = 0; $i < $numcols; $i++) {
21539 if (isset($widthcols[$i]['wpercent'])) {
21540 $tablewidth += $widthcols[$i]['maw'];
21541 $percentages_set = 1;
21542 } elseif (isset($widthcols[$i]['w'])) {
21543 $tablewidth += $widthcols[$i]['miw'];
21544 } else {
21545 $tablewidth += $widthcols[$i]['maw'];
21547 $totaltextlength += isset($table['l']) ? $table['l'][$i] : 0;
21550 if (!$totaltextlength) {
21551 $totaltextlength = 1;
21554 $tablewidth += $tblbw; // Outer half of table borders
21556 if ($tablewidth > $temppgwidth) {
21557 $table['w'] = $temppgwidth;
21558 } elseif ($tablewidth < $temppgwidth && !isset($table['w']) && $percentages_set) { // if any widths set as percentages and max width fits < page width
21559 $table['w'] = $table['maw'];
21562 // if table width is set and is > allowed width
21563 if (isset($table['w']) && $table['w'] > $temppgwidth) {
21564 $table['w'] = $temppgwidth;
21567 // IF the table width is now set - Need to distribute columns widths
21568 // mPDF 5.7.3
21569 // If the table width is already set to the maximum width (e.g. nested table), then use maximum column widths exactly
21570 if (isset($table['w']) && ($table['w'] == $tablewidth) && !$percentages_set) {
21572 // This sets the columns all to maximum width
21573 for ($i = 0; $i < $numcols; $i++) {
21574 $widthcols[$i] = $widthcols[$i]['maw'];
21577 } elseif (isset($table['w'])) { // elseif the table width is set distribute width using algorithm
21579 $wis = $wisa = 0;
21580 $list = [];
21581 $notsetlist = [];
21583 for ($i = 0; $i < $numcols; $i++) {
21584 $wis += $widthcols[$i]['miw'];
21585 if (!isset($widthcols[$i]['w']) || ($widthcols[$i]['w'] && $table['w'] > $temppgwidth && !$this->keep_table_proportions && !$notfullwidth )) {
21586 $list[] = $i;
21587 $wisa += $widthcols[$i]['miw'];
21588 $totalatextlength += $table['l'][$i];
21592 if (!$totalatextlength) {
21593 $totalatextlength = 1;
21596 // Allocate spare (more than col's minimum width) across the cols according to their approx total text length
21597 // Do it by setting minimum width here
21598 if ($table['w'] > $wis + $tblbw) {
21600 // First set any cell widths set as percentages
21601 if ($table['w'] < $temppgwidth || $this->keep_table_proportions) {
21602 for ($k = 0; $k < $numcols; $k++) {
21603 if (isset($widthcols[$k]['wpercent'])) {
21604 $curr = $widthcols[$k]['miw'];
21605 $widthcols[$k]['miw'] = ($table['w'] - $tblbw) * $widthcols[$k]['wpercent'] / 100;
21606 $wis += $widthcols[$k]['miw'] - $curr;
21607 $wisa += $widthcols[$k]['miw'] - $curr;
21612 // Now allocate surplus up to maximum width of each column
21613 $surplus = 0;
21614 $ttl = 0; // number of surplus columns
21616 if (!count($list)) {
21618 $wi = ($table['w'] - ($wis + $tblbw)); // i.e. extra space to distribute
21620 for ($k = 0; $k < $numcols; $k++) {
21622 $spareratio = ($table['l'][$k] / $totaltextlength); // gives ratio to divide up free space
21624 // Don't allocate more than Maximum required width - save rest in surplus
21625 if ($widthcols[$k]['miw'] + ($wi * $spareratio) >= $widthcols[$k]['maw']) { // mPDF 5.7.3
21626 $surplus += ($wi * $spareratio) - ($widthcols[$k]['maw'] - $widthcols[$k]['miw']);
21627 $widthcols[$k]['miw'] = $widthcols[$k]['maw'];
21628 } else {
21629 $notsetlist[] = $k;
21630 $ttl += $table['l'][$k];
21631 $widthcols[$k]['miw'] += ($wi * $spareratio);
21635 } else {
21637 $wi = ($table['w'] - ($wis + $tblbw)); // i.e. extra space to distribute
21639 foreach ($list as $k) {
21641 $spareratio = ($table['l'][$k] / $totalatextlength); // gives ratio to divide up free space
21643 // Don't allocate more than Maximum required width - save rest in surplus
21644 if ($widthcols[$k]['miw'] + ($wi * $spareratio) >= $widthcols[$k]['maw']) { // mPDF 5.7.3
21645 $surplus += ($wi * $spareratio) - ($widthcols[$k]['maw'] - $widthcols[$k]['miw']);
21646 $widthcols[$k]['miw'] = $widthcols[$k]['maw'];
21647 } else {
21648 $notsetlist[] = $k;
21649 $ttl += $table['l'][$k];
21650 $widthcols[$k]['miw'] += ($wi * $spareratio);
21655 // If surplus still left over apportion it across columns
21656 if ($surplus) {
21658 if (count($notsetlist) && count($notsetlist) < $numcols) { // if some are set only add to remaining - otherwise add to all of them
21659 foreach ($notsetlist as $i) {
21660 if ($ttl) {
21661 $widthcols[$i]['miw'] += $surplus * $table['l'][$i] / $ttl;
21664 } elseif (count($list) && count($list) < $numcols) { // If some widths are defined, and others have been added up to their maxmum
21665 foreach ($list as $i) {
21666 $widthcols[$i]['miw'] += $surplus / count($list);
21668 } elseif ($numcols) { // If all columns
21669 $ttl = array_sum($table['l']);
21670 if ($ttl) {
21671 for ($i = 0; $i < $numcols; $i++) {
21672 $widthcols[$i]['miw'] += $surplus * $table['l'][$i] / $ttl;
21679 // This sets the columns all to minimum width (which has been increased above if appropriate)
21680 for ($i = 0; $i < $numcols; $i++) {
21681 $widthcols[$i] = $widthcols[$i]['miw'];
21684 // TABLE NOT WIDE ENOUGH EVEN FOR MINIMUM CONTENT WIDTH
21685 // If sum of column widths set are too wide for table
21686 $checktablewidth = 0;
21687 for ($i = 0; $i < $numcols; $i++) {
21688 $checktablewidth += $widthcols[$i];
21691 if ($checktablewidth > ($temppgwidth + 0.001 - $tblbw)) {
21693 $usedup = 0;
21694 $numleft = 0;
21696 for ($i = 0; $i < $numcols; $i++) {
21697 if ((isset($widthcols[$i]) && $widthcols[$i] > (($temppgwidth - $tblbw) / $numcols)) && (!isset($widthcols[$i]['w']))) {
21698 $numleft++;
21699 unset($widthcols[$i]);
21700 } else {
21701 $usedup += $widthcols[$i];
21705 for ($i = 0; $i < $numcols; $i++) {
21706 if (!isset($widthcols[$i]) || !$widthcols[$i]) {
21707 $widthcols[$i] = ((($temppgwidth - $tblbw) - $usedup) / ($numleft));
21712 } else { // table has no width defined
21714 $table['w'] = $tablewidth;
21716 for ($i = 0; $i < $numcols; $i++) {
21718 if (isset($widthcols[$i]['wpercent']) && $this->keep_table_proportions) {
21719 $colwidth = $widthcols[$i]['maw'];
21720 } elseif (isset($widthcols[$i]['w'])) {
21721 $colwidth = $widthcols[$i]['miw'];
21722 } else {
21723 $colwidth = $widthcols[$i]['maw'];
21726 unset($widthcols[$i]);
21727 $widthcols[$i] = $colwidth;
21732 if ($table['overflow'] === 'visible' && $table['level'] == 1) {
21734 if ($tablewidth > $this->blk[$this->blklvl]['inner_width']) {
21736 for ($j = 0; $j < $numcols; $j++) { // columns
21738 for ($i = 0; $i < $table['nr']; $i++) { // rows
21740 if (isset($table['cells'][$i][$j]) && $table['cells'][$i][$j]) {
21742 $colspan = (isset($table['cells'][$i][$j]['colspan']) ? $table['cells'][$i][$j]['colspan'] : 1);
21744 if ($colspan > 1) {
21745 $w = 0;
21747 for ($c = $j; $c < ($j + $colspan); $c++) {
21748 $w += $widthcols[$c];
21751 if ($w > $this->blk[$this->blklvl]['inner_width']) {
21752 $diff = $w - ($this->blk[$this->blklvl]['inner_width'] - $tblbw);
21753 for ($c = $j; $c < ($j + $colspan); $c++) {
21754 $widthcols[$c] -= $diff * ($widthcols[$c] / $w);
21756 $table['w'] -= $diff;
21757 $table['csp'][$j] = $w - $diff;
21765 $pgNo = 0;
21766 $currWc = 0;
21768 for ($i = 0; $i < $numcols; $i++) { // columns
21770 if (isset($table['csp'][$i])) {
21771 $w = $table['csp'][$i];
21772 unset($table['csp'][$i]);
21773 } else {
21774 $w = $widthcols[$i];
21777 if (($currWc + $w + $tblbw) > $this->blk[$this->blklvl]['inner_width']) {
21778 $pgNo++;
21779 $currWc = $widthcols[$i];
21780 } else {
21781 $currWc += $widthcols[$i];
21784 $table['colPg'][$i] = $pgNo;
21789 function _tableHeight(&$table)
21791 $level = $table['level'];
21792 $levelid = $table['levelid'];
21793 $cells = &$table['cells'];
21794 $numcols = $table['nc'];
21795 $numrows = $table['nr'];
21796 $listspan = [];
21797 $checkmaxheight = 0;
21798 $headerrowheight = 0;
21799 $checkmaxheightplus = 0;
21800 $headerrowheightplus = 0;
21801 $firstrowheight = 0;
21803 $footerrowheight = 0;
21804 $footerrowheightplus = 0;
21805 if ($this->table_rotate) {
21806 $temppgheight = $this->tbrot_maxh;
21807 $remainingpage = $this->tbrot_maxh;
21808 } else {
21809 $temppgheight = ($this->h - $this->bMargin - $this->tMargin) - $this->kwt_height;
21810 $remainingpage = ($this->h - $this->bMargin - $this->y) - $this->kwt_height;
21812 // If it is less than 1/20th of the remaining page height to finish the DIV (i.e. DIV padding + table bottom margin)
21813 // then allow for this
21814 $enddiv = $this->blk[$this->blklvl]['padding_bottom'] + $this->blk[$this->blklvl]['border_bottom']['w'] + $table['margin']['B'];
21815 if ($remainingpage > $enddiv && $enddiv / $remainingpage < 0.05) {
21816 $remainingpage -= $enddiv;
21817 } elseif ($remainingpage == 0) {
21818 $remainingpage = 0.001;
21820 if ($temppgheight > $enddiv && $enddiv / $temppgheight < 0.05) {
21821 $temppgheight -= $enddiv;
21822 } elseif ($temppgheight == 0) {
21823 $temppgheight = 0.001;
21826 if ($remainingpage < 0) {
21827 $remainingpage = 0.001;
21829 if ($temppgheight < 0) {
21830 $temppgheight = 0.001;
21833 for ($i = 0; $i < $numrows; $i++) { // rows
21834 $heightrow = &$table['hr'][$i];
21835 for ($j = 0; $j < $numcols; $j++) { // columns
21836 if (isset($cells[$i][$j]) && $cells[$i][$j]) {
21837 $c = &$cells[$i][$j];
21839 if ($this->simpleTables) {
21840 if ($table['borders_separate']) { // NB twice border width
21841 $extraWLR = ($table['simple']['border_details']['L']['w'] + $table['simple']['border_details']['R']['w']) + ($c['padding']['L'] + $c['padding']['R']) + $table['border_spacing_H'];
21842 $extrh = ($table['simple']['border_details']['T']['w'] + $table['simple']['border_details']['B']['w']) + ($c['padding']['T'] + $c['padding']['B']) + $table['border_spacing_V'];
21843 } else {
21844 $extraWLR = ($table['simple']['border_details']['L']['w'] + $table['simple']['border_details']['R']['w']) / 2 + ($c['padding']['L'] + $c['padding']['R']);
21845 $extrh = ($table['simple']['border_details']['T']['w'] + $table['simple']['border_details']['B']['w']) / 2 + ($c['padding']['T'] + $c['padding']['B']);
21847 } else {
21848 if ($this->packTableData) {
21849 list($bt, $br, $bb, $bl) = $this->_getBorderWidths($c['borderbin']);
21850 } else {
21851 $bt = $c['border_details']['T']['w'];
21852 $bb = $c['border_details']['B']['w'];
21853 $br = $c['border_details']['R']['w'];
21854 $bl = $c['border_details']['L']['w'];
21856 if ($table['borders_separate']) { // NB twice border width
21857 $extraWLR = $bl + $br + $c['padding']['L'] + $c['padding']['R'] + $table['border_spacing_H'];
21858 $extrh = $bt + $bb + $c['padding']['T'] + $c['padding']['B'] + $table['border_spacing_V'];
21859 } else {
21860 $extraWLR = $bl / 2 + $br / 2 + $c['padding']['L'] + $c['padding']['R'];
21861 $extrh = $bt / 2 + $bb / 2 + $c['padding']['T'] + $c['padding']['B'];
21865 if ($table['overflow'] == 'visible' && $level == 1) {
21866 list($x, $cw) = $this->_splitTableGetWidth($table, $i, $j);
21867 } else {
21868 list($x, $cw) = $this->_tableGetWidth($table, $i, $j);
21872 // Get CELL HEIGHT
21873 // ++ extra parameter forces wrap to break word
21874 if ($c['R'] && isset($c['textbuffer'])) {
21875 $str = '';
21876 foreach ($c['textbuffer'] as $t) {
21877 $str .= $t[0] . ' ';
21879 $str = rtrim($str);
21880 $s_fs = $this->FontSizePt;
21881 $s_f = $this->FontFamily;
21882 $s_st = $this->FontStyle;
21883 $this->SetFont($c['textbuffer'][0][4], $c['textbuffer'][0][2], $c['textbuffer'][0][11] / $this->shrin_k, true, true);
21884 $tempch = $this->GetStringWidth($str, true, $c['textbuffer'][0][18], $c['textbuffer'][0][8]);
21885 if ($c['R'] >= 45 && $c['R'] < 90) {
21886 $tempch = ((sin(deg2rad($c['R']))) * $tempch ) + ((sin(deg2rad($c['R']))) * (($c['textbuffer'][0][11] / Mpdf::SCALE) / $this->shrin_k));
21888 $this->SetFont($s_f, $s_st, $s_fs, true, true);
21889 $ch = ($tempch ) + $extrh;
21890 } else {
21891 if (isset($c['textbuffer']) && !empty($c['textbuffer'])) {
21892 $this->cellLineHeight = $c['cellLineHeight'];
21893 $this->cellLineStackingStrategy = $c['cellLineStackingStrategy'];
21894 $this->cellLineStackingShift = $c['cellLineStackingShift'];
21895 $this->divwidth = $cw - $extraWLR;
21896 $tempch = $this->printbuffer($c['textbuffer'], '', true, true);
21897 } else {
21898 $tempch = 0;
21901 // Added cellpadding top and bottom. (Lineheight already adjusted)
21902 $ch = $tempch + $extrh;
21904 // If height is defined and it is bigger than calculated $ch then update values
21905 if (isset($c['h']) && $c['h'] > $ch) {
21906 $c['mih'] = $ch; // in order to keep valign working
21907 $ch = $c['h'];
21908 } else {
21909 $c['mih'] = $ch;
21911 if (isset($c['rowspan'])) {
21912 $listspan[] = [$i, $j];
21913 } elseif ($heightrow < $ch) {
21914 $heightrow = $ch;
21917 // this is the extra used in _tableWrite to determine whether to trigger a page change
21918 if ($table['borders_separate']) {
21919 if ($i == ($numrows - 1) || (isset($c['rowspan']) && ($i + $c['rowspan']) == ($numrows))) {
21920 $extra = $table['margin']['B'] + $table['padding']['B'] + $table['border_details']['B']['w'] + $table['border_spacing_V'] / 2;
21921 } else {
21922 $extra = $table['border_spacing_V'] / 2;
21924 } else {
21925 if (!$this->simpleTables) {
21926 $extra = $bb / 2;
21927 } elseif ($this->simpleTables) {
21928 $extra = $table['simple']['border_details']['B']['w'] / 2;
21931 if (isset($table['is_thead'][$i]) && $table['is_thead'][$i]) {
21932 if ($j == 0) {
21933 $headerrowheight += $ch;
21934 $headerrowheightplus += $ch + $extra;
21936 } elseif (isset($table['is_tfoot'][$i]) && $table['is_tfoot'][$i]) {
21937 if ($j == 0) {
21938 $footerrowheight += $ch;
21939 $footerrowheightplus += $ch + $extra;
21941 } else {
21942 $checkmaxheight = max($checkmaxheight, $ch);
21943 $checkmaxheightplus = max($checkmaxheightplus, $ch + $extra);
21945 if ($this->tableLevel == 1 && $i == (isset($table['headernrows']) ? $table['headernrows'] : 0)) {
21946 $firstrowheight = max($ch, $firstrowheight);
21948 unset($c);
21950 }//end of columns
21951 }//end of rows
21953 $heightrow = &$table['hr'];
21954 foreach ($listspan as $span) {
21955 list($i, $j) = $span;
21956 $c = &$cells[$i][$j];
21957 $lr = $i + $c['rowspan'];
21958 if ($lr > $numrows) {
21959 $lr = $numrows;
21961 $hs = $hsa = 0;
21962 $list = [];
21963 for ($k = $i; $k < $lr; $k++) {
21964 $hs += $heightrow[$k];
21965 // mPDF 6
21966 $sh = false; // specified height
21967 for ($m = 0; $m < $numcols; $m++) { // columns
21968 $tc = &$cells[$k][$m];
21969 if (isset($tc['rowspan'])) {
21970 continue;
21972 if (isset($tc['h'])) {
21973 $sh = true;
21974 break;
21977 if (!$sh) {
21978 $list[] = $k;
21982 if ($table['borders_separate']) {
21983 if ($i == ($numrows - 1) || ($i + $c['rowspan']) == ($numrows)) {
21984 $extra = $table['margin']['B'] + $table['padding']['B'] + $table['border_details']['B']['w'] + $table['border_spacing_V'] / 2;
21985 } else {
21986 $extra = $table['border_spacing_V'] / 2;
21988 } else {
21989 if (!$this->simpleTables) {
21990 if ($this->packTableData) {
21991 list($bt, $br, $bb, $bl) = $this->_getBorderWidths($c['borderbin']);
21992 } else {
21993 $bb = $c['border_details']['B']['w'];
21995 $extra = $bb / 2;
21996 } elseif ($this->simpleTables) {
21997 $extra = $table['simple']['border_details']['B']['w'] / 2;
22000 if (!empty($table['is_thead'][$i])) {
22001 $headerrowheight = max($headerrowheight, $hs);
22002 $headerrowheightplus = max($headerrowheightplus, $hs + $extra);
22003 } elseif (!empty($table['is_tfoot'][$i])) {
22004 $footerrowheight = max($footerrowheight, $hs);
22005 $footerrowheightplus = max($footerrowheightplus, $hs + $extra);
22006 } else {
22007 $checkmaxheight = max($checkmaxheight, $hs);
22008 $checkmaxheightplus = max($checkmaxheightplus, $hs + $extra);
22010 if ($this->tableLevel == 1 && $i == (isset($table['headernrows']) ? $table['headernrows'] : 0)) {
22011 $firstrowheight = max($hs, $firstrowheight);
22014 if ($c['mih'] > $hs) {
22015 if (!$hs) {
22016 for ($k = $i; $k < $lr; $k++) {
22017 $heightrow[$k] = $c['mih'] / $c['rowspan'];
22019 } elseif (!count($list)) { // no rows in the rowspan have a height specified, so share amongst all rows equally
22020 $hi = $c['mih'] - $hs;
22021 for ($k = $i; $k < $lr; $k++) {
22022 $heightrow[$k] += ($heightrow[$k] / $hs) * $hi;
22024 } else {
22025 $hi = $c['mih'] - $hs; // mPDF 6
22026 foreach ($list as $k) {
22027 $heightrow[$k] += $hi / (count($list)); // mPDF 6
22031 unset($c);
22033 // If rowspans overlap so that one or more rows do not have a height set...
22034 // i.e. for one or more rows, the only cells (explicit) in that row have rowspan>1
22035 // so heightrow is still == 0
22036 if ($heightrow[$i] == 0) {
22037 // Get row extent to analyse above and below
22038 $top = $i;
22039 foreach ($listspan as $checkspan) {
22040 list($cki, $ckj) = $checkspan;
22041 $c = &$cells[$cki][$ckj];
22042 if (isset($c['rowspan']) && $c['rowspan'] > 1) {
22043 if (($cki + $c['rowspan'] - 1) >= $i) {
22044 $top = min($top, $cki);
22048 $bottom = $i + $c['rowspan'] - 1;
22049 // Check for overconstrained conditions
22050 for ($k = $top; $k <= $bottom; $k++) {
22051 // if ['hr'] for any of the others is also 0, then abort (too complicated)
22052 if ($k != $i && $heightrow[$k] == 0) {
22053 break(1);
22055 // check again that top and bottom are not crossed by rowspans - or abort (too complicated)
22056 if ($k == $top) {
22057 // ???? take account of colspan as well???
22058 for ($m = 0; $m < $numcols; $m++) { // columns
22059 if (!isset($cells[$k][$m]) || $cells[$k][$m] == 0) {
22060 break(2);
22063 } elseif ($k == $bottom) {
22064 // ???? take account of colspan as well???
22065 for ($m = 0; $m < $numcols; $m++) { // columns
22066 $c = &$cells[$k][$m];
22067 if (isset($c['rowspan']) && $c['rowspan'] > 1) {
22068 break(2);
22073 // By columns add up col height using ['h'] if set or ['mih'] if not
22074 // Intentionally do not substract border-spacing
22075 $colH = [];
22076 $extH = 0;
22077 $newhr = [];
22078 for ($m = 0; $m < $numcols; $m++) { // columns
22079 for ($k = $top; $k <= $bottom; $k++) {
22080 if (isset($cells[$k][$m]) && $cells[$k][$m] != 0) {
22081 $c = &$cells[$k][$m];
22082 if (isset($c['h']) && $c['h']) {
22083 $useh = $c['h'];
22084 } // ???? take account of colspan as well???
22085 else {
22086 $useh = $c['mih'];
22088 if (isset($colH[$m])) {
22089 $colH[$m] += $useh;
22090 } else {
22091 $colH[$m] = $useh;
22093 if (!isset($c['rowspan']) || $c['rowspan'] < 2) {
22094 $newhr[$k] = max((isset($newhr[$k]) ? $newhr[$k] : 0), $useh);
22098 $extH = max($extH, $colH[$m]); // mPDF 6
22100 $newhr[$i] = $extH - array_sum($newhr);
22101 for ($k = $top; $k <= $bottom; $k++) {
22102 $heightrow[$k] = $newhr[$k];
22107 unset($c);
22110 $table['h'] = array_sum($heightrow);
22111 unset($heightrow);
22113 if ($table['borders_separate']) {
22114 $table['h'] += $table['margin']['T'] + $table['margin']['B'] + $table['border_details']['T']['w'] + $table['border_details']['B']['w'] + $table['border_spacing_V'] + $table['padding']['T'] + $table['padding']['B'];
22115 } else {
22116 $table['h'] += $table['margin']['T'] + $table['margin']['B'] + $table['max_cell_border_width']['T'] / 2 + $table['max_cell_border_width']['B'] / 2;
22119 $maxrowheight = $checkmaxheightplus + $headerrowheightplus + $footerrowheightplus;
22120 $maxfirstrowheight = $firstrowheight + $headerrowheightplus + $footerrowheightplus; // includes thead, 1st row and tfoot
22121 return [$table['h'], $maxrowheight, $temppgheight, $remainingpage, $maxfirstrowheight];
22124 function _tableGetWidth(&$table, $i, $j)
22126 $cell = &$table['cells'][$i][$j];
22127 if ($cell) {
22128 if (isset($cell['x0'])) {
22129 return [$cell['x0'], $cell['w0']];
22131 $x = 0;
22132 $widthcols = &$table['wc'];
22133 for ($k = 0; $k < $j; $k++) {
22134 $x += $widthcols[$k];
22136 $w = $widthcols[$j];
22137 if (isset($cell['colspan'])) {
22138 for ($k = $j + $cell['colspan'] - 1; $k > $j; $k--) {
22139 $w += $widthcols[$k];
22142 $cell['x0'] = $x;
22143 $cell['w0'] = $w;
22144 return [$x, $w];
22146 return [0, 0];
22149 function _splitTableGetWidth(&$table, $i, $j)
22151 $cell = &$table['cells'][$i][$j];
22152 if ($cell) {
22153 if (isset($cell['x0'])) {
22154 return [$cell['x0'], $cell['w0']];
22156 $x = 0;
22157 $widthcols = &$table['wc'];
22158 $pg = $table['colPg'][$j];
22159 for ($k = 0; $k < $j; $k++) {
22160 if ($table['colPg'][$k] == $pg) {
22161 $x += $widthcols[$k];
22164 $w = $widthcols[$j];
22165 if (isset($cell['colspan'])) {
22166 for ($k = $j + $cell['colspan'] - 1; $k > $j; $k--) {
22167 if ($table['colPg'][$k] == $pg) {
22168 $w += $widthcols[$k];
22172 $cell['x0'] = $x;
22173 $cell['w0'] = $w;
22174 return [$x, $w];
22176 return [0, 0];
22179 function _tableGetHeight(&$table, $i, $j)
22181 $cell = &$table['cells'][$i][$j];
22182 if ($cell) {
22183 if (isset($cell['y0'])) {
22184 return [$cell['y0'], $cell['h0']];
22186 $y = 0;
22187 $heightrow = &$table['hr'];
22188 for ($k = 0; $k < $i; $k++) {
22189 $y += $heightrow[$k];
22191 $h = $heightrow[$i];
22192 if (isset($cell['rowspan'])) {
22193 for ($k = $i + $cell['rowspan'] - 1; $k > $i; $k--) {
22194 $h += $heightrow[$k];
22197 $cell['y0'] = $y;
22198 $cell['h0'] = $h;
22199 return [$y, $h];
22201 return [0, 0];
22204 function _tableGetMaxRowHeight($table, $row)
22206 if ($row == $table['nc'] - 1) {
22207 return $table['hr'][$row];
22209 $maxrowheight = $table['hr'][$row];
22210 for ($i = $row + 1; $i < $table['nr']; $i++) {
22211 $cellsset = 0;
22212 for ($j = 0; $j < $table['nc']; $j++) {
22213 if ($table['cells'][$i][$j]) {
22214 if (isset($table['cells'][$i][$j]['colspan'])) {
22215 $cellsset += $table['cells'][$i][$j]['colspan'];
22216 } else {
22217 $cellsset += 1;
22221 if ($cellsset == $table['nc']) {
22222 return $maxrowheight;
22223 } else {
22224 $maxrowheight += $table['hr'][$i];
22227 return $maxrowheight;
22230 // CHANGED TO ALLOW TABLE BORDER TO BE SPECIFIED CORRECTLY - added border_details
22231 function _tableRect($x, $y, $w, $h, $bord = -1, $details = [], $buffer = false, $bSeparate = false, $cort = 'cell', $tablecorner = '', $bsv = 0, $bsh = 0)
22233 $cellBorderOverlay = [];
22235 if ($bord == -1) {
22236 $this->Rect($x, $y, $w, $h);
22237 } elseif ($this->simpleTables && ($cort == 'cell')) {
22238 $this->SetLineWidth($details['L']['w']);
22239 if ($details['L']['c']) {
22240 $this->SetDColor($details['L']['c']);
22241 } else {
22242 $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
22244 $this->SetLineJoin(0);
22245 $this->Rect($x, $y, $w, $h);
22246 } elseif ($bord) {
22247 if (!$bSeparate && $buffer) {
22248 $priority = 'LRTB';
22249 for ($p = 0; $p < strlen($priority); $p++) {
22250 $side = $priority[$p];
22251 $details['p'] = $side;
22253 $dom = 0;
22254 if (isset($details[$side]['w'])) {
22255 $dom += ($details[$side]['w'] * 100000);
22257 if (isset($details[$side]['style'])) {
22258 $dom += (array_search($details[$side]['style'], $this->borderstyles) * 100);
22260 if (isset($details[$side]['dom'])) {
22261 $dom += ($details[$side]['dom'] * 10);
22264 // Precedence to darker colours at joins
22265 $coldom = 0;
22266 if (isset($details[$side]['c']) && is_array($details[$side]['c'])) {
22267 if ($details[$side]['c']{0} == 3) { // RGB
22268 $coldom = 10 - (((ord($details[$side]['c']{1}) * 1.00) + (ord($details[$side]['c']{2}) * 1.00) + (ord($details[$side]['c']{3}) * 1.00)) / 76.5);
22270 } // 10 black - 0 white
22271 if ($coldom) {
22272 $dom += $coldom;
22274 // Lastly precedence to RIGHT and BOTTOM cells at joins
22275 if (isset($details['cellposdom'])) {
22276 $dom += $details['cellposdom'];
22279 $save = false;
22280 if ($side == 'T' && $this->issetBorder($bord, Border::TOP)) {
22281 $cbord = Border::TOP;
22282 $save = true;
22283 } elseif ($side == 'L' && $this->issetBorder($bord, Border::LEFT)) {
22284 $cbord = Border::LEFT;
22285 $save = true;
22286 } elseif ($side == 'R' && $this->issetBorder($bord, Border::RIGHT)) {
22287 $cbord = Border::RIGHT;
22288 $save = true;
22289 } elseif ($side == 'B' && $this->issetBorder($bord, Border::BOTTOM)) {
22290 $cbord = Border::BOTTOM;
22291 $save = true;
22294 if ($save) {
22295 $this->cellBorderBuffer[] = pack("A16nCnda6A10d14", str_pad(sprintf("%08.7f", $dom), 16, "0", STR_PAD_LEFT), $cbord, ord($side), $details[$side]['s'], $details[$side]['w'], $details[$side]['c'], $details[$side]['style'], $x, $y, $w, $h, $details['mbw']['BL'], $details['mbw']['BR'], $details['mbw']['RT'], $details['mbw']['RB'], $details['mbw']['TL'], $details['mbw']['TR'], $details['mbw']['LT'], $details['mbw']['LB'], $details['cellposdom'], 0);
22296 if ($details[$side]['style'] == 'ridge' || $details[$side]['style'] == 'groove' || $details[$side]['style'] == 'inset' || $details[$side]['style'] == 'outset' || $details[$side]['style'] == 'double') {
22297 $details[$side]['overlay'] = true;
22298 $this->cellBorderBuffer[] = pack("A16nCnda6A10d14", str_pad(sprintf("%08.7f", ($dom + 4)), 16, "0", STR_PAD_LEFT), $cbord, ord($side), $details[$side]['s'], $details[$side]['w'], $details[$side]['c'], $details[$side]['style'], $x, $y, $w, $h, $details['mbw']['BL'], $details['mbw']['BR'], $details['mbw']['RT'], $details['mbw']['RB'], $details['mbw']['TL'], $details['mbw']['TR'], $details['mbw']['LT'], $details['mbw']['LB'], $details['cellposdom'], 1);
22302 return;
22305 if (isset($details['p']) && strlen($details['p']) > 1) {
22306 $priority = $details['p'];
22307 } else {
22308 $priority = 'LTRB';
22310 $Tw = 0;
22311 $Rw = 0;
22312 $Bw = 0;
22313 $Lw = 0;
22314 if (isset($details['T']['w'])) {
22315 $Tw = $details['T']['w'];
22317 if (isset($details['R']['w'])) {
22318 $Rw = $details['R']['w'];
22320 if (isset($details['B']['w'])) {
22321 $Bw = $details['B']['w'];
22323 if (isset($details['L']['w'])) {
22324 $Lw = $details['L']['w'];
22327 $x2 = $x + $w;
22328 $y2 = $y + $h;
22329 $oldlinewidth = $this->LineWidth;
22331 for ($p = 0; $p < strlen($priority); $p++) {
22332 $side = $priority[$p];
22333 $xadj = 0;
22334 $xadj2 = 0;
22335 $yadj = 0;
22336 $yadj2 = 0;
22337 $print = false;
22338 if ($Tw && $side == 'T' && $this->issetBorder($bord, Border::TOP)) { // TOP
22339 $ly1 = $y;
22340 $ly2 = $y;
22341 $lx1 = $x;
22342 $lx2 = $x2;
22343 $this->SetLineWidth($Tw);
22344 if ($cort == 'cell' || strpos($tablecorner, 'L') !== false) {
22345 if ($Tw > $Lw) {
22346 $xadj = ($Tw - $Lw) / 2;
22348 if ($Tw < $Lw) {
22349 $xadj = ($Tw + $Lw) / 2;
22351 } else {
22352 $xadj = $Tw / 2 - $bsh / 2;
22354 if ($cort == 'cell' || strpos($tablecorner, 'R') !== false) {
22355 if ($Tw > $Rw) {
22356 $xadj2 = ($Tw - $Rw) / 2;
22358 if ($Tw < $Rw) {
22359 $xadj2 = ($Tw + $Rw) / 2;
22361 } else {
22362 $xadj2 = $Tw / 2 - $bsh / 2;
22364 if (!$bSeparate && !empty($details['mbw']) && !empty($details['mbw']['TL'])) {
22365 $xadj = ($Tw - $details['mbw']['TL']) / 2;
22367 if (!$bSeparate && !empty($details['mbw']) && !empty($details['mbw']['TR'])) {
22368 $xadj2 = ($Tw - $details['mbw']['TR']) / 2;
22370 $print = true;
22372 if ($Lw && $side == 'L' && $this->issetBorder($bord, Border::LEFT)) { // LEFT
22373 $ly1 = $y;
22374 $ly2 = $y2;
22375 $lx1 = $x;
22376 $lx2 = $x;
22377 $this->SetLineWidth($Lw);
22378 if ($cort == 'cell' || strpos($tablecorner, 'T') !== false) {
22379 if ($Lw > $Tw) {
22380 $yadj = ($Lw - $Tw) / 2;
22382 if ($Lw < $Tw) {
22383 $yadj = ($Lw + $Tw) / 2;
22385 } else {
22386 $yadj = $Lw / 2 - $bsv / 2;
22388 if ($cort == 'cell' || strpos($tablecorner, 'B') !== false) {
22389 if ($Lw > $Bw) {
22390 $yadj2 = ($Lw - $Bw) / 2;
22392 if ($Lw < $Bw) {
22393 $yadj2 = ($Lw + $Bw) / 2;
22395 } else {
22396 $yadj2 = $Lw / 2 - $bsv / 2;
22398 if (!$bSeparate && $details['mbw']['LT']) {
22399 $yadj = ($Lw - $details['mbw']['LT']) / 2;
22401 if (!$bSeparate && $details['mbw']['LB']) {
22402 $yadj2 = ($Lw - $details['mbw']['LB']) / 2;
22404 $print = true;
22406 if ($Rw && $side == 'R' && $this->issetBorder($bord, Border::RIGHT)) { // RIGHT
22407 $ly1 = $y;
22408 $ly2 = $y2;
22409 $lx1 = $x2;
22410 $lx2 = $x2;
22411 $this->SetLineWidth($Rw);
22412 if ($cort == 'cell' || strpos($tablecorner, 'T') !== false) {
22413 if ($Rw < $Tw) {
22414 $yadj = ($Rw + $Tw) / 2;
22416 if ($Rw > $Tw) {
22417 $yadj = ($Rw - $Tw) / 2;
22419 } else {
22420 $yadj = $Rw / 2 - $bsv / 2;
22423 if ($cort == 'cell' || strpos($tablecorner, 'B') !== false) {
22424 if ($Rw > $Bw) {
22425 $yadj2 = ($Rw - $Bw) / 2;
22427 if ($Rw < $Bw) {
22428 $yadj2 = ($Rw + $Bw) / 2;
22430 } else {
22431 $yadj2 = $Rw / 2 - $bsv / 2;
22434 if (!$bSeparate && !empty($details['mbw']) && !empty($details['mbw']['RT'])) {
22435 $yadj = ($Rw - $details['mbw']['RT']) / 2;
22437 if (!$bSeparate && !empty($details['mbw']) && !empty($details['mbw']['RB'])) {
22438 $yadj2 = ($Rw - $details['mbw']['RB']) / 2;
22440 $print = true;
22442 if ($Bw && $side == 'B' && $this->issetBorder($bord, Border::BOTTOM)) { // BOTTOM
22443 $ly1 = $y2;
22444 $ly2 = $y2;
22445 $lx1 = $x;
22446 $lx2 = $x2;
22447 $this->SetLineWidth($Bw);
22448 if ($cort == 'cell' || strpos($tablecorner, 'L') !== false) {
22449 if ($Bw > $Lw) {
22450 $xadj = ($Bw - $Lw) / 2;
22452 if ($Bw < $Lw) {
22453 $xadj = ($Bw + $Lw) / 2;
22455 } else {
22456 $xadj = $Bw / 2 - $bsh / 2;
22458 if ($cort == 'cell' || strpos($tablecorner, 'R') !== false) {
22459 if ($Bw > $Rw) {
22460 $xadj2 = ($Bw - $Rw) / 2;
22462 if ($Bw < $Rw) {
22463 $xadj2 = ($Bw + $Rw) / 2;
22465 } else {
22466 $xadj2 = $Bw / 2 - $bsh / 2;
22468 if (!$bSeparate && isset($details['mbw']) && isset($details['mbw']['BL'])) {
22469 $xadj = ($Bw - $details['mbw']['BL']) / 2;
22471 if (!$bSeparate && isset($details['mbw']) && isset($details['mbw']['BR'])) {
22472 $xadj2 = ($Bw - $details['mbw']['BR']) / 2;
22474 $print = true;
22477 // Now draw line
22478 if ($print) {
22479 /* -- TABLES-ADVANCED-BORDERS -- */
22480 if ($details[$side]['style'] == 'double') {
22481 if (!isset($details[$side]['overlay']) || !$details[$side]['overlay'] || $bSeparate) {
22482 if ($details[$side]['c']) {
22483 $this->SetDColor($details[$side]['c']);
22484 } else {
22485 $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
22487 $this->Line($lx1 + $xadj, $ly1 + $yadj, $lx2 - $xadj2, $ly2 - $yadj2);
22489 if ((isset($details[$side]['overlay']) && $details[$side]['overlay']) || $bSeparate) {
22490 if ($bSeparate && $cort == 'table') {
22491 if ($side == 'T') {
22492 $xadj -= $this->LineWidth / 2;
22493 $xadj2 -= $this->LineWidth;
22494 if ($this->issetBorder($bord, Border::LEFT)) {
22495 $xadj += $this->LineWidth / 2;
22497 if ($this->issetBorder($bord, Border::RIGHT)) {
22498 $xadj2 += $this->LineWidth;
22501 if ($side == 'L') {
22502 $yadj -= $this->LineWidth / 2;
22503 $yadj2 -= $this->LineWidth;
22504 if ($this->issetBorder($bord, Border::TOP)) {
22505 $yadj += $this->LineWidth / 2;
22507 if ($this->issetBorder($bord, Border::BOTTOM)) {
22508 $yadj2 += $this->LineWidth;
22511 if ($side == 'B') {
22512 $xadj -= $this->LineWidth / 2;
22513 $xadj2 -= $this->LineWidth;
22514 if ($this->issetBorder($bord, Border::LEFT)) {
22515 $xadj += $this->LineWidth / 2;
22517 if ($this->issetBorder($bord, Border::RIGHT)) {
22518 $xadj2 += $this->LineWidth;
22521 if ($side == 'R') {
22522 $yadj -= $this->LineWidth / 2;
22523 $yadj2 -= $this->LineWidth;
22524 if ($this->issetBorder($bord, Border::TOP)) {
22525 $yadj += $this->LineWidth / 2;
22527 if ($this->issetBorder($bord, Border::BOTTOM)) {
22528 $yadj2 += $this->LineWidth;
22533 $this->SetLineWidth($this->LineWidth / 3);
22535 $tbcol = $this->colorConverter->convert(255, $this->PDFAXwarnings);
22536 for ($l = 0; $l <= $this->blklvl; $l++) {
22537 if ($this->blk[$l]['bgcolor']) {
22538 $tbcol = ($this->blk[$l]['bgcolorarray']);
22542 if ($bSeparate) {
22543 $cellBorderOverlay[] = [
22544 'x' => $lx1 + $xadj,
22545 'y' => $ly1 + $yadj,
22546 'x2' => $lx2 - $xadj2,
22547 'y2' => $ly2 - $yadj2,
22548 'col' => $tbcol,
22549 'lw' => $this->LineWidth,
22551 } else {
22552 $this->SetDColor($tbcol);
22553 $this->Line($lx1 + $xadj, $ly1 + $yadj, $lx2 - $xadj2, $ly2 - $yadj2);
22556 } elseif (isset($details[$side]['style']) && ($details[$side]['style'] == 'ridge' || $details[$side]['style'] == 'groove' || $details[$side]['style'] == 'inset' || $details[$side]['style'] == 'outset')) {
22557 if (!isset($details[$side]['overlay']) || !$details[$side]['overlay'] || $bSeparate) {
22558 if ($details[$side]['c']) {
22559 $this->SetDColor($details[$side]['c']);
22560 } else {
22561 $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
22563 if ($details[$side]['style'] == 'outset' || $details[$side]['style'] == 'groove') {
22564 $nc = $this->colorConverter->darken($details[$side]['c']);
22565 $this->SetDColor($nc);
22566 } elseif ($details[$side]['style'] == 'ridge' || $details[$side]['style'] == 'inset') {
22567 $nc = $this->colorConverter->lighten($details[$side]['c']);
22568 $this->SetDColor($nc);
22570 $this->Line($lx1 + $xadj, $ly1 + $yadj, $lx2 - $xadj2, $ly2 - $yadj2);
22572 if ((isset($details[$side]['overlay']) && $details[$side]['overlay']) || $bSeparate) {
22573 if ($details[$side]['c']) {
22574 $this->SetDColor($details[$side]['c']);
22575 } else {
22576 $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
22578 $doubleadj = ($this->LineWidth) / 3;
22579 $this->SetLineWidth($this->LineWidth / 2);
22580 $xadj3 = $yadj3 = $wadj3 = $hadj3 = 0;
22582 if ($details[$side]['style'] == 'ridge' || $details[$side]['style'] == 'inset') {
22583 $nc = $this->colorConverter->darken($details[$side]['c']);
22585 if ($bSeparate && $cort == 'table') {
22586 if ($side == 'T') {
22587 $yadj3 = $this->LineWidth / 2;
22588 $xadj3 = -$this->LineWidth / 2;
22589 $wadj3 = $this->LineWidth;
22590 if ($this->issetBorder($bord, Border::LEFT)) {
22591 $xadj3 += $this->LineWidth;
22592 $wadj3 -= $this->LineWidth;
22594 if ($this->issetBorder($bord, Border::RIGHT)) {
22595 $wadj3 -= $this->LineWidth * 2;
22598 if ($side == 'L') {
22599 $xadj3 = $this->LineWidth / 2;
22600 $yadj3 = -$this->LineWidth / 2;
22601 $hadj3 = $this->LineWidth;
22602 if ($this->issetBorder($bord, Border::TOP)) {
22603 $yadj3 += $this->LineWidth;
22604 $hadj3 -= $this->LineWidth;
22606 if ($this->issetBorder($bord, Border::BOTTOM)) {
22607 $hadj3 -= $this->LineWidth * 2;
22610 if ($side == 'B') {
22611 $yadj3 = $this->LineWidth / 2;
22612 $xadj3 = -$this->LineWidth / 2;
22613 $wadj3 = $this->LineWidth;
22615 if ($side == 'R') {
22616 $xadj3 = $this->LineWidth / 2;
22617 $yadj3 = -$this->LineWidth / 2;
22618 $hadj3 = $this->LineWidth;
22620 } elseif ($side == 'T') {
22621 $yadj3 = $this->LineWidth / 2;
22622 $xadj3 = $this->LineWidth / 2;
22623 $wadj3 = -$this->LineWidth * 2;
22624 } elseif ($side == 'L') {
22625 $xadj3 = $this->LineWidth / 2;
22626 $yadj3 = $this->LineWidth / 2;
22627 $hadj3 = -$this->LineWidth * 2;
22628 } elseif ($side == 'B' && $bSeparate) {
22629 $yadj3 = $this->LineWidth / 2;
22630 $wadj3 = $this->LineWidth / 2;
22631 } elseif ($side == 'R' && $bSeparate) {
22632 $xadj3 = $this->LineWidth / 2;
22633 $hadj3 = $this->LineWidth / 2;
22634 } elseif ($side == 'B') {
22635 $yadj3 = $this->LineWidth / 2;
22636 $xadj3 = $this->LineWidth / 2;
22637 } elseif ($side == 'R') {
22638 $xadj3 = $this->LineWidth / 2;
22639 $yadj3 = $this->LineWidth / 2;
22641 } else {
22642 $nc = $this->colorConverter->lighten($details[$side]['c']);
22644 if ($bSeparate && $cort == 'table') {
22645 if ($side == 'T') {
22646 $yadj3 = $this->LineWidth / 2;
22647 $xadj3 = -$this->LineWidth / 2;
22648 $wadj3 = $this->LineWidth;
22649 if ($this->issetBorder($bord, Border::LEFT)) {
22650 $xadj3 += $this->LineWidth;
22651 $wadj3 -= $this->LineWidth;
22654 if ($side == 'L') {
22655 $xadj3 = $this->LineWidth / 2;
22656 $yadj3 = -$this->LineWidth / 2;
22657 $hadj3 = $this->LineWidth;
22658 if ($this->issetBorder($bord, Border::TOP)) {
22659 $yadj3 += $this->LineWidth;
22660 $hadj3 -= $this->LineWidth;
22663 if ($side == 'B') {
22664 $yadj3 = $this->LineWidth / 2;
22665 $xadj3 = -$this->LineWidth / 2;
22666 $wadj3 = $this->LineWidth;
22667 if ($this->issetBorder($bord, Border::LEFT)) {
22668 $xadj3 += $this->LineWidth;
22669 $wadj3 -= $this->LineWidth;
22672 if ($side == 'R') {
22673 $xadj3 = $this->LineWidth / 2;
22674 $yadj3 = -$this->LineWidth / 2;
22675 $hadj3 = $this->LineWidth;
22676 if ($this->issetBorder($bord, Border::TOP)) {
22677 $yadj3 += $this->LineWidth;
22678 $hadj3 -= $this->LineWidth;
22681 } elseif ($side == 'T') {
22682 $yadj3 = $this->LineWidth / 2;
22683 $xadj3 = $this->LineWidth / 2;
22684 } elseif ($side == 'L') {
22685 $xadj3 = $this->LineWidth / 2;
22686 $yadj3 = $this->LineWidth / 2;
22687 } elseif ($side == 'B' && $bSeparate) {
22688 $yadj3 = $this->LineWidth / 2;
22689 $xadj3 = $this->LineWidth / 2;
22690 } elseif ($side == 'R' && $bSeparate) {
22691 $xadj3 = $this->LineWidth / 2;
22692 $yadj3 = $this->LineWidth / 2;
22693 } elseif ($side == 'B') {
22694 $yadj3 = $this->LineWidth / 2;
22695 $xadj3 = -$this->LineWidth / 2;
22696 $wadj3 = $this->LineWidth;
22697 } elseif ($side == 'R') {
22698 $xadj3 = $this->LineWidth / 2;
22699 $yadj3 = -$this->LineWidth / 2;
22700 $hadj3 = $this->LineWidth;
22704 if ($bSeparate) {
22705 $cellBorderOverlay[] = [
22706 'x' => $lx1 + $xadj + $xadj3,
22707 'y' => $ly1 + $yadj + $yadj3,
22708 'x2' => $lx2 - $xadj2 + $xadj3 + $wadj3,
22709 'y2' => $ly2 - $yadj2 + $yadj3 + $hadj3,
22710 'col' => $nc,
22711 'lw' => $this->LineWidth,
22713 } else {
22714 $this->SetDColor($nc);
22715 $this->Line($lx1 + $xadj + $xadj3, $ly1 + $yadj + $yadj3, $lx2 - $xadj2 + $xadj3 + $wadj3, $ly2 - $yadj2 + $yadj3 + $hadj3);
22718 } else {
22719 /* -- END TABLES-ADVANCED-BORDERS -- */
22720 if ($details[$side]['style'] == 'dashed') {
22721 $dashsize = 2; // final dash will be this + 1*linewidth
22722 $dashsizek = 1.5; // ratio of Dash/Blank
22723 $this->SetDash($dashsize, ($dashsize / $dashsizek) + ($this->LineWidth * 2));
22724 } elseif ($details[$side]['style'] == 'dotted') {
22725 $this->SetLineJoin(1);
22726 $this->SetLineCap(1);
22727 $this->SetDash(0.001, ($this->LineWidth * 2));
22729 if ($details[$side]['c']) {
22730 $this->SetDColor($details[$side]['c']);
22731 } else {
22732 $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
22734 $this->Line($lx1 + $xadj, $ly1 + $yadj, $lx2 - $xadj2, $ly2 - $yadj2);
22735 /* -- TABLES-ADVANCED-BORDERS -- */
22737 /* -- END TABLES-ADVANCED-BORDERS -- */
22739 // Reset Corners
22740 $this->SetDash();
22741 // BUTT style line cap
22742 $this->SetLineCap(2);
22746 if ($bSeparate && count($cellBorderOverlay)) {
22747 foreach ($cellBorderOverlay as $cbo) {
22748 $this->SetLineWidth($cbo['lw']);
22749 $this->SetDColor($cbo['col']);
22750 $this->Line($cbo['x'], $cbo['y'], $cbo['x2'], $cbo['y2']);
22754 // $this->SetLineWidth($oldlinewidth);
22755 // $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
22759 /* -- TABLES -- */
22760 /* -- TABLES-ADVANCED-BORDERS -- */
22762 /* -- END TABLES-ADVANCED-BORDERS -- */
22764 function setBorder(&$var, $flag, $set = true)
22766 $flag = intval($flag);
22767 if ($set) {
22768 $set = true;
22770 $var = intval($var);
22771 $var = $set ? ($var | $flag) : ($var & ~$flag);
22774 function issetBorder($var, $flag)
22776 $flag = intval($flag);
22777 $var = intval($var);
22778 return (($var & $flag) == $flag);
22781 function _table2cellBorder(&$tableb, &$cbdb, &$cellb, $bval)
22783 if ($tableb && $tableb['w'] > $cbdb['w']) {
22784 $cbdb = $tableb;
22785 $this->setBorder($cellb, $bval);
22786 } elseif ($tableb && $tableb['w'] == $cbdb['w'] && array_search($tableb['style'], $this->borderstyles) > array_search($cbdb['style'], $this->borderstyles)) {
22787 $cbdb = $tableb;
22788 $this->setBorder($cellb, $bval);
22792 // FIX BORDERS ********************************************
22793 function _fixTableBorders(&$table)
22795 if (!$table['borders_separate'] && $table['border_details']['L']['w']) {
22796 $table['max_cell_border_width']['L'] = $table['border_details']['L']['w'];
22798 if (!$table['borders_separate'] && $table['border_details']['R']['w']) {
22799 $table['max_cell_border_width']['R'] = $table['border_details']['R']['w'];
22801 if (!$table['borders_separate'] && $table['border_details']['T']['w']) {
22802 $table['max_cell_border_width']['T'] = $table['border_details']['T']['w'];
22804 if (!$table['borders_separate'] && $table['border_details']['B']['w']) {
22805 $table['max_cell_border_width']['B'] = $table['border_details']['B']['w'];
22807 if ($this->simpleTables) {
22808 return;
22810 $cells = &$table['cells'];
22811 $numcols = $table['nc'];
22812 $numrows = $table['nr'];
22813 /* -- TABLES-ADVANCED-BORDERS -- */
22814 if (isset($table['topntail']) && $table['topntail']) {
22815 $tntborddet = $this->border_details($table['topntail']);
22817 if (isset($table['thead-underline']) && $table['thead-underline']) {
22818 $thuborddet = $this->border_details($table['thead-underline']);
22820 /* -- END TABLES-ADVANCED-BORDERS -- */
22822 for ($i = 0; $i < $numrows; $i++) { // Rows
22823 for ($j = 0; $j < $numcols; $j++) { // Columns
22824 if (isset($cells[$i][$j]) && $cells[$i][$j]) {
22825 $cell = &$cells[$i][$j];
22826 if ($this->packTableData) {
22827 $cbord = $this->_unpackCellBorder($cell['borderbin']);
22828 } else {
22829 $cbord = &$cells[$i][$j];
22832 // mPDF 5.7.3
22833 if (!$cbord['border'] && $cbord['border'] !== 0 && isset($table['border']) && $table['border'] && $this->table_border_attr_set) {
22834 $cbord['border'] = $table['border'];
22835 $cbord['border_details'] = $table['border_details'];
22838 if (isset($cell['colspan']) && $cell['colspan'] > 1) {
22839 $ccolsp = $cell['colspan'];
22840 } else {
22841 $ccolsp = 1;
22843 if (isset($cell['rowspan']) && $cell['rowspan'] > 1) {
22844 $crowsp = $cell['rowspan'];
22845 } else {
22846 $crowsp = 1;
22849 $cbord['border_details']['cellposdom'] = ((($i + 1) / $numrows) / 10000 ) + ((($j + 1) / $numcols) / 10 );
22850 // Inherit Cell border from Table border
22851 if ($this->table_border_css_set && !$table['borders_separate']) {
22852 if ($i == 0) {
22853 $this->_table2cellBorder($table['border_details']['T'], $cbord['border_details']['T'], $cbord['border'], Border::TOP);
22855 if ($i == ($numrows - 1) || ($i + $crowsp) == ($numrows)) {
22856 $this->_table2cellBorder($table['border_details']['B'], $cbord['border_details']['B'], $cbord['border'], Border::BOTTOM);
22858 if ($j == 0) {
22859 $this->_table2cellBorder($table['border_details']['L'], $cbord['border_details']['L'], $cbord['border'], Border::LEFT);
22861 if ($j == ($numcols - 1) || ($j + $ccolsp) == ($numcols)) {
22862 $this->_table2cellBorder($table['border_details']['R'], $cbord['border_details']['R'], $cbord['border'], Border::RIGHT);
22866 /* -- TABLES-ADVANCED-BORDERS -- */
22867 $fixbottom = true;
22868 if (isset($table['topntail']) && $table['topntail']) {
22869 if ($i == 0) {
22870 $cbord['border_details']['T'] = $tntborddet;
22871 $this->setBorder($cbord['border'], Border::TOP);
22873 if ($this->tableLevel == 1 && $table['headernrows'] > 0 && $i == $table['headernrows'] - 1) {
22874 $cbord['border_details']['B'] = $tntborddet;
22875 $this->setBorder($cbord['border'], Border::BOTTOM);
22876 $fixbottom = false;
22877 } elseif ($this->tableLevel == 1 && $table['headernrows'] > 0 && $i == $table['headernrows']) {
22878 if (!$table['borders_separate']) {
22879 $cbord['border_details']['T'] = $tntborddet;
22880 $this->setBorder($cbord['border'], Border::TOP);
22883 if ($this->tableLevel == 1 && $table['footernrows'] > 0 && $i == ($numrows - $table['footernrows'] - 1)) {
22884 if (!$table['borders_separate']) {
22885 $cbord['border_details']['B'] = $tntborddet;
22886 $this->setBorder($cbord['border'], Border::BOTTOM);
22887 $fixbottom = false;
22889 } elseif ($this->tableLevel == 1 && $table['footernrows'] > 0 && $i == ($numrows - $table['footernrows'])) {
22890 $cbord['border_details']['T'] = $tntborddet;
22891 $this->setBorder($cbord['border'], Border::TOP);
22893 if ($this->tabletheadjustfinished) { // $this->tabletheadjustfinished called from tableheader
22894 if (!$table['borders_separate']) {
22895 $cbord['border_details']['T'] = $tntborddet;
22896 $this->setBorder($cbord['border'], Border::TOP);
22899 if ($i == ($numrows - 1) || ($i + $crowsp) == ($numrows)) {
22900 $cbord['border_details']['B'] = $tntborddet;
22901 $this->setBorder($cbord['border'], Border::BOTTOM);
22904 if (isset($table['thead-underline']) && $table['thead-underline']) {
22905 if ($table['borders_separate']) {
22906 if ($i == 0) {
22907 $cbord['border_details']['B'] = $thuborddet;
22908 $this->setBorder($cbord['border'], Border::BOTTOM);
22909 $fixbottom = false;
22911 } else {
22912 if ($this->tableLevel == 1 && $table['headernrows'] > 0 && $i == $table['headernrows'] - 1) {
22913 $cbord['border_details']['T'] = $thuborddet;
22914 $this->setBorder($cbord['border'], Border::TOP);
22915 } elseif ($this->tabletheadjustfinished) { // $this->tabletheadjustfinished called from tableheader
22916 $cbord['border_details']['T'] = $thuborddet;
22917 $this->setBorder($cbord['border'], Border::TOP);
22922 // Collapse Border - Algorithm for conflicting borders
22923 // Hidden >> Width >> double>solid>dashed>dotted... >> style set on cell>table >> top/left>bottom/right
22924 // Do not turn off border which is overridden
22925 // Needed for page break for TOP/BOTTOM both to be defined in Collapsed borders
22926 // Means it is painted twice. (Left/Right can still disable overridden border)
22927 if (!$table['borders_separate']) {
22929 if (($i < ($numrows - 1) || ($i + $crowsp) < $numrows ) && $fixbottom) { // Bottom
22931 for ($cspi = 0; $cspi < $ccolsp; $cspi++) {
22933 // already defined Top for adjacent cell below
22934 if (isset($cells[($i + $crowsp)][$j + $cspi])) {
22935 if ($this->packTableData) {
22936 $adjc = $cells[($i + $crowsp)][$j + $cspi];
22937 $celladj = $this->_unpackCellBorder($adjc['borderbin']);
22938 } else {
22939 $celladj = & $cells[($i + $crowsp)][$j + $cspi];
22941 } else {
22942 $celladj = false;
22945 if ($celladj && $celladj['border_details']['T']['s'] == 1) {
22947 $csadj = $celladj['border_details']['T']['w'];
22948 $csthis = $cbord['border_details']['B']['w'];
22950 // Hidden
22951 if ($cbord['border_details']['B']['style'] == 'hidden') {
22953 $celladj['border_details']['T'] = $cbord['border_details']['B'];
22954 $this->setBorder($celladj['border'], Border::TOP, false);
22955 $this->setBorder($cbord['border'], Border::BOTTOM, false);
22957 } elseif ($celladj['border_details']['T']['style'] == 'hidden') {
22959 $cbord['border_details']['B'] = $celladj['border_details']['T'];
22960 $this->setBorder($cbord['border'], Border::BOTTOM, false);
22961 $this->setBorder($celladj['border'], Border::TOP, false);
22963 } elseif ($csthis > $csadj) { // Width
22965 if (!isset($cells[($i + $crowsp)][$j + $cspi]['colspan']) || (isset($cells[($i + $crowsp)][$j + $cspi]['colspan']) && $cells[($i + $crowsp)][$j + $cspi]['colspan'] < 2)) { // don't overwrite bordering cells that span
22966 $celladj['border_details']['T'] = $cbord['border_details']['B'];
22967 $this->setBorder($cbord['border'], Border::BOTTOM);
22970 } elseif ($csadj > $csthis) {
22972 if ($ccolsp < 2) { // don't overwrite this cell if it spans
22973 $cbord['border_details']['B'] = $celladj['border_details']['T'];
22974 $this->setBorder($celladj['border'], Border::TOP);
22977 } elseif (array_search($cbord['border_details']['B']['style'], $this->borderstyles) > array_search($celladj['border_details']['T']['style'], $this->borderstyles)) { // double>solid>dashed>dotted...
22979 if (!isset($cells[($i + $crowsp)][$j + $cspi]['colspan']) || (isset($cells[($i + $crowsp)][$j + $cspi]['colspan']) && $cells[($i + $crowsp)][$j + $cspi]['colspan'] < 2)) { // don't overwrite bordering cells that span
22980 $celladj['border_details']['T'] = $cbord['border_details']['B'];
22981 $this->setBorder($cbord['border'], Border::BOTTOM);
22984 } elseif (array_search($celladj['border_details']['T']['style'], $this->borderstyles) > array_search($cbord['border_details']['B']['style'], $this->borderstyles)) {
22986 if ($ccolsp < 2) { // don't overwrite this cell if it spans
22987 $cbord['border_details']['B'] = $celladj['border_details']['T'];
22988 $this->setBorder($celladj['border'], Border::TOP);
22991 } elseif ($celladj['border_details']['T']['dom'] > $celladj['border_details']['B']['dom']) { // Style set on cell vs. table
22993 if ($ccolsp < 2) { // don't overwrite this cell if it spans
22994 $cbord['border_details']['B'] = $celladj['border_details']['T'];
22995 $this->setBorder($celladj['border'], Border::TOP);
22998 } else { // Style set on cell vs. table - OR - LEFT/TOP (cell) in preference to BOTTOM/RIGHT
23000 if (!isset($cells[($i + $crowsp)][$j + $cspi]['colspan']) || (isset($cells[($i + $crowsp)][$j + $cspi]['colspan']) && $cells[($i + $crowsp)][$j + $cspi]['colspan'] < 2)) { // don't overwrite bordering cells that span
23001 $celladj['border_details']['T'] = $cbord['border_details']['B'];
23002 $this->setBorder($cbord['border'], Border::BOTTOM);
23007 } elseif ($celladj) {
23009 if (!isset($cells[($i + $crowsp)][$j + $cspi]['colspan']) || (isset($cells[($i + $crowsp)][$j + $cspi]['colspan']) && $cells[($i + $crowsp)][$j + $cspi]['colspan'] < 2)) { // don't overwrite bordering cells that span
23010 $celladj['border_details']['T'] = $cbord['border_details']['B'];
23015 // mPDF 5.7.4
23016 if ($celladj && $this->packTableData) {
23017 $cells[$i + $crowsp][$j + $cspi]['borderbin'] = $this->_packCellBorder($celladj);
23020 unset($celladj);
23024 if ($j < ($numcols - 1) || ($j + $ccolsp) < $numcols) { // Right-Left
23026 for ($cspi = 0; $cspi < $crowsp; $cspi++) {
23028 // already defined Left for adjacent cell to R
23029 if (isset($cells[($i + $cspi)][$j + $ccolsp])) {
23030 if ($this->packTableData) {
23031 $adjc = $cells[($i + $cspi)][$j + $ccolsp];
23032 $celladj = $this->_unpackCellBorder($adjc['borderbin']);
23033 } else {
23034 $celladj = & $cells[$i + $cspi][$j + $ccolsp];
23036 } else {
23037 $celladj = false;
23039 if ($celladj && $celladj['border_details']['L']['s'] == 1) {
23040 $csadj = $celladj['border_details']['L']['w'];
23041 $csthis = $cbord['border_details']['R']['w'];
23042 // Hidden
23043 if ($cbord['border_details']['R']['style'] == 'hidden') {
23044 $celladj['border_details']['L'] = $cbord['border_details']['R'];
23045 $this->setBorder($celladj['border'], Border::LEFT, false);
23046 $this->setBorder($cbord['border'], Border::RIGHT, false);
23047 } elseif ($celladj['border_details']['L']['style'] == 'hidden') {
23048 $cbord['border_details']['R'] = $celladj['border_details']['L'];
23049 $this->setBorder($cbord['border'], Border::RIGHT, false);
23050 $this->setBorder($celladj['border'], Border::LEFT, false);
23051 } // Width
23052 elseif ($csthis > $csadj) {
23053 if (!isset($cells[($i + $cspi)][$j + $ccolsp]['rowspan']) || (isset($cells[($i + $cspi)][$j + $ccolsp]['rowspan']) && $cells[($i + $cspi)][$j + $ccolsp]['rowspan'] < 2)) { // don't overwrite bordering cells that span
23054 $celladj['border_details']['L'] = $cbord['border_details']['R'];
23055 $this->setBorder($cbord['border'], Border::RIGHT);
23056 $this->setBorder($celladj['border'], Border::LEFT, false);
23058 } elseif ($csadj > $csthis) {
23059 if ($crowsp < 2) { // don't overwrite this cell if it spans
23060 $cbord['border_details']['R'] = $celladj['border_details']['L'];
23061 $this->setBorder($cbord['border'], Border::RIGHT, false);
23062 $this->setBorder($celladj['border'], Border::LEFT);
23064 } // double>solid>dashed>dotted...
23065 elseif (array_search($cbord['border_details']['R']['style'], $this->borderstyles) > array_search($celladj['border_details']['L']['style'], $this->borderstyles)) {
23066 if (!isset($cells[($i + $cspi)][$j + $ccolsp]['rowspan']) || (isset($cells[($i + $cspi)][$j + $ccolsp]['rowspan']) && $cells[($i + $cspi)][$j + $ccolsp]['rowspan'] < 2)) { // don't overwrite bordering cells that span
23067 $celladj['border_details']['L'] = $cbord['border_details']['R'];
23068 $this->setBorder($celladj['border'], Border::LEFT, false);
23069 $this->setBorder($cbord['border'], Border::RIGHT);
23071 } elseif (array_search($celladj['border_details']['L']['style'], $this->borderstyles) > array_search($cbord['border_details']['R']['style'], $this->borderstyles)) {
23072 if ($crowsp < 2) { // don't overwrite this cell if it spans
23073 $cbord['border_details']['R'] = $celladj['border_details']['L'];
23074 $this->setBorder($cbord['border'], Border::RIGHT, false);
23075 $this->setBorder($celladj['border'], Border::LEFT);
23077 } // Style set on cell vs. table
23078 elseif ($celladj['border_details']['L']['dom'] > $cbord['border_details']['R']['dom']) {
23079 if ($crowsp < 2) { // don't overwrite this cell if it spans
23080 $cbord['border_details']['R'] = $celladj['border_details']['L'];
23081 $this->setBorder($celladj['border'], Border::LEFT);
23083 } // Style set on cell vs. table - OR - LEFT/TOP (cell) in preference to BOTTOM/RIGHT
23084 else {
23085 if (!isset($cells[($i + $cspi)][$j + $ccolsp]['rowspan']) || (isset($cells[($i + $cspi)][$j + $ccolsp]['rowspan']) && $cells[($i + $cspi)][$j + $ccolsp]['rowspan'] < 2)) { // don't overwrite bordering cells that span
23086 $celladj['border_details']['L'] = $cbord['border_details']['R'];
23087 $this->setBorder($cbord['border'], Border::RIGHT);
23090 } elseif ($celladj) {
23091 // if right-cell border is not set
23092 if (!isset($cells[($i + $cspi)][$j + $ccolsp]['rowspan']) || (isset($cells[($i + $cspi)][$j + $ccolsp]['rowspan']) && $cells[($i + $cspi)][$j + $ccolsp]['rowspan'] < 2)) { // don't overwrite bordering cells that span
23093 $celladj['border_details']['L'] = $cbord['border_details']['R'];
23096 // mPDF 5.7.4
23097 if ($celladj && $this->packTableData) {
23098 $cells[$i + $cspi][$j + $ccolsp]['borderbin'] = $this->_packCellBorder($celladj);
23100 unset($celladj);
23106 // Set maximum cell border width meeting at LRTB edges of cell - used for extended cell border
23107 // ['border_details']['mbw']['LT'] = meeting border width - Left border - Top end
23108 if (!$table['borders_separate']) {
23109 $cbord['border_details']['mbw']['BL'] = max($cbord['border_details']['mbw']['BL'], $cbord['border_details']['L']['w']);
23110 $cbord['border_details']['mbw']['BR'] = max($cbord['border_details']['mbw']['BR'], $cbord['border_details']['R']['w']);
23111 $cbord['border_details']['mbw']['RT'] = max($cbord['border_details']['mbw']['RT'], $cbord['border_details']['T']['w']);
23112 $cbord['border_details']['mbw']['RB'] = max($cbord['border_details']['mbw']['RB'], $cbord['border_details']['B']['w']);
23113 $cbord['border_details']['mbw']['TL'] = max($cbord['border_details']['mbw']['TL'], $cbord['border_details']['L']['w']);
23114 $cbord['border_details']['mbw']['TR'] = max($cbord['border_details']['mbw']['TR'], $cbord['border_details']['R']['w']);
23115 $cbord['border_details']['mbw']['LT'] = max($cbord['border_details']['mbw']['LT'], $cbord['border_details']['T']['w']);
23116 $cbord['border_details']['mbw']['LB'] = max($cbord['border_details']['mbw']['LB'], $cbord['border_details']['B']['w']);
23117 if (($i + $crowsp) < $numrows && isset($cells[$i + $crowsp][$j])) { // Has Bottom adjoining cell
23118 if ($this->packTableData) {
23119 $adjc = $cells[$i + $crowsp][$j];
23120 $celladj = $this->_unpackCellBorder($adjc['borderbin']);
23121 } else {
23122 $celladj = & $cells[$i + $crowsp][$j];
23124 $cbord['border_details']['mbw']['BL'] = max($cbord['border_details']['mbw']['BL'], $celladj['border_details']['L']['w'], $celladj['border_details']['mbw']['TL']);
23125 $cbord['border_details']['mbw']['BR'] = max($cbord['border_details']['mbw']['BR'], $celladj['border_details']['R']['w'], $celladj['border_details']['mbw']['TR']);
23126 $cbord['border_details']['mbw']['LB'] = max($cbord['border_details']['mbw']['LB'], $celladj['border_details']['mbw']['LT']);
23127 $cbord['border_details']['mbw']['RB'] = max($cbord['border_details']['mbw']['RB'], $celladj['border_details']['mbw']['RT']);
23128 unset($celladj);
23130 if (($j + $ccolsp) < $numcols && isset($cells[$i][$j + $ccolsp])) { // Has Right adjoining cell
23131 if ($this->packTableData) {
23132 $adjc = $cells[$i][$j + $ccolsp];
23133 $celladj = $this->_unpackCellBorder($adjc['borderbin']);
23134 } else {
23135 $celladj = & $cells[$i][$j + $ccolsp];
23137 $cbord['border_details']['mbw']['RT'] = max($cbord['border_details']['mbw']['RT'], $celladj['border_details']['T']['w'], $celladj['border_details']['mbw']['LT']);
23138 $cbord['border_details']['mbw']['RB'] = max($cbord['border_details']['mbw']['RB'], $celladj['border_details']['B']['w'], $celladj['border_details']['mbw']['LB']);
23139 $cbord['border_details']['mbw']['TR'] = max($cbord['border_details']['mbw']['TR'], $celladj['border_details']['mbw']['TL']);
23140 $cbord['border_details']['mbw']['BR'] = max($cbord['border_details']['mbw']['BR'], $celladj['border_details']['mbw']['BL']);
23141 unset($celladj);
23144 if ($i > 0 && isset($cells[$i - 1][$j]) && (($this->packTableData && $cells[$i - 1][$j]['borderbin']) || $cells[$i - 1][$j]['border'])) { // Has Top adjoining cell
23145 if ($this->packTableData) {
23146 $adjc = $cells[$i - 1][$j];
23147 $celladj = $this->_unpackCellBorder($adjc['borderbin']);
23148 } else {
23149 $celladj = & $cells[$i - 1][$j];
23151 $cbord['border_details']['mbw']['TL'] = max($cbord['border_details']['mbw']['TL'], $celladj['border_details']['L']['w'], $celladj['border_details']['mbw']['BL']);
23152 $cbord['border_details']['mbw']['TR'] = max($cbord['border_details']['mbw']['TR'], $celladj['border_details']['R']['w'], $celladj['border_details']['mbw']['BR']);
23153 $cbord['border_details']['mbw']['LT'] = max($cbord['border_details']['mbw']['LT'], $celladj['border_details']['mbw']['LB']);
23154 $cbord['border_details']['mbw']['RT'] = max($cbord['border_details']['mbw']['RT'], $celladj['border_details']['mbw']['RB']);
23156 if ($celladj['border_details']['mbw']['BL']) {
23157 $celladj['border_details']['mbw']['BL'] = max($cbord['border_details']['mbw']['TL'], $celladj['border_details']['mbw']['BL']);
23159 if ($celladj['border_details']['mbw']['BR']) {
23160 $celladj['border_details']['mbw']['BR'] = max($celladj['border_details']['mbw']['BR'], $cbord['border_details']['mbw']['TR']);
23162 if ($this->packTableData) {
23163 $cells[$i - 1][$j]['borderbin'] = $this->_packCellBorder($celladj);
23165 unset($celladj);
23167 if ($j > 0 && isset($cells[$i][$j - 1]) && (($this->packTableData && $cells[$i][$j - 1]['borderbin']) || $cells[$i][$j - 1]['border'])) { // Has Left adjoining cell
23168 if ($this->packTableData) {
23169 $adjc = $cells[$i][$j - 1];
23170 $celladj = $this->_unpackCellBorder($adjc['borderbin']);
23171 } else {
23172 $celladj = & $cells[$i][$j - 1];
23174 $cbord['border_details']['mbw']['LT'] = max($cbord['border_details']['mbw']['LT'], $celladj['border_details']['T']['w'], $celladj['border_details']['mbw']['RT']);
23175 $cbord['border_details']['mbw']['LB'] = max($cbord['border_details']['mbw']['LB'], $celladj['border_details']['B']['w'], $celladj['border_details']['mbw']['RB']);
23176 $cbord['border_details']['mbw']['BL'] = max($cbord['border_details']['mbw']['BL'], $celladj['border_details']['mbw']['BR']);
23177 $cbord['border_details']['mbw']['TL'] = max($cbord['border_details']['mbw']['TL'], $celladj['border_details']['mbw']['TR']);
23179 if ($celladj['border_details']['mbw']['RT']) {
23180 $celladj['border_details']['mbw']['RT'] = max($celladj['border_details']['mbw']['RT'], $cbord['border_details']['mbw']['LT']);
23182 if ($celladj['border_details']['mbw']['RB']) {
23183 $celladj['border_details']['mbw']['RB'] = max($celladj['border_details']['mbw']['RB'], $cbord['border_details']['mbw']['LB']);
23185 if ($this->packTableData) {
23186 $cells[$i][$j - 1]['borderbin'] = $this->_packCellBorder($celladj);
23188 unset($celladj);
23192 // Update maximum cell border width at LRTB edges of table - used for overall table width
23193 if ($j == 0 && $cbord['border_details']['L']['w']) {
23194 $table['max_cell_border_width']['L'] = max($table['max_cell_border_width']['L'], $cbord['border_details']['L']['w']);
23196 if (($j == ($numcols - 1) || ($j + $ccolsp) == $numcols ) && $cbord['border_details']['R']['w']) {
23197 $table['max_cell_border_width']['R'] = max($table['max_cell_border_width']['R'], $cbord['border_details']['R']['w']);
23199 if ($i == 0 && $cbord['border_details']['T']['w']) {
23200 $table['max_cell_border_width']['T'] = max($table['max_cell_border_width']['T'], $cbord['border_details']['T']['w']);
23202 if (($i == ($numrows - 1) || ($i + $crowsp) == $numrows ) && $cbord['border_details']['B']['w']) {
23203 $table['max_cell_border_width']['B'] = max($table['max_cell_border_width']['B'], $cbord['border_details']['B']['w']);
23206 /* -- END TABLES-ADVANCED-BORDERS -- */
23208 if ($this->packTableData) {
23209 $cell['borderbin'] = $this->_packCellBorder($cbord);
23212 unset($cbord);
23214 unset($cell);
23218 unset($cell);
23221 // END FIX BORDERS ************************************************************************************
23223 function _reverseTableDir(&$table)
23225 $cells = &$table['cells'];
23226 $numcols = $table['nc'];
23227 $numrows = $table['nr'];
23228 for ($i = 0; $i < $numrows; $i++) { // Rows
23229 $row = [];
23230 for ($j = ($numcols - 1); $j >= 0; $j--) { // Columns
23231 if (isset($cells[$i][$j]) && $cells[$i][$j]) {
23232 $cell = &$cells[$i][$j];
23233 $col = $numcols - $j - 1;
23234 if (isset($cell['colspan']) && $cell['colspan'] > 1) {
23235 $col -= ($cell['colspan'] - 1);
23237 // Nested content
23238 if (isset($cell['textbuffer'])) {
23239 for ($n = 0; $n < count($cell['textbuffer']); $n++) {
23240 $t = $cell['textbuffer'][$n][0];
23241 if (substr($t, 0, 19) == "\xbb\xa4\xactype=nestedtable") {
23242 $objattr = $this->_getObjAttr($t);
23243 $objattr['col'] = $col;
23244 $cell['textbuffer'][$n][0] = "\xbb\xa4\xactype=nestedtable,objattr=" . serialize($objattr) . "\xbb\xa4\xac";
23245 $this->table[($this->tableLevel + 1)][$objattr['nestedcontent']]['nestedpos'][1] = $col;
23249 $row[$col] = $cells[$i][$j];
23250 unset($cell);
23253 for ($f = 0; $f < $numcols; $f++) {
23254 if (!isset($row[$f])) {
23255 $row[$f] = 0;
23258 $table['cells'][$i] = $row;
23262 function _tableWrite(&$table, $split = false, $startrow = 0, $startcol = 0, $splitpg = 0, $rety = 0)
23264 $level = $table['level'];
23265 $levelid = $table['levelid'];
23267 $cells = &$table['cells'];
23268 $numcols = $table['nc'];
23269 $numrows = $table['nr'];
23270 $maxbwtop = 0;
23271 if ($this->ColActive && $level == 1) {
23272 $this->breakpoints[$this->CurrCol][] = $this->y;
23273 } // *COLUMNS*
23275 if (!$split || ($startrow == 0 && $splitpg == 0) || $startrow > 0) {
23276 // TABLE TOP MARGIN
23277 if ($table['margin']['T']) {
23278 if (!$this->table_rotate && $level == 1) {
23279 $this->DivLn($table['margin']['T'], $this->blklvl, true, 1); // collapsible
23280 } else {
23281 $this->y += ($table['margin']['T']);
23284 // Advance down page by half width of top border
23285 if ($table['borders_separate']) {
23286 if ($startrow > 0 && (!isset($table['is_thead']) || count($table['is_thead']) == 0)) {
23287 $adv = $table['border_spacing_V'] / 2;
23288 } else {
23289 $adv = $table['padding']['T'] + $table['border_details']['T']['w'] + $table['border_spacing_V'] / 2;
23291 } else {
23292 $adv = $table['max_cell_border_width']['T'] / 2;
23294 if (!$this->table_rotate && $level == 1) {
23295 $this->DivLn($adv);
23296 } else {
23297 $this->y += $adv;
23301 if ($level == 1) {
23302 $this->x = $this->lMargin + $this->blk[$this->blklvl]['outer_left_margin'] + $this->blk[$this->blklvl]['padding_left'] + $this->blk[$this->blklvl]['border_left']['w'];
23303 $x0 = $this->x;
23304 $y0 = $this->y;
23305 $right = $x0 + $this->blk[$this->blklvl]['inner_width'];
23306 $outerfilled = $this->y; // Keep track of how far down the outer DIV bgcolor is painted (NB rowspans)
23307 $this->outerfilled = $this->y;
23308 $this->colsums = [];
23309 } else {
23310 $x0 = $this->x;
23311 $y0 = $this->y;
23312 $right = $x0 + $table['w'];
23315 if ($this->table_rotate) {
23316 $temppgwidth = $this->tbrot_maxw;
23317 $this->PageBreakTrigger = $pagetrigger = $y0 + ($this->blk[$this->blklvl]['inner_width']);
23318 if ($level == 1) {
23319 $this->tbrot_y0 = $this->y - $adv - $table['margin']['T'];
23320 $this->tbrot_x0 = $this->x;
23321 $this->tbrot_w = $table['w'];
23322 if ($table['borders_separate']) {
23323 $this->tbrot_h = $table['margin']['T'] + $table['padding']['T'] + $table['border_details']['T']['w'] + $table['border_spacing_V'] / 2;
23324 } else {
23325 $this->tbrot_h = $table['margin']['T'] + $table['padding']['T'] + $table['max_cell_border_width']['T'];
23328 } else {
23329 $this->PageBreakTrigger = $pagetrigger = ($this->h - $this->bMargin);
23330 if ($level == 1) {
23331 $temppgwidth = $this->blk[$this->blklvl]['inner_width'];
23332 if (isset($table['a']) and ( $table['w'] < $this->blk[$this->blklvl]['inner_width'])) {
23333 if ($table['a'] == 'C') {
23334 $x0 += ((($right - $x0) - $table['w']) / 2);
23335 } elseif ($table['a'] == 'R') {
23336 $x0 = $right - $table['w'];
23339 } else {
23340 $temppgwidth = $table['w'];
23343 if (!isset($table['overflow'])) {
23344 $table['overflow'] = null;
23346 if ($table['overflow'] == 'hidden' && $level == 1 && !$this->table_rotate && !$this->ColActive) {
23347 // Bounding rectangle to clip
23348 $this->tableClipPath = sprintf('q %.3F %.3F %.3F %.3F re W n', $x0 * Mpdf::SCALE, $this->h * Mpdf::SCALE, $this->blk[$this->blklvl]['inner_width'] * Mpdf::SCALE, -$this->h * Mpdf::SCALE);
23349 $this->_out($this->tableClipPath);
23350 } else {
23351 $this->tableClipPath = '';
23355 if ($table['borders_separate']) {
23356 $indent = $table['margin']['L'] + $table['border_details']['L']['w'] + $table['padding']['L'] + $table['border_spacing_H'] / 2;
23357 } else {
23358 $indent = $table['margin']['L'] + $table['max_cell_border_width']['L'] / 2;
23360 $x0 += $indent;
23362 $returny = 0;
23363 $lastCol = 0;
23364 $tableheader = [];
23365 $tablefooter = [];
23366 $tableheaderrowheight = 0;
23367 $tablefooterrowheight = 0;
23368 $footery = 0;
23370 // mPD 3.0 Set the Page & Column where table starts
23371 if (($this->mirrorMargins) && (($this->page) % 2 == 0)) { // EVEN
23372 $tablestartpage = 'EVEN';
23373 } elseif (($this->mirrorMargins) && (($this->page) % 2 == 1)) { // ODD
23374 $tablestartpage = 'ODD';
23375 } else {
23376 $tablestartpage = '';
23378 if ($this->ColActive) {
23379 $tablestartcolumn = $this->CurrCol;
23380 } else {
23381 $tablestartcolumn = '';
23384 $y = $h = 0;
23385 for ($i = 0; $i < $numrows; $i++) { // Rows
23386 if (isset($table['is_tfoot'][$i]) && $table['is_tfoot'][$i] && $level == 1) {
23387 $tablefooterrowheight += $table['hr'][$i];
23388 $tablefooter[$i][0]['trbackground-images'] = $table['trbackground-images'][$i];
23389 $tablefooter[$i][0]['trgradients'] = $table['trgradients'][$i];
23390 $tablefooter[$i][0]['trbgcolor'] = $table['bgcolor'][$i];
23391 for ($j = $startcol; $j < $numcols; $j++) { // Columns
23392 if (isset($cells[$i][$j]) && $cells[$i][$j]) {
23393 $cell = &$cells[$i][$j];
23394 if ($split) {
23395 if ($table['colPg'][$j] != $splitpg) {
23396 continue;
23398 list($x, $w) = $this->_splitTableGetWidth($table, $i, $j);
23399 $js = $j - $startcol;
23400 } else {
23401 list($x, $w) = $this->_tableGetWidth($table, $i, $j);
23402 $js = $j;
23405 list($y, $h) = $this->_tableGetHeight($table, $i, $j);
23406 $x += $x0;
23407 $y += $y0;
23408 // Get info of tfoot ==>> table footer
23409 $tablefooter[$i][$js]['x'] = $x;
23410 $tablefooter[$i][$js]['y'] = $y;
23411 $tablefooter[$i][$js]['h'] = $h;
23412 $tablefooter[$i][$js]['w'] = $w;
23413 if (isset($cell['textbuffer'])) {
23414 $tablefooter[$i][$js]['textbuffer'] = $cell['textbuffer'];
23415 } else {
23416 $tablefooter[$i][$js]['textbuffer'] = '';
23418 $tablefooter[$i][$js]['a'] = $cell['a'];
23419 $tablefooter[$i][$js]['R'] = $cell['R'];
23420 $tablefooter[$i][$js]['va'] = $cell['va'];
23421 $tablefooter[$i][$js]['mih'] = $cell['mih'];
23422 if (isset($cell['gradient'])) {
23423 $tablefooter[$i][$js]['gradient'] = $cell['gradient']; // *BACKGROUNDS*
23425 if (isset($cell['background-image'])) {
23426 $tablefooter[$i][$js]['background-image'] = $cell['background-image']; // *BACKGROUNDS*
23429 // CELL FILL BGCOLOR
23430 if (!$this->simpleTables) {
23431 if ($this->packTableData) {
23432 $c = $this->_unpackCellBorder($cell['borderbin']);
23433 $tablefooter[$i][$js]['border'] = $c['border'];
23434 $tablefooter[$i][$js]['border_details'] = $c['border_details'];
23435 } else {
23436 $tablefooter[$i][$js]['border'] = $cell['border'];
23437 $tablefooter[$i][$js]['border_details'] = $cell['border_details'];
23439 } elseif ($this->simpleTables) {
23440 $tablefooter[$i][$js]['border'] = $table['simple']['border'];
23441 $tablefooter[$i][$js]['border_details'] = $table['simple']['border_details'];
23443 $tablefooter[$i][$js]['bgcolor'] = $cell['bgcolor'];
23444 $tablefooter[$i][$js]['padding'] = $cell['padding'];
23445 if (isset($cell['rowspan'])) {
23446 $tablefooter[$i][$js]['rowspan'] = $cell['rowspan'];
23448 if (isset($cell['colspan'])) {
23449 $tablefooter[$i][$js]['colspan'] = $cell['colspan'];
23451 if (isset($cell['direction'])) {
23452 $tablefooter[$i][$js]['direction'] = $cell['direction'];
23454 if (isset($cell['cellLineHeight'])) {
23455 $tablefooter[$i][$js]['cellLineHeight'] = $cell['cellLineHeight'];
23457 if (isset($cell['cellLineStackingStrategy'])) {
23458 $tablefooter[$i][$js]['cellLineStackingStrategy'] = $cell['cellLineStackingStrategy'];
23460 if (isset($cell['cellLineStackingShift'])) {
23461 $tablefooter[$i][$js]['cellLineStackingShift'] = $cell['cellLineStackingShift'];
23468 if ($level == 1) {
23469 $this->_out('___TABLE___BACKGROUNDS' . $this->uniqstr);
23471 $tableheaderadj = 0;
23472 $tablefooteradj = 0;
23474 $tablestartpageno = $this->page;
23476 // Draw Table Contents and Borders
23477 for ($i = 0; $i < $numrows; $i++) { // Rows
23478 if ($split && $startrow > 0) {
23479 $thnr = (isset($table['is_thead']) ? count($table['is_thead']) : 0);
23480 if ($i >= $thnr && $i < $startrow) {
23481 continue;
23483 if ($i == $startrow) {
23484 $returny = $rety - $tableheaderrowheight;
23488 // Get Maximum row/cell height in row - including rowspan>1 + 1 overlapping
23489 $maxrowheight = $this->_tableGetMaxRowHeight($table, $i);
23491 $skippage = false;
23492 $newpagestarted = false;
23493 for ($j = $startcol; $j < $numcols; $j++) { // Columns
23494 if ($split) {
23495 if ($table['colPg'][$j] > $splitpg) {
23496 break;
23498 $lastCol = $j;
23500 if (isset($cells[$i][$j]) && $cells[$i][$j]) {
23501 $cell = &$cells[$i][$j];
23502 if ($split) {
23503 $lastCol = $j + (isset($cell['colspan']) ? ($cell['colspan'] - 1) : 0);
23504 list($x, $w) = $this->_splitTableGetWidth($table, $i, $j);
23505 } else {
23506 list($x, $w) = $this->_tableGetWidth($table, $i, $j);
23509 list($y, $h) = $this->_tableGetHeight($table, $i, $j);
23510 $x += $x0;
23511 $y += $y0;
23512 $y -= $returny;
23514 if ($table['borders_separate']) {
23515 if (!empty($tablefooter) || $i == ($numrows - 1) || (isset($cell['rowspan']) && ($i + $cell['rowspan']) == $numrows) || (!isset($cell['rowspan']) && ($i + 1) == $numrows)) {
23516 $extra = $table['padding']['B'] + $table['border_details']['B']['w'] + $table['border_spacing_V'] / 2;
23517 // $extra = $table['margin']['B'] + $table['padding']['B'] + $table['border_details']['B']['w'] + $table['border_spacing_V']/2;
23518 } else {
23519 $extra = $table['border_spacing_V'] / 2;
23521 } else {
23522 $extra = $table['max_cell_border_width']['B'] / 2;
23525 if ($j == $startcol && ((($y + $maxrowheight + $extra ) > ($pagetrigger + 0.001)) || (($this->keepColumns || !$this->ColActive) && !empty($tablefooter) && ($y + $maxrowheight + $tablefooterrowheight + $extra) > $pagetrigger) && ($this->tableLevel == 1 && $i < ($numrows - $table['headernrows']))) && ($y0 > 0 || $x0 > 0) && !$this->InFooter && $this->autoPageBreak) {
23526 if (!$skippage) {
23527 $finalSpread = true;
23528 $firstSpread = true;
23529 if ($split) {
23530 for ($t = $startcol; $t < $numcols; $t++) {
23531 // Are there more columns to print on a next page?
23532 if ($table['colPg'][$t] > $splitpg) {
23533 $finalSpread = false;
23534 break;
23537 if ($startcol > 0) {
23538 $firstSpread = false;
23542 if (($this->keepColumns || !$this->ColActive) && !empty($tablefooter) && $i > 0) {
23543 $this->y = $y;
23544 $ya = $this->y;
23545 $this->TableHeaderFooter($tablefooter, $tablestartpage, $tablestartcolumn, 'F', $level, $firstSpread, $finalSpread);
23546 if ($this->table_rotate) {
23547 $this->tbrot_h += $this->y - $ya;
23549 $tablefooteradj = $this->y - $ya;
23551 $y -= $y0;
23552 $returny += $y;
23554 $oldcolumn = $this->CurrCol;
23555 if ($this->AcceptPageBreak()) {
23556 $newpagestarted = true;
23557 $this->y = $y + $y0;
23559 // Move down to account for border-spacing or
23560 // extra half border width in case page breaks in middle
23561 if ($i > 0 && !$this->table_rotate && $level == 1 && !$this->ColActive) {
23562 if ($table['borders_separate']) {
23563 $adv = $table['border_spacing_V'] / 2;
23564 // If table footer
23565 if (($this->keepColumns || !$this->ColActive) && !empty($tablefooter) && $i > 0) {
23566 $adv += ($table['padding']['B'] + $table['border_details']['B']['w']);
23568 } else {
23569 $maxbwtop = 0;
23570 $maxbwbottom = 0;
23571 if (!$this->simpleTables) {
23572 if (!empty($tablefooter)) {
23573 $maxbwbottom = $table['max_cell_border_width']['B'];
23574 } else {
23575 $brow = $i - 1;
23576 for ($ctj = 0; $ctj < $numcols; $ctj++) {
23577 if (isset($cells[$brow][$ctj]) && $cells[$brow][$ctj]) {
23578 if ($this->packTableData) {
23579 list($bt, $br, $bb, $bl) = $this->_getBorderWidths($cells[$brow][$ctj]['borderbin']);
23580 } else {
23581 $bb = $cells[$brow][$ctj]['border_details']['B']['w'];
23583 $maxbwbottom = max($maxbwbottom, $bb);
23587 if (!empty($tableheader)) {
23588 $maxbwtop = $table['max_cell_border_width']['T'];
23589 } else {
23590 $trow = $i - 1;
23591 for ($ctj = 0; $ctj < $numcols; $ctj++) {
23592 if (isset($cells[$trow][$ctj]) && $cells[$trow][$ctj]) {
23593 if ($this->packTableData) {
23594 list($bt, $br, $bb, $bl) = $this->_getBorderWidths($cells[$trow][$ctj]['borderbin']);
23595 } else {
23596 $bt = $cells[$trow][$ctj]['border_details']['T']['w'];
23598 $maxbwtop = max($maxbwtop, $bt);
23602 } elseif ($this->simpleTables) {
23603 $maxbwtop = $table['simple']['border_details']['T']['w'];
23604 $maxbwbottom = $table['simple']['border_details']['B']['w'];
23606 $adv = $maxbwbottom / 2;
23608 $this->y += $adv;
23611 // Rotated table split over pages - needs this->y for borders/backgrounds
23612 if ($i > 0 && $this->table_rotate && $level == 1) {
23613 // $this->y = $y0 + $this->tbrot_w;
23616 if ($this->tableClipPath) {
23617 $this->_out("Q");
23620 $bx = $x0;
23621 $by = $y0;
23623 if ($table['borders_separate']) {
23624 $bx -= ($table['padding']['L'] + $table['border_details']['L']['w'] + $table['border_spacing_H'] / 2);
23625 if ($tablestartpageno != $this->page) { // IF already broken across a previous pagebreak
23626 $by += $table['max_cell_border_width']['T'] / 2;
23627 if (empty($tableheader)) {
23628 $by -= ($table['border_spacing_V'] / 2);
23630 } else {
23631 $by -= ($table['padding']['T'] + $table['border_details']['T']['w'] + $table['border_spacing_V'] / 2);
23633 } elseif ($tablestartpageno != $this->page && !empty($tableheader)) {
23634 $by += $maxbwtop / 2;
23637 $by -= $tableheaderadj;
23638 $bh = $this->y - $by + $tablefooteradj;
23639 if (!$table['borders_separate']) {
23640 $bh -= $adv;
23642 if ($split) {
23643 $bw = 0;
23644 for ($t = $startcol; $t < $numcols; $t++) {
23645 if ($table['colPg'][$t] == $splitpg) {
23646 $bw += $table['wc'][$t];
23648 if ($table['colPg'][$t] > $splitpg) {
23649 break;
23652 if ($table['borders_separate']) {
23653 if ($firstSpread) {
23654 $bw += $table['padding']['L'] + $table['border_details']['L']['w'] + $table['border_spacing_H'];
23655 } else {
23656 $bx += ($table['padding']['L'] + $table['border_details']['L']['w']);
23657 $bw += $table['border_spacing_H'];
23659 if ($finalSpread) {
23660 $bw += $table['padding']['R'] + $table['border_details']['R']['w'] / 2 + $table['border_spacing_H'];
23663 } else {
23664 $bw = $table['w'] - ($table['max_cell_border_width']['L'] / 2) - ($table['max_cell_border_width']['R'] / 2) - $table['margin']['L'] - $table['margin']['R'];
23667 if ($this->splitTableBorderWidth && ($this->keepColumns || !$this->ColActive) && empty($tablefooter) && $i > 0 && $table['border_details']['B']['w']) {
23668 $prevDrawColor = $this->DrawColor;
23669 $lw = $this->LineWidth;
23670 $this->SetLineWidth($this->splitTableBorderWidth);
23671 $this->SetDColor($table['border_details']['B']['c']);
23672 $this->SetLineJoin(0);
23673 $this->SetLineCap(0);
23674 $blx = $bx;
23675 $blw = $bw;
23676 if (!$table['borders_separate']) {
23677 $blx -= ($table['max_cell_border_width']['L'] / 2);
23678 $blw += ($table['max_cell_border_width']['L'] / 2 + $table['max_cell_border_width']['R'] / 2);
23680 $this->Line($blx, $this->y + ($this->splitTableBorderWidth / 2), $blx + $blw, $this->y + ($this->splitTableBorderWidth / 2));
23681 $this->DrawColor = $prevDrawColor;
23682 $this->_out($this->DrawColor);
23683 $this->SetLineWidth($lw);
23684 $this->SetLineJoin(2);
23685 $this->SetLineCap(2);
23688 if (!$this->ColActive && ($i > 0 || $j > 0)) {
23689 if (isset($table['bgcolor'][-1])) {
23690 $color = $this->colorConverter->convert($table['bgcolor'][-1], $this->PDFAXwarnings);
23691 if ($color) {
23692 if (!$table['borders_separate']) {
23693 $bh -= $table['max_cell_border_width']['B'] / 2;
23695 $this->tableBackgrounds[$level * 9][] = ['gradient' => false, 'x' => $bx, 'y' => $by, 'w' => $bw, 'h' => $bh, 'col' => $color];
23699 /* -- BACKGROUNDS -- */
23700 if (isset($table['gradient'])) {
23701 $g = $this->gradient->parseBackgroundGradient($table['gradient']);
23702 if ($g) {
23703 $this->tableBackgrounds[$level * 9 + 1][] = ['gradient' => true, 'x' => $bx, 'y' => $by, 'w' => $bw, 'h' => $bh, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => ''];
23707 if (isset($table['background-image'])) {
23708 if ($table['background-image']['gradient'] && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $table['background-image']['gradient'])) {
23709 $g = $this->gradient->parseMozGradient($table['background-image']['gradient']);
23710 if ($g) {
23711 $this->tableBackgrounds[$level * 9 + 1][] = ['gradient' => true, 'x' => $bx, 'y' => $by, 'w' => $bw, 'h' => $bh, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => ''];
23713 } else {
23714 $image_id = $table['background-image']['image_id'];
23715 $orig_w = $table['background-image']['orig_w'];
23716 $orig_h = $table['background-image']['orig_h'];
23717 $x_pos = $table['background-image']['x_pos'];
23718 $y_pos = $table['background-image']['y_pos'];
23719 $x_repeat = $table['background-image']['x_repeat'];
23720 $y_repeat = $table['background-image']['y_repeat'];
23721 $resize = $table['background-image']['resize'];
23722 $opacity = $table['background-image']['opacity'];
23723 $itype = $table['background-image']['itype'];
23724 $this->tableBackgrounds[$level * 9 + 2][] = ['x' => $bx, 'y' => $by, 'w' => $bw, 'h' => $bh, 'image_id' => $image_id, 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $x_pos, 'y_pos' => $y_pos, 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'clippath' => '', 'resize' => $resize, 'opacity' => $opacity, 'itype' => $itype];
23727 /* -- END BACKGROUNDS -- */
23730 // $this->AcceptPageBreak() has moved tablebuffer to $this->pages content
23731 if ($this->tableBackgrounds) {
23732 $s = $this->PrintTableBackgrounds();
23733 if ($this->bufferoutput) {
23734 $this->headerbuffer = preg_replace('/(___TABLE___BACKGROUNDS' . $this->uniqstr . ')/', '\\1' . "\n" . $s . "\n", $this->headerbuffer);
23735 $this->headerbuffer = preg_replace('/(___TABLE___BACKGROUNDS' . $this->uniqstr . ')/', " ", $this->headerbuffer);
23736 } else {
23737 $this->pages[$this->page] = preg_replace('/(___TABLE___BACKGROUNDS' . $this->uniqstr . ')/', '\\1' . "\n" . $s . "\n", $this->pages[$this->page]);
23738 $this->pages[$this->page] = preg_replace('/(___TABLE___BACKGROUNDS' . $this->uniqstr . ')/', " ", $this->pages[$this->page]);
23740 $this->tableBackgrounds = [];
23743 if ($split) {
23744 if ($i == 0 && $j == 0) {
23745 $y0 = -1;
23746 } elseif ($finalSpread) {
23747 $splitpg = 0;
23748 $startcol = 0;
23749 $startrow = $i;
23750 } else {
23751 $splitpg++;
23752 $startcol = $t;
23753 $returny -= $y;
23755 return [false, $startrow, $startcol, $splitpg, $returny, $y0];
23758 $this->AddPage($this->CurOrientation);
23760 $this->_out('___TABLE___BACKGROUNDS' . $this->uniqstr);
23763 if ($this->tableClipPath) {
23764 $this->_out($this->tableClipPath);
23767 // Added to correct for OddEven Margins
23768 $x = $x + $this->MarginCorrection;
23769 $x0 = $x0 + $this->MarginCorrection;
23771 if ($this->splitTableBorderWidth && ($this->keepColumns || !$this->ColActive) && empty($tableheader) && $i > 0 && $table['border_details']['T']['w']) {
23772 $prevDrawColor = $this->DrawColor;
23773 $lw = $this->LineWidth;
23774 $this->SetLineWidth($this->splitTableBorderWidth);
23775 $this->SetDColor($table['border_details']['T']['c']);
23776 $this->SetLineJoin(0);
23777 $this->SetLineCap(0);
23778 $blx += $this->MarginCorrection;
23779 $this->Line($blx, $this->y - ($this->splitTableBorderWidth / 2), $blx + $blw, $this->y - ($this->splitTableBorderWidth / 2));
23780 $this->DrawColor = $prevDrawColor;
23781 $this->_out($this->DrawColor);
23782 $this->SetLineWidth($lw);
23783 $this->SetLineJoin(2);
23784 $this->SetLineCap(2);
23787 // Move down to account for half of top border-spacing or
23788 // extra half border width in case page was broken in middle
23789 if ($i > 0 && !$this->table_rotate && $level == 1 && $table['headernrows'] == 0) {
23790 if ($table['borders_separate']) {
23791 $adv = $table['border_spacing_V'] / 2;
23792 } else {
23793 $maxbwtop = 0;
23794 for ($ctj = 0; $ctj < $numcols; $ctj++) {
23795 if (isset($cells[$i][$ctj]) && $cells[$i][$ctj]) {
23796 if (!$this->simpleTables) {
23797 if ($this->packTableData) {
23798 list($bt, $br, $bb, $bl) = $this->_getBorderWidths($cells[$i][$ctj]['borderbin']);
23799 } else {
23800 $bt = $cells[$i][$ctj]['border_details']['T']['w'];
23802 $maxbwtop = max($maxbwtop, $bt);
23803 } elseif ($this->simpleTables) {
23804 $maxbwtop = max($maxbwtop, $table['simple']['border_details']['T']['w']);
23808 $adv = $maxbwtop / 2;
23810 $this->y += $adv;
23814 if ($this->table_rotate) {
23815 $this->tbrot_x0 = $this->lMargin + $this->blk[$this->blklvl]['outer_left_margin'] + $this->blk[$this->blklvl]['padding_left'] + $this->blk[$this->blklvl]['border_left']['w'];
23816 if ($table['borders_separate']) {
23817 $this->tbrot_h = $table['margin']['T'] + $table['padding']['T'] + $table['border_details']['T']['w'] + $table['border_spacing_V'] / 2;
23818 } else {
23819 $this->tbrot_h = $table['margin']['T'] + $table['max_cell_border_width']['T'];
23821 $this->tbrot_y0 = $this->y;
23822 $pagetrigger = $y0 - $tableheaderadj + ($this->blk[$this->blklvl]['inner_width']);
23823 } else {
23824 $pagetrigger = $this->PageBreakTrigger;
23827 if ($this->kwt_saved && $level == 1) {
23828 $this->kwt_moved = true;
23832 if (!empty($tableheader)) {
23833 $ya = $this->y;
23834 $this->TableHeaderFooter($tableheader, $tablestartpage, $tablestartcolumn, 'H', $level);
23835 if ($this->table_rotate) {
23836 $this->tbrot_h = $this->y - $ya;
23838 $tableheaderadj = $this->y - $ya;
23839 } elseif ($i == 0 && !$this->table_rotate && $level == 1 && !$this->ColActive) {
23840 // Advance down page
23841 if ($table['borders_separate']) {
23842 $adv = $table['border_spacing_V'] / 2 + $table['border_details']['T']['w'] + $table['padding']['T'];
23843 } else {
23844 $adv = $table['max_cell_border_width']['T'] / 2;
23846 if ($adv) {
23847 if ($this->table_rotate) {
23848 $this->y += ($adv);
23849 } else {
23850 $this->DivLn($adv, $this->blklvl, true);
23855 $outerfilled = 0;
23856 $y = $y0 = $this->y;
23859 /* -- COLUMNS -- */
23860 // COLS
23861 // COLUMN CHANGE
23862 if ($this->CurrCol != $oldcolumn) {
23863 // Added to correct for Columns
23864 $x += $this->ChangeColumn * ($this->ColWidth + $this->ColGap);
23865 $x0 += $this->ChangeColumn * ($this->ColWidth + $this->ColGap);
23866 if ($this->CurrCol == 0) { // just added a page - possibly with tableheader
23867 $y0 = $this->y; // this->y0 is global used by Columns - $y0 is internal to tablewrite
23868 } else {
23869 $y0 = $this->y0; // this->y0 is global used by Columns - $y0 is internal to tablewrite
23871 $y = $y0;
23872 $outerfilled = 0;
23873 if ($this->CurrCol != 0 && ($this->keepColumns && $this->ColActive) && !empty($tableheader) && $i > 0) {
23874 $this->x = $x;
23875 $this->y = $y;
23876 $this->TableHeaderFooter($tableheader, $tablestartpage, $tablestartcolumn, 'H', $level);
23877 $y0 = $y = $this->y;
23880 /* -- END COLUMNS -- */
23882 $skippage = true;
23885 $this->x = $x;
23886 $this->y = $y;
23888 if ($this->kwt_saved && $level == 1) {
23889 $this->printkwtbuffer();
23890 $x0 = $x = $this->x;
23891 $y0 = $y = $this->y;
23892 $this->kwt_moved = false;
23893 $this->kwt_saved = false;
23897 // Set the Page & Column where table actually starts
23898 if ($i == 0 && $j == 0 && $level == 1) {
23899 if (($this->mirrorMargins) && (($this->page) % 2 == 0)) { // EVEN
23900 $tablestartpage = 'EVEN';
23901 } elseif (($this->mirrorMargins) && (($this->page) % 2 == 1)) { // ODD
23902 $tablestartpage = 'ODD';
23903 } else {
23904 $tablestartpage = '';
23906 $tablestartpageno = $this->page;
23907 if ($this->ColActive) {
23908 $tablestartcolumn = $this->CurrCol;
23909 } // *COLUMNS*
23912 // ALIGN
23913 $align = $cell['a'];
23915 /* -- COLUMNS -- */
23916 // If outside columns, this is done in PaintDivBB
23917 if ($this->ColActive) {
23918 // OUTER FILL BGCOLOR of DIVS
23919 if ($this->blklvl > 0 && ($j == 0) && !$this->table_rotate && $level == 1) {
23920 $firstblockfill = $this->GetFirstBlockFill();
23921 if ($firstblockfill && $this->blklvl >= $firstblockfill) {
23922 $divh = $maxrowheight;
23923 // Last row
23924 if ((!isset($cell['rowspan']) && $i == $numrows - 1) || (isset($cell['rowspan']) && (($i == $numrows - 1 && $cell['rowspan'] < 2) || ($cell['rowspan'] > 1 && ($i + $cell['rowspan'] - 1) == $numrows - 1)))) {
23925 if ($table['borders_separate']) {
23926 $adv = $table['margin']['B'] + $table['padding']['B'] + $table['border_details']['B']['w'] + $table['border_spacing_V'] / 2;
23927 } else {
23928 $adv = $table['margin']['B'] + $table['max_cell_border_width']['B'] / 2;
23930 $divh += $adv; // last row: fill bottom half of bottom border (y advanced at end)
23933 if (($this->y + $divh) > $outerfilled) { // if not already painted by previous rowspan
23934 $bak_x = $this->x;
23935 $bak_y = $this->y;
23936 if ($outerfilled > $this->y) {
23937 $divh = ($this->y + $divh) - $outerfilled;
23938 $this->y = $outerfilled;
23941 $this->DivLn($divh, -3, false);
23942 $outerfilled = $this->y + $divh;
23943 // Reset current block fill
23944 $bcor = $this->blk[$this->blklvl]['bgcolorarray'];
23945 if ($bcor) {
23946 $this->SetFColor($bcor);
23948 $this->x = $bak_x;
23949 $this->y = $bak_y;
23955 // TABLE BACKGROUND FILL BGCOLOR - for cellSpacing
23956 if ($this->ColActive) {
23957 if ($table['borders_separate']) {
23958 $fill = isset($table['bgcolor'][-1]) ? $table['bgcolor'][-1] : 0;
23959 if ($fill) {
23960 $color = $this->colorConverter->convert($fill, $this->PDFAXwarnings);
23961 if ($color) {
23962 $xadj = ($table['border_spacing_H'] / 2);
23963 $yadj = ($table['border_spacing_V'] / 2);
23964 $wadj = $table['border_spacing_H'];
23965 $hadj = $table['border_spacing_V'];
23966 if ($i == 0) { // Top
23967 $yadj += $table['padding']['T'] + $table['border_details']['T']['w'];
23968 $hadj += $table['padding']['T'] + $table['border_details']['T']['w'];
23970 if ($j == 0) { // Left
23971 $xadj += $table['padding']['L'] + $table['border_details']['L']['w'];
23972 $wadj += $table['padding']['L'] + $table['border_details']['L']['w'];
23974 if ($i == ($numrows - 1) || (isset($cell['rowspan']) && ($i + $cell['rowspan']) == $numrows) || (!isset($cell['rowspan']) && ($i + 1) == $numrows)) { // Bottom
23975 $hadj += $table['padding']['B'] + $table['border_details']['B']['w'];
23977 if ($j == ($numcols - 1) || (isset($cell['colspan']) && ($j + $cell['colspan']) == $numcols) || (!isset($cell['colspan']) && ($j + 1) == $numcols)) { // Right
23978 $wadj += $table['padding']['R'] + $table['border_details']['R']['w'];
23980 $this->SetFColor($color);
23981 $this->Rect($x - $xadj, $y - $yadj, $w + $wadj, $h + $hadj, 'F');
23986 /* -- END COLUMNS -- */
23988 if ($table['empty_cells'] != 'hide' || !empty($cell['textbuffer']) || (isset($cell['nestedcontent']) && $cell['nestedcontent']) || !$table['borders_separate']) {
23989 $paintcell = true;
23990 } else {
23991 $paintcell = false;
23994 // Set Borders
23995 $bord = 0;
23996 $bord_det = [];
23998 if (!$this->simpleTables) {
23999 if ($this->packTableData) {
24000 $c = $this->_unpackCellBorder($cell['borderbin']);
24001 $bord = $c['border'];
24002 $bord_det = $c['border_details'];
24003 } else {
24004 $bord = $cell['border'];
24005 $bord_det = $cell['border_details'];
24007 } elseif ($this->simpleTables) {
24008 $bord = $table['simple']['border'];
24009 $bord_det = $table['simple']['border_details'];
24012 // TABLE ROW OR CELL FILL BGCOLOR
24013 $fill = 0;
24014 if (isset($cell['bgcolor']) && $cell['bgcolor'] && $cell['bgcolor'] != 'transparent') {
24015 $fill = $cell['bgcolor'];
24016 $leveladj = 6;
24017 } elseif (isset($table['bgcolor'][$i]) && $table['bgcolor'][$i] && $table['bgcolor'][$i] != 'transparent') { // Row color
24018 $fill = $table['bgcolor'][$i];
24019 $leveladj = 3;
24021 if ($fill && $paintcell) {
24022 $color = $this->colorConverter->convert($fill, $this->PDFAXwarnings);
24023 if ($color) {
24024 if ($table['borders_separate']) {
24025 if ($this->ColActive) {
24026 $this->SetFColor($color);
24027 $this->Rect($x + ($table['border_spacing_H'] / 2), $y + ($table['border_spacing_V'] / 2), $w - $table['border_spacing_H'], $h - $table['border_spacing_V'], 'F');
24028 } else {
24029 $this->tableBackgrounds[$level * 9 + $leveladj][] = ['gradient' => false, 'x' => ($x + ($table['border_spacing_H'] / 2)), 'y' => ($y + ($table['border_spacing_V'] / 2)), 'w' => ($w - $table['border_spacing_H']), 'h' => ($h - $table['border_spacing_V']), 'col' => $color];
24031 } else {
24032 if ($this->ColActive) {
24033 $this->SetFColor($color);
24034 $this->Rect($x, $y, $w, $h, 'F');
24035 } else {
24036 $this->tableBackgrounds[$level * 9 + $leveladj][] = ['gradient' => false, 'x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'col' => $color];
24042 /* -- BACKGROUNDS -- */
24043 if (isset($cell['gradient']) && $cell['gradient'] && $paintcell) {
24044 $g = $this->gradient->parseBackgroundGradient($cell['gradient']);
24045 if ($g) {
24046 if ($table['borders_separate']) {
24047 $px = $x + ($table['border_spacing_H'] / 2);
24048 $py = $y + ($table['border_spacing_V'] / 2);
24049 $pw = $w - $table['border_spacing_H'];
24050 $ph = $h - $table['border_spacing_V'];
24051 } else {
24052 $px = $x;
24053 $py = $y;
24054 $pw = $w;
24055 $ph = $h;
24057 if ($this->ColActive) {
24058 $this->gradient->Gradient($px, $py, $pw, $ph, $g['type'], $g['stops'], $g['colorspace'], $g['coords'], $g['extend']);
24059 } else {
24060 $this->tableBackgrounds[$level * 9 + 7][] = ['gradient' => true, 'x' => $px, 'y' => $py, 'w' => $pw, 'h' => $ph, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => ''];
24065 if (isset($cell['background-image']) && $paintcell) {
24066 if (isset($cell['background-image']['gradient']) && $cell['background-image']['gradient'] && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $cell['background-image']['gradient'])) {
24067 $g = $this->gradient->parseMozGradient($cell['background-image']['gradient']);
24068 if ($g) {
24069 if ($table['borders_separate']) {
24070 $px = $x + ($table['border_spacing_H'] / 2);
24071 $py = $y + ($table['border_spacing_V'] / 2);
24072 $pw = $w - $table['border_spacing_H'];
24073 $ph = $h - $table['border_spacing_V'];
24074 } else {
24075 $px = $x;
24076 $py = $y;
24077 $pw = $w;
24078 $ph = $h;
24080 if ($this->ColActive) {
24081 $this->gradient->Gradient($px, $py, $pw, $ph, $g['type'], $g['stops'], $g['colorspace'], $g['coords'], $g['extend']);
24082 } else {
24083 $this->tableBackgrounds[$level * 9 + 7][] = ['gradient' => true, 'x' => $px, 'y' => $py, 'w' => $pw, 'h' => $ph, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => ''];
24086 } elseif (isset($cell['background-image']['image_id']) && $cell['background-image']['image_id']) { // Background pattern
24087 $n = count($this->patterns) + 1;
24088 if ($table['borders_separate']) {
24089 $px = $x + ($table['border_spacing_H'] / 2);
24090 $py = $y + ($table['border_spacing_V'] / 2);
24091 $pw = $w - $table['border_spacing_H'];
24092 $ph = $h - $table['border_spacing_V'];
24093 } else {
24094 $px = $x;
24095 $py = $y;
24096 $pw = $w;
24097 $ph = $h;
24099 if ($this->ColActive) {
24100 list($orig_w, $orig_h, $x_repeat, $y_repeat) = $this->_resizeBackgroundImage($cell['background-image']['orig_w'], $cell['background-image']['orig_h'], $pw, $ph, $cell['background-image']['resize'], $cell['background-image']['x_repeat'], $cell['background-image']['y_repeat']);
24101 $this->patterns[$n] = ['x' => $px, 'y' => $py, 'w' => $pw, 'h' => $ph, 'pgh' => $this->h, 'image_id' => $cell['background-image']['image_id'], 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $cell['background-image']['x_pos'], 'y_pos' => $cell['background-image']['y_pos'], 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat];
24102 if ($cell['background-image']['opacity'] > 0 && $cell['background-image']['opacity'] < 1) {
24103 $opac = $this->SetAlpha($cell['background-image']['opacity'], 'Normal', true);
24104 } else {
24105 $opac = '';
24107 $this->_out(sprintf('q /Pattern cs /P%d scn %s %.3F %.3F %.3F %.3F re f Q', $n, $opac, $px * Mpdf::SCALE, ($this->h - $py) * Mpdf::SCALE, $pw * Mpdf::SCALE, -$ph * Mpdf::SCALE));
24108 } else {
24109 $image_id = $cell['background-image']['image_id'];
24110 $orig_w = $cell['background-image']['orig_w'];
24111 $orig_h = $cell['background-image']['orig_h'];
24112 $x_pos = $cell['background-image']['x_pos'];
24113 $y_pos = $cell['background-image']['y_pos'];
24114 $x_repeat = $cell['background-image']['x_repeat'];
24115 $y_repeat = $cell['background-image']['y_repeat'];
24116 $resize = $cell['background-image']['resize'];
24117 $opacity = $cell['background-image']['opacity'];
24118 $itype = $cell['background-image']['itype'];
24119 $this->tableBackgrounds[$level * 9 + 8][] = ['x' => $px, 'y' => $py, 'w' => $pw, 'h' => $ph, 'image_id' => $image_id, 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $x_pos, 'y_pos' => $y_pos, 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'clippath' => '', 'resize' => $resize, 'opacity' => $opacity, 'itype' => $itype];
24123 /* -- END BACKGROUNDS -- */
24125 if (isset($cell['colspan']) && $cell['colspan'] > 1) {
24126 $ccolsp = $cell['colspan'];
24127 } else {
24128 $ccolsp = 1;
24130 if (isset($cell['rowspan']) && $cell['rowspan'] > 1) {
24131 $crowsp = $cell['rowspan'];
24132 } else {
24133 $crowsp = 1;
24137 // but still need to do this for repeated headers...
24138 if (!$table['borders_separate'] && $this->tabletheadjustfinished && !$this->simpleTables) {
24139 if (isset($table['topntail']) && $table['topntail']) {
24140 $bord_det['T'] = $this->border_details($table['topntail']);
24141 $bord_det['T']['w'] /= $this->shrin_k;
24142 $this->setBorder($bord, Border::TOP);
24144 if (isset($table['thead-underline']) && $table['thead-underline']) {
24145 $bord_det['T'] = $this->border_details($table['thead-underline']);
24146 $bord_det['T']['w'] /= $this->shrin_k;
24147 $this->setBorder($bord, Border::TOP);
24152 // Get info of first row ==>> table header
24153 // Use > 1 row if THEAD
24154 if (isset($table['is_thead'][$i]) && $table['is_thead'][$i] && $level == 1) {
24155 if ($j == 0) {
24156 $tableheaderrowheight += $table['hr'][$i];
24158 $tableheader[$i][0]['trbackground-images'] = (isset($table['trbackground-images'][$i]) ? $table['trbackground-images'][$i] : null);
24159 $tableheader[$i][0]['trgradients'] = (isset($table['trgradients'][$i]) ? $table['trgradients'][$i] : null);
24160 $tableheader[$i][0]['trbgcolor'] = (isset($table['bgcolor'][$i]) ? $table['bgcolor'][$i] : null);
24161 $tableheader[$i][$j]['x'] = $x;
24162 $tableheader[$i][$j]['y'] = $y;
24163 $tableheader[$i][$j]['h'] = $h;
24164 $tableheader[$i][$j]['w'] = $w;
24165 if (isset($cell['textbuffer'])) {
24166 $tableheader[$i][$j]['textbuffer'] = $cell['textbuffer'];
24167 } else {
24168 $tableheader[$i][$j]['textbuffer'] = '';
24170 $tableheader[$i][$j]['a'] = $cell['a'];
24171 $tableheader[$i][$j]['R'] = $cell['R'];
24173 $tableheader[$i][$j]['va'] = $cell['va'];
24174 $tableheader[$i][$j]['mih'] = $cell['mih'];
24175 $tableheader[$i][$j]['gradient'] = (isset($cell['gradient']) ? $cell['gradient'] : null); // *BACKGROUNDS*
24176 $tableheader[$i][$j]['background-image'] = (isset($cell['background-image']) ? $cell['background-image'] : null); // *BACKGROUNDS*
24177 $tableheader[$i][$j]['rowspan'] = (isset($cell['rowspan']) ? $cell['rowspan'] : null);
24178 $tableheader[$i][$j]['colspan'] = (isset($cell['colspan']) ? $cell['colspan'] : null);
24179 $tableheader[$i][$j]['bgcolor'] = $cell['bgcolor'];
24181 if (!$this->simpleTables) {
24182 $tableheader[$i][$j]['border'] = $bord;
24183 $tableheader[$i][$j]['border_details'] = $bord_det;
24184 } elseif ($this->simpleTables) {
24185 $tableheader[$i][$j]['border'] = $table['simple']['border'];
24186 $tableheader[$i][$j]['border_details'] = $table['simple']['border_details'];
24188 $tableheader[$i][$j]['padding'] = $cell['padding'];
24189 if (isset($cell['direction'])) {
24190 $tableheader[$i][$j]['direction'] = $cell['direction'];
24192 if (isset($cell['cellLineHeight'])) {
24193 $tableheader[$i][$j]['cellLineHeight'] = $cell['cellLineHeight'];
24195 if (isset($cell['cellLineStackingStrategy'])) {
24196 $tableheader[$i][$j]['cellLineStackingStrategy'] = $cell['cellLineStackingStrategy'];
24198 if (isset($cell['cellLineStackingShift'])) {
24199 $tableheader[$i][$j]['cellLineStackingShift'] = $cell['cellLineStackingShift'];
24203 // CELL BORDER
24204 if ($bord) {
24205 if ($table['borders_separate'] && $paintcell) {
24206 $this->_tableRect($x + ($table['border_spacing_H'] / 2) + ($bord_det['L']['w'] / 2), $y + ($table['border_spacing_V'] / 2) + ($bord_det['T']['w'] / 2), $w - $table['border_spacing_H'] - ($bord_det['L']['w'] / 2) - ($bord_det['R']['w'] / 2), $h - $table['border_spacing_V'] - ($bord_det['T']['w'] / 2) - ($bord_det['B']['w'] / 2), $bord, $bord_det, false, $table['borders_separate']);
24207 } elseif (!$table['borders_separate']) {
24208 $this->_tableRect($x, $y, $w, $h, $bord, $bord_det, true, $table['borders_separate']); // true causes buffer
24212 // VERTICAL ALIGN
24213 if ($cell['R'] && intval($cell['R']) > 0 && intval($cell['R']) < 90 && isset($cell['va']) && $cell['va'] != 'B') {
24214 $cell['va'] = 'B';
24216 if (!isset($cell['va']) || $cell['va'] == 'M') {
24217 $this->y += ($h - $cell['mih']) / 2;
24218 } elseif (isset($cell['va']) && $cell['va'] == 'B') {
24219 $this->y += $h - $cell['mih'];
24222 // NESTED CONTENT
24223 // TEXT (and nested tables)
24225 $this->divwidth = $w;
24226 if (!empty($cell['textbuffer'])) {
24227 $this->cellTextAlign = $align;
24228 $this->cellLineHeight = $cell['cellLineHeight'];
24229 $this->cellLineStackingStrategy = $cell['cellLineStackingStrategy'];
24230 $this->cellLineStackingShift = $cell['cellLineStackingShift'];
24231 if ($level == 1) {
24232 if (isset($table['is_tfoot'][$i]) && $table['is_tfoot'][$i]) {
24233 if (preg_match('/{colsum([0-9]*)[_]*}/', $cell['textbuffer'][0][0], $m)) {
24234 $rep = sprintf("%01." . intval($m[1]) . "f", $this->colsums[$j]);
24235 $cell['textbuffer'][0][0] = preg_replace('/{colsum[0-9_]*}/', $rep, $cell['textbuffer'][0][0]);
24237 } elseif (!isset($table['is_thead'][$i])) {
24238 if (isset($this->colsums[$j])) {
24239 $this->colsums[$j] += $this->toFloat($cell['textbuffer'][0][0]);
24240 } else {
24241 $this->colsums[$j] = $this->toFloat($cell['textbuffer'][0][0]);
24245 $opy = $this->y;
24246 // mPDF ITERATION
24247 if ($this->iterationCounter) {
24248 foreach ($cell['textbuffer'] as $k => $t) {
24249 if (preg_match('/{iteration ([a-zA-Z0-9_]+)}/', $t[0], $m)) {
24250 $vname = '__' . $m[1] . '_';
24251 if (!isset($this->$vname)) {
24252 $this->$vname = 1;
24253 } else {
24254 $this->$vname++;
24256 $cell['textbuffer'][$k][0] = preg_replace('/{iteration ' . $m[1] . '}/', $this->$vname, $cell['textbuffer'][$k][0]);
24262 if ($cell['R']) {
24263 $cellPtSize = $cell['textbuffer'][0][11] / $this->shrin_k;
24264 if (!$cellPtSize) {
24265 $cellPtSize = $this->default_font_size;
24267 $cellFontHeight = ($cellPtSize / Mpdf::SCALE);
24268 $opx = $this->x;
24269 $angle = intval($cell['R']);
24270 // Only allow 45 to 89 degrees (when bottom-aligned) or exactly 90 or -90
24271 if ($angle > 90) {
24272 $angle = 90;
24273 } elseif ($angle > 0 && $angle < 45) {
24274 $angle = 45;
24275 } elseif ($angle < 0) {
24276 $angle = -90;
24278 $offset = ((sin(deg2rad($angle))) * 0.37 * $cellFontHeight);
24279 if (isset($cell['a']) && $cell['a'] == 'R') {
24280 $this->x += ($w) + ($offset) - ($cellFontHeight / 3) - ($cell['padding']['R'] + ($table['border_spacing_H'] / 2));
24281 } elseif (!isset($cell['a']) || $cell['a'] == 'C') {
24282 $this->x += ($w / 2) + ($offset);
24283 } else {
24284 $this->x += ($offset) + ($cellFontHeight / 3) + ($cell['padding']['L'] + ($table['border_spacing_H'] / 2));
24286 $str = '';
24287 foreach ($cell['textbuffer'] as $t) {
24288 $str .= $t[0] . ' ';
24290 $str = rtrim($str);
24291 if (!isset($cell['va']) || $cell['va'] == 'M') {
24292 $this->y -= ($h - $cell['mih']) / 2; // Undo what was added earlier VERTICAL ALIGN
24293 if ($angle > 0) {
24294 $this->y += (($h - $cell['mih']) / 2) + $cell['padding']['T'] + ($cell['mih'] - ($cell['padding']['T'] + $cell['padding']['B']));
24295 } elseif ($angle < 0) {
24296 $this->y += (($h - $cell['mih']) / 2) + ($cell['padding']['T'] + ($table['border_spacing_V'] / 2));
24298 } elseif (isset($cell['va']) && $cell['va'] == 'B') {
24299 $this->y -= $h - $cell['mih']; // Undo what was added earlier VERTICAL ALIGN
24300 if ($angle > 0) {
24301 $this->y += $h - ($cell['padding']['B'] + ($table['border_spacing_V'] / 2));
24302 } elseif ($angle < 0) {
24303 $this->y += $h - $cell['mih'] + ($cell['padding']['T'] + ($table['border_spacing_V'] / 2));
24305 } elseif (isset($cell['va']) && $cell['va'] == 'T') {
24306 if ($angle > 0) {
24307 $this->y += $cell['mih'] - ($cell['padding']['B'] + ($table['border_spacing_V'] / 2));
24308 } elseif ($angle < 0) {
24309 $this->y += ($cell['padding']['T'] + ($table['border_spacing_V'] / 2));
24312 $this->Rotate($angle, $this->x, $this->y);
24313 $s_fs = $this->FontSizePt;
24314 $s_f = $this->FontFamily;
24315 $s_st = $this->FontStyle;
24316 if (!empty($cell['textbuffer'][0][3])) { // Font Color
24317 $cor = $cell['textbuffer'][0][3];
24318 $this->SetTColor($cor);
24320 $this->SetFont($cell['textbuffer'][0][4], $cell['textbuffer'][0][2], $cellPtSize, true, true);
24322 $this->magic_reverse_dir($str, $this->directionality, $cell['textbuffer'][0][18]);
24323 $this->Text($this->x, $this->y, $str, $cell['textbuffer'][0][18], $cell['textbuffer'][0][8]); // textvar
24324 $this->Rotate(0);
24325 $this->SetFont($s_f, $s_st, $s_fs, true, true);
24326 $this->SetTColor(0);
24327 $this->x = $opx;
24328 } else {
24329 if (!$this->simpleTables) {
24330 if ($bord_det) {
24331 $btlw = $bord_det['L']['w'];
24332 $btrw = $bord_det['R']['w'];
24333 $bttw = $bord_det['T']['w'];
24334 } else {
24335 $btlw = 0;
24336 $btrw = 0;
24337 $bttw = 0;
24339 if ($table['borders_separate']) {
24340 $xadj = $btlw + $cell['padding']['L'] + ($table['border_spacing_H'] / 2);
24341 $wadj = $btlw + $btrw + $cell['padding']['L'] + $cell['padding']['R'] + $table['border_spacing_H'];
24342 $yadj = $bttw + $cell['padding']['T'] + ($table['border_spacing_H'] / 2);
24343 } else {
24344 $xadj = $btlw / 2 + $cell['padding']['L'];
24345 $wadj = ($btlw + $btrw) / 2 + $cell['padding']['L'] + $cell['padding']['R'];
24346 $yadj = $bttw / 2 + $cell['padding']['T'];
24348 } elseif ($this->simpleTables) {
24349 if ($table['borders_separate']) { // NB twice border width
24350 $xadj = $table['simple']['border_details']['L']['w'] + $cell['padding']['L'] + ($table['border_spacing_H'] / 2);
24351 $wadj = $table['simple']['border_details']['L']['w'] + $table['simple']['border_details']['R']['w'] + $cell['padding']['L'] + $cell['padding']['R'] + $table['border_spacing_H'];
24352 $yadj = $table['simple']['border_details']['T']['w'] + $cell['padding']['T'] + ($table['border_spacing_H'] / 2);
24353 } else {
24354 $xadj = $table['simple']['border_details']['L']['w'] / 2 + $cell['padding']['L'];
24355 $wadj = ($table['simple']['border_details']['L']['w'] + $table['simple']['border_details']['R']['w']) / 2 + $cell['padding']['L'] + $cell['padding']['R'];
24356 $yadj = $table['simple']['border_details']['T']['w'] / 2 + $cell['padding']['T'];
24359 $this->decimal_offset = 0;
24360 if (substr($cell['a'], 0, 1) == 'D') {
24361 if (isset($cell['colspan']) && $cell['colspan'] > 1) {
24362 $this->cellTextAlign = $c['a'] = substr($cell['a'], 2, 1);
24363 } else {
24364 $smax = $table['decimal_align'][$j]['maxs0'];
24365 $d_content = $table['decimal_align'][$j]['maxs0'] + $table['decimal_align'][$j]['maxs1'];
24366 $this->decimal_offset = $smax;
24367 $extra = ($w - $d_content - $wadj);
24368 if ($extra > 0) {
24369 if (substr($cell['a'], 2, 1) == 'R') {
24370 $this->decimal_offset += $extra;
24371 } elseif (substr($cell['a'], 2, 1) == 'C') {
24372 $this->decimal_offset += ($extra) / 2;
24377 $this->divwidth = $w - $wadj;
24378 if ($this->divwidth == 0) {
24379 $this->divwidth = 0.0001;
24381 $this->x += $xadj;
24382 $this->y += $yadj;
24383 $this->printbuffer($cell['textbuffer'], '', true, false, $cell['direction']);
24385 $this->y = $opy;
24388 /* -- BACKGROUNDS -- */
24389 if (!$this->ColActive) {
24390 if (isset($table['trgradients'][$i]) && ($j == 0 || $table['borders_separate'])) {
24391 $g = $this->gradient->parseBackgroundGradient($table['trgradients'][$i]);
24392 if ($g) {
24393 $gx = $x0;
24394 $gy = $y;
24395 $gh = $h;
24396 $gw = $table['w'] - ($table['max_cell_border_width']['L'] / 2) - ($table['max_cell_border_width']['R'] / 2) - $table['margin']['L'] - $table['margin']['R'];
24397 if ($table['borders_separate']) {
24398 $gw -= ($table['padding']['L'] + $table['border_details']['L']['w'] + $table['padding']['R'] + $table['border_details']['R']['w'] + $table['border_spacing_H']);
24399 $clx = $x + ($table['border_spacing_H'] / 2);
24400 $cly = $y + ($table['border_spacing_V'] / 2);
24401 $clw = $w - $table['border_spacing_H'];
24402 $clh = $h - $table['border_spacing_V'];
24403 // Set clipping path
24404 $s = $this->_setClippingPath($clx, $cly, $clw, $clh); // mPDF 6
24405 $this->tableBackgrounds[$level * 9 + 4][] = ['gradient' => true, 'x' => $gx + ($table['border_spacing_H'] / 2), 'y' => $gy + ($table['border_spacing_V'] / 2), 'w' => $gw - $table['border_spacing_V'], 'h' => $gh - $table['border_spacing_H'], 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => $s];
24406 } else {
24407 $this->tableBackgrounds[$level * 9 + 4][] = ['gradient' => true, 'x' => $gx, 'y' => $gy, 'w' => $gw, 'h' => $gh, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => ''];
24411 if (isset($table['trbackground-images'][$i]) && ($j == 0 || $table['borders_separate'])) {
24412 if (isset($table['trbackground-images'][$i]['gradient']) && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $table['trbackground-images'][$i]['gradient'])) {
24413 $g = $this->gradient->parseMozGradient($table['trbackground-images'][$i]['gradient']);
24414 if ($g) {
24415 $gx = $x0;
24416 $gy = $y;
24417 $gh = $h;
24418 $gw = $table['w'] - ($table['max_cell_border_width']['L'] / 2) - ($table['max_cell_border_width']['R'] / 2) - $table['margin']['L'] - $table['margin']['R'];
24419 if ($table['borders_separate']) {
24420 $gw -= ($table['padding']['L'] + $table['border_details']['L']['w'] + $table['padding']['R'] + $table['border_details']['R']['w'] + $table['border_spacing_H']);
24421 $clx = $x + ($table['border_spacing_H'] / 2);
24422 $cly = $y + ($table['border_spacing_V'] / 2);
24423 $clw = $w - $table['border_spacing_H'];
24424 $clh = $h - $table['border_spacing_V'];
24425 // Set clipping path
24426 $s = $this->_setClippingPath($clx, $cly, $clw, $clh); // mPDF 6
24427 $this->tableBackgrounds[$level * 9 + 4][] = ['gradient' => true, 'x' => $gx + ($table['border_spacing_H'] / 2), 'y' => $gy + ($table['border_spacing_V'] / 2), 'w' => $gw - $table['border_spacing_V'], 'h' => $gh - $table['border_spacing_H'], 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => $s];
24428 } else {
24429 $this->tableBackgrounds[$level * 9 + 4][] = ['gradient' => true, 'x' => $gx, 'y' => $gy, 'w' => $gw, 'h' => $gh, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => ''];
24432 } else {
24433 $image_id = $table['trbackground-images'][$i]['image_id'];
24434 $orig_w = $table['trbackground-images'][$i]['orig_w'];
24435 $orig_h = $table['trbackground-images'][$i]['orig_h'];
24436 $x_pos = $table['trbackground-images'][$i]['x_pos'];
24437 $y_pos = $table['trbackground-images'][$i]['y_pos'];
24438 $x_repeat = $table['trbackground-images'][$i]['x_repeat'];
24439 $y_repeat = $table['trbackground-images'][$i]['y_repeat'];
24440 $resize = $table['trbackground-images'][$i]['resize'];
24441 $opacity = $table['trbackground-images'][$i]['opacity'];
24442 $itype = $table['trbackground-images'][$i]['itype'];
24443 $clippath = '';
24444 $gx = $x0;
24445 $gy = $y;
24446 $gh = $h;
24447 $gw = $table['w'] - ($table['max_cell_border_width']['L'] / 2) - ($table['max_cell_border_width']['R'] / 2) - $table['margin']['L'] - $table['margin']['R'];
24448 if ($table['borders_separate']) {
24449 $gw -= ($table['padding']['L'] + $table['border_details']['L']['w'] + $table['padding']['R'] + $table['border_details']['R']['w'] + $table['border_spacing_H']);
24450 $clx = $x + ($table['border_spacing_H'] / 2);
24451 $cly = $y + ($table['border_spacing_V'] / 2);
24452 $clw = $w - $table['border_spacing_H'];
24453 $clh = $h - $table['border_spacing_V'];
24454 // Set clipping path
24455 $s = $this->_setClippingPath($clx, $cly, $clw, $clh); // mPDF 6
24456 $this->tableBackgrounds[$level * 9 + 5][] = ['x' => $gx + ($table['border_spacing_H'] / 2), 'y' => $gy + ($table['border_spacing_V'] / 2), 'w' => $gw - $table['border_spacing_V'], 'h' => $gh - $table['border_spacing_H'], 'image_id' => $image_id, 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $x_pos, 'y_pos' => $y_pos, 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'clippath' => $s, 'resize' => $resize, 'opacity' => $opacity, 'itype' => $itype];
24457 } else {
24458 $this->tableBackgrounds[$level * 9 + 5][] = ['x' => $gx, 'y' => $gy, 'w' => $gw, 'h' => $gh, 'image_id' => $image_id, 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $x_pos, 'y_pos' => $y_pos, 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'clippath' => '', 'resize' => $resize, 'opacity' => $opacity, 'itype' => $itype];
24464 /* -- END BACKGROUNDS -- */
24466 // TABLE BORDER - if separate
24467 if (($table['borders_separate'] || ($this->simpleTables && !$table['simple']['border'])) && $table['border']) {
24468 $halfspaceL = $table['padding']['L'] + ($table['border_spacing_H'] / 2);
24469 $halfspaceR = $table['padding']['R'] + ($table['border_spacing_H'] / 2);
24470 $halfspaceT = $table['padding']['T'] + ($table['border_spacing_V'] / 2);
24471 $halfspaceB = $table['padding']['B'] + ($table['border_spacing_V'] / 2);
24472 $tbx = $x;
24473 $tby = $y;
24474 $tbw = $w;
24475 $tbh = $h;
24476 $tab_bord = 0;
24478 $corner = '';
24479 if ($i == 0) { // Top
24480 $tby -= $halfspaceT + ($table['border_details']['T']['w'] / 2);
24481 $tbh += $halfspaceT + ($table['border_details']['T']['w'] / 2);
24482 $this->setBorder($tab_bord, Border::TOP);
24483 $corner .= 'T';
24485 if ($i == ($numrows - 1) || (isset($cell['rowspan']) && ($i + $cell['rowspan']) == $numrows)) { // Bottom
24486 $tbh += $halfspaceB + ($table['border_details']['B']['w'] / 2);
24487 $this->setBorder($tab_bord, Border::BOTTOM);
24488 $corner .= 'B';
24490 if ($j == 0) { // Left
24491 $tbx -= $halfspaceL + ($table['border_details']['L']['w'] / 2);
24492 $tbw += $halfspaceL + ($table['border_details']['L']['w'] / 2);
24493 $this->setBorder($tab_bord, Border::LEFT);
24494 $corner .= 'L';
24496 if ($j == ($numcols - 1) || (isset($cell['colspan']) && ($j + $cell['colspan']) == $numcols)) { // Right
24497 $tbw += $halfspaceR + ($table['border_details']['R']['w'] / 2);
24498 $this->setBorder($tab_bord, Border::RIGHT);
24499 $corner .= 'R';
24501 $this->_tableRect($tbx, $tby, $tbw, $tbh, $tab_bord, $table['border_details'], false, $table['borders_separate'], 'table', $corner, $table['border_spacing_V'], $table['border_spacing_H']);
24504 unset($cell);
24505 // Reset values
24506 $this->Reset();
24507 }//end of (if isset(cells)...)
24508 }// end of columns
24510 $newpagestarted = false;
24511 $this->tabletheadjustfinished = false;
24513 /* -- COLUMNS -- */
24514 if ($this->ColActive) {
24515 if (!$this->table_keep_together && $i < $numrows - 1 && $level == 1) {
24516 $this->breakpoints[$this->CurrCol][] = $y + $h;
24517 } // mPDF 6
24518 if (count($this->cellBorderBuffer)) {
24519 $this->printcellbuffer();
24522 /* -- END COLUMNS -- */
24524 if ($i == $numrows - 1) {
24525 $this->y = $y + $h;
24526 } // last row jump (update this->y position)
24527 if ($this->table_rotate && $level == 1) {
24528 $this->tbrot_h += $h;
24530 } // end of rows
24532 if (count($this->cellBorderBuffer)) {
24533 $this->printcellbuffer();
24537 if ($this->tableClipPath) {
24538 $this->_out("Q");
24540 $this->tableClipPath = '';
24542 // Advance down page by half width of bottom border
24543 if ($table['borders_separate']) {
24544 $this->y += $table['padding']['B'] + $table['border_details']['B']['w'] + $table['border_spacing_V'] / 2;
24545 } else {
24546 $this->y += $table['max_cell_border_width']['B'] / 2;
24549 if ($table['borders_separate'] && $level == 1) {
24550 $this->tbrot_h += $table['margin']['B'] + $table['padding']['B'] + $table['border_details']['B']['w'] + $table['border_spacing_V'] / 2;
24551 } elseif ($level == 1) {
24552 $this->tbrot_h += $table['margin']['B'] + $table['max_cell_border_width']['B'] / 2;
24555 $bx = $x0;
24556 $by = $y0;
24557 if ($table['borders_separate']) {
24558 $bx -= ($table['padding']['L'] + $table['border_details']['L']['w'] + $table['border_spacing_H'] / 2);
24559 if ($tablestartpageno != $this->page) { // IF broken across page
24560 $by += $table['max_cell_border_width']['T'] / 2;
24561 if (empty($tableheader)) {
24562 $by -= ($table['border_spacing_V'] / 2);
24564 } elseif ($split && $startrow > 0 && empty($tableheader)) {
24565 $by -= ($table['border_spacing_V'] / 2);
24566 } else {
24567 $by -= ($table['padding']['T'] + $table['border_details']['T']['w'] + $table['border_spacing_V'] / 2);
24569 } elseif ($tablestartpageno != $this->page && !empty($tableheader)) {
24570 $by += $maxbwtop / 2;
24572 $by -= $tableheaderadj;
24573 $bh = $this->y - $by;
24574 if (!$table['borders_separate']) {
24575 $bh -= $table['max_cell_border_width']['B'] / 2;
24578 if ($split) {
24579 $bw = 0;
24580 $finalSpread = true;
24581 for ($t = $startcol; $t < $numcols; $t++) {
24582 if ($table['colPg'][$t] == $splitpg) {
24583 $bw += $table['wc'][$t];
24585 if ($table['colPg'][$t] > $splitpg) {
24586 $finalSpread = false;
24587 break;
24590 if ($startcol == 0) {
24591 $firstSpread = true;
24592 } else {
24593 $firstSpread = false;
24595 if ($table['borders_separate']) {
24596 $bw += $table['border_spacing_H'];
24597 if ($firstSpread) {
24598 $bw += $table['padding']['L'] + $table['border_details']['L']['w'];
24599 } else {
24600 $bx += ($table['padding']['L'] + $table['border_details']['L']['w']);
24602 if ($finalSpread) {
24603 $bw += $table['padding']['R'] + $table['border_details']['R']['w'];
24606 } else {
24607 $bw = $table['w'] - ($table['max_cell_border_width']['L'] / 2) - ($table['max_cell_border_width']['R'] / 2) - $table['margin']['L'] - $table['margin']['R'];
24610 if (!$this->ColActive) {
24611 if (isset($table['bgcolor'][-1])) {
24612 $color = $this->colorConverter->convert($table['bgcolor'][-1], $this->PDFAXwarnings);
24613 if ($color) {
24614 $this->tableBackgrounds[$level * 9][] = ['gradient' => false, 'x' => $bx, 'y' => $by, 'w' => $bw, 'h' => $bh, 'col' => $color];
24618 /* -- BACKGROUNDS -- */
24619 if (isset($table['gradient'])) {
24620 $g = $this->gradient->parseBackgroundGradient($table['gradient']);
24621 if ($g) {
24622 $this->tableBackgrounds[$level * 9 + 1][] = ['gradient' => true, 'x' => $bx, 'y' => $by, 'w' => $bw, 'h' => $bh, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => ''];
24626 if (isset($table['background-image'])) {
24627 if (isset($table['background-image']['gradient']) && $table['background-image']['gradient'] && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $table['background-image']['gradient'])) {
24628 $g = $this->gradient->parseMozGradient($table['background-image']['gradient']);
24629 if ($g) {
24630 $this->tableBackgrounds[$level * 9 + 1][] = ['gradient' => true, 'x' => $bx, 'y' => $by, 'w' => $bw, 'h' => $bh, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => ''];
24632 } else {
24633 $image_id = $table['background-image']['image_id'];
24634 $orig_w = $table['background-image']['orig_w'];
24635 $orig_h = $table['background-image']['orig_h'];
24636 $x_pos = $table['background-image']['x_pos'];
24637 $y_pos = $table['background-image']['y_pos'];
24638 $x_repeat = $table['background-image']['x_repeat'];
24639 $y_repeat = $table['background-image']['y_repeat'];
24640 $resize = $table['background-image']['resize'];
24641 $opacity = $table['background-image']['opacity'];
24642 $itype = $table['background-image']['itype'];
24643 $this->tableBackgrounds[$level * 9 + 2][] = ['x' => $bx, 'y' => $by, 'w' => $bw, 'h' => $bh, 'image_id' => $image_id, 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $x_pos, 'y_pos' => $y_pos, 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'clippath' => '', 'resize' => $resize, 'opacity' => $opacity, 'itype' => $itype];
24646 /* -- END BACKGROUNDS -- */
24649 if ($this->tableBackgrounds && $level == 1) {
24650 $s = $this->PrintTableBackgrounds();
24651 if ($this->table_rotate && !$this->processingHeader && !$this->processingFooter) {
24652 $this->tablebuffer = preg_replace('/(___TABLE___BACKGROUNDS' . $this->uniqstr . ')/', '\\1' . "\n" . $s . "\n", $this->tablebuffer);
24653 if ($level == 1) {
24654 $this->tablebuffer = preg_replace('/(___TABLE___BACKGROUNDS' . $this->uniqstr . ')/', " ", $this->tablebuffer);
24656 } elseif ($this->bufferoutput) {
24657 $this->headerbuffer = preg_replace('/(___TABLE___BACKGROUNDS' . $this->uniqstr . ')/', '\\1' . "\n" . $s . "\n", $this->headerbuffer);
24658 if ($level == 1) {
24659 $this->headerbuffer = preg_replace('/(___TABLE___BACKGROUNDS' . $this->uniqstr . ')/', " ", $this->headerbuffer);
24661 } else {
24662 $this->pages[$this->page] = preg_replace('/(___TABLE___BACKGROUNDS' . $this->uniqstr . ')/', '\\1' . "\n" . $s . "\n", $this->pages[$this->page]);
24663 if ($level == 1) {
24664 $this->pages[$this->page] = preg_replace('/(___TABLE___BACKGROUNDS' . $this->uniqstr . ')/', " ", $this->pages[$this->page]);
24667 $this->tableBackgrounds = [];
24671 // TABLE BOTTOM MARGIN
24672 if ($table['margin']['B']) {
24673 if (!$this->table_rotate && $level == 1) {
24674 $this->DivLn($table['margin']['B'], $this->blklvl, true); // collapsible
24675 } else {
24676 $this->y += ($table['margin']['B']);
24680 if ($this->ColActive && $level == 1) {
24681 $this->breakpoints[$this->CurrCol][] = $this->y;
24682 } // *COLUMNS*
24684 if ($split) {
24685 // Are there more columns to print on a next page?
24686 if ($lastCol < $numcols - 1) {
24687 $splitpg++;
24688 $startcol = $lastCol + 1;
24689 return [false, $startrow, $startcol, $splitpg, $returny, $y0];
24690 } else {
24691 return [true, 0, 0, 0, false, false];
24696 // END OF FUNCTION _tableWrite()
24697 /////////////////////////END OF TABLE CODE//////////////////////////////////
24698 /* -- END TABLES -- */
24700 function _putextgstates()
24702 for ($i = 1; $i <= count($this->extgstates); $i++) {
24703 $this->_newobj();
24704 $this->extgstates[$i]['n'] = $this->n;
24705 $this->_out('<</Type /ExtGState');
24706 foreach ($this->extgstates[$i]['parms'] as $k => $v) {
24707 $this->_out('/' . $k . ' ' . $v);
24709 $this->_out('>>');
24710 $this->_out('endobj');
24714 function _putocg()
24716 if ($this->hasOC) {
24717 $this->_newobj();
24718 $this->n_ocg_print = $this->n;
24719 $this->_out('<</Type /OCG /Name ' . $this->_textstring('Print only'));
24720 $this->_out('/Usage <</Print <</PrintState /ON>> /View <</ViewState /OFF>>>>>>');
24721 $this->_out('endobj');
24722 $this->_newobj();
24723 $this->n_ocg_view = $this->n;
24724 $this->_out('<</Type /OCG /Name ' . $this->_textstring('Screen only'));
24725 $this->_out('/Usage <</Print <</PrintState /OFF>> /View <</ViewState /ON>>>>>>');
24726 $this->_out('endobj');
24727 $this->_newobj();
24728 $this->n_ocg_hidden = $this->n;
24729 $this->_out('<</Type /OCG /Name ' . $this->_textstring('Hidden'));
24730 $this->_out('/Usage <</Print <</PrintState /OFF>> /View <</ViewState /OFF>>>>>>');
24731 $this->_out('endobj');
24733 if (count($this->layers)) {
24734 ksort($this->layers);
24735 foreach ($this->layers as $id => $layer) {
24736 $this->_newobj();
24737 $this->layers[$id]['n'] = $this->n;
24738 if (isset($this->layerDetails[$id]['name']) && $this->layerDetails[$id]['name']) {
24739 $name = $this->layerDetails[$id]['name'];
24740 } else {
24741 $name = $layer['name'];
24743 $this->_out('<</Type /OCG /Name ' . $this->_UTF16BEtextstring($name) . '>>');
24744 $this->_out('endobj');
24749 /* -- IMPORTS -- */
24751 // from mPDFI
24752 function _putimportedobjects()
24754 if (is_array($this->parsers) && count($this->parsers) > 0) {
24755 foreach ($this->parsers as $filename => $p) {
24756 $this->current_parser = $this->parsers[$filename];
24757 if (is_array($this->_obj_stack[$filename])) {
24758 while ($n = key($this->_obj_stack[$filename])) {
24759 $nObj = $this->current_parser->resolveObject($this->_obj_stack[$filename][$n][1]);
24760 $this->_newobj($this->_obj_stack[$filename][$n][0]);
24761 if ($nObj[0] == pdf_parser::TYPE_STREAM) {
24762 $this->pdf_write_value($nObj);
24763 } else {
24764 $this->pdf_write_value($nObj[1]);
24766 $this->_out('endobj');
24767 $this->_obj_stack[$filename][$n] = null; // free memory
24768 unset($this->_obj_stack[$filename][$n]);
24769 reset($this->_obj_stack[$filename]);
24776 function _putformxobjects()
24778 $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
24779 reset($this->tpls);
24780 foreach ($this->tpls as $tplidx => $tpl) {
24781 $p = ($this->compress) ? gzcompress($tpl['buffer']) : $tpl['buffer'];
24782 $this->_newobj();
24783 $this->tpls[$tplidx]['n'] = $this->n;
24784 $this->_out('<<' . $filter . '/Type /XObject');
24785 $this->_out('/Subtype /Form');
24786 $this->_out('/FormType 1');
24787 // Left/Bottom/Right/Top
24788 $this->_out(sprintf('/BBox [%.2F %.2F %.2F %.2F]', $tpl['box']['x'] * Mpdf::SCALE, $tpl['box']['y'] * Mpdf::SCALE, ($tpl['box']['x'] + $tpl['box']['w']) * Mpdf::SCALE, ($tpl['box']['y'] + $tpl['box']['h']) * Mpdf::SCALE));
24791 if (isset($tpl['box'])) {
24792 $this->_out(sprintf('/Matrix [1 0 0 1 %.5F %.5F]', -$tpl['box']['x'] * Mpdf::SCALE, -$tpl['box']['y'] * Mpdf::SCALE));
24794 $this->_out('/Resources ');
24796 if (isset($tpl['resources'])) {
24797 $this->current_parser = $tpl['parser'];
24798 $this->pdf_write_value($tpl['resources']);
24799 } else {
24800 $this->_out('<</ProcSet [/PDF /Text /ImageB /ImageC /ImageI]');
24801 if (isset($this->_res['tpl'][$tplidx]['fonts']) && count($this->_res['tpl'][$tplidx]['fonts'])) {
24802 $this->_out('/Font <<');
24803 foreach ($this->_res['tpl'][$tplidx]['fonts'] as $font) {
24804 $this->_out('/F' . $font['i'] . ' ' . $font['n'] . ' 0 R');
24806 $this->_out('>>');
24808 if (isset($this->_res['tpl'][$tplidx]['images']) && count($this->_res['tpl'][$tplidx]['images']) ||
24809 isset($this->_res['tpl'][$tplidx]['tpls']) && count($this->_res['tpl'][$tplidx]['tpls'])) {
24810 $this->_out('/XObject <<');
24811 if (isset($this->_res['tpl'][$tplidx]['images']) && count($this->_res['tpl'][$tplidx]['images'])) {
24812 foreach ($this->_res['tpl'][$tplidx]['images'] as $image) {
24813 $this->_out('/I' . $image['i'] . ' ' . $image['n'] . ' 0 R');
24816 if (isset($this->_res['tpl'][$tplidx]['tpls']) && count($this->_res['tpl'][$tplidx]['tpls'])) {
24817 foreach ($this->_res['tpl'][$tplidx]['tpls'] as $i => $itpl) {
24818 $this->_out($this->tplprefix . $i . ' ' . $itpl['n'] . ' 0 R');
24821 $this->_out('>>');
24823 $this->_out('>>');
24826 $this->_out('/Length ' . strlen($p) . ' >>');
24827 $this->_putstream($p);
24828 $this->_out('endobj');
24832 /* -- END IMPORTS -- */
24834 function _putpatterns()
24836 for ($i = 1; $i <= count($this->patterns); $i++) {
24837 $x = $this->patterns[$i]['x'];
24838 $y = $this->patterns[$i]['y'];
24839 $w = $this->patterns[$i]['w'];
24840 $h = $this->patterns[$i]['h'];
24841 $pgh = $this->patterns[$i]['pgh'];
24842 $orig_w = $this->patterns[$i]['orig_w'];
24843 $orig_h = $this->patterns[$i]['orig_h'];
24844 $image_id = $this->patterns[$i]['image_id'];
24845 $itype = $this->patterns[$i]['itype'];
24847 if (isset($this->patterns[$i]['bpa'])) {
24848 $bpa = $this->patterns[$i]['bpa'];
24849 } else {
24850 $bpa = []; // background positioning area
24853 if ($this->patterns[$i]['x_repeat']) {
24854 $x_repeat = true;
24855 } else {
24856 $x_repeat = false;
24859 if ($this->patterns[$i]['y_repeat']) {
24860 $y_repeat = true;
24861 } else {
24862 $y_repeat = false;
24865 $x_pos = $this->patterns[$i]['x_pos'];
24867 if (stristr($x_pos, '%')) {
24868 $x_pos = (float) $x_pos;
24869 $x_pos /= 100;
24871 if (isset($bpa['w']) && $bpa['w']) {
24872 $x_pos = ($bpa['w'] * $x_pos) - ($orig_w / Mpdf::SCALE * $x_pos);
24873 } else {
24874 $x_pos = ($w * $x_pos) - ($orig_w / Mpdf::SCALE * $x_pos);
24878 $y_pos = $this->patterns[$i]['y_pos'];
24880 if (stristr($y_pos, '%')) {
24881 $y_pos = (float) $y_pos;
24882 $y_pos /= 100;
24884 if (isset($bpa['h']) && $bpa['h']) {
24885 $y_pos = ($bpa['h'] * $y_pos) - ($orig_h / Mpdf::SCALE * $y_pos);
24886 } else {
24887 $y_pos = ($h * $y_pos) - ($orig_h / Mpdf::SCALE * $y_pos);
24891 if (isset($bpa['x']) && $bpa['x']) {
24892 $adj_x = ($x_pos + $bpa['x']) * Mpdf::SCALE;
24893 } else {
24894 $adj_x = ($x_pos + $x) * Mpdf::SCALE;
24897 if (isset($bpa['y']) && $bpa['y']) {
24898 $adj_y = (($pgh - $y_pos - $bpa['y']) * Mpdf::SCALE) - $orig_h;
24899 } else {
24900 $adj_y = (($pgh - $y_pos - $y) * Mpdf::SCALE) - $orig_h;
24903 $img_obj = false;
24905 if ($itype == 'svg' || $itype == 'wmf') {
24906 foreach ($this->formobjects as $fo) {
24907 if ($fo['i'] == $image_id) {
24908 $img_obj = $fo['n'];
24909 $fo_w = $fo['w'];
24910 $fo_h = -$fo['h'];
24911 $wmf_x = $fo['x'];
24912 $wmf_y = $fo['y'];
24913 break;
24916 } else {
24917 foreach ($this->images as $img) {
24918 if ($img['i'] == $image_id) {
24919 $img_obj = $img['n'];
24920 break;
24925 if (!$img_obj) {
24926 throw new \Mpdf\MpdfException("Problem: Image object not found for background pattern " . $img['i']);
24929 $this->_newobj();
24930 $this->_out('<</ProcSet [/PDF /Text /ImageB /ImageC /ImageI]');
24931 if ($itype == 'svg' || $itype == 'wmf') {
24932 $this->_out('/XObject <</FO' . $image_id . ' ' . $img_obj . ' 0 R >>');
24933 // ******* ADD ANY ExtGStates, Shading AND Fonts needed for the FormObject
24934 // Set in classes/svg array['fo'] = true
24935 // Required that _putshaders comes before _putpatterns in _putresources
24936 // This adds any resources associated with any FormObject to every Formobject - overkill but works!
24937 if (count($this->extgstates)) {
24938 $this->_out('/ExtGState <<');
24939 foreach ($this->extgstates as $k => $extgstate) {
24940 if (isset($extgstate['fo']) && $extgstate['fo']) {
24941 if (isset($extgstate['trans'])) {
24942 $this->_out('/' . $extgstate['trans'] . ' ' . $extgstate['n'] . ' 0 R');
24943 } else {
24944 $this->_out('/GS' . $k . ' ' . $extgstate['n'] . ' 0 R');
24948 $this->_out('>>');
24950 /* -- BACKGROUNDS -- */
24951 if (isset($this->gradients) and ( count($this->gradients) > 0)) {
24952 $this->_out('/Shading <<');
24953 foreach ($this->gradients as $id => $grad) {
24954 if (isset($grad['fo']) && $grad['fo']) {
24955 $this->_out('/Sh' . $id . ' ' . $grad['id'] . ' 0 R');
24958 $this->_out('>>');
24960 /* -- END BACKGROUNDS -- */
24961 $this->_out('/Font <<');
24962 foreach ($this->fonts as $font) {
24963 if (!$font['used'] && $font['type'] == 'TTF') {
24964 continue;
24966 if (isset($font['fo']) && $font['fo']) {
24967 if ($font['type'] == 'TTF' && ($font['sip'] || $font['smp'])) {
24968 foreach ($font['n'] as $k => $fid) {
24969 $this->_out('/F' . $font['subsetfontids'][$k] . ' ' . $font['n'][$k] . ' 0 R');
24971 } else {
24972 $this->_out('/F' . $font['i'] . ' ' . $font['n'] . ' 0 R');
24976 $this->_out('>>');
24977 } else {
24978 $this->_out('/XObject <</I' . $image_id . ' ' . $img_obj . ' 0 R >>');
24980 $this->_out('>>');
24981 $this->_out('endobj');
24983 $this->_newobj();
24984 $this->patterns[$i]['n'] = $this->n;
24985 $this->_out('<< /Type /Pattern /PatternType 1 /PaintType 1 /TilingType 2');
24986 $this->_out('/Resources ' . ($this->n - 1) . ' 0 R');
24988 $this->_out(sprintf('/BBox [0 0 %.3F %.3F]', $orig_w, $orig_h));
24989 if ($x_repeat) {
24990 $this->_out(sprintf('/XStep %.3F', $orig_w));
24991 } else {
24992 $this->_out(sprintf('/XStep %d', 99999));
24994 if ($y_repeat) {
24995 $this->_out(sprintf('/YStep %.3F', $orig_h));
24996 } else {
24997 $this->_out(sprintf('/YStep %d', 99999));
25000 if ($itype == 'svg' || $itype == 'wmf') {
25001 $this->_out(sprintf('/Matrix [1 0 0 -1 %.3F %.3F]', $adj_x, ($adj_y + $orig_h)));
25002 $s = sprintf("q %.3F 0 0 %.3F %.3F %.3F cm /FO%d Do Q", ($orig_w / $fo_w), (-$orig_h / $fo_h), -($orig_w / $fo_w) * $wmf_x, ($orig_w / $fo_w) * $wmf_y, $image_id);
25003 } else {
25004 $this->_out(sprintf('/Matrix [1 0 0 1 %.3F %.3F]', $adj_x, $adj_y));
25005 $s = sprintf("q %.3F 0 0 %.3F 0 0 cm /I%d Do Q", $orig_w, $orig_h, $image_id);
25008 if ($this->compress) {
25009 $this->_out('/Filter /FlateDecode');
25010 $s = gzcompress($s);
25012 $this->_out('/Length ' . strlen($s) . '>>');
25013 $this->_putstream($s);
25014 $this->_out('endobj');
25018 /* -- BACKGROUNDS -- */
25020 function _putshaders()
25022 $maxid = count($this->gradients); // index for transparency gradients
25023 foreach ($this->gradients as $id => $grad) {
25024 if (($grad['type'] == 2 || $grad['type'] == 3) && empty($grad['is_mask'])) {
25025 $this->_newobj();
25026 $this->_out('<<');
25027 $this->_out('/FunctionType 3');
25028 $this->_out('/Domain [0 1]');
25029 $fn = [];
25030 $bd = [];
25031 $en = [];
25032 for ($i = 0; $i < (count($grad['stops']) - 1); $i++) {
25033 $fn[] = ($this->n + 1 + $i) . ' 0 R';
25034 $en[] = '0 1';
25035 if ($i > 0) {
25036 $bd[] = sprintf('%.3F', $grad['stops'][$i]['offset']);
25039 $this->_out('/Functions [' . implode(' ', $fn) . ']');
25040 $this->_out('/Bounds [' . implode(' ', $bd) . ']');
25041 $this->_out('/Encode [' . implode(' ', $en) . ']');
25042 $this->_out('>>');
25043 $this->_out('endobj');
25044 $f1 = $this->n;
25045 for ($i = 0; $i < (count($grad['stops']) - 1); $i++) {
25046 $this->_newobj();
25047 $this->_out('<<');
25048 $this->_out('/FunctionType 2');
25049 $this->_out('/Domain [0 1]');
25050 $this->_out('/C0 [' . $grad['stops'][$i]['col'] . ']');
25051 $this->_out('/C1 [' . $grad['stops'][$i + 1]['col'] . ']');
25052 $this->_out('/N 1');
25053 $this->_out('>>');
25054 $this->_out('endobj');
25057 if ($grad['type'] == 2 || $grad['type'] == 3) {
25058 if (isset($grad['trans']) && $grad['trans']) {
25059 $this->_newobj();
25060 $this->_out('<<');
25061 $this->_out('/FunctionType 3');
25062 $this->_out('/Domain [0 1]');
25063 $fn = [];
25064 $bd = [];
25065 $en = [];
25066 for ($i = 0; $i < (count($grad['stops']) - 1); $i++) {
25067 $fn[] = ($this->n + 1 + $i) . ' 0 R';
25068 $en[] = '0 1';
25069 if ($i > 0) {
25070 $bd[] = sprintf('%.3F', $grad['stops'][$i]['offset']);
25073 $this->_out('/Functions [' . implode(' ', $fn) . ']');
25074 $this->_out('/Bounds [' . implode(' ', $bd) . ']');
25075 $this->_out('/Encode [' . implode(' ', $en) . ']');
25076 $this->_out('>>');
25077 $this->_out('endobj');
25078 $f2 = $this->n;
25079 for ($i = 0; $i < (count($grad['stops']) - 1); $i++) {
25080 $this->_newobj();
25081 $this->_out('<<');
25082 $this->_out('/FunctionType 2');
25083 $this->_out('/Domain [0 1]');
25084 $this->_out(sprintf('/C0 [%.3F]', $grad['stops'][$i]['opacity']));
25085 $this->_out(sprintf('/C1 [%.3F]', $grad['stops'][$i + 1]['opacity']));
25086 $this->_out('/N 1');
25087 $this->_out('>>');
25088 $this->_out('endobj');
25093 if (empty($grad['is_mask'])) {
25094 $this->_newobj();
25095 $this->_out('<<');
25096 $this->_out('/ShadingType ' . $grad['type']);
25097 if (isset($grad['colorspace'])) {
25098 $this->_out('/ColorSpace /Device' . $grad['colorspace']); // Can use CMYK if all C0 and C1 above have 4 values
25099 } else {
25100 $this->_out('/ColorSpace /DeviceRGB');
25102 if ($grad['type'] == 2) {
25103 $this->_out(sprintf('/Coords [%.3F %.3F %.3F %.3F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3]));
25104 $this->_out('/Function ' . $f1 . ' 0 R');
25105 $this->_out('/Extend [' . $grad['extend'][0] . ' ' . $grad['extend'][1] . '] ');
25106 $this->_out('>>');
25107 } elseif ($grad['type'] == 3) {
25108 // x0, y0, r0, x1, y1, r1
25109 // at this this time radius of inner circle is 0
25110 $ir = 0;
25111 if (isset($grad['coords'][5]) && $grad['coords'][5]) {
25112 $ir = $grad['coords'][5];
25114 $this->_out(sprintf('/Coords [%.3F %.3F %.3F %.3F %.3F %.3F]', $grad['coords'][0], $grad['coords'][1], $ir, $grad['coords'][2], $grad['coords'][3], $grad['coords'][4]));
25115 $this->_out('/Function ' . $f1 . ' 0 R');
25116 $this->_out('/Extend [' . $grad['extend'][0] . ' ' . $grad['extend'][1] . '] ');
25117 $this->_out('>>');
25118 } elseif ($grad['type'] == 6) {
25119 $this->_out('/BitsPerCoordinate 16');
25120 $this->_out('/BitsPerComponent 8');
25121 if ($grad['colorspace'] == 'CMYK') {
25122 $this->_out('/Decode[0 1 0 1 0 1 0 1 0 1 0 1]');
25123 } elseif ($grad['colorspace'] == 'Gray') {
25124 $this->_out('/Decode[0 1 0 1 0 1]');
25125 } else {
25126 $this->_out('/Decode[0 1 0 1 0 1 0 1 0 1]');
25128 $this->_out('/BitsPerFlag 8');
25129 $this->_out('/Length ' . strlen($grad['stream']));
25130 $this->_out('>>');
25131 $this->_putstream($grad['stream']);
25133 $this->_out('endobj');
25136 $this->gradients[$id]['id'] = $this->n;
25138 // set pattern object
25139 $this->_newobj();
25140 $out = '<< /Type /Pattern /PatternType 2';
25141 $out .= ' /Shading ' . $this->gradients[$id]['id'] . ' 0 R';
25142 $out .= ' >>';
25143 $out .= "\n" . 'endobj';
25144 $this->_out($out);
25147 $this->gradients[$id]['pattern'] = $this->n;
25149 if (isset($grad['trans']) && $grad['trans']) {
25150 // luminosity pattern
25151 $transid = $id + $maxid;
25152 $this->_newobj();
25153 $this->_out('<<');
25154 $this->_out('/ShadingType ' . $grad['type']);
25155 $this->_out('/ColorSpace /DeviceGray');
25156 if ($grad['type'] == 2) {
25157 $this->_out(sprintf('/Coords [%.3F %.3F %.3F %.3F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3]));
25158 $this->_out('/Function ' . $f2 . ' 0 R');
25159 $this->_out('/Extend [' . $grad['extend'][0] . ' ' . $grad['extend'][1] . '] ');
25160 $this->_out('>>');
25161 } elseif ($grad['type'] == 3) {
25162 // x0, y0, r0, x1, y1, r1
25163 // at this this time radius of inner circle is 0
25164 $ir = 0;
25165 if (isset($grad['coords'][5]) && $grad['coords'][5]) {
25166 $ir = $grad['coords'][5];
25168 $this->_out(sprintf('/Coords [%.3F %.3F %.3F %.3F %.3F %.3F]', $grad['coords'][0], $grad['coords'][1], $ir, $grad['coords'][2], $grad['coords'][3], $grad['coords'][4]));
25169 $this->_out('/Function ' . $f2 . ' 0 R');
25170 $this->_out('/Extend [' . $grad['extend'][0] . ' ' . $grad['extend'][1] . '] ');
25171 $this->_out('>>');
25172 } elseif ($grad['type'] == 6) {
25173 $this->_out('/BitsPerCoordinate 16');
25174 $this->_out('/BitsPerComponent 8');
25175 $this->_out('/Decode[0 1 0 1 0 1]');
25176 $this->_out('/BitsPerFlag 8');
25177 $this->_out('/Length ' . strlen($grad['stream_trans']));
25178 $this->_out('>>');
25179 $this->_putstream($grad['stream_trans']);
25181 $this->_out('endobj');
25183 $this->gradients[$transid]['id'] = $this->n;
25184 $this->_newobj();
25185 $this->_out('<< /Type /Pattern /PatternType 2');
25186 $this->_out('/Shading ' . $this->gradients[$transid]['id'] . ' 0 R');
25187 $this->_out('>>');
25188 $this->_out('endobj');
25189 $this->gradients[$transid]['pattern'] = $this->n;
25190 $this->_newobj();
25191 // Need to extend size of viewing box in case of transformations
25192 $str = 'q /a0 gs /Pattern cs /p' . $transid . ' scn -' . ($this->wPt / 2) . ' -' . ($this->hPt / 2) . ' ' . (2 * $this->wPt) . ' ' . (2 * $this->hPt) . ' re f Q';
25193 $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
25194 $p = ($this->compress) ? gzcompress($str) : $str;
25195 $this->_out('<< /Type /XObject /Subtype /Form /FormType 1 ' . $filter);
25196 $this->_out('/Length ' . strlen($p));
25197 $this->_out('/BBox [-' . ($this->wPt / 2) . ' -' . ($this->hPt / 2) . ' ' . (2 * $this->wPt) . ' ' . (2 * $this->hPt) . ']');
25198 $this->_out('/Group << /Type /Group /S /Transparency /CS /DeviceGray >>');
25199 $this->_out('/Resources <<');
25200 $this->_out('/ExtGState << /a0 << /ca 1 /CA 1 >> >>');
25201 $this->_out('/Pattern << /p' . $transid . ' ' . $this->gradients[$transid]['pattern'] . ' 0 R >>');
25202 $this->_out('>>');
25203 $this->_out('>>');
25204 $this->_putstream($p);
25205 $this->_out('endobj');
25206 $this->_newobj();
25207 $this->_out('<< /Type /Mask /S /Luminosity /G ' . ($this->n - 1) . ' 0 R >>' . "\n" . 'endobj');
25208 $this->_newobj();
25209 $this->_out('<< /Type /ExtGState /SMask ' . ($this->n - 1) . ' 0 R /AIS false >>' . "\n" . 'endobj');
25210 if (isset($grad['fo']) && $grad['fo']) {
25211 $this->extgstates[] = ['n' => $this->n, 'trans' => 'TGS' . $id, 'fo' => true];
25212 } else {
25213 $this->extgstates[] = ['n' => $this->n, 'trans' => 'TGS' . $id];
25219 /* -- END BACKGROUNDS -- */
25221 function _putspotcolors()
25223 foreach ($this->spotColors as $name => $color) {
25224 $this->_newobj();
25225 $this->_out('[/Separation /' . str_replace(' ', '#20', $name));
25226 $this->_out('/DeviceCMYK <<');
25227 $this->_out('/Range [0 1 0 1 0 1 0 1] /C0 [0 0 0 0] ');
25228 $this->_out(sprintf('/C1 [%.3F %.3F %.3F %.3F] ', $color['c'] / 100, $color['m'] / 100, $color['y'] / 100, $color['k'] / 100));
25229 $this->_out('/FunctionType 2 /Domain [0 1] /N 1>>]');
25230 $this->_out('endobj');
25231 $this->spotColors[$name]['n'] = $this->n;
25235 function _putresources()
25237 if ($this->hasOC || count($this->layers)) {
25238 $this->_putocg();
25240 $this->_putextgstates();
25241 $this->_putspotcolors();
25243 // @log Compiling Fonts
25245 $this->_putfonts();
25247 // @log Compiling Images
25249 $this->_putimages();
25250 $this->_putformobjects(); // *IMAGES-CORE*
25252 /* -- IMPORTS -- */
25253 if ($this->enableImports) {
25254 $this->_putformxobjects();
25255 $this->_putimportedobjects();
25257 /* -- END IMPORTS -- */
25259 /* -- BACKGROUNDS -- */
25260 $this->_putshaders();
25261 $this->_putpatterns();
25262 /* -- END BACKGROUNDS -- */
25265 // Resource dictionary
25266 $this->offsets[2] = strlen($this->buffer);
25267 $this->_out('2 0 obj');
25268 $this->_out('<</ProcSet [/PDF /Text /ImageB /ImageC /ImageI]');
25270 $this->_out('/Font <<');
25271 foreach ($this->fonts as $font) {
25272 if (isset($font['type']) && $font['type'] == 'TTF' && !$font['used']) {
25273 continue;
25275 if (isset($font['type']) && $font['type'] == 'TTF' && ($font['sip'] || $font['smp'])) {
25276 foreach ($font['n'] as $k => $fid) {
25277 $this->_out('/F' . $font['subsetfontids'][$k] . ' ' . $font['n'][$k] . ' 0 R');
25279 } else {
25280 $this->_out('/F' . $font['i'] . ' ' . $font['n'] . ' 0 R');
25283 $this->_out('>>');
25285 if (count($this->spotColors)) {
25286 $this->_out('/ColorSpace <<');
25287 foreach ($this->spotColors as $color) {
25288 $this->_out('/CS' . $color['i'] . ' ' . $color['n'] . ' 0 R');
25290 $this->_out('>>');
25293 if (count($this->extgstates)) {
25294 $this->_out('/ExtGState <<');
25295 foreach ($this->extgstates as $k => $extgstate) {
25296 if (isset($extgstate['trans'])) {
25297 $this->_out('/' . $extgstate['trans'] . ' ' . $extgstate['n'] . ' 0 R');
25298 } else {
25299 $this->_out('/GS' . $k . ' ' . $extgstate['n'] . ' 0 R');
25302 $this->_out('>>');
25305 /* -- BACKGROUNDS -- */
25306 if ((isset($this->gradients) and ( count($this->gradients) > 0)) || ($this->enableImports && count($this->tpls))) { // mPDF 5.7.3
25308 $this->_out('/Shading <<');
25310 foreach ($this->gradients as $id => $grad) {
25311 $this->_out('/Sh' . $id . ' ' . $grad['id'] . ' 0 R');
25314 // mPDF 5.7.3
25315 // If a shading dictionary is in an object (tpl) imported from another PDF, it needs to be included
25316 // in the document resources, as well as the object resources
25317 // Otherwise get an error in some PDF viewers
25318 if ($this->enableImports && count($this->tpls)) {
25320 foreach ($this->tpls as $tplidx => $tpl) {
25322 if (isset($tpl['resources'])) {
25324 $this->current_parser = $tpl['parser'];
25326 foreach ($tpl['resources'][1] as $k => $v) {
25327 if ($k == '/Shading') {
25328 foreach ($v[1] as $k2 => $v2) {
25329 $this->_out($k2 . " ", false);
25330 $this->pdf_write_value($v2);
25338 $this->_out('>>');
25341 // ??? Not needed !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
25342 $this->_out('/Pattern <<');
25343 foreach ($this->gradients as $id => $grad) {
25344 $this->_out('/P'.$id.' '.$grad['pattern'].' 0 R');
25346 $this->_out('>>');
25349 /* -- END BACKGROUNDS -- */
25351 if (count($this->images) || count($this->formobjects) || ($this->enableImports && count($this->tpls))) {
25352 $this->_out('/XObject <<');
25353 foreach ($this->images as $image) {
25354 $this->_out('/I' . $image['i'] . ' ' . $image['n'] . ' 0 R');
25356 foreach ($this->formobjects as $formobject) {
25357 $this->_out('/FO' . $formobject['i'] . ' ' . $formobject['n'] . ' 0 R');
25359 /* -- IMPORTS -- */
25360 if ($this->enableImports && count($this->tpls)) {
25361 foreach ($this->tpls as $tplidx => $tpl) {
25362 $this->_out($this->tplprefix . $tplidx . ' ' . $tpl['n'] . ' 0 R');
25365 /* -- END IMPORTS -- */
25366 $this->_out('>>');
25369 /* -- BACKGROUNDS -- */
25371 if (count($this->patterns)) {
25372 $this->_out('/Pattern <<');
25373 foreach ($this->patterns as $k => $patterns) {
25374 $this->_out('/P' . $k . ' ' . $patterns['n'] . ' 0 R');
25376 $this->_out('>>');
25378 /* -- END BACKGROUNDS -- */
25380 if ($this->hasOC || count($this->layers)) {
25381 $this->_out('/Properties <<');
25382 if ($this->hasOC) {
25383 $this->_out('/OC1 ' . $this->n_ocg_print . ' 0 R /OC2 ' . $this->n_ocg_view . ' 0 R /OC3 ' . $this->n_ocg_hidden . ' 0 R ');
25385 if (count($this->layers)) {
25386 foreach ($this->layers as $id => $layer) {
25387 $this->_out('/ZI' . $id . ' ' . $layer['n'] . ' 0 R');
25390 $this->_out('>>');
25393 $this->_out('>>');
25394 $this->_out('endobj'); // end resource dictionary
25396 $this->_putbookmarks();
25398 if (isset($this->js) && $this->js) {
25399 $this->_putjavascript();
25402 if ($this->encrypted) {
25403 $this->_newobj();
25404 $this->enc_obj_id = $this->n;
25405 $this->_out('<<');
25406 $this->_putencryption();
25407 $this->_out('>>');
25408 $this->_out('endobj');
25412 function _putjavascript()
25414 $this->_newobj();
25415 $this->n_js = $this->n;
25416 $this->_out('<<');
25417 $this->_out('/Names [(EmbeddedJS) ' . (1 + $this->n) . ' 0 R ]');
25418 $this->_out('>>');
25419 $this->_out('endobj');
25421 $this->_newobj();
25422 $this->_out('<<');
25423 $this->_out('/S /JavaScript');
25424 $this->_out('/JS ' . $this->_textstring($this->js));
25425 $this->_out('>>');
25426 $this->_out('endobj');
25429 function _putencryption()
25431 $this->_out('/Filter /Standard');
25432 if ($this->protection->getUseRC128Encryption()) {
25433 $this->_out('/V 2');
25434 $this->_out('/R 3');
25435 $this->_out('/Length 128');
25436 } else {
25437 $this->_out('/V 1');
25438 $this->_out('/R 2');
25440 $this->_out('/O (' . $this->_escape($this->protection->getOValue()) . ')');
25441 $this->_out('/U (' . $this->_escape($this->protection->getUvalue()) . ')');
25442 $this->_out('/P ' . $this->protection->getPvalue());
25445 function _puttrailer()
25447 $this->_out('/Size ' . ($this->n + 1));
25448 $this->_out('/Root ' . $this->n . ' 0 R');
25449 $this->_out('/Info ' . $this->InfoRoot . ' 0 R');
25451 if ($this->encrypted) {
25452 $this->_out('/Encrypt ' . $this->enc_obj_id . ' 0 R');
25453 $this->_out('/ID [<' . $this->protection->getUniqid() . '> <' . $this->protection->getUniqid() . '>]');
25454 } else {
25455 $uniqid = md5(time() . $this->buffer);
25456 $this->_out('/ID [<' . $uniqid . '> <' . $uniqid . '>]');
25460 function SetProtection($permissions = [], $user_pass = '', $owner_pass = null, $length = 40)
25462 if (!$this->protection) {
25463 $this->protection = new Protection(new UniqidGenerator());
25466 $this->encrypted = $this->protection->setProtection($permissions, $user_pass, $owner_pass, $length);
25469 // =========================================
25470 /* -- BOOKMARKS -- */
25471 // FROM class PDF_Bookmark
25472 function Bookmark($txt, $level = 0, $y = 0)
25474 $txt = $this->purify_utf8_text($txt);
25475 if ($this->text_input_as_HTML) {
25476 $txt = $this->all_entities_to_utf8($txt);
25478 if ($y == -1) {
25479 if (!$this->ColActive) {
25480 $y = $this->y;
25481 } else {
25482 $y = $this->y0;
25483 } // If columns are on - mark top of columns
25485 // else y is used as set, or =0 i.e. top of page
25486 // DIRECTIONALITY RTL
25487 $bmo = ['t' => $txt, 'l' => $level, 'y' => $y, 'p' => $this->page];
25488 if ($this->keep_block_together) {
25489 // do nothing
25490 } /* -- TABLES -- */ elseif ($this->table_rotate) {
25491 $this->tbrot_BMoutlines[] = $bmo;
25492 } elseif ($this->kwt) {
25493 $this->kwt_BMoutlines[] = $bmo;
25494 } /* -- END TABLES -- */ elseif ($this->ColActive) { // *COLUMNS*
25495 $this->col_BMoutlines[] = $bmo; // *COLUMNS*
25496 } // *COLUMNS*
25497 else {
25498 $this->BMoutlines[] = $bmo;
25502 function _putbookmarks()
25504 $nb = count($this->BMoutlines);
25505 if ($nb == 0) {
25506 return;
25509 $bmo = $this->BMoutlines;
25510 $this->BMoutlines = [];
25511 $lastlevel = -1;
25512 for ($i = 0; $i < count($bmo); $i++) {
25513 if ($bmo[$i]['l'] > 0) {
25514 while ($bmo[$i]['l'] - $lastlevel > 1) { // If jump down more than one level, insert a new entry
25515 $new = $bmo[$i];
25516 $new['t'] = "[" . $new['t'] . "]"; // Put [] around text/title to highlight
25517 $new['l'] = $lastlevel + 1;
25518 $lastlevel++;
25519 $this->BMoutlines[] = $new;
25522 $this->BMoutlines[] = $bmo[$i];
25523 $lastlevel = $bmo[$i]['l'];
25525 $nb = count($this->BMoutlines);
25527 $lru = [];
25528 $level = 0;
25529 foreach ($this->BMoutlines as $i => $o) {
25530 if ($o['l'] > 0) {
25531 $parent = $lru[$o['l'] - 1];
25532 // Set parent and last pointers
25533 $this->BMoutlines[$i]['parent'] = $parent;
25534 $this->BMoutlines[$parent]['last'] = $i;
25535 if ($o['l'] > $level) {
25536 // Level increasing: set first pointer
25537 $this->BMoutlines[$parent]['first'] = $i;
25539 } else {
25540 $this->BMoutlines[$i]['parent'] = $nb;
25542 if ($o['l'] <= $level and $i > 0) {
25543 // Set prev and next pointers
25544 $prev = $lru[$o['l']];
25545 $this->BMoutlines[$prev]['next'] = $i;
25546 $this->BMoutlines[$i]['prev'] = $prev;
25548 $lru[$o['l']] = $i;
25549 $level = $o['l'];
25553 // Outline items
25554 $n = $this->n + 1;
25555 foreach ($this->BMoutlines as $i => $o) {
25556 $this->_newobj();
25557 $this->_out('<</Title ' . $this->_UTF16BEtextstring($o['t']));
25558 $this->_out('/Parent ' . ($n + $o['parent']) . ' 0 R');
25559 if (isset($o['prev'])) {
25560 $this->_out('/Prev ' . ($n + $o['prev']) . ' 0 R');
25562 if (isset($o['next'])) {
25563 $this->_out('/Next ' . ($n + $o['next']) . ' 0 R');
25565 if (isset($o['first'])) {
25566 $this->_out('/First ' . ($n + $o['first']) . ' 0 R');
25568 if (isset($o['last'])) {
25569 $this->_out('/Last ' . ($n + $o['last']) . ' 0 R');
25573 if (isset($this->pageDim[$o['p']]['h'])) {
25574 $h = $this->pageDim[$o['p']]['h'];
25575 } else {
25576 $h = 0;
25579 $this->_out(sprintf('/Dest [%d 0 R /XYZ 0 %.3F null]', 1 + 2 * ($o['p']), ($h - $o['y']) * Mpdf::SCALE));
25580 if (isset($this->bookmarkStyles) && isset($this->bookmarkStyles[$o['l']])) {
25581 // font style
25582 $bms = $this->bookmarkStyles[$o['l']]['style'];
25583 $style = 0;
25584 if (strpos($bms, 'B') !== false) {
25585 $style += 2;
25587 if (strpos($bms, 'I') !== false) {
25588 $style += 1;
25590 $this->_out(sprintf('/F %d', $style));
25591 // Colour
25592 $col = $this->bookmarkStyles[$o['l']]['color'];
25593 if (isset($col) && is_array($col) && count($col) == 3) {
25594 $this->_out(sprintf('/C [%.3F %.3F %.3F]', ($col[0] / 255), ($col[1] / 255), ($col[2] / 255)));
25598 $this->_out('/Count 0>>');
25599 $this->_out('endobj');
25601 // Outline root
25602 $this->_newobj();
25603 $this->OutlineRoot = $this->n;
25604 $this->_out('<</Type /BMoutlines /First ' . $n . ' 0 R');
25605 $this->_out('/Last ' . ($n + $lru[0]) . ' 0 R>>');
25606 $this->_out('endobj');
25609 /* -- END BOOKMARKS -- */
25612 * Initiate, and Mark a place for the Table of Contents to be inserted
25614 function TOC(
25615 $tocfont = '',
25616 $tocfontsize = 0,
25617 $tocindent = 0,
25618 $resetpagenum = '',
25619 $pagenumstyle = '',
25620 $suppress = '',
25621 $toc_orientation = '',
25622 $TOCusePaging = true,
25623 $TOCuseLinking = false,
25624 $toc_id = 0,
25625 $tocoutdent = ''
25628 $this->tableOfContents->TOC(
25629 $tocfont,
25630 $tocfontsize,
25631 $tocindent,
25632 $resetpagenum,
25633 $pagenumstyle,
25634 $suppress,
25635 $toc_orientation,
25636 $TOCusePaging,
25637 $TOCuseLinking,
25638 $toc_id,
25639 $tocoutdent
25643 function TOCpagebreakByArray($a)
25645 if (!is_array($a)) {
25646 $a = [];
25648 $tocoutdent = (isset($a['tocoutdent']) ? $a['tocoutdent'] : (isset($a['outdent']) ? $a['outdent'] : ''));
25649 $TOCusePaging = (isset($a['TOCusePaging']) ? $a['TOCusePaging'] : (isset($a['paging']) ? $a['paging'] : true));
25650 $TOCuseLinking = (isset($a['TOCuseLinking']) ? $a['TOCuseLinking'] : (isset($a['links']) ? $a['links'] : ''));
25651 $toc_orientation = (isset($a['toc_orientation']) ? $a['toc_orientation'] : (isset($a['toc-orientation']) ? $a['toc-orientation'] : ''));
25652 $toc_mgl = (isset($a['toc_mgl']) ? $a['toc_mgl'] : (isset($a['toc-margin-left']) ? $a['toc-margin-left'] : ''));
25653 $toc_mgr = (isset($a['toc_mgr']) ? $a['toc_mgr'] : (isset($a['toc-margin-right']) ? $a['toc-margin-right'] : ''));
25654 $toc_mgt = (isset($a['toc_mgt']) ? $a['toc_mgt'] : (isset($a['toc-margin-top']) ? $a['toc-margin-top'] : ''));
25655 $toc_mgb = (isset($a['toc_mgb']) ? $a['toc_mgb'] : (isset($a['toc-margin-bottom']) ? $a['toc-margin-bottom'] : ''));
25656 $toc_mgh = (isset($a['toc_mgh']) ? $a['toc_mgh'] : (isset($a['toc-margin-header']) ? $a['toc-margin-header'] : ''));
25657 $toc_mgf = (isset($a['toc_mgf']) ? $a['toc_mgf'] : (isset($a['toc-margin-footer']) ? $a['toc-margin-footer'] : ''));
25658 $toc_ohname = (isset($a['toc_ohname']) ? $a['toc_ohname'] : (isset($a['toc-odd-header-name']) ? $a['toc-odd-header-name'] : ''));
25659 $toc_ehname = (isset($a['toc_ehname']) ? $a['toc_ehname'] : (isset($a['toc-even-header-name']) ? $a['toc-even-header-name'] : ''));
25660 $toc_ofname = (isset($a['toc_ofname']) ? $a['toc_ofname'] : (isset($a['toc-odd-footer-name']) ? $a['toc-odd-footer-name'] : ''));
25661 $toc_efname = (isset($a['toc_efname']) ? $a['toc_efname'] : (isset($a['toc-even-footer-name']) ? $a['toc-even-footer-name'] : ''));
25662 $toc_ohvalue = (isset($a['toc_ohvalue']) ? $a['toc_ohvalue'] : (isset($a['toc-odd-header-value']) ? $a['toc-odd-header-value'] : 0));
25663 $toc_ehvalue = (isset($a['toc_ehvalue']) ? $a['toc_ehvalue'] : (isset($a['toc-even-header-value']) ? $a['toc-even-header-value'] : 0));
25664 $toc_ofvalue = (isset($a['toc_ofvalue']) ? $a['toc_ofvalue'] : (isset($a['toc-odd-footer-value']) ? $a['toc-odd-footer-value'] : 0));
25665 $toc_efvalue = (isset($a['toc_efvalue']) ? $a['toc_efvalue'] : (isset($a['toc-even-footer-value']) ? $a['toc-even-footer-value'] : 0));
25666 $toc_preHTML = (isset($a['toc_preHTML']) ? $a['toc_preHTML'] : (isset($a['toc-preHTML']) ? $a['toc-preHTML'] : ''));
25667 $toc_postHTML = (isset($a['toc_postHTML']) ? $a['toc_postHTML'] : (isset($a['toc-postHTML']) ? $a['toc-postHTML'] : ''));
25668 $toc_bookmarkText = (isset($a['toc_bookmarkText']) ? $a['toc_bookmarkText'] : (isset($a['toc-bookmarkText']) ? $a['toc-bookmarkText'] : ''));
25669 $resetpagenum = (isset($a['resetpagenum']) ? $a['resetpagenum'] : '');
25670 $pagenumstyle = (isset($a['pagenumstyle']) ? $a['pagenumstyle'] : '');
25671 $suppress = (isset($a['suppress']) ? $a['suppress'] : '');
25672 $orientation = (isset($a['orientation']) ? $a['orientation'] : '');
25673 $mgl = (isset($a['mgl']) ? $a['mgl'] : (isset($a['margin-left']) ? $a['margin-left'] : ''));
25674 $mgr = (isset($a['mgr']) ? $a['mgr'] : (isset($a['margin-right']) ? $a['margin-right'] : ''));
25675 $mgt = (isset($a['mgt']) ? $a['mgt'] : (isset($a['margin-top']) ? $a['margin-top'] : ''));
25676 $mgb = (isset($a['mgb']) ? $a['mgb'] : (isset($a['margin-bottom']) ? $a['margin-bottom'] : ''));
25677 $mgh = (isset($a['mgh']) ? $a['mgh'] : (isset($a['margin-header']) ? $a['margin-header'] : ''));
25678 $mgf = (isset($a['mgf']) ? $a['mgf'] : (isset($a['margin-footer']) ? $a['margin-footer'] : ''));
25679 $ohname = (isset($a['ohname']) ? $a['ohname'] : (isset($a['odd-header-name']) ? $a['odd-header-name'] : ''));
25680 $ehname = (isset($a['ehname']) ? $a['ehname'] : (isset($a['even-header-name']) ? $a['even-header-name'] : ''));
25681 $ofname = (isset($a['ofname']) ? $a['ofname'] : (isset($a['odd-footer-name']) ? $a['odd-footer-name'] : ''));
25682 $efname = (isset($a['efname']) ? $a['efname'] : (isset($a['even-footer-name']) ? $a['even-footer-name'] : ''));
25683 $ohvalue = (isset($a['ohvalue']) ? $a['ohvalue'] : (isset($a['odd-header-value']) ? $a['odd-header-value'] : 0));
25684 $ehvalue = (isset($a['ehvalue']) ? $a['ehvalue'] : (isset($a['even-header-value']) ? $a['even-header-value'] : 0));
25685 $ofvalue = (isset($a['ofvalue']) ? $a['ofvalue'] : (isset($a['odd-footer-value']) ? $a['odd-footer-value'] : 0));
25686 $efvalue = (isset($a['efvalue']) ? $a['efvalue'] : (isset($a['even-footer-value']) ? $a['even-footer-value'] : 0));
25687 $toc_id = (isset($a['toc_id']) ? $a['toc_id'] : (isset($a['name']) ? $a['name'] : 0));
25688 $pagesel = (isset($a['pagesel']) ? $a['pagesel'] : (isset($a['pageselector']) ? $a['pageselector'] : ''));
25689 $toc_pagesel = (isset($a['toc_pagesel']) ? $a['toc_pagesel'] : (isset($a['toc-pageselector']) ? $a['toc-pageselector'] : ''));
25690 $sheetsize = (isset($a['sheetsize']) ? $a['sheetsize'] : (isset($a['sheet-size']) ? $a['sheet-size'] : ''));
25691 $toc_sheetsize = (isset($a['toc_sheetsize']) ? $a['toc_sheetsize'] : (isset($a['toc-sheet-size']) ? $a['toc-sheet-size'] : ''));
25693 $this->TOCpagebreak($tocfont, $tocfontsize, $tocindent, $TOCusePaging, $TOCuseLinking, $toc_orientation, $toc_mgl, $toc_mgr, $toc_mgt, $toc_mgb, $toc_mgh, $toc_mgf, $toc_ohname, $toc_ehname, $toc_ofname, $toc_efname, $toc_ohvalue, $toc_ehvalue, $toc_ofvalue, $toc_efvalue, $toc_preHTML, $toc_postHTML, $toc_bookmarkText, $resetpagenum, $pagenumstyle, $suppress, $orientation, $mgl, $mgr, $mgt, $mgb, $mgh, $mgf, $ohname, $ehname, $ofname, $efname, $ohvalue, $ehvalue, $ofvalue, $efvalue, $toc_id, $pagesel, $toc_pagesel, $sheetsize, $toc_sheetsize, $tocoutdent);
25696 function TOCpagebreak($tocfont = '', $tocfontsize = '', $tocindent = '', $TOCusePaging = true, $TOCuseLinking = '', $toc_orientation = '', $toc_mgl = '', $toc_mgr = '', $toc_mgt = '', $toc_mgb = '', $toc_mgh = '', $toc_mgf = '', $toc_ohname = '', $toc_ehname = '', $toc_ofname = '', $toc_efname = '', $toc_ohvalue = 0, $toc_ehvalue = 0, $toc_ofvalue = 0, $toc_efvalue = 0, $toc_preHTML = '', $toc_postHTML = '', $toc_bookmarkText = '', $resetpagenum = '', $pagenumstyle = '', $suppress = '', $orientation = '', $mgl = '', $mgr = '', $mgt = '', $mgb = '', $mgh = '', $mgf = '', $ohname = '', $ehname = '', $ofname = '', $efname = '', $ohvalue = 0, $ehvalue = 0, $ofvalue = 0, $efvalue = 0, $toc_id = 0, $pagesel = '', $toc_pagesel = '', $sheetsize = '', $toc_sheetsize = '', $tocoutdent = '')
25698 // Start a new page
25699 if ($this->state == 0) {
25700 $this->AddPage();
25702 if ($this->y == $this->tMargin && (!$this->mirrorMargins || ($this->mirrorMargins && $this->page % 2 == 1))) {
25703 // Don't add a page
25704 if ($this->page == 1 && count($this->PageNumSubstitutions) == 0) {
25705 if (!$suppress) {
25706 $suppress = 'off';
25708 // $this->PageNumSubstitutions[] = array('from'=>1, 'reset'=> $resetpagenum, 'type'=>$pagenumstyle, 'suppress'=> $suppress);
25710 $this->PageNumSubstitutions[] = ['from' => $this->page, 'reset' => $resetpagenum, 'type' => $pagenumstyle, 'suppress' => $suppress];
25711 } else {
25712 $this->AddPage($orientation, 'NEXT-ODD', $resetpagenum, $pagenumstyle, $suppress, $mgl, $mgr, $mgt, $mgb, $mgh, $mgf, $ohname, $ehname, $ofname, $efname, $ohvalue, $ehvalue, $ofvalue, $efvalue, $pagesel, $sheetsize);
25714 $this->tableOfContents->TOCpagebreak($tocfont, $tocfontsize, $tocindent, $TOCusePaging, $TOCuseLinking, $toc_orientation, $toc_mgl, $toc_mgr, $toc_mgt, $toc_mgb, $toc_mgh, $toc_mgf, $toc_ohname, $toc_ehname, $toc_ofname, $toc_efname, $toc_ohvalue, $toc_ehvalue, $toc_ofvalue, $toc_efvalue, $toc_preHTML, $toc_postHTML, $toc_bookmarkText, $resetpagenum, $pagenumstyle, $suppress, $orientation, $mgl, $mgr, $mgt, $mgb, $mgh, $mgf, $ohname, $ehname, $ofname, $efname, $ohvalue, $ehvalue, $ofvalue, $efvalue, $toc_id, $pagesel, $toc_pagesel, $sheetsize, $toc_sheetsize, $tocoutdent);
25717 function TOC_Entry($txt, $level = 0, $toc_id = 0)
25719 if ($this->ColActive) {
25720 $ily = $this->y0;
25721 } else {
25722 $ily = $this->y;
25723 } // use top of columns
25725 $linkn = $this->AddLink();
25726 $uid = '__mpdfinternallink_' . $linkn;
25727 if ($this->table_rotate) {
25728 $this->internallink[$uid] = ["Y" => $ily, "PAGE" => $this->page, "tbrot" => true];
25729 } elseif ($this->kwt) {
25730 $this->internallink[$uid] = ["Y" => $ily, "PAGE" => $this->page, "kwt" => true];
25731 } elseif ($this->ColActive) {
25732 $this->internallink[$uid] = ["Y" => $ily, "PAGE" => $this->page, "col" => $this->CurrCol];
25733 } elseif (!$this->keep_block_together) {
25734 $this->internallink[$uid] = ["Y" => $ily, "PAGE" => $this->page];
25736 $this->internallink['#' . $uid] = $linkn;
25737 $this->SetLink($linkn, $ily, $this->page);
25739 if (strtoupper($toc_id) == 'ALL') {
25740 $toc_id = '_mpdf_all';
25741 } elseif (!$toc_id) {
25742 $toc_id = 0;
25743 } else {
25744 $toc_id = strtolower($toc_id);
25746 $btoc = ['t' => $txt, 'l' => $level, 'p' => $this->page, 'link' => $linkn, 'toc_id' => $toc_id];
25747 if ($this->keep_block_together) {
25748 // do nothing
25749 } /* -- TABLES -- */ elseif ($this->table_rotate) {
25750 $this->tbrot_toc[] = $btoc;
25751 } elseif ($this->kwt) {
25752 $this->kwt_toc[] = $btoc;
25753 } /* -- END TABLES -- */ elseif ($this->ColActive) { // *COLUMNS*
25754 $this->col_toc[] = $btoc; // *COLUMNS*
25755 } // *COLUMNS*
25756 else {
25757 $this->tableOfContents->_toc[] = $btoc;
25761 /* -- END TOC -- */
25763 // ======================================================
25764 function MovePages($target_page, $start_page, $end_page = -1)
25766 // move a page/pages EARLIER in the document
25767 if ($end_page < 1) {
25768 $end_page = $start_page;
25770 $n_toc = $end_page - $start_page + 1;
25772 // Set/Update PageNumSubstitutions changes before moving anything
25773 if (count($this->PageNumSubstitutions)) {
25774 $tp_present = false;
25775 $sp_present = false;
25776 $ep_present = false;
25777 foreach ($this->PageNumSubstitutions as $k => $v) {
25778 if ($this->PageNumSubstitutions[$k]['from'] == $target_page) {
25779 $tp_present = true;
25780 if ($this->PageNumSubstitutions[$k]['suppress'] != 'on' && $this->PageNumSubstitutions[$k]['suppress'] != 1) {
25781 $this->PageNumSubstitutions[$k]['suppress'] = 'off';
25784 if ($this->PageNumSubstitutions[$k]['from'] == $start_page) {
25785 $sp_present = true;
25786 if ($this->PageNumSubstitutions[$k]['suppress'] != 'on' && $this->PageNumSubstitutions[$k]['suppress'] != 1) {
25787 $this->PageNumSubstitutions[$k]['suppress'] = 'off';
25790 if ($this->PageNumSubstitutions[$k]['from'] == ($end_page + 1)) {
25791 $ep_present = true;
25792 if ($this->PageNumSubstitutions[$k]['suppress'] != 'on' && $this->PageNumSubstitutions[$k]['suppress'] != 1) {
25793 $this->PageNumSubstitutions[$k]['suppress'] = 'off';
25798 if (!$tp_present) {
25799 list($tp_type, $tp_suppress, $tp_reset) = $this->docPageSettings($target_page);
25801 if (!$sp_present) {
25802 list($sp_type, $sp_suppress, $sp_reset) = $this->docPageSettings($start_page);
25804 if (!$ep_present) {
25805 list($ep_type, $ep_suppress, $ep_reset) = $this->docPageSettings($start_page - 1);
25809 $last = [];
25810 // store pages
25811 for ($i = $start_page; $i <= $end_page; $i++) {
25812 $last[] = $this->pages[$i];
25814 // move pages
25815 for ($i = $start_page - 1; $i >= ($target_page); $i--) {
25816 $this->pages[$i + $n_toc] = $this->pages[$i];
25818 // Put toc pages at insert point
25819 for ($i = 0; $i < $n_toc; $i++) {
25820 $this->pages[$target_page + $i] = $last[$i];
25823 /* -- BOOKMARKS -- */
25824 // Update Bookmarks
25825 foreach ($this->BMoutlines as $i => $o) {
25826 if ($o['p'] >= $target_page) {
25827 $this->BMoutlines[$i]['p'] += $n_toc;
25830 /* -- END BOOKMARKS -- */
25832 // Update Page Links
25833 if (count($this->PageLinks)) {
25834 $newarr = [];
25835 foreach ($this->PageLinks as $i => $o) {
25836 foreach ($this->PageLinks[$i] as $key => $pl) {
25837 if (strpos($pl[4], '@') === 0) {
25838 $p = substr($pl[4], 1);
25839 if ($p >= $start_page && $p <= $end_page) {
25840 $this->PageLinks[$i][$key][4] = '@' . ($p + ($target_page - $start_page));
25841 } elseif ($p >= $target_page && $p < $start_page) {
25842 $this->PageLinks[$i][$key][4] = '@' . ($p + $n_toc);
25846 if ($i >= $start_page && $i <= $end_page) {
25847 $newarr[($i + ($target_page - $start_page))] = $this->PageLinks[$i];
25848 } elseif ($i >= $target_page && $i < $start_page) {
25849 $newarr[($i + $n_toc)] = $this->PageLinks[$i];
25850 } else {
25851 $newarr[$i] = $this->PageLinks[$i];
25854 $this->PageLinks = $newarr;
25857 // OrientationChanges
25858 if (count($this->OrientationChanges)) {
25859 $newarr = [];
25860 foreach ($this->OrientationChanges as $p => $v) {
25861 if ($p >= $start_page && $p <= $end_page) {
25862 $newarr[($p + ($target_page - $start_page))] = $this->OrientationChanges[$p];
25863 } elseif ($p >= $target_page && $p < $start_page) {
25864 $newarr[$p + $n_toc] = $this->OrientationChanges[$p];
25865 } else {
25866 $newarr[$p] = $this->OrientationChanges[$p];
25869 ksort($newarr);
25870 $this->OrientationChanges = $newarr;
25873 // Page Dimensions
25874 if (count($this->pageDim)) {
25875 $newarr = [];
25876 foreach ($this->pageDim as $p => $v) {
25877 if ($p >= $start_page && $p <= $end_page) {
25878 $newarr[($p + ($target_page - $start_page))] = $this->pageDim[$p];
25879 } elseif ($p >= $target_page && $p < $start_page) {
25880 $newarr[$p + $n_toc] = $this->pageDim[$p];
25881 } else {
25882 $newarr[$p] = $this->pageDim[$p];
25885 ksort($newarr);
25886 $this->pageDim = $newarr;
25889 // HTML Headers & Footers
25890 if (count($this->saveHTMLHeader)) {
25891 $newarr = [];
25892 foreach ($this->saveHTMLHeader as $p => $v) {
25893 if ($p >= $start_page && $p <= $end_page) {
25894 $newarr[($p + ($target_page - $start_page))] = $this->saveHTMLHeader[$p];
25895 } elseif ($p >= $target_page && $p < $start_page) {
25896 $newarr[$p + $n_toc] = $this->saveHTMLHeader[$p];
25897 } else {
25898 $newarr[$p] = $this->saveHTMLHeader[$p];
25901 ksort($newarr);
25902 $this->saveHTMLHeader = $newarr;
25904 if (count($this->saveHTMLFooter)) {
25905 $newarr = [];
25906 foreach ($this->saveHTMLFooter as $p => $v) {
25907 if ($p >= $start_page && $p <= $end_page) {
25908 $newarr[($p + ($target_page - $start_page))] = $this->saveHTMLFooter[$p];
25909 } elseif ($p >= $target_page && $p < $start_page) {
25910 $newarr[$p + $n_toc] = $this->saveHTMLFooter[$p];
25911 } else {
25912 $newarr[$p] = $this->saveHTMLFooter[$p];
25915 ksort($newarr);
25916 $this->saveHTMLFooter = $newarr;
25919 // Update Internal Links
25920 if (count($this->internallink)) {
25921 foreach ($this->internallink as $key => $o) {
25922 if ($o['PAGE'] >= $start_page && $o['PAGE'] <= $end_page) {
25923 $this->internallink[$key]['PAGE'] += ($target_page - $start_page);
25924 } elseif ($o['PAGE'] >= $target_page && $o['PAGE'] < $start_page) {
25925 $this->internallink[$key]['PAGE'] += $n_toc;
25930 // Update Links
25931 if (count($this->links)) {
25932 foreach ($this->links as $key => $o) {
25933 if ($o[0] >= $start_page && $o[0] <= $end_page) {
25934 $this->links[$key][0] += ($target_page - $start_page);
25936 if ($o[0] >= $target_page && $o[0] < $start_page) {
25937 $this->links[$key][0] += $n_toc;
25942 // Update Form fields
25943 if (count($this->form->forms)) {
25944 foreach ($this->form->forms as $key => $f) {
25945 if ($f['page'] >= $start_page && $f['page'] <= $end_page) {
25946 $this->form->forms[$key]['page'] += ($target_page - $start_page);
25948 if ($f['page'] >= $target_page && $f['page'] < $start_page) {
25949 $this->form->forms[$key]['page'] += $n_toc;
25954 /* -- ANNOTATIONS -- */
25955 // Update Annotations
25956 if (count($this->PageAnnots)) {
25957 $newarr = [];
25958 foreach ($this->PageAnnots as $p => $anno) {
25959 if ($p >= $start_page && $p <= $end_page) {
25960 $np = $p + ($target_page - $start_page);
25961 foreach ($anno as $o) {
25962 $newarr[$np][] = $o;
25964 } elseif ($p >= $target_page && $p < $start_page) {
25965 $np = $p + $n_toc;
25966 foreach ($anno as $o) {
25967 $newarr[$np][] = $o;
25969 } else {
25970 $newarr[$p] = $this->PageAnnots[$p];
25973 $this->PageAnnots = $newarr;
25974 unset($newarr);
25976 /* -- END ANNOTATIONS -- */
25978 // Update TOC pages
25979 if (count($this->tableOfContents->_toc)) {
25980 foreach ($this->tableOfContents->_toc as $key => $t) {
25981 if ($t['p'] >= $start_page && $t['p'] <= $end_page) {
25982 $this->tableOfContents->_toc[$key]['p'] += ($target_page - $start_page);
25984 if ($t['p'] >= $target_page && $t['p'] < $start_page) {
25985 $this->tableOfContents->_toc[$key]['p'] += $n_toc;
25990 // Update PageNumSubstitutions
25991 if (count($this->PageNumSubstitutions)) {
25992 $newarr = [];
25993 foreach ($this->PageNumSubstitutions as $k => $v) {
25994 if ($this->PageNumSubstitutions[$k]['from'] >= $start_page && $this->PageNumSubstitutions[$k]['from'] <= $end_page) {
25995 $this->PageNumSubstitutions[$k]['from'] += ($target_page - $start_page);
25996 $newarr[$this->PageNumSubstitutions[$k]['from']] = $this->PageNumSubstitutions[$k];
25997 } elseif ($this->PageNumSubstitutions[$k]['from'] >= $target_page && $this->PageNumSubstitutions[$k]['from'] < $start_page) {
25998 $this->PageNumSubstitutions[$k]['from'] += $n_toc;
25999 $newarr[$this->PageNumSubstitutions[$k]['from']] = $this->PageNumSubstitutions[$k];
26000 } else {
26001 $newarr[$this->PageNumSubstitutions[$k]['from']] = $this->PageNumSubstitutions[$k];
26005 if (!$sp_present) {
26006 $newarr[$target_page] = ['from' => $target_page, 'suppress' => $sp_suppress, 'reset' => $sp_reset, 'type' => $sp_type];
26008 if (!$tp_present) {
26009 $newarr[($target_page + $n_toc)] = ['from' => ($target_page + $n_toc), 'suppress' => $tp_suppress, 'reset' => $tp_reset, 'type' => $tp_type];
26011 if (!$ep_present && $end_page > count($this->pages)) {
26012 $newarr[($end_page + 1)] = ['from' => ($end_page + 1), 'suppress' => $ep_suppress, 'reset' => $ep_reset, 'type' => $ep_type];
26014 ksort($newarr);
26015 $this->PageNumSubstitutions = [];
26016 foreach ($newarr as $v) {
26017 $this->PageNumSubstitutions[] = $v;
26022 function DeletePages($start_page, $end_page = -1)
26024 // move a page/pages EARLIER in the document
26025 if ($end_page < 1) {
26026 $end_page = $start_page;
26028 $n_tod = $end_page - $start_page + 1;
26029 $last_page = count($this->pages);
26030 $n_atend = $last_page - $end_page + 1;
26032 // move pages
26033 for ($i = 0; $i < $n_atend; $i++) {
26034 $this->pages[$start_page + $i] = $this->pages[$end_page + 1 + $i];
26036 // delete pages
26037 for ($i = 0; $i < $n_tod; $i++) {
26038 unset($this->pages[$last_page - $i]);
26042 /* -- BOOKMARKS -- */
26043 // Update Bookmarks
26044 foreach ($this->BMoutlines as $i => $o) {
26045 if ($o['p'] >= $end_page) {
26046 $this->BMoutlines[$i]['p'] -= $n_tod;
26047 } elseif ($p < $start_page) {
26048 unset($this->BMoutlines[$i]);
26051 /* -- END BOOKMARKS -- */
26053 // Update Page Links
26054 if (count($this->PageLinks)) {
26055 $newarr = [];
26056 foreach ($this->PageLinks as $i => $o) {
26057 foreach ($this->PageLinks[$i] as $key => $pl) {
26058 if (strpos($pl[4], '@') === 0) {
26059 $p = substr($pl[4], 1);
26060 if ($p > $end_page) {
26061 $this->PageLinks[$i][$key][4] = '@' . ($p - $n_tod);
26062 } elseif ($p < $start_page) {
26063 unset($this->PageLinks[$i][$key]);
26067 if ($i > $end_page) {
26068 $newarr[($i - $n_tod)] = $this->PageLinks[$i];
26069 } elseif ($p < $start_page) {
26070 $newarr[$i] = $this->PageLinks[$i];
26073 $this->PageLinks = $newarr;
26076 // OrientationChanges
26077 if (count($this->OrientationChanges)) {
26078 $newarr = [];
26079 foreach ($this->OrientationChanges as $p => $v) {
26080 if ($p > $end_page) {
26081 $newarr[($p - $t_tod)] = $this->OrientationChanges[$p];
26082 } elseif ($p < $start_page) {
26083 $newarr[$p] = $this->OrientationChanges[$p];
26086 ksort($newarr);
26087 $this->OrientationChanges = $newarr;
26090 // Page Dimensions
26091 if (count($this->pageDim)) {
26092 $newarr = [];
26093 foreach ($this->pageDim as $p => $v) {
26094 if ($p > $end_page) {
26095 $newarr[($p - $n_tod)] = $this->pageDim[$p];
26096 } elseif ($p < $start_page) {
26097 $newarr[$p] = $this->pageDim[$p];
26100 ksort($newarr);
26101 $this->pageDim = $newarr;
26104 // HTML Headers & Footers
26105 if (count($this->saveHTMLHeader)) {
26106 foreach ($this->saveHTMLHeader as $p => $v) {
26107 if ($p > $end_page) {
26108 $newarr[($p - $n_tod)] = $this->saveHTMLHeader[$p];
26109 } // mPDF 5.7.3
26110 elseif ($p < $start_page) {
26111 $newarr[$p] = $this->saveHTMLHeader[$p];
26114 ksort($newarr);
26115 $this->saveHTMLHeader = $newarr;
26117 if (count($this->saveHTMLFooter)) {
26118 $newarr = [];
26119 foreach ($this->saveHTMLFooter as $p => $v) {
26120 if ($p > $end_page) {
26121 $newarr[($p - $n_tod)] = $this->saveHTMLFooter[$p];
26122 } elseif ($p < $start_page) {
26123 $newarr[$p] = $this->saveHTMLFooter[$p];
26126 ksort($newarr);
26127 $this->saveHTMLFooter = $newarr;
26130 // Update Internal Links
26131 foreach ($this->internallink as $key => $o) {
26132 if ($o['PAGE'] > $end_page) {
26133 $this->internallink[$key]['PAGE'] -= $n_tod;
26134 } elseif ($o['PAGE'] < $start_page) {
26135 unset($this->internallink[$key]);
26139 // Update Links
26140 foreach ($this->links as $key => $o) {
26141 if ($o[0] > $end_page) {
26142 $this->links[$key][0] -= $n_tod;
26143 } elseif ($o[0] < $start_page) {
26144 unset($this->links[$key]);
26148 // Update Form fields
26149 foreach ($this->form->forms as $key => $f) {
26150 if ($f['page'] > $end_page) {
26151 $this->form->forms[$key]['page'] -= $n_tod;
26152 } elseif ($f['page'] < $start_page) {
26153 unset($this->form->forms[$key]);
26157 /* -- ANNOTATIONS -- */
26158 // Update Annotations
26159 if (count($this->PageAnnots)) {
26160 $newarr = [];
26161 foreach ($this->PageAnnots as $p => $anno) {
26162 if ($p > $end_page) {
26163 foreach ($anno as $o) {
26164 $newarr[($p - $n_tod)][] = $o;
26166 } elseif ($p < $start_page) {
26167 $newarr[$p] = $this->PageAnnots[$p];
26170 ksort($newarr);
26171 $this->PageAnnots = $newarr;
26173 /* -- END ANNOTATIONS -- */
26175 // Update PageNumSubstitutions
26176 foreach ($this->PageNumSubstitutions as $k => $v) {
26177 if ($this->PageNumSubstitutions[$k]['from'] > $end_page) {
26178 $this->PageNumSubstitutions[$k]['from'] -= $n_tod;
26179 } elseif ($this->PageNumSubstitutions[$k]['from'] < $start_page) {
26180 unset($this->PageNumSubstitutions[$k]);
26184 unset($newarr);
26185 $this->page = count($this->pages);
26188 // ======================================================
26189 /* -- INDEX -- */
26190 // FROM class PDF_Ref == INDEX
26192 function IndexEntry($txt, $xref = '')
26194 if ($xref) {
26195 $this->IndexEntrySee($txt, $xref);
26196 return;
26199 // Search the reference (AND Ref/PageNo) in the array
26200 $Present = false;
26201 if ($this->keep_block_together) {
26202 // do nothing
26203 } /* -- TABLES -- */ elseif ($this->kwt) {
26204 $size = count($this->kwt_Reference);
26205 for ($i = 0; $i < $size; $i++) {
26206 if (isset($this->kwt_Reference[$i]['t']) && $this->kwt_Reference[$i]['t'] == $txt) {
26207 $Present = true;
26208 if ($this->page != $this->kwt_Reference[$i]['op']) {
26209 $this->kwt_Reference[$i]['op'] = $this->page;
26213 if (!$Present) { // If not found, add it
26214 $this->kwt_Reference[] = ['t' => $txt, 'op' => $this->page];
26216 } /* -- END TABLES -- */ else {
26217 $size = count($this->Reference);
26218 for ($i = 0; $i < $size; $i++) {
26219 if (isset($this->Reference[$i]['t']) && $this->Reference[$i]['t'] == $txt) {
26220 $Present = true;
26221 if (!in_array($this->page, $this->Reference[$i]['p'])) {
26222 $this->Reference[$i]['p'][] = $this->page;
26226 if (!$Present) { // If not found, add it
26227 $this->Reference[] = ['t' => $txt, 'p' => [$this->page]];
26232 // Added function to add a reference "Elephants. See Chickens"
26233 function IndexEntrySee($txta, $txtb)
26235 if ($this->directionality == 'rtl') { // *OTL*
26236 // ONLY DO THIS IF NOT IN TAGS
26237 if ($txta == strip_tags($txta)) {
26238 $txta = str_replace(':', ' - ', $txta); // *OTL*
26240 if ($txtb == strip_tags($txtb)) {
26241 $txtb = str_replace(':', ' - ', $txtb); // *OTL*
26243 } // *OTL*
26244 else { // *OTL*
26245 if ($txta == strip_tags($txta)) {
26246 $txta = str_replace(':', ', ', $txta);
26248 if ($txtb == strip_tags($txtb)) {
26249 $txtb = str_replace(':', ', ', $txtb);
26251 } // *OTL*
26252 $this->Reference[] = ['t' => $txta . ' - see ' . $txtb, 'p' => []];
26255 function InsertIndex($usedivletters = 1, $useLinking = false, $indexCollationLocale = '', $indexCollationGroup = '')
26257 $size = count($this->Reference);
26258 if ($size == 0) {
26259 return false;
26262 // $spacer used after named entry
26263 // $sep separates number [groups], $joiner joins numbers in range
26264 // e.g. "elephant 73, 97-99" = elephant[$spacer]73[$sep]97[$joiner]99
26265 // $subEntrySeparator separates main and subentry (if $this->indexUseSubentries == false;) e.g.
26266 // Mammal:elephant => Mammal[$subEntrySeparator]elephant
26267 // $subEntryInset specifies what precedes a subentry (if $this->indexUseSubentries == true;) e.g.
26268 // Mammal:elephant => [$subEntryInset]elephant
26269 $spacer = "\xc2\xa0 ";
26270 if ($this->directionality == 'rtl') {
26271 $sep = '&#x060c; ';
26272 $joiner = '-';
26273 $subEntrySeparator = '&#x060c; ';
26274 $subEntryInset = ' - ';
26275 } else {
26276 $sep = ', ';
26277 $joiner = '-';
26278 $subEntrySeparator = ', ';
26279 $subEntryInset = ' - ';
26282 for ($i = 0; $i < $size; $i++) {
26283 $txt = $this->Reference[$i]['t'];
26284 $txt = strip_tags($txt); // mPDF 6
26285 $txt = $this->purify_utf8($txt);
26286 $this->Reference[$i]['uf'] = $txt; // Unformatted e.g. pure utf-8 encoded characters, no mark-up/tags
26287 // Used for ordering and collation
26290 if ($usedivletters) {
26291 if ($indexCollationGroup) {
26292 $collation = require __DIR__ . '/../data/collations/' . $indexCollationGroup . '.php';
26293 } else {
26294 $collation = [];
26296 for ($i = 0; $i < $size; $i++) {
26297 if ($this->Reference[$i]['uf']) {
26298 $l = mb_substr($this->Reference[$i]['uf'], 0, 1, 'UTF-8');
26299 if (isset($indexCollationGroup) && $indexCollationGroup) {
26300 $uni = $this->UTF8StringToArray($l);
26301 $ucode = $uni[0];
26302 if (isset($collation[$ucode])) {
26303 $this->Reference[$i]['d'] = UtfString::code2utf($collation[$ucode]);
26304 } else {
26305 $this->Reference[$i]['d'] = mb_strtolower($l, 'UTF-8');
26307 } else {
26308 $this->Reference[$i]['d'] = mb_strtolower($l, 'UTF-8');
26314 // Alphabetic sort of the references
26315 $originalLocale = setlocale(LC_COLLATE, 0);
26316 if ($indexCollationLocale) {
26317 setlocale(LC_COLLATE, $indexCollationLocale);
26320 usort($this->Reference, function ($a, $b) {
26321 return strcoll(strtolower($a['uf']), strtolower($b['uf']));
26324 if ($indexCollationLocale) {
26325 setlocale(LC_COLLATE, $originalLocale);
26328 $html = '<div class="mpdf_index_main">';
26330 $lett = '';
26331 $last_lett = '';
26332 $mainentry = '';
26333 for ($i = 0; $i < $size; $i++) {
26334 if ($this->Reference[$i]['t']) {
26335 if ($usedivletters) {
26336 $lett = $this->Reference[$i]['d'];
26337 if ($lett != $last_lett) {
26338 $html .= '<div class="mpdf_index_letter">' . $lett . '</div>';
26341 $txt = $this->Reference[$i]['t'];
26343 // Sub-entries e.g. Mammals:elephant
26344 // But allow for tags e.g. <b>Mammal</b>:elephants
26345 $a = preg_split('/(<.*?>)/', $txt, -1, PREG_SPLIT_DELIM_CAPTURE);
26346 $txt = '';
26347 $marker = false;
26348 foreach ($a as $k => $e) {
26349 if ($k % 2 == 0 && !$marker) {
26350 if (strpos($e, ':') !== false) { // == SubEntry
26351 if ($this->indexUseSubentries) {
26352 // If the Main entry does not have any page numbers associated with it
26353 // create and insert an entry
26354 list($txtmain, $sub) = preg_split('/[:]/', $e, 2);
26355 if (strip_tags($txt . $txtmain) != $mainentry) {
26356 $html .= '<div class="mpdf_index_entry">' . $txt . $txtmain . '</div>';
26357 $mainentry = strip_tags($txt . $txtmain);
26360 $txt = $subEntryInset;
26361 $e = $sub; // Only replace first one
26362 } else {
26363 $e = preg_replace('/[:]/', $subEntrySeparator, $e, 1); // Only replace first one
26365 $marker = true; // Don't replace any more once the subentry marker has been found
26368 $txt .= $e;
26371 if (!$marker) {
26372 $mainentry = strip_tags($txt);
26375 $html .= '<div class="mpdf_index_entry">';
26376 $html .= $txt;
26377 $ppp = $this->Reference[$i]['p']; // = array of page numbers to point to
26378 if (count($ppp)) {
26379 sort($ppp);
26380 $newarr = [];
26381 $range_start = $ppp[0];
26382 $range_end = 0;
26384 $html .= $spacer;
26386 for ($zi = 1; $zi < count($ppp); $zi++) {
26387 if ($ppp[$zi] == ($ppp[($zi - 1)] + 1)) {
26388 $range_end = $ppp[$zi];
26389 } else {
26390 if ($range_end) {
26391 if ($range_end == $range_start + 1) {
26392 if ($useLinking) {
26393 $html .= '<a class="mpdf_index_link" href="@' . $range_start . '">';
26395 $html .= $this->docPageNum($range_start);
26396 if ($useLinking) {
26397 $html .= '</a>';
26399 $html .= $sep;
26401 if ($useLinking) {
26402 $html .= '<a class="mpdf_index_link" href="@' . $ppp[$zi - 1] . '">';
26404 $html .= $this->docPageNum($ppp[$zi - 1]);
26405 if ($useLinking) {
26406 $html .= '</a>';
26408 $html .= $sep;
26410 } else {
26411 if ($useLinking) {
26412 $html .= '<a class="mpdf_index_link" href="@' . $ppp[$zi - 1] . '">';
26414 $html .= $this->docPageNum($ppp[$zi - 1]);
26415 if ($useLinking) {
26416 $html .= '</a>';
26418 $html .= $sep;
26420 $range_start = $ppp[$zi];
26421 $range_end = 0;
26425 if ($range_end) {
26426 if ($useLinking) {
26427 $html .= '<a class="mpdf_index_link" href="@' . $range_start . '">';
26429 $html .= $this->docPageNum($range_start);
26430 if ($range_end == $range_start + 1) {
26431 if ($useLinking) {
26432 $html .= '</a>';
26434 $html .= $sep;
26435 if ($useLinking) {
26436 $html .= '<a class="mpdf_index_link" href="@' . $range_end . '">';
26438 $html .= $this->docPageNum($range_end);
26439 if ($useLinking) {
26440 $html .= '</a>';
26442 } else {
26443 $html .= $joiner;
26444 $html .= $this->docPageNum($range_end);
26445 if ($useLinking) {
26446 $html .= '</a>';
26449 } else {
26450 if ($useLinking) {
26451 $html .= '<a class="mpdf_index_link" href="@' . $ppp[(count($ppp) - 1)] . '">';
26453 $html .= $this->docPageNum($ppp[(count($ppp) - 1)]);
26454 if ($useLinking) {
26455 $html .= '</a>';
26460 $html .= '</div>';
26461 $last_lett = $lett;
26463 $html .= '</div>';
26464 $save_fpb = $this->fixedPosBlockSave;
26465 $this->WriteHTML($html);
26466 $this->fixedPosBlockSave = $save_fpb;
26468 $this->breakpoints[$this->CurrCol][] = $this->y; // *COLUMNS*
26471 /* -- END INDEX -- */
26473 function AcceptPageBreak()
26475 if (count($this->cellBorderBuffer)) {
26476 $this->printcellbuffer();
26477 } // *TABLES*
26478 /* -- COLUMNS -- */
26479 if ($this->ColActive == 1) {
26480 if ($this->CurrCol < $this->NbCol - 1) {
26481 // Go to the next column
26482 $this->CurrCol++;
26483 $this->SetCol($this->CurrCol);
26484 $this->y = $this->y0;
26485 $this->ChangeColumn = 1; // Number (and direction) of columns changed +1, +2, -2 etc.
26486 // DIRECTIONALITY RTL
26487 if ($this->directionality == 'rtl') {
26488 $this->ChangeColumn = -($this->ChangeColumn);
26489 } // *OTL*
26490 // Stay on the page
26491 return false;
26492 } else {
26493 // Go back to the first column - NEW PAGE
26494 if (count($this->columnbuffer)) {
26495 $this->printcolumnbuffer();
26497 $this->SetCol(0);
26498 $this->y0 = $this->tMargin;
26499 $this->ChangeColumn = -($this->NbCol - 1);
26500 // DIRECTIONALITY RTL
26501 if ($this->directionality == 'rtl') {
26502 $this->ChangeColumn = -($this->ChangeColumn);
26503 } // *OTL*
26504 // Page break
26505 return true;
26507 } /* -- END COLUMNS -- */
26508 /* -- TABLES -- */ elseif ($this->table_rotate) {
26509 if ($this->tablebuffer) {
26510 $this->printtablebuffer();
26512 return true;
26513 } /* -- END TABLES -- */ else { // *COLUMNS*
26514 $this->ChangeColumn = 0;
26515 return $this->autoPageBreak;
26516 } // *COLUMNS*
26517 return $this->autoPageBreak;
26520 // ----------- COLUMNS ---------------------
26521 /* -- COLUMNS -- */
26523 function SetColumns($NbCol, $vAlign = '', $gap = 5)
26525 // NbCol = number of columns
26526 // Anything less than 2 turns columns off
26527 if ($NbCol < 2) { // SET COLUMNS OFF
26528 if ($this->ColActive) {
26529 $this->ColActive = 0;
26530 if (count($this->columnbuffer)) {
26531 $this->printcolumnbuffer();
26533 $this->NbCol = 1;
26534 $this->ResetMargins();
26535 $this->pgwidth = $this->w - $this->lMargin - $this->rMargin;
26536 $this->divwidth = 0;
26537 $this->Ln();
26539 $this->ColActive = 0;
26540 $this->columnbuffer = [];
26541 $this->ColDetails = [];
26542 $this->columnLinks = [];
26543 $this->columnAnnots = [];
26544 $this->columnForms = [];
26545 $this->col_BMoutlines = [];
26546 $this->col_toc = [];
26547 $this->breakpoints = [];
26548 } else { // SET COLUMNS ON
26549 if ($this->ColActive) {
26550 $this->ColActive = 0;
26551 if (count($this->columnbuffer)) {
26552 $this->printcolumnbuffer();
26554 $this->ResetMargins();
26556 if (isset($this->y) && $this->y > $this->tMargin) {
26557 $this->Ln();
26559 $this->NbCol = $NbCol;
26560 $this->ColGap = $gap;
26561 $this->divwidth = 0;
26562 $this->ColActive = 1;
26563 $this->ColumnAdjust = true; // enables column height adjustment for the page
26564 $this->columnbuffer = [];
26565 $this->ColDetails = [];
26566 $this->columnLinks = [];
26567 $this->columnAnnots = [];
26568 $this->columnForms = [];
26569 $this->col_BMoutlines = [];
26570 $this->col_toc = [];
26571 $this->breakpoints = [];
26572 if ((strtoupper($vAlign) == 'J') || (strtoupper($vAlign) == 'JUSTIFY')) {
26573 $vAlign = 'J';
26574 } else {
26575 $vAlign = '';
26577 $this->colvAlign = $vAlign;
26578 // Save the ordinate
26579 $absL = $this->DeflMargin - ($gap / 2);
26580 $absR = $this->DefrMargin - ($gap / 2);
26581 $PageWidth = $this->w - $absL - $absR; // virtual pagewidth for calculation only
26582 $ColWidth = (($PageWidth - ($gap * ($NbCol))) / $NbCol);
26583 $this->ColWidth = $ColWidth;
26584 /* -- OTL -- */
26586 if ($this->directionality == 'rtl') {
26587 for ($i = 0; $i < $this->NbCol; $i++) {
26588 $this->ColL[$i] = $absL + ($gap / 2) + (($NbCol - ($i + 1)) * ($PageWidth / $NbCol));
26589 $this->ColR[$i] = $this->ColL[$i] + $ColWidth; // NB This is not R margin -> R pos
26591 } else {
26592 /* -- END OTL -- */
26593 for ($i = 0; $i < $this->NbCol; $i++) {
26594 $this->ColL[$i] = $absL + ($gap / 2) + ($i * ($PageWidth / $NbCol) );
26595 $this->ColR[$i] = $this->ColL[$i] + $ColWidth; // NB This is not R margin -> R pos
26597 } // *OTL*
26598 $this->pgwidth = $ColWidth;
26599 $this->SetCol(0);
26600 $this->y0 = $this->y;
26602 $this->x = $this->lMargin;
26605 function SetCol($CurrCol)
26607 // Used internally to set column by number: 0 is 1st column
26608 // Set position on a column
26609 $this->CurrCol = $CurrCol;
26610 $x = $this->ColL[$CurrCol];
26611 $xR = $this->ColR[$CurrCol]; // NB This is not R margin -> R pos
26612 if (($this->mirrorMargins) && (($this->page) % 2 == 0)) { // EVEN
26613 $x += $this->MarginCorrection;
26614 $xR += $this->MarginCorrection;
26616 $this->SetMargins($x, ($this->w - $xR), $this->tMargin);
26619 function AddColumn()
26621 $this->NewColumn();
26622 $this->ColumnAdjust = false; // disables all column height adjustment for the page.
26625 function NewColumn()
26627 if ($this->ColActive == 1) {
26628 if ($this->CurrCol < $this->NbCol - 1) {
26629 // Go to the next column
26630 $this->CurrCol++;
26631 $this->SetCol($this->CurrCol);
26632 $this->y = $this->y0;
26633 $this->ChangeColumn = 1;
26634 // DIRECTIONALITY RTL
26635 if ($this->directionality == 'rtl') {
26636 $this->ChangeColumn = -($this->ChangeColumn);
26637 } // *OTL*
26638 // Stay on the page
26639 } else {
26640 // Go back to the first column
26641 // Page break
26642 if (count($this->columnbuffer)) {
26643 $this->printcolumnbuffer();
26645 $this->AddPage($this->CurOrientation);
26646 $this->SetCol(0);
26647 $this->y0 = $this->tMargin;
26648 $this->ChangeColumn = -($this->NbCol - 1);
26649 // DIRECTIONALITY RTL
26650 if ($this->directionality == 'rtl') {
26651 $this->ChangeColumn = -($this->ChangeColumn);
26652 } // *OTL*
26654 $this->x = $this->lMargin;
26655 } else {
26656 $this->AddPage($this->CurOrientation);
26660 function printcolumnbuffer()
26662 // Columns ended (but page not ended) -> try to match all columns - unless disabled by using a custom column-break
26663 if (!$this->ColActive && $this->ColumnAdjust && !$this->keepColumns) {
26664 // Calculate adjustment to add to each column to calculate rel_y value
26665 $this->ColDetails[0]['add_y'] = 0;
26666 $last_col = 0;
26667 // Recursively add previous column's height
26668 for ($i = 1; $i < $this->NbCol; $i++) {
26669 if (isset($this->ColDetails[$i]['bottom_margin']) && $this->ColDetails[$i]['bottom_margin']) { // If any entries in the column
26670 $this->ColDetails[$i]['add_y'] = ($this->ColDetails[$i - 1]['bottom_margin'] - $this->y0) + $this->ColDetails[$i - 1]['add_y'];
26671 $last_col = $i; // Last column actually printed
26675 // Calculate value for each position sensitive entry as though for one column
26676 foreach ($this->columnbuffer as $key => $s) {
26677 $t = $s['s'];
26678 if ($t == 'ACROFORM') {
26679 $this->columnbuffer[$key]['rel_y'] = $s['y'] + $this->ColDetails[$s['col']]['add_y'] - $this->y0;
26680 $this->columnbuffer[$key]['s'] = '';
26681 } elseif (preg_match('/BT \d+\.\d\d+ (\d+\.\d\d+) Td/', $t)) {
26682 $this->columnbuffer[$key]['rel_y'] = $s['y'] + $this->ColDetails[$s['col']]['add_y'] - $this->y0;
26683 } elseif (preg_match('/\d+\.\d\d+ (\d+\.\d\d+) \d+\.\d\d+ [\-]{0,1}\d+\.\d\d+ re/', $t)) {
26684 $this->columnbuffer[$key]['rel_y'] = $s['y'] + $this->ColDetails[$s['col']]['add_y'] - $this->y0;
26685 } elseif (preg_match('/\d+\.\d\d+ (\d+\.\d\d+) m/', $t)) {
26686 $this->columnbuffer[$key]['rel_y'] = $s['y'] + $this->ColDetails[$s['col']]['add_y'] - $this->y0;
26687 } elseif (preg_match('/\d+\.\d\d+ (\d+\.\d\d+) l/', $t)) {
26688 $this->columnbuffer[$key]['rel_y'] = $s['y'] + $this->ColDetails[$s['col']]['add_y'] - $this->y0;
26689 } elseif (preg_match('/q \d+\.\d\d+ 0 0 \d+\.\d\d+ \d+\.\d\d+ (\d+\.\d\d+) cm \/(I|FO)\d+ Do Q/', $t)) {
26690 $this->columnbuffer[$key]['rel_y'] = $s['y'] + $this->ColDetails[$s['col']]['add_y'] - $this->y0;
26691 } elseif (preg_match('/\d+\.\d\d+ (\d+\.\d\d+) \d+\.\d\d+ \d+\.\d\d+ \d+\.\d\d+ \d+\.\d\d+ c/', $t)) {
26692 $this->columnbuffer[$key]['rel_y'] = $s['y'] + $this->ColDetails[$s['col']]['add_y'] - $this->y0;
26695 foreach ($this->internallink as $key => $f) {
26696 if (is_array($f) && isset($f['col'])) {
26697 $this->internallink[$key]['rel_y'] = $f['Y'] + $this->ColDetails[$f['col']]['add_y'] - $this->y0;
26701 $breaks = [];
26702 foreach ($this->breakpoints as $c => $bpa) {
26703 foreach ($bpa as $rely) {
26704 $breaks[] = $rely + $this->ColDetails[$c]['add_y'] - $this->y0;
26709 if (isset($this->ColDetails[$last_col]['bottom_margin'])) {
26710 $lcbm = $this->ColDetails[$last_col]['bottom_margin'];
26711 } else {
26712 $lcbm = 0;
26714 $sum_h = $this->ColDetails[$last_col]['add_y'] + $lcbm - $this->y0;
26715 // $sum_h = max($this->ColDetails[$last_col]['add_y'] + $this->ColDetails[$last_col]['bottom_margin'] - $this->y0, end($breaks));
26716 $target_h = ($sum_h / $this->NbCol);
26718 $cbr = [];
26719 for ($i = 1; $i < $this->NbCol; $i++) {
26720 $th = ($sum_h * $i / $this->NbCol);
26721 foreach ($breaks as $bk => $val) {
26722 if ($val > $th) {
26723 if (($val - $th) < ($th - $breaks[$bk - 1])) {
26724 $cbr[$i - 1] = $val;
26725 } else {
26726 $cbr[$i - 1] = $breaks[$bk - 1];
26728 break;
26732 $cbr[($this->NbCol - 1)] = $sum_h;
26734 // mPDF 6
26735 // Avoid outputing with 1st column empty
26736 if (isset($cbr[0]) && $cbr[0] == 0) {
26737 for ($i = 0; $i < $this->NbCol - 1; $i++) {
26738 $cbr[$i] = $cbr[$i + 1];
26742 // Now update the columns - divide into columns of approximately equal value
26743 $last_new_col = 0;
26744 $yadj = 0; // mm
26745 $xadj = 0;
26746 $last_col_bottom = 0;
26747 $lowest_bottom_y = 0;
26748 $block_bottom = 0;
26749 $newcolumn = 0;
26750 foreach ($this->columnbuffer as $key => $s) {
26751 if (isset($s['rel_y'])) { // only process position sensitive data
26752 if ($s['rel_y'] >= $cbr[$newcolumn]) {
26753 $newcolumn++;
26754 } else {
26755 $newcolumn = $last_new_col;
26759 $block_bottom = max($block_bottom, ($s['rel_y'] + $s['h']));
26761 if ($this->directionality == 'rtl') { // *OTL*
26762 $xadj = -(($newcolumn - $s['col']) * ($this->ColWidth + $this->ColGap)); // *OTL*
26763 } // *OTL*
26764 else { // *OTL*
26765 $xadj = ($newcolumn - $s['col']) * ($this->ColWidth + $this->ColGap);
26766 } // *OTL*
26768 if ($last_new_col != $newcolumn) { // Added new column
26769 $last_col_bottom = $this->columnbuffer[$key]['rel_y'];
26770 $block_bottom = 0;
26772 $yadj = ($s['rel_y'] - $s['y']) - ($last_col_bottom) + $this->y0;
26773 // callback function
26774 $t = $s['s'];
26776 // mPDF 5.7+
26777 $t = $this->columnAdjustPregReplace('Td', $xadj, $yadj, '/BT (\d+\.\d\d+) (\d+\.\d\d+) Td/', $t);
26778 $t = $this->columnAdjustPregReplace('re', $xadj, $yadj, '/(\d+\.\d\d+) (\d+\.\d\d+) (\d+\.\d\d+) ([\-]{0,1}\d+\.\d\d+) re/', $t);
26779 $t = $this->columnAdjustPregReplace('l', $xadj, $yadj, '/(\d+\.\d\d+) (\d+\.\d\d+) l/', $t);
26780 $t = $this->columnAdjustPregReplace('img', $xadj, $yadj, '/q (\d+\.\d\d+) 0 0 (\d+\.\d\d+) (\d+\.\d\d+) (\d+\.\d\d+) cm \/(I|FO)/', $t);
26781 $t = $this->columnAdjustPregReplace('draw', $xadj, $yadj, '/(\d+\.\d\d+) (\d+\.\d\d+) m/', $t);
26782 $t = $this->columnAdjustPregReplace('bezier', $xadj, $yadj, '/(\d+\.\d\d+) (\d+\.\d\d+) (\d+\.\d\d+) (\d+\.\d\d+) (\d+\.\d\d+) (\d+\.\d\d+) c/', $t);
26784 $this->columnbuffer[$key]['s'] = $t;
26785 $this->columnbuffer[$key]['newcol'] = $newcolumn;
26786 $this->columnbuffer[$key]['newy'] = $s['y'] + $yadj;
26787 $last_new_col = $newcolumn;
26788 $clb = $s['y'] + $yadj + $s['h']; // bottom_margin of current
26789 if ((isset($this->ColDetails[$newcolumn]['max_bottom']) && $clb > $this->ColDetails[$newcolumn]['max_bottom']) || (!isset($this->ColDetails[$newcolumn]['max_bottom']) && $clb)) {
26790 $this->ColDetails[$newcolumn]['max_bottom'] = $clb;
26792 if ($clb > $lowest_bottom_y) {
26793 $lowest_bottom_y = $clb;
26795 // Adjust LINKS
26796 if (isset($this->columnLinks[$s['col']][intval($s['x'])][intval($s['y'])])) {
26797 $ref = $this->columnLinks[$s['col']][intval($s['x'])][intval($s['y'])];
26798 $this->PageLinks[$this->page][$ref][0] += ($xadj * Mpdf::SCALE);
26799 $this->PageLinks[$this->page][$ref][1] -= ($yadj * Mpdf::SCALE);
26800 unset($this->columnLinks[$s['col']][intval($s['x'])][intval($s['y'])]);
26802 // Adjust FORM FIELDS
26803 if (isset($this->columnForms[$s['col']][intval($s['x'])][intval($s['y'])])) {
26804 $ref = $this->columnForms[$s['col']][intval($s['x'])][intval($s['y'])];
26805 $this->form->forms[$ref]['x'] += ($xadj);
26806 $this->form->forms[$ref]['y'] += ($yadj);
26807 unset($this->columnForms[$s['col']][intval($s['x'])][intval($s['y'])]);
26809 /* -- ANNOTATIONS -- */
26810 if (isset($this->columnAnnots[$s['col']][intval($s['x'])][intval($s['y'])])) {
26811 $ref = $this->columnAnnots[$s['col']][intval($s['x'])][intval($s['y'])];
26812 if ($this->PageAnnots[$this->page][$ref]['x'] < 0) {
26813 $this->PageAnnots[$this->page][$ref]['x'] -= ($xadj);
26814 } else {
26815 $this->PageAnnots[$this->page][$ref]['x'] += ($xadj);
26817 $this->PageAnnots[$this->page][$ref]['y'] += ($yadj); // unlike PageLinks, Page annots has y values from top in mm
26818 unset($this->columnAnnots[$s['col']][intval($s['x'])][intval($s['y'])]);
26820 /* -- END ANNOTATIONS -- */
26824 /* -- BOOKMARKS -- */
26825 // Adjust Bookmarks
26826 foreach ($this->col_BMoutlines as $v) {
26827 $this->BMoutlines[] = ['t' => $v['t'], 'l' => $v['l'], 'y' => $this->y0, 'p' => $v['p']];
26829 /* -- END BOOKMARKS -- */
26831 /* -- TOC -- */
26833 // Adjust ToC
26834 foreach ($this->col_toc as $v) {
26835 $this->tableOfContents->_toc[] = ['t' => $v['t'], 'l' => $v['l'], 'p' => $v['p'], 'link' => $v['link'], 'toc_id' => $v['toc_id']];
26836 $this->links[$v['link']][1] = $this->y0;
26838 /* -- END TOC -- */
26840 // Adjust column length to be equal
26841 if ($this->colvAlign == 'J') {
26842 foreach ($this->columnbuffer as $key => $s) {
26843 if (isset($s['rel_y'])) { // only process position sensitive data
26844 // Set ratio to expand y values or heights
26845 if (isset($this->ColDetails[$s['newcol']]['max_bottom']) && $this->ColDetails[$s['newcol']]['max_bottom'] && $this->ColDetails[$s['newcol']]['max_bottom'] != $this->y0) {
26846 $ratio = ($lowest_bottom_y - ($this->y0)) / ($this->ColDetails[$s['newcol']]['max_bottom'] - ($this->y0));
26847 } else {
26848 $ratio = 1;
26850 if (($ratio > 1) && ($ratio <= $this->max_colH_correction)) {
26851 $yadj = ($s['newy'] - $this->y0) * ($ratio - 1);
26853 // Adjust LINKS
26854 if (isset($this->columnLinks[$s['col']][intval($s['x'])][intval($s['y'])])) {
26855 $ref = $this->columnLinks[$s['col']][intval($s['x'])][intval($s['y'])];
26856 $this->PageLinks[$this->page][$ref][1] -= ($yadj * Mpdf::SCALE); // y value
26857 $this->PageLinks[$this->page][$ref][3] *= $ratio; // height
26858 unset($this->columnLinks[$s['col']][intval($s['x'])][intval($s['y'])]);
26860 // Adjust FORM FIELDS
26861 if (isset($this->columnForms[$s['col']][intval($s['x'])][intval($s['y'])])) {
26862 $ref = $this->columnForms[$s['col']][intval($s['x'])][intval($s['y'])];
26863 $this->form->forms[$ref]['x'] += ($xadj);
26864 $this->form->forms[$ref]['y'] += ($yadj);
26865 unset($this->columnForms[$s['col']][intval($s['x'])][intval($s['y'])]);
26867 /* -- ANNOTATIONS -- */
26868 if (isset($this->columnAnnots[$s['col']][intval($s['x'])][intval($s['y'])])) {
26869 $ref = $this->columnAnnots[$s['col']][intval($s['x'])][intval($s['y'])];
26870 $this->PageAnnots[$this->page][$ref]['y'] += ($yadj);
26871 unset($this->columnAnnots[$s['col']][intval($s['x'])][intval($s['y'])]);
26873 /* -- END ANNOTATIONS -- */
26877 foreach ($this->internallink as $key => $f) {
26878 if (is_array($f) && isset($f['col'])) {
26879 $last_col_bottom = 0;
26880 for ($nbc = 0; $nbc < $this->NbCol; $nbc++) {
26881 if ($f['rel_y'] >= $cbr[$nbc]) {
26882 $last_col_bottom = $cbr[$nbc];
26885 $yadj = ($f['rel_y'] - $f['Y']) - $last_col_bottom + $this->y0;
26886 $f['Y'] += $yadj;
26887 unset($f['col']);
26888 unset($f['rel_y']);
26889 $this->internallink[$key] = $f;
26893 $last_col = -1;
26894 $trans_on = false;
26895 foreach ($this->columnbuffer as $key => $s) {
26896 if (isset($s['rel_y'])) { // only process position sensitive data
26897 // Set ratio to expand y values or heights
26898 if (isset($this->ColDetails[$s['newcol']]['max_bottom']) && $this->ColDetails[$s['newcol']]['max_bottom'] && $this->ColDetails[$s['newcol']]['max_bottom'] != $this->y0) {
26899 $ratio = ($lowest_bottom_y - ($this->y0)) / ($this->ColDetails[$s['newcol']]['max_bottom'] - ($this->y0));
26900 } else {
26901 $ratio = 1;
26903 if (($ratio > 1) && ($ratio <= $this->max_colH_correction)) {
26904 // Start Transformation
26905 $this->pages[$this->page] .= $this->StartTransform(true) . "\n";
26906 $this->pages[$this->page] .= $this->transformScale(100, $ratio * 100, $x = '', $this->y0, true) . "\n";
26907 $trans_on = true;
26910 // Now output the adjusted values
26911 $this->pages[$this->page] .= $s['s'] . "\n";
26912 if (isset($s['rel_y']) && ($ratio > 1) && ($ratio <= $this->max_colH_correction)) { // only process position sensitive data
26913 // Stop Transformation
26914 $this->pages[$this->page] .= $this->StopTransform(true) . "\n";
26915 $trans_on = false;
26918 if ($trans_on) {
26919 $this->pages[$this->page] .= $this->StopTransform(true) . "\n";
26921 } else { // if NOT $this->colvAlign == 'J'
26922 // Now output the adjusted values
26923 foreach ($this->columnbuffer as $s) {
26924 $this->pages[$this->page] .= $s['s'] . "\n";
26927 if ($lowest_bottom_y > 0) {
26928 $this->y = $lowest_bottom_y;
26930 } // Columns not ended but new page -> align columns (can leave the columns alone - just tidy up the height)
26931 elseif ($this->colvAlign == 'J' && $this->ColumnAdjust && !$this->keepColumns) {
26932 // calculate the lowest bottom margin
26933 $lowest_bottom_y = 0;
26934 foreach ($this->columnbuffer as $key => $s) {
26935 // Only process output data
26936 $t = $s['s'];
26937 if ($t == 'ACROFORM' || (preg_match('/BT \d+\.\d\d+ (\d+\.\d\d+) Td/', $t)) || (preg_match('/\d+\.\d\d+ (\d+\.\d\d+) \d+\.\d\d+ [\-]{0,1}\d+\.\d\d+ re/', $t)) ||
26938 (preg_match('/\d+\.\d\d+ (\d+\.\d\d+) l/', $t)) ||
26939 (preg_match('/q \d+\.\d\d+ 0 0 \d+\.\d\d+ \d+\.\d\d+ (\d+\.\d\d+) cm \/(I|FO)\d+ Do Q/', $t)) ||
26940 (preg_match('/\d+\.\d\d+ (\d+\.\d\d+) m/', $t)) ||
26941 (preg_match('/\d+\.\d\d+ (\d+\.\d\d+) \d+\.\d\d+ \d+\.\d\d+ \d+\.\d\d+ \d+\.\d\d+ c/', $t))) {
26942 $clb = $s['y'] + $s['h'];
26943 if ((isset($this->ColDetails[$s['col']]['max_bottom']) && $clb > $this->ColDetails[$s['col']]['max_bottom']) || !isset($this->ColDetails[$s['col']]['max_bottom'])) {
26944 $this->ColDetails[$s['col']]['max_bottom'] = $clb;
26946 if ($clb > $lowest_bottom_y) {
26947 $lowest_bottom_y = $clb;
26949 $this->columnbuffer[$key]['rel_y'] = $s['y']; // Marks position sensitive data to process later
26950 if ($t == 'ACROFORM') {
26951 $this->columnbuffer[$key]['s'] = '';
26955 // Adjust column length equal
26956 foreach ($this->columnbuffer as $key => $s) {
26957 // Set ratio to expand y values or heights
26958 if (isset($this->ColDetails[$s['col']]['max_bottom']) && $this->ColDetails[$s['col']]['max_bottom']) {
26959 $ratio = ($lowest_bottom_y - ($this->y0)) / ($this->ColDetails[$s['col']]['max_bottom'] - ($this->y0));
26960 } else {
26961 $ratio = 1;
26963 if (($ratio > 1) && ($ratio <= $this->max_colH_correction)) {
26964 $yadj = ($s['y'] - $this->y0) * ($ratio - 1);
26966 // Adjust LINKS
26967 if (isset($s['rel_y'])) { // only process position sensitive data
26968 // otherwise triggers for all entries in column buffer (.e.g. formatting) and makes below adjustments more than once
26969 if (isset($this->columnLinks[$s['col']][intval($s['x'])][intval($s['y'])])) {
26970 $ref = $this->columnLinks[$s['col']][intval($s['x'])][intval($s['y'])];
26971 $this->PageLinks[$this->page][$ref][1] -= ($yadj * Mpdf::SCALE); // y value
26972 $this->PageLinks[$this->page][$ref][3] *= $ratio; // height
26973 unset($this->columnLinks[$s['col']][intval($s['x'])][intval($s['y'])]);
26975 // Adjust FORM FIELDS
26976 if (isset($this->columnForms[$s['col']][intval($s['x'])][intval($s['y'])])) {
26977 $ref = $this->columnForms[$s['col']][intval($s['x'])][intval($s['y'])];
26978 $this->form->forms[$ref]['x'] += ($xadj);
26979 $this->form->forms[$ref]['y'] += ($yadj);
26980 unset($this->columnForms[$s['col']][intval($s['x'])][intval($s['y'])]);
26982 /* -- ANNOTATIONS -- */
26983 if (isset($this->columnAnnots[$s['col']][intval($s['x'])][intval($s['y'])])) {
26984 $ref = $this->columnAnnots[$s['col']][intval($s['x'])][intval($s['y'])];
26985 $this->PageAnnots[$this->page][$ref]['y'] += ($yadj);
26986 unset($this->columnAnnots[$s['col']][intval($s['x'])][intval($s['y'])]);
26988 /* -- END ANNOTATIONS -- */
26993 /* -- BOOKMARKS -- */
26995 // Adjust Bookmarks
26996 foreach ($this->col_BMoutlines as $v) {
26997 $this->BMoutlines[] = ['t' => $v['t'], 'l' => $v['l'], 'y' => $this->y0, 'p' => $v['p']];
26999 /* -- END BOOKMARKS -- */
27001 /* -- TOC -- */
27003 // Adjust ToC
27004 foreach ($this->col_toc as $v) {
27005 $this->tableOfContents->_toc[] = ['t' => $v['t'], 'l' => $v['l'], 'p' => $v['p'], 'link' => $v['link'], 'toc_id' => $v['toc_id']];
27006 $this->links[$v['link']][1] = $this->y0;
27008 /* -- END TOC -- */
27009 $trans_on = false;
27010 foreach ($this->columnbuffer as $key => $s) {
27011 if (isset($s['rel_y'])) { // only process position sensitive data
27012 // Set ratio to expand y values or heights
27013 if ($this->ColDetails[$s['col']]['max_bottom']) {
27014 $ratio = ($lowest_bottom_y - ($this->y0)) / ($this->ColDetails[$s['col']]['max_bottom'] - ($this->y0));
27015 } else {
27016 $ratio = 1;
27018 if (($ratio > 1) && ($ratio <= $this->max_colH_correction)) {
27019 // Start Transformation
27020 $this->pages[$this->page] .= $this->StartTransform(true) . "\n";
27021 $this->pages[$this->page] .= $this->transformScale(100, $ratio * 100, $x = '', $this->y0, true) . "\n";
27022 $trans_on = true;
27025 // Now output the adjusted values
27026 $this->pages[$this->page] .= $s['s'] . "\n";
27027 if (isset($s['rel_y']) && ($ratio > 1) && ($ratio <= $this->max_colH_correction)) {
27028 // Stop Transformation
27029 $this->pages[$this->page] .= $this->StopTransform(true) . "\n";
27030 $trans_on = false;
27033 if ($trans_on) {
27034 $this->pages[$this->page] .= $this->StopTransform(true) . "\n";
27037 if ($lowest_bottom_y > 0) {
27038 $this->y = $lowest_bottom_y;
27040 } // Just reproduce the page as it was
27041 else {
27042 // If page has not ended but height adjustment was disabled by custom column-break - adjust y
27043 $lowest_bottom_y = 0;
27044 if (!$this->ColActive && (!$this->ColumnAdjust || $this->keepColumns)) {
27045 // calculate the lowest bottom margin
27046 foreach ($this->columnbuffer as $key => $s) {
27047 // Only process output data
27048 $t = $s['s'];
27049 if ($t == 'ACROFORM' || (preg_match('/BT \d+\.\d\d+ (\d+\.\d\d+) Td/', $t)) || (preg_match('/\d+\.\d\d+ (\d+\.\d\d+) \d+\.\d\d+ [\-]{0,1}\d+\.\d\d+ re/', $t)) ||
27050 (preg_match('/\d+\.\d\d+ (\d+\.\d\d+) l/', $t)) ||
27051 (preg_match('/q \d+\.\d\d+ 0 0 \d+\.\d\d+ \d+\.\d\d+ (\d+\.\d\d+) cm \/(I|FO)\d+ Do Q/', $t)) ||
27052 (preg_match('/\d+\.\d\d+ (\d+\.\d\d+) m/', $t)) ||
27053 (preg_match('/\d+\.\d\d+ (\d+\.\d\d+) \d+\.\d\d+ \d+\.\d\d+ \d+\.\d\d+ \d+\.\d\d+ c/', $t))) {
27054 $clb = $s['y'] + $s['h'];
27055 if ($clb > $this->ColDetails[$s['col']]['max_bottom']) {
27056 $this->ColDetails[$s['col']]['max_bottom'] = $clb;
27058 if ($clb > $lowest_bottom_y) {
27059 $lowest_bottom_y = $clb;
27064 foreach ($this->columnbuffer as $key => $s) {
27065 if ($s['s'] != 'ACROFORM') {
27066 $this->pages[$this->page] .= $s['s'] . "\n";
27069 if ($lowest_bottom_y > 0) {
27070 $this->y = $lowest_bottom_y;
27072 /* -- BOOKMARKS -- */
27073 // Output Bookmarks
27074 foreach ($this->col_BMoutlines as $v) {
27075 $this->BMoutlines[] = ['t' => $v['t'], 'l' => $v['l'], 'y' => $v['y'], 'p' => $v['p']];
27077 /* -- END BOOKMARKS -- */
27078 /* -- TOC -- */
27079 // Output ToC
27080 foreach ($this->col_toc as $v) {
27081 $this->tableOfContents->_toc[] = ['t' => $v['t'], 'l' => $v['l'], 'p' => $v['p'], 'link' => $v['link'], 'toc_id' => $v['toc_id']];
27083 /* -- END TOC -- */
27085 foreach ($this->internallink as $key => $f) {
27086 if (isset($this->internallink[$key]['col'])) {
27087 unset($this->internallink[$key]['col']);
27089 if (isset($this->internallink[$key]['rel_y'])) {
27090 unset($this->internallink[$key]['rel_y']);
27094 $this->columnbuffer = [];
27095 $this->ColDetails = [];
27096 $this->columnLinks = [];
27097 $this->columnAnnots = [];
27098 $this->columnForms = [];
27100 $this->col_BMoutlines = [];
27101 $this->col_toc = [];
27102 $this->breakpoints = [];
27105 // mPDF 5.7+
27106 function columnAdjustPregReplace($type, $xadj, $yadj, $pattern, $subject)
27108 preg_match($pattern, $subject, $matches);
27109 if (!count($matches)) {
27110 return $subject;
27112 if (!isset($matches[3])) {
27113 $matches[3] = 0;
27115 if (!isset($matches[4])) {
27116 $matches[4] = 0;
27118 if (!isset($matches[5])) {
27119 $matches[5] = 0;
27121 if (!isset($matches[6])) {
27122 $matches[6] = 0;
27124 return str_replace($matches[0], $this->columnAdjustAdd($type, Mpdf::SCALE, $xadj, $yadj, $matches[1], $matches[2], $matches[3], $matches[4], $matches[5], $matches[6]), $subject);
27127 /* -- END COLUMNS -- */
27129 // ==================================================================
27130 /* -- TABLES -- */
27131 function printcellbuffer()
27133 if (count($this->cellBorderBuffer)) {
27134 sort($this->cellBorderBuffer);
27135 foreach ($this->cellBorderBuffer as $cbb) {
27136 $cba = unpack("A16dom/nbord/A1side/ns/dbw/a6ca/A10style/dx/dy/dw/dh/dmbl/dmbr/dmrt/dmrb/dmtl/dmtr/dmlt/dmlb/dcpd/dover/", $cbb);
27137 $side = $cba['side'];
27138 $color = str_pad($cba['ca'], 6, "\x00");
27139 $details = [];
27140 $details[$side]['dom'] = (float) $cba['dom'];
27141 $details[$side]['s'] = $cba['s'];
27142 $details[$side]['w'] = $cba['bw'];
27143 $details[$side]['c'] = $color;
27144 $details[$side]['style'] = trim($cba['style']);
27145 $details['mbw']['BL'] = $cba['mbl'];
27146 $details['mbw']['BR'] = $cba['mbr'];
27147 $details['mbw']['RT'] = $cba['mrt'];
27148 $details['mbw']['RB'] = $cba['mrb'];
27149 $details['mbw']['TL'] = $cba['mtl'];
27150 $details['mbw']['TR'] = $cba['mtr'];
27151 $details['mbw']['LT'] = $cba['mlt'];
27152 $details['mbw']['LB'] = $cba['mlb'];
27153 $details['cellposdom'] = $cba['cpd'];
27154 $details['p'] = $side;
27155 if ($cba['over'] == 1) {
27156 $details[$side]['overlay'] = true;
27157 } else {
27158 $details[$side]['overlay'] = false;
27160 $this->_tableRect($cba['x'], $cba['y'], $cba['w'], $cba['h'], $cba['bord'], $details, false, false);
27162 $this->cellBorderBuffer = [];
27166 // ==================================================================
27167 function printtablebuffer()
27170 if (!$this->table_rotate) {
27171 $this->pages[$this->page] .= $this->tablebuffer;
27172 foreach ($this->tbrot_Links as $p => $l) {
27173 foreach ($l as $v) {
27174 $this->PageLinks[$p][] = $v;
27177 $this->tbrot_Links = [];
27178 /* -- ANNOTATIONS -- */
27179 foreach ($this->tbrot_Annots as $p => $l) {
27180 foreach ($l as $v) {
27181 $this->PageAnnots[$p][] = $v;
27184 $this->tbrot_Annots = [];
27185 /* -- END ANNOTATIONS -- */
27187 /* -- BOOKMARKS -- */
27188 // Output Bookmarks
27189 foreach ($this->tbrot_BMoutlines as $v) {
27190 $this->BMoutlines[] = ['t' => $v['t'], 'l' => $v['l'], 'y' => $v['y'], 'p' => $v['p']];
27192 $this->tbrot_BMoutlines = [];
27193 /* -- END BOOKMARKS -- */
27195 /* -- TOC -- */
27196 // Output ToC
27197 foreach ($this->tbrot_toc as $v) {
27198 $this->tableOfContents->_toc[] = ['t' => $v['t'], 'l' => $v['l'], 'p' => $v['p'], 'link' => $v['link'], 'toc_id' => $v['toc_id']];
27200 $this->tbrot_toc = [];
27201 /* -- END TOC -- */
27203 return;
27205 // elseif rotated
27206 $lm = $this->lMargin + $this->blk[$this->blklvl]['outer_left_margin'] + $this->blk[$this->blklvl]['border_left']['w'] + $this->blk[$this->blklvl]['padding_left'];
27207 $pw = $this->blk[$this->blklvl]['inner_width'];
27208 // Start Transformation
27209 $this->pages[$this->page] .= $this->StartTransform(true) . "\n";
27211 if ($this->table_rotate > 1) { // clockwise
27212 if ($this->tbrot_align == 'L') {
27213 $xadj = $this->tbrot_h; // align L (as is)
27214 } elseif ($this->tbrot_align == 'R') {
27215 $xadj = $lm - $this->tbrot_x0 + ($pw); // align R
27216 } else {
27217 $xadj = $lm - $this->tbrot_x0 + (($pw + $this->tbrot_h) / 2); // align C
27219 $yadj = 0;
27220 } else { // anti-clockwise
27221 if ($this->tbrot_align == 'L') {
27222 $xadj = 0; // align L (as is)
27223 } elseif ($this->tbrot_align == 'R') {
27224 $xadj = $lm - $this->tbrot_x0 + ($pw - $this->tbrot_h); // align R
27225 } else {
27226 $xadj = $lm - $this->tbrot_x0 + (($pw - $this->tbrot_h) / 2); // align C
27228 $yadj = $this->tbrot_w;
27232 $this->pages[$this->page] .= $this->transformTranslate($xadj, $yadj, true) . "\n";
27233 $this->pages[$this->page] .= $this->transformRotate($this->table_rotate, $this->tbrot_x0, $this->tbrot_y0, true) . "\n";
27235 // Now output the adjusted values
27236 $this->pages[$this->page] .= $this->tablebuffer;
27239 foreach ($this->tbrot_Links as $p => $l) {
27240 foreach ($l as $v) {
27241 $w = $v[2] / Mpdf::SCALE;
27242 $h = $v[3] / Mpdf::SCALE;
27243 $ax = ($v[0] / Mpdf::SCALE) - $this->tbrot_x0;
27244 $ay = (($this->hPt - $v[1]) / Mpdf::SCALE) - $this->tbrot_y0;
27245 if ($this->table_rotate > 1) { // clockwise
27246 $bx = $this->tbrot_x0 + $xadj - $ay - $h;
27247 $by = $this->tbrot_y0 + $yadj + $ax;
27248 } else {
27249 $bx = $this->tbrot_x0 + $xadj + $ay;
27250 $by = $this->tbrot_y0 + $yadj - $ax - $w;
27252 $v[0] = $bx * Mpdf::SCALE;
27253 $v[1] = ($this->h - $by) * Mpdf::SCALE;
27254 $v[2] = $h * Mpdf::SCALE; // swap width and height
27255 $v[3] = $w * Mpdf::SCALE;
27256 $this->PageLinks[$p][] = $v;
27259 $this->tbrot_Links = [];
27260 foreach ($this->internallink as $key => $f) {
27261 if (is_array($f) && isset($f['tbrot'])) {
27262 $f['Y'] = $this->tbrot_y0;
27263 $f['PAGE'] = $this->page;
27264 unset($f['tbrot']);
27265 $this->internallink[$key] = $f;
27268 /* -- ANNOTATIONS -- */
27269 foreach ($this->tbrot_Annots as $p => $l) {
27270 foreach ($l as $v) {
27271 $ax = abs($v['x']) - $this->tbrot_x0; // abs because -ve values are internally set and held for reference if annotMargin set
27272 $ay = $v['y'] - $this->tbrot_y0;
27273 if ($this->table_rotate > 1) { // clockwise
27274 $bx = $this->tbrot_x0 + $xadj - $ay;
27275 $by = $this->tbrot_y0 + $yadj + $ax;
27276 } else {
27277 $bx = $this->tbrot_x0 + $xadj + $ay;
27278 $by = $this->tbrot_y0 + $yadj - $ax;
27280 if ($v['x'] < 0) {
27281 $v['x'] = -$bx;
27282 } else {
27283 $v['x'] = $bx;
27285 $v['y'] = ($by);
27286 $this->PageAnnots[$p][] = $v;
27289 $this->tbrot_Annots = [];
27290 /* -- END ANNOTATIONS -- */
27293 /* -- BOOKMARKS -- */
27295 // Adjust Bookmarks
27296 foreach ($this->tbrot_BMoutlines as $v) {
27297 $v['y'] = $this->tbrot_y0;
27298 $this->BMoutlines[] = ['t' => $v['t'], 'l' => $v['l'], 'y' => $v['y'], 'p' => $this->page];
27300 /* -- END BOOKMARKS -- */
27302 /* -- TOC -- */
27304 // Adjust ToC - uses document page number
27305 foreach ($this->tbrot_toc as $v) {
27306 $this->tableOfContents->_toc[] = ['t' => $v['t'], 'l' => $v['l'], 'p' => $this->page, 'link' => $v['link'], 'toc_id' => $v['toc_id']];
27307 $this->links[$v['link']][1] = $this->tbrot_y0;
27309 /* -- END TOC -- */
27313 $this->tbrot_BMoutlines = [];
27314 $this->tbrot_toc = [];
27316 // Stop Transformation
27317 $this->pages[$this->page] .= $this->StopTransform(true) . "\n";
27320 $this->y = $this->tbrot_y0 + $this->tbrot_w;
27321 $this->x = $this->lMargin;
27323 $this->tablebuffer = '';
27326 // ==================================================================
27327 // Keep-with-table This buffers contents of h1-6 to keep on page with table
27328 function printkwtbuffer()
27330 if (!$this->kwt_moved) {
27331 foreach ($this->kwt_buffer as $s) {
27332 $this->pages[$this->page] .= $s['s'] . "\n";
27334 foreach ($this->kwt_Links as $p => $l) {
27335 foreach ($l as $v) {
27336 $this->PageLinks[$p][] = $v;
27339 $this->kwt_Links = [];
27340 /* -- ANNOTATIONS -- */
27341 foreach ($this->kwt_Annots as $p => $l) {
27342 foreach ($l as $v) {
27343 $this->PageAnnots[$p][] = $v;
27346 $this->kwt_Annots = [];
27347 /* -- END ANNOTATIONS -- */
27349 /* -- INDEX -- */
27350 // Output Reference (index)
27351 foreach ($this->kwt_Reference as $v) {
27352 $Present = 0;
27353 for ($i = 0; $i < count($this->Reference); $i++) {
27354 if ($this->Reference[$i]['t'] == $v['t']) {
27355 $Present = 1;
27356 if (!in_array($v['op'], $this->Reference[$i]['p'])) {
27357 $this->Reference[$i]['p'][] = $v['op'];
27361 if ($Present == 0) {
27362 $this->Reference[] = ['t' => $v['t'], 'p' => [$v['op']]];
27365 $this->kwt_Reference = [];
27366 /* -- END INDEX -- */
27368 /* -- BOOKMARKS -- */
27369 // Output Bookmarks
27370 foreach ($this->kwt_BMoutlines as $v) {
27371 $this->BMoutlines[] = ['t' => $v['t'], 'l' => $v['l'], 'y' => $v['y'], 'p' => $v['p']];
27373 $this->kwt_BMoutlines = [];
27374 /* -- END BOOKMARKS -- */
27376 /* -- TOC -- */
27377 // Output ToC
27378 foreach ($this->kwt_toc as $v) {
27379 $this->tableOfContents->_toc[] = ['t' => $v['t'], 'l' => $v['l'], 'p' => $v['p'], 'link' => $v['link'], 'toc_id' => $v['toc_id']];
27381 $this->kwt_toc = [];
27382 /* -- END TOC -- */
27384 $this->pageoutput[$this->page] = []; // mPDF 6
27385 return;
27388 // Start Transformation
27389 $this->pages[$this->page] .= $this->StartTransform(true) . "\n";
27390 $xadj = $this->lMargin - $this->kwt_x0;
27391 // $yadj = $this->y - $this->kwt_y0 ;
27392 $yadj = $this->tMargin - $this->kwt_y0;
27394 $this->pages[$this->page] .= $this->transformTranslate($xadj, $yadj, true) . "\n";
27396 // Now output the adjusted values
27397 foreach ($this->kwt_buffer as $s) {
27398 $this->pages[$this->page] .= $s['s'] . "\n";
27401 // Adjust hyperLinks
27402 foreach ($this->kwt_Links as $p => $l) {
27403 foreach ($l as $v) {
27404 $bx = $this->kwt_x0 + $xadj;
27405 $by = $this->kwt_y0 + $yadj;
27406 $v[0] = $bx * Mpdf::SCALE;
27407 $v[1] = ($this->h - $by) * Mpdf::SCALE;
27408 $this->PageLinks[$p][] = $v;
27411 foreach ($this->internallink as $key => $f) {
27412 if (is_array($f) && isset($f['kwt'])) {
27413 $f['Y'] += $yadj;
27414 $f['PAGE'] = $this->page;
27415 unset($f['kwt']);
27416 $this->internallink[$key] = $f;
27419 /* -- ANNOTATIONS -- */
27420 foreach ($this->kwt_Annots as $p => $l) {
27421 foreach ($l as $v) {
27422 $bx = $this->kwt_x0 + $xadj;
27423 $by = $this->kwt_y0 + $yadj;
27424 if ($v['x'] < 0) {
27425 $v['x'] = -$bx;
27426 } else {
27427 $v['x'] = $bx;
27429 $v['y'] = $by;
27430 $this->PageAnnots[$p][] = $v;
27433 /* -- END ANNOTATIONS -- */
27435 /* -- BOOKMARKS -- */
27437 // Adjust Bookmarks
27438 foreach ($this->kwt_BMoutlines as $v) {
27439 if ($v['y'] != 0) {
27440 $v['y'] += $yadj;
27442 $this->BMoutlines[] = ['t' => $v['t'], 'l' => $v['l'], 'y' => $v['y'], 'p' => $this->page];
27444 /* -- END BOOKMARKS -- */
27446 /* -- INDEX -- */
27448 // Adjust Reference (index)
27449 foreach ($this->kwt_Reference as $v) {
27450 $Present = 0;
27451 // Search the reference (AND Ref/PageNo) in the array
27452 for ($i = 0; $i < count($this->Reference); $i++) {
27453 if ($this->Reference[$i]['t'] == $v['t']) {
27454 $Present = 1;
27455 if (!in_array($this->page, $this->Reference[$i]['p'])) {
27456 $this->Reference[$i]['p'][] = $this->page;
27460 if ($Present == 0) {
27461 $this->Reference[] = ['t' => $v['t'], 'p' => [$this->page]];
27464 /* -- END INDEX -- */
27466 /* -- TOC -- */
27468 // Adjust ToC
27469 foreach ($this->kwt_toc as $v) {
27470 $this->tableOfContents->_toc[] = ['t' => $v['t'], 'l' => $v['l'], 'p' => $this->page, 'link' => $v['link'], 'toc_id' => $v['toc_id']];
27471 $this->links[$v['link']][0] = $this->page;
27472 $this->links[$v['link']][1] += $yadj;
27474 /* -- END TOC -- */
27477 $this->kwt_Links = [];
27478 $this->kwt_Annots = [];
27480 $this->kwt_Reference = [];
27481 $this->kwt_BMoutlines = [];
27482 $this->kwt_toc = [];
27483 // Stop Transformation
27484 $this->pages[$this->page] .= $this->StopTransform(true) . "\n";
27486 $this->kwt_buffer = [];
27488 $this->y += $this->kwt_height;
27489 $this->pageoutput[$this->page] = []; // mPDF 6
27492 /* -- END TABLES -- */
27494 // ==================================================================
27496 function printfloatbuffer()
27498 if (count($this->floatbuffer)) {
27499 $this->objectbuffer = $this->floatbuffer;
27500 $this->printobjectbuffer(false);
27501 $this->objectbuffer = [];
27502 $this->floatbuffer = [];
27503 $this->floatmargins = [];
27507 // ==================================================================
27508 // ==================================================================
27509 // Added ELLIPSES and CIRCLES
27510 function Circle($x, $y, $r, $style = 'S')
27512 $this->Ellipse($x, $y, $r, $r, $style);
27515 function Ellipse($x, $y, $rx, $ry, $style = 'S')
27517 if ($style == 'F') {
27518 $op = 'f';
27519 } elseif ($style == 'FD' or $style == 'DF') {
27520 $op = 'B';
27521 } else {
27522 $op = 'S';
27524 $lx = 4 / 3 * (M_SQRT2 - 1) * $rx;
27525 $ly = 4 / 3 * (M_SQRT2 - 1) * $ry;
27526 $h = $this->h;
27527 $this->_out(sprintf('%.3F %.3F m %.3F %.3F %.3F %.3F %.3F %.3F c', ($x + $rx) * Mpdf::SCALE, ($h - $y) * Mpdf::SCALE, ($x + $rx) * Mpdf::SCALE, ($h - ($y - $ly)) * Mpdf::SCALE, ($x + $lx) * Mpdf::SCALE, ($h - ($y - $ry)) * Mpdf::SCALE, $x * Mpdf::SCALE, ($h - ($y - $ry)) * Mpdf::SCALE));
27528 $this->_out(sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c', ($x - $lx) * Mpdf::SCALE, ($h - ($y - $ry)) * Mpdf::SCALE, ($x - $rx) * Mpdf::SCALE, ($h - ($y - $ly)) * Mpdf::SCALE, ($x - $rx) * Mpdf::SCALE, ($h - $y) * Mpdf::SCALE));
27529 $this->_out(sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c', ($x - $rx) * Mpdf::SCALE, ($h - ($y + $ly)) * Mpdf::SCALE, ($x - $lx) * Mpdf::SCALE, ($h - ($y + $ry)) * Mpdf::SCALE, $x * Mpdf::SCALE, ($h - ($y + $ry)) * Mpdf::SCALE));
27530 $this->_out(sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c %s', ($x + $lx) * Mpdf::SCALE, ($h - ($y + $ry)) * Mpdf::SCALE, ($x + $rx) * Mpdf::SCALE, ($h - ($y + $ly)) * Mpdf::SCALE, ($x + $rx) * Mpdf::SCALE, ($h - $y) * Mpdf::SCALE, $op));
27533 /* -- DIRECTW -- */
27535 // Added adaptation of shaded_box = AUTOSIZE-TEXT
27536 function AutosizeText($text, $w, $font, $style, $szfont = 72)
27539 $text = ' ' . $text . ' ';
27541 $this->SetFont($font, $style, $szfont, false);
27543 $text = $this->purify_utf8_text($text);
27544 if ($this->text_input_as_HTML) {
27545 $text = $this->all_entities_to_utf8($text);
27547 if ($this->usingCoreFont) {
27548 $text = mb_convert_encoding($text, $this->mb_enc, 'UTF-8');
27551 // DIRECTIONALITY
27552 if (preg_match("/([" . $this->pregRTLchars . "])/u", $text)) {
27553 $this->biDirectional = true;
27554 } // *OTL*
27556 $textvar = 0;
27557 $save_OTLtags = $this->OTLtags;
27558 $this->OTLtags = [];
27559 if ($this->useKerning) {
27560 if ($this->CurrentFont['haskernGPOS']) {
27561 $this->OTLtags['Plus'] .= ' kern';
27562 } else {
27563 $textvar = ($textvar | TextVars::FC_KERNING);
27567 /* -- OTL -- */
27568 // Use OTL OpenType Table Layout - GSUB & GPOS
27569 if (isset($this->CurrentFont['useOTL']) && $this->CurrentFont['useOTL']) {
27570 $text = $this->otl->applyOTL($text, $this->CurrentFont['useOTL']);
27571 $OTLdata = $this->otl->OTLdata;
27573 /* -- END OTL -- */
27574 $this->OTLtags = $save_OTLtags;
27576 $this->magic_reverse_dir($text, $this->directionality, $OTLdata);
27579 $width = $this->sizeConverter->convert($w);
27580 $loop = 0;
27581 while ($loop == 0) {
27582 $this->SetFont($font, $style, $szfont, false);
27583 $sz = $this->GetStringWidth($text, true, $OTLdata, $textvar);
27584 if ($sz > $w) {
27585 $szfont --;
27586 } else {
27587 $loop ++;
27590 $this->SetFont($font, $style, $szfont, true, true);
27591 $this->Cell($w, 0, $text, 0, 0, "C", 0, '', 0, 0, 0, 'M', 0, false, $OTLdata, $textvar);
27594 /* -- END DIRECTW -- */
27596 // ====================================================
27597 // ====================================================
27599 function magic_reverse_dir(&$chunk, $dir, &$chunkOTLdata)
27601 /* -- OTL -- */
27602 if ($this->usingCoreFont) {
27603 return 0;
27605 if ($chunk == '') {
27606 return 0;
27609 if ($this->biDirectional || $dir == 'rtl') {
27610 // check if string contains RTL text
27611 // including any added from OTL tables (in PUA)
27612 $pregRTLchars = $this->pregRTLchars;
27613 if (isset($this->CurrentFont['rtlPUAstr']) && $this->CurrentFont['rtlPUAstr']) {
27614 $pregRTLchars .= $this->CurrentFont['rtlPUAstr'];
27616 if (!preg_match("/[" . $pregRTLchars . "]/u", $chunk) && $dir != 'rtl') {
27617 return 0;
27618 } // Chunk doesn't contain RTL characters
27620 $unicode = $this->UTF8StringToArray($chunk, false);
27622 $is_strong = false;
27623 if (empty($chunkOTLdata)) {
27624 $this->getBasicOTLdata($chunkOTLdata, $unicode, $is_strong);
27627 if (isset($this->CurrentFont['useOTL']) && ($this->CurrentFont['useOTL'] & 0x80)) {
27628 $useGPOS = true;
27629 } else {
27630 $useGPOS = false;
27633 // NB Returned $chunk may be a shorter string (with adjusted $cOTLdata) by removal of LRE, RLE etc embedding codes.
27634 list($chunk, $rtl_content) = $this->otl->bidiSort($unicode, $chunk, $dir, $chunkOTLdata, $useGPOS);
27636 return $rtl_content;
27638 /* -- END OTL -- */
27639 return 0;
27642 /* -- OTL -- */
27644 function getBasicOTLdata(&$chunkOTLdata, $unicode, &$is_strong)
27646 if (empty($this->otl)) {
27647 $this->otl = new Otl($this, $this->fontCache);
27649 $chunkOTLdata['group'] = '';
27650 $chunkOTLdata['GPOSinfo'] = [];
27651 $chunkOTLdata['char_data'] = [];
27652 foreach ($unicode as $char) {
27653 $ucd_record = Ucdn::get_ucd_record($char);
27654 $chunkOTLdata['char_data'][] = ['bidi_class' => $ucd_record[2], 'uni' => $char];
27655 if ($ucd_record[2] == 0 || $ucd_record[2] == 3 || $ucd_record[2] == 4) {
27656 $is_strong = true;
27657 } // contains strong character
27658 if ($ucd_record[0] == Ucdn::UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK) {
27659 $chunkOTLdata['group'] .= 'M';
27660 } elseif ($char == 32 || $char == 12288) {
27661 $chunkOTLdata['group'] .= 'S';
27662 } else {
27663 $chunkOTLdata['group'] .= 'C';
27668 function _setBidiCodes($mode = 'start', $bdf = '')
27670 $s = '';
27671 if ($mode == 'end') {
27672 // PDF comes before PDI to close isolate-override (e.g. "LRILROPDFPDI")
27673 if (strpos($bdf, 'PDF') !== false) {
27674 $s .= UtfString::code2utf(0x202C);
27675 } // POP DIRECTIONAL FORMATTING
27676 if (strpos($bdf, 'PDI') !== false) {
27677 $s .= UtfString::code2utf(0x2069);
27678 } // POP DIRECTIONAL ISOLATE
27679 } elseif ($mode == 'start') {
27680 // LRI comes before LRO to open isolate-override (e.g. "LRILROPDFPDI")
27681 if (strpos($bdf, 'LRI') !== false) {
27682 $s .= UtfString::code2utf(0x2066);
27683 } // U+2066 LRI
27684 elseif (strpos($bdf, 'RLI') !== false) {
27685 $s .= UtfString::code2utf(0x2067);
27686 } // U+2067 RLI
27687 elseif (strpos($bdf, 'FSI') !== false) {
27688 $s .= UtfString::code2utf(0x2068);
27689 } // U+2068 FSI
27690 if (strpos($bdf, 'LRO') !== false) {
27691 $s .= UtfString::code2utf(0x202D);
27692 } // U+202D LRO
27693 elseif (strpos($bdf, 'RLO') !== false) {
27694 $s .= UtfString::code2utf(0x202E);
27695 } // U+202E RLO
27696 elseif (strpos($bdf, 'LRE') !== false) {
27697 $s .= UtfString::code2utf(0x202A);
27698 } // U+202A LRE
27699 elseif (strpos($bdf, 'RLE') !== false) {
27700 $s .= UtfString::code2utf(0x202B);
27701 } // U+202B RLE
27703 return $s;
27706 /* -- END OTL -- */
27709 // ****************************
27710 // ****************************
27713 function SetSubstitutions()
27715 $subsarray = [];
27716 require __DIR__ . '/../data/subs_win-1252.php';
27717 $this->substitute = [];
27718 foreach ($subsarray as $key => $val) {
27719 $this->substitute[UtfString::code2utf($key)] = $val;
27723 function SubstituteChars($html)
27725 // only substitute characters between tags
27726 if (count($this->substitute)) {
27727 $a = preg_split('/(<.*?>)/ms', $html, -1, PREG_SPLIT_DELIM_CAPTURE);
27728 $html = '';
27729 foreach ($a as $i => $e) {
27730 if ($i % 2 == 0) {
27731 $e = strtr($e, $this->substitute);
27733 $html .= $e;
27736 return $html;
27739 function SubstituteCharsSIP(&$writehtml_a, &$writehtml_i, &$writehtml_e)
27741 if (preg_match("/^(.*?)([\x{20000}-\x{2FFFF}]+)(.*)/u", $writehtml_e, $m)) {
27742 if (isset($this->CurrentFont['sipext']) && $this->CurrentFont['sipext']) {
27743 $font = $this->CurrentFont['sipext'];
27744 if (!in_array($font, $this->available_unifonts)) {
27745 return 0;
27747 $writehtml_a[$writehtml_i] = $writehtml_e = $m[1];
27748 array_splice($writehtml_a, $writehtml_i + 1, 0, ['span style="font-family: ' . $font . '"', $m[2], '/span', $m[3]]);
27749 $this->subPos = $writehtml_i;
27750 return 4;
27753 return 0;
27756 // If core font is selected in document which is not onlyCoreFonts - substitute with non-core font
27757 function SubstituteCharsNonCore(&$writehtml_a, &$writehtml_i, &$writehtml_e)
27759 // Ignore if in Textarea
27760 if ($writehtml_i > 0 && strtolower(substr($writehtml_a[$writehtml_i - 1], 0, 8)) == 'textarea') {
27761 return 0;
27763 if (mb_convert_encoding(mb_convert_encoding($writehtml_e, $this->mb_enc, "UTF-8"), "UTF-8", $this->mb_enc) == $writehtml_e) {
27764 return 0;
27766 $cw = &$this->CurrentFont['cw'];
27767 $unicode = $this->UTF8StringToArray($writehtml_e, false);
27768 $start = -1;
27769 $end = 0;
27770 $flag = 0;
27771 $ftype = '';
27772 $u = [];
27773 if (!$this->subArrMB) {
27774 require __DIR__ . '/../data/subs_core.php';
27775 $this->subArrMB['a'] = $aarr;
27776 $this->subArrMB['s'] = $sarr;
27777 $this->subArrMB['z'] = $zarr;
27779 foreach ($unicode as $c => $char) {
27780 if (($char > 127 || ($flag == 1 && $char == 32)) && $char != 173 && (!isset($this->subArrMB['a'][$char]) || ($flag == 1 && $char == 32)) && ($char < 1536 || ($char > 1791 && $char < 2304) || $char > 3455)) {
27781 if ($flag == 0) {
27782 $start = $c;
27784 $flag = 1;
27785 $u[] = $char;
27786 } elseif ($flag > 0) {
27787 $end = $c - 1;
27788 break;
27791 if ($flag > 0 && !$end) {
27792 $end = count($unicode) - 1;
27794 if ($start == -1) {
27795 return 0;
27798 // TRY IN BACKUP SUBS FONT
27799 if (!is_array($this->backupSubsFont)) {
27800 $this->backupSubsFont = ["$this->backupSubsFont"];
27803 foreach ($this->backupSubsFont as $bsfctr => $bsf) {
27804 if ($this->fonttrans[$bsf] == 'chelvetica' || $this->fonttrans[$bsf] == 'ctimes' || $this->fonttrans[$bsf] == 'ccourier') {
27805 continue;
27808 $font = $bsf;
27809 unset($cw);
27810 $cw = '';
27812 if (isset($this->fonts[$font])) {
27813 $cw = &$this->fonts[$font]['cw'];
27814 } elseif ($this->fontCache->has($font . '.cw.dat')) {
27815 $cw = $this->fontCache->load($font . '.cw.dat');
27816 } else {
27817 $prevFontFamily = $this->FontFamily;
27818 $prevFontStyle = $this->currentfontstyle;
27819 $prevFontSizePt = $this->FontSizePt;
27820 $this->SetFont($bsf, '', '', false);
27821 $this->SetFont($prevFontFamily, $prevFontStyle, $prevFontSizePt, false);
27824 if (!$cw) {
27825 continue;
27828 $l = 0;
27829 foreach ($u as $char) {
27830 if ($char == 173 || $this->_charDefined($cw, $char) || ($char > 1536 && $char < 1791) || ($char > 2304 && $char < 3455 )) {
27831 $l++;
27832 } else {
27833 if ($l == 0 && $bsfctr == (count($this->backupSubsFont) - 1)) { // Not found even in last backup font
27834 $cont = mb_substr($writehtml_e, $start + 1);
27835 $writehtml_e = mb_substr($writehtml_e, 0, $start + 1, 'UTF-8');
27836 array_splice($writehtml_a, $writehtml_i + 1, 0, ['', $cont]);
27837 $this->subPos = $writehtml_i + 1;
27838 return 2;
27839 } else {
27840 break;
27845 if ($l > 0) {
27846 $patt = mb_substr($writehtml_e, $start, $l, 'UTF-8');
27847 if (preg_match("/(.*?)(" . preg_quote($patt, '/') . ")(.*)/u", $writehtml_e, $m)) {
27848 $writehtml_e = $m[1];
27849 array_splice($writehtml_a, $writehtml_i + 1, 0, ['span style="font-family: ' . $font . '"', $m[2], '/span', $m[3]]);
27850 $this->subPos = $writehtml_i + 3;
27851 return 4;
27856 unset($cw);
27857 return 0;
27860 function SubstituteCharsMB(&$writehtml_a, &$writehtml_i, &$writehtml_e)
27862 // Ignore if in Textarea
27863 if ($writehtml_i > 0 && strtolower(substr($writehtml_a[$writehtml_i - 1], 0, 8)) == 'textarea') {
27864 return 0;
27866 $cw = &$this->CurrentFont['cw'];
27867 $unicode = $this->UTF8StringToArray($writehtml_e, false);
27868 $start = -1;
27869 $end = 0;
27870 $flag = 0;
27871 $ftype = '';
27872 $u = [];
27873 foreach ($unicode as $c => $char) {
27874 if (($flag == 0 || $flag == 2) && (!$this->_charDefined($cw, $char) || ($flag == 2 && $char == 32)) && $this->checkSIP && $char > 131071) { // Unicode Plane 2 (SIP)
27875 if (in_array($this->FontFamily, $this->available_CJK_fonts)) {
27876 return 0;
27878 if ($flag == 0) {
27879 $start = $c;
27881 $flag = 2;
27882 $u[] = $char;
27883 } // elseif (($flag == 0 || $flag==1) && $char != 173 && !$this->_charDefined($cw,$char) && ($char<1423 || ($char>3583 && $char < 11263))) {
27884 elseif (($flag == 0 || $flag == 1) && $char != 173 && (!$this->_charDefined($cw, $char) || ($flag == 1 && $char == 32)) && ($char < 1536 || ($char > 1791 && $char < 2304) || $char > 3455)) {
27885 if ($flag == 0) {
27886 $start = $c;
27888 $flag = 1;
27889 $u[] = $char;
27890 } elseif ($flag > 0) {
27891 $end = $c - 1;
27892 break;
27895 if ($flag > 0 && !$end) {
27896 $end = count($unicode) - 1;
27898 if ($start == -1) {
27899 return 0;
27902 if ($flag == 2) { // SIP
27903 // Check if current CJK font has a ext-B related font
27904 if (isset($this->CurrentFont['sipext']) && $this->CurrentFont['sipext']) {
27905 $font = $this->CurrentFont['sipext'];
27906 unset($cw);
27907 $cw = '';
27908 if (isset($this->fonts[$font])) {
27909 $cw = &$this->fonts[$font]['cw'];
27910 } elseif ($this->fontCache->has($font . '.cw.dat')) {
27911 $cw = $this->fontCache->load($font . '.cw.dat');
27912 } else {
27913 $prevFontFamily = $this->FontFamily;
27914 $prevFontStyle = $this->currentfontstyle;
27915 $prevFontSizePt = $this->FontSizePt;
27916 $this->SetFont($font, '', '', false);
27917 $this->SetFont($prevFontFamily, $prevFontStyle, $prevFontSizePt, false);
27920 if (!$cw) {
27921 return 0;
27924 $l = 0;
27925 foreach ($u as $char) {
27926 if ($this->_charDefined($cw, $char) || $char > 131071) {
27927 $l++;
27928 } else {
27929 break;
27933 if ($l > 0) {
27934 $patt = mb_substr($writehtml_e, $start, $l);
27935 if (preg_match("/(.*?)(" . preg_quote($patt, '/') . ")(.*)/u", $writehtml_e, $m)) {
27936 $writehtml_e = $m[1];
27937 array_splice($writehtml_a, $writehtml_i + 1, 0, ['span style="font-family: ' . $font . '"', $m[2], '/span', $m[3]]);
27938 $this->subPos = $writehtml_i + 3;
27939 return 4;
27943 // Check Backup SIP font (defined in Config\FontVariables)
27944 if (isset($this->backupSIPFont) && $this->backupSIPFont) {
27945 if ($this->currentfontfamily != $this->backupSIPFont) {
27946 $font = $this->backupSIPFont;
27947 } else {
27948 unset($cw);
27949 return 0;
27952 unset($cw);
27953 $cw = '';
27955 if (isset($this->fonts[$font])) {
27956 $cw = &$this->fonts[$font]['cw'];
27957 } elseif ($this->fontCache->has($font . '.cw.dat')) {
27958 $cw = $this->fontCache->load($font . '.cw.dat');
27959 } else {
27960 $prevFontFamily = $this->FontFamily;
27961 $prevFontStyle = $this->currentfontstyle;
27962 $prevFontSizePt = $this->FontSizePt;
27963 $this->SetFont($this->backupSIPFont, '', '', false);
27964 $this->SetFont($prevFontFamily, $prevFontStyle, $prevFontSizePt, false);
27967 if (!$cw) {
27968 return 0;
27971 $l = 0;
27972 foreach ($u as $char) {
27973 if ($this->_charDefined($cw, $char) || $char > 131071) {
27974 $l++;
27975 } else {
27976 break;
27979 if ($l > 0) {
27980 $patt = mb_substr($writehtml_e, $start, $l);
27981 if (preg_match("/(.*?)(" . preg_quote($patt, '/') . ")(.*)/u", $writehtml_e, $m)) {
27982 $writehtml_e = $m[1];
27983 array_splice($writehtml_a, $writehtml_i + 1, 0, ['span style="font-family: ' . $font . '"', $m[2], '/span', $m[3]]);
27984 $this->subPos = $writehtml_i + 3;
27985 return 4;
27989 return 0;
27993 // FIRST TRY CORE FONTS (when appropriate)
27994 if (!$this->PDFA && !$this->PDFX && !$this->biDirectional) { // mPDF 6
27995 $repl = [];
27996 if (!$this->subArrMB) {
27997 require __DIR__ . '/../data/subs_core.php';
27998 $this->subArrMB['a'] = $aarr;
27999 $this->subArrMB['s'] = $sarr;
28000 $this->subArrMB['z'] = $zarr;
28002 if (isset($this->subArrMB['a'][$u[0]])) {
28003 $font = 'tta';
28004 $ftype = 'C';
28005 foreach ($u as $char) {
28006 if (isset($this->subArrMB['a'][$char])) {
28007 $repl[] = $this->subArrMB['a'][$char];
28008 } else {
28009 break;
28012 } elseif (isset($this->subArrMB['z'][$u[0]])) {
28013 $font = 'ttz';
28014 $ftype = 'C';
28015 foreach ($u as $char) {
28016 if (isset($this->subArrMB['z'][$char])) {
28017 $repl[] = $this->subArrMB['z'][$char];
28018 } else {
28019 break;
28022 } elseif (isset($this->subArrMB['s'][$u[0]])) {
28023 $font = 'tts';
28024 $ftype = 'C';
28025 foreach ($u as $char) {
28026 if (isset($this->subArrMB['s'][$char])) {
28027 $repl[] = $this->subArrMB['s'][$char];
28028 } else {
28029 break;
28033 if ($ftype == 'C') {
28034 $patt = mb_substr($writehtml_e, $start, count($repl));
28035 if (preg_match("/(.*?)(" . preg_quote($patt, '/') . ")(.*)/u", $writehtml_e, $m)) {
28036 $writehtml_e = $m[1];
28037 array_splice($writehtml_a, $writehtml_i + 1, 0, [$font, implode('|', $repl), '/' . $font, $m[3]]); // e.g. <tts>
28038 $this->subPos = $writehtml_i + 3;
28039 return 4;
28041 return 0;
28045 // LASTLY TRY IN BACKUP SUBS FONT
28046 if (!is_array($this->backupSubsFont)) {
28047 $this->backupSubsFont = ["$this->backupSubsFont"];
28049 foreach ($this->backupSubsFont as $bsfctr => $bsf) {
28050 if ($this->currentfontfamily != $bsf) {
28051 $font = $bsf;
28052 } else {
28053 continue;
28056 unset($cw);
28057 $cw = '';
28059 if (isset($this->fonts[$font])) {
28060 $cw = &$this->fonts[$font]['cw'];
28061 } elseif ($this->fontCache->has($font . '.cw.dat')) {
28062 $cw = $this->fontCache->load($font . '.cw.dat');
28063 } else {
28064 $prevFontFamily = $this->FontFamily;
28065 $prevFontStyle = $this->currentfontstyle;
28066 $prevFontSizePt = $this->FontSizePt;
28067 $this->SetFont($bsf, '', '', false);
28068 $this->SetFont($prevFontFamily, $prevFontStyle, $prevFontSizePt, false);
28071 if (!$cw) {
28072 continue;
28075 $l = 0;
28076 foreach ($u as $char) {
28077 if ($char == 173 || $this->_charDefined($cw, $char) || ($char > 1536 && $char < 1791) || ($char > 2304 && $char < 3455 )) { // Arabic and Indic
28078 $l++;
28079 } else {
28080 if ($l == 0 && $bsfctr == (count($this->backupSubsFont) - 1)) { // Not found even in last backup font
28081 $cont = mb_substr($writehtml_e, $start + 1);
28082 $writehtml_e = mb_substr($writehtml_e, 0, $start + 1);
28083 array_splice($writehtml_a, $writehtml_i + 1, 0, ['', $cont]);
28084 $this->subPos = $writehtml_i + 1;
28085 return 2;
28086 } else {
28087 break;
28091 if ($l > 0) {
28092 $patt = mb_substr($writehtml_e, $start, $l);
28093 if (preg_match("/(.*?)(" . preg_quote($patt, '/') . ")(.*)/u", $writehtml_e, $m)) {
28094 $writehtml_e = $m[1];
28095 array_splice($writehtml_a, $writehtml_i + 1, 0, ['span style="font-family: ' . $font . '"', $m[2], '/span', $m[3]]);
28096 $this->subPos = $writehtml_i + 3;
28097 return 4;
28102 unset($cw);
28103 return 0;
28106 function setHiEntitySubstitutions()
28108 $entarr = [
28109 'nbsp' => '160', 'iexcl' => '161', 'cent' => '162', 'pound' => '163', 'curren' => '164', 'yen' => '165', 'brvbar' => '166', 'sect' => '167',
28110 'uml' => '168', 'copy' => '169', 'ordf' => '170', 'laquo' => '171', 'not' => '172', 'shy' => '173', 'reg' => '174', 'macr' => '175',
28111 'deg' => '176', 'plusmn' => '177', 'sup2' => '178', 'sup3' => '179', 'acute' => '180', 'micro' => '181', 'para' => '182', 'middot' => '183',
28112 'cedil' => '184', 'sup1' => '185', 'ordm' => '186', 'raquo' => '187', 'frac14' => '188', 'frac12' => '189', 'frac34' => '190',
28113 'iquest' => '191', 'Agrave' => '192', 'Aacute' => '193', 'Acirc' => '194', 'Atilde' => '195', 'Auml' => '196', 'Aring' => '197',
28114 'AElig' => '198', 'Ccedil' => '199', 'Egrave' => '200', 'Eacute' => '201', 'Ecirc' => '202', 'Euml' => '203', 'Igrave' => '204',
28115 'Iacute' => '205', 'Icirc' => '206', 'Iuml' => '207', 'ETH' => '208', 'Ntilde' => '209', 'Ograve' => '210', 'Oacute' => '211',
28116 'Ocirc' => '212', 'Otilde' => '213', 'Ouml' => '214', 'times' => '215', 'Oslash' => '216', 'Ugrave' => '217', 'Uacute' => '218',
28117 'Ucirc' => '219', 'Uuml' => '220', 'Yacute' => '221', 'THORN' => '222', 'szlig' => '223', 'agrave' => '224', 'aacute' => '225',
28118 'acirc' => '226', 'atilde' => '227', 'auml' => '228', 'aring' => '229', 'aelig' => '230', 'ccedil' => '231', 'egrave' => '232',
28119 'eacute' => '233', 'ecirc' => '234', 'euml' => '235', 'igrave' => '236', 'iacute' => '237', 'icirc' => '238', 'iuml' => '239',
28120 'eth' => '240', 'ntilde' => '241', 'ograve' => '242', 'oacute' => '243', 'ocirc' => '244', 'otilde' => '245', 'ouml' => '246',
28121 'divide' => '247', 'oslash' => '248', 'ugrave' => '249', 'uacute' => '250', 'ucirc' => '251', 'uuml' => '252', 'yacute' => '253',
28122 'thorn' => '254', 'yuml' => '255', 'OElig' => '338', 'oelig' => '339', 'Scaron' => '352', 'scaron' => '353', 'Yuml' => '376',
28123 'fnof' => '402', 'circ' => '710', 'tilde' => '732', 'Alpha' => '913', 'Beta' => '914', 'Gamma' => '915', 'Delta' => '916',
28124 'Epsilon' => '917', 'Zeta' => '918', 'Eta' => '919', 'Theta' => '920', 'Iota' => '921', 'Kappa' => '922', 'Lambda' => '923',
28125 'Mu' => '924', 'Nu' => '925', 'Xi' => '926', 'Omicron' => '927', 'Pi' => '928', 'Rho' => '929', 'Sigma' => '931', 'Tau' => '932',
28126 'Upsilon' => '933', 'Phi' => '934', 'Chi' => '935', 'Psi' => '936', 'Omega' => '937', 'alpha' => '945', 'beta' => '946', 'gamma' => '947',
28127 'delta' => '948', 'epsilon' => '949', 'zeta' => '950', 'eta' => '951', 'theta' => '952', 'iota' => '953', 'kappa' => '954',
28128 'lambda' => '955', 'mu' => '956', 'nu' => '957', 'xi' => '958', 'omicron' => '959', 'pi' => '960', 'rho' => '961', 'sigmaf' => '962',
28129 'sigma' => '963', 'tau' => '964', 'upsilon' => '965', 'phi' => '966', 'chi' => '967', 'psi' => '968', 'omega' => '969',
28130 'thetasym' => '977', 'upsih' => '978', 'piv' => '982', 'ensp' => '8194', 'emsp' => '8195', 'thinsp' => '8201', 'zwnj' => '8204',
28131 'zwj' => '8205', 'lrm' => '8206', 'rlm' => '8207', 'ndash' => '8211', 'mdash' => '8212', 'lsquo' => '8216', 'rsquo' => '8217',
28132 'sbquo' => '8218', 'ldquo' => '8220', 'rdquo' => '8221', 'bdquo' => '8222', 'dagger' => '8224', 'Dagger' => '8225', 'bull' => '8226',
28133 'hellip' => '8230', 'permil' => '8240', 'prime' => '8242', 'Prime' => '8243', 'lsaquo' => '8249', 'rsaquo' => '8250', 'oline' => '8254',
28134 'frasl' => '8260', 'euro' => '8364', 'image' => '8465', 'weierp' => '8472', 'real' => '8476', 'trade' => '8482', 'alefsym' => '8501',
28135 'larr' => '8592', 'uarr' => '8593', 'rarr' => '8594', 'darr' => '8595', 'harr' => '8596', 'crarr' => '8629', 'lArr' => '8656',
28136 'uArr' => '8657', 'rArr' => '8658', 'dArr' => '8659', 'hArr' => '8660', 'forall' => '8704', 'part' => '8706', 'exist' => '8707',
28137 'empty' => '8709', 'nabla' => '8711', 'isin' => '8712', 'notin' => '8713', 'ni' => '8715', 'prod' => '8719', 'sum' => '8721',
28138 'minus' => '8722', 'lowast' => '8727', 'radic' => '8730', 'prop' => '8733', 'infin' => '8734', 'ang' => '8736', 'and' => '8743',
28139 'or' => '8744', 'cap' => '8745', 'cup' => '8746', 'int' => '8747', 'there4' => '8756', 'sim' => '8764', 'cong' => '8773',
28140 'asymp' => '8776', 'ne' => '8800', 'equiv' => '8801', 'le' => '8804', 'ge' => '8805', 'sub' => '8834', 'sup' => '8835', 'nsub' => '8836',
28141 'sube' => '8838', 'supe' => '8839', 'oplus' => '8853', 'otimes' => '8855', 'perp' => '8869', 'sdot' => '8901', 'lceil' => '8968',
28142 'rceil' => '8969', 'lfloor' => '8970', 'rfloor' => '8971', 'lang' => '9001', 'rang' => '9002', 'loz' => '9674', 'spades' => '9824',
28143 'clubs' => '9827', 'hearts' => '9829', 'diams' => '9830',
28145 foreach ($entarr as $key => $val) {
28146 $this->entsearch[] = '&' . $key . ';';
28147 $this->entsubstitute[] = UtfString::code2utf($val);
28151 function SubstituteHiEntities($html)
28153 // converts html_entities > ASCII 127 to unicode
28154 // Leaves in particular &lt; to distinguish from tag marker
28155 if (count($this->entsearch)) {
28156 $html = str_replace($this->entsearch, $this->entsubstitute, $html);
28158 return $html;
28161 // Edited v1.2 Pass by reference; option to continue if invalid UTF-8 chars
28162 function is_utf8(&$string)
28164 if ($string === mb_convert_encoding(mb_convert_encoding($string, "UTF-32", "UTF-8"), "UTF-8", "UTF-32")) {
28165 return true;
28166 } else {
28167 if ($this->ignore_invalid_utf8) {
28168 $string = mb_convert_encoding(mb_convert_encoding($string, "UTF-32", "UTF-8"), "UTF-8", "UTF-32");
28169 return true;
28170 } else {
28171 return false;
28176 function purify_utf8($html, $lo = true)
28178 // For HTML
28179 // Checks string is valid UTF-8 encoded
28180 // converts html_entities > ASCII 127 to UTF-8
28181 // Only exception - leaves low ASCII entities e.g. &lt; &amp; etc.
28182 // Leaves in particular &lt; to distinguish from tag marker
28183 if (!$this->is_utf8($html)) {
28184 while (mb_convert_encoding(mb_convert_encoding($html, "UTF-32", "UTF-8"), "UTF-8", "UTF-32") != $html) {
28185 $a = iconv('UTF-8', 'UTF-8', $html);
28186 // echo ($a);
28187 $pos = $start = strlen($a);
28188 $err = '';
28189 while (ord(substr($html, $pos, 1)) > 128) {
28190 $err .= '[[#' . ord(substr($html, $pos, 1)) . ']]';
28191 $pos++;
28193 $this->logger->error($err, ['context' => LogContext::UTF8]);
28194 $html = substr($html, $pos);
28196 throw new \Mpdf\MpdfException("HTML contains invalid UTF-8 character(s). See log for further details");
28198 $html = preg_replace("/\r/", "", $html);
28200 // converts html_entities > ASCII 127 to UTF-8
28201 // Leaves in particular &lt; to distinguish from tag marker
28202 $html = $this->SubstituteHiEntities($html);
28204 // converts all &#nnn; or &#xHHH; to UTF-8 multibyte
28205 // If $lo==true then includes ASCII < 128
28206 $html = UtfString::strcode2utf($html, $lo);
28207 return ($html);
28210 function purify_utf8_text($txt)
28212 // For TEXT
28213 // Make sure UTF-8 string of characters
28214 if (!$this->is_utf8($txt)) {
28215 throw new \Mpdf\MpdfException("Text contains invalid UTF-8 character(s)");
28218 $txt = preg_replace("/\r/", "", $txt);
28220 return ($txt);
28223 function all_entities_to_utf8($txt)
28225 // converts txt_entities > ASCII 127 to UTF-8
28226 // Leaves in particular &lt; to distinguish from tag marker
28227 $txt = $this->SubstituteHiEntities($txt);
28229 // converts all &#nnn; or &#xHHH; to UTF-8 multibyte
28230 $txt = UtfString::strcode2utf($txt);
28232 $txt = $this->lesser_entity_decode($txt);
28233 return ($txt);
28236 // ====================================================
28237 /* -- BARCODES -- */
28238 // UPC/EAN barcode
28239 // EAN13, EAN8, UPCA, UPCE, ISBN, ISSN
28240 // Accepts 12 or 13 digits with or without - hyphens
28241 function WriteBarcode($code, $showtext = 1, $x = '', $y = '', $size = 1, $border = 0, $paddingL = 1, $paddingR = 1, $paddingT = 2, $paddingB = 2, $height = 1, $bgcol = false, $col = false, $btype = 'ISBN', $supplement = '0', $supplement_code = '', $k = 1)
28243 if (empty($code)) {
28244 return;
28246 $codestr = $code;
28247 $code = preg_replace('/\-/', '', $code);
28249 $this->barcode = new Barcode();
28250 if ($btype == 'ISSN' || $btype == 'ISBN') {
28251 $arrcode = $this->barcode->getBarcodeArray($code, 'EAN13');
28252 } else {
28253 $arrcode = $this->barcode->getBarcodeArray($code, $btype);
28256 if ($arrcode === false) {
28257 throw new \Mpdf\MpdfException('Error in barcode string: ' . $codestr);
28259 if ((($btype == 'EAN13' || $btype == 'ISBN' || $btype == 'ISSN') && strlen($code) == 12) || ($btype == 'UPCA' && strlen($code) == 11) || ($btype == 'UPCE' && strlen($code) == 11) || ($btype == 'EAN8' && strlen($code) == 7)) {
28260 $code .= $arrcode['checkdigit'];
28261 if (stristr($codestr, '-')) {
28262 $codestr .= '-' . $arrcode['checkdigit'];
28263 } else {
28264 $codestr .= $arrcode['checkdigit'];
28267 if ($btype == 'ISBN') {
28268 $codestr = 'ISBN ' . $codestr;
28270 if ($btype == 'ISSN') {
28271 $codestr = 'ISSN ' . $codestr;
28274 if (empty($x)) {
28275 $x = $this->x;
28277 if (empty($y)) {
28278 $y = $this->y;
28280 // set foreground color
28281 $prevDrawColor = $this->DrawColor;
28282 $prevTextColor = $this->TextColor;
28283 $prevFillColor = $this->FillColor;
28284 $lw = $this->LineWidth;
28285 $this->SetLineWidth(0.01);
28287 $size /= $k; // in case resized in a table
28289 $xres = $arrcode['nom-X'] * $size;
28290 $llm = $arrcode['lightmL'] * $arrcode['nom-X'] * $size; // Left Light margin
28291 $rlm = $arrcode['lightmR'] * $arrcode['nom-X'] * $size; // Right Light margin
28293 $bcw = ($arrcode["maxw"] * $xres); // Barcode width = Should always be 31.35mm * $size
28295 $fbw = $bcw + $llm + $rlm; // Full barcode width incl. light margins
28296 $ow = $fbw + $paddingL + $paddingR; // Full overall width incl. user-defined padding
28298 $fbwi = $fbw - 2; // Full barcode width incl. light margins - 2mm - for isbn string
28299 // cf. http://www.gs1uk.org/downloads/bar_code/Bar coding getting it right.pdf
28300 $num_height = 3 * $size; // Height of numerals
28301 $fbh = $arrcode['nom-H'] * $size * $height; // Full barcode height incl. numerals
28302 $bch = $fbh - (1.5 * $size); // Barcode height of bars (3mm for numerals)
28304 if (($btype == 'EAN13' && $showtext) || $btype == 'ISSN' || $btype == 'ISBN') { // Add height for ISBN string + margin from top of bars
28305 $tisbnm = 1.5 * $size; // Top margin between isbn (if shown) & bars
28306 $codestr_fontsize = 2.1 * $size;
28307 $paddingT += $codestr_fontsize + $tisbnm;
28309 $oh = $fbh + $paddingT + $paddingB; // Full overall height incl. user-defined padding
28310 // PRINT border background color
28311 $xpos = $x;
28312 $ypos = $y;
28313 if ($col) {
28314 $this->SetDColor($col);
28315 $this->SetTColor($col);
28316 } else {
28317 $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
28318 $this->SetTColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
28320 if ($bgcol) {
28321 $this->SetFColor($bgcol);
28322 } else {
28323 $this->SetFColor($this->colorConverter->convert(255, $this->PDFAXwarnings));
28325 if (!$bgcol && !$col) { // fn. called directly - not via HTML
28326 if ($border) {
28327 $fillb = 'DF';
28328 } else {
28329 $fillb = 'F';
28331 $this->Rect($xpos, $ypos, $ow, $oh, $fillb);
28335 // PRINT BARS
28336 $xpos = $x + $paddingL + $llm;
28337 $ypos = $y + $paddingT;
28338 if ($col) {
28339 $this->SetFColor($col);
28340 } else {
28341 $this->SetFColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
28343 if ($arrcode !== false) {
28344 foreach ($arrcode["bcode"] as $v) {
28345 $bw = ($v["w"] * $xres);
28346 if ($v["t"]) {
28347 // draw a vertical bar
28348 $this->Rect($xpos, $ypos, $bw, $bch, 'F');
28350 $xpos += $bw;
28355 // print text
28356 $prevFontFamily = $this->FontFamily;
28357 $prevFontStyle = $this->FontStyle;
28358 $prevFontSizePt = $this->FontSizePt;
28360 // ISBN string
28361 if (($btype == 'EAN13' && $showtext) || $btype == 'ISBN' || $btype == 'ISSN') {
28362 if ($this->onlyCoreFonts) {
28363 $this->SetFont('chelvetica');
28364 } else {
28365 $this->SetFont('sans');
28368 if ($bgcol) {
28369 $this->SetFColor($bgcol);
28370 } else {
28371 $this->SetFColor($this->colorConverter->convert(255, $this->PDFAXwarnings));
28373 $this->x = $x + $paddingL + 1; // 1mm left margin (cf. $fbwi above)
28374 // max width is $fbwi
28375 $loop = 0;
28376 while ($loop == 0) {
28377 $this->SetFontSize($codestr_fontsize * 1.4 * Mpdf::SCALE, false); // don't write
28378 $sz = $this->GetStringWidth($codestr);
28379 if ($sz > $fbwi) {
28380 $codestr_fontsize -= 0.1;
28381 } else {
28382 $loop ++;
28385 $this->SetFont('', '', $codestr_fontsize * 1.4 * Mpdf::SCALE, true, true); // * 1.4 because font height is only 7/10 of given mm
28386 // WORD SPACING
28387 if ($fbwi > $sz) {
28388 $xtra = $fbwi - $sz;
28389 $charspacing = $xtra / (strlen($codestr) - 1);
28390 if ($charspacing) {
28391 $this->_out(sprintf('BT %.3F Tc ET', $charspacing * Mpdf::SCALE));
28394 $this->y = $y + $paddingT - ($codestr_fontsize ) - $tisbnm;
28395 $this->Cell($fbw, $codestr_fontsize, $codestr);
28396 if ($charspacing) {
28397 $this->_out('BT 0 Tc ET');
28402 // Bottom NUMERALS
28403 // mPDF 5.7.4
28404 if ($this->onlyCoreFonts) {
28405 $this->SetFont('ccourier');
28406 $fh = 1.3;
28407 } else {
28408 $this->SetFont('ocrb');
28409 $fh = 1.06;
28411 $charRO = '';
28412 if ($btype == 'EAN13' || $btype == 'ISBN' || $btype == 'ISSN') {
28413 $outerfontsize = 3; // Inner fontsize = 3
28414 $outerp = $xres * 4;
28415 $innerp = $xres * 2.5;
28416 $textw = ($bcw * 0.5) - $outerp - $innerp;
28417 $chars = 6; // number of numerals in each half
28418 $charLO = substr($code, 0, 1); // Left Outer
28419 $charLI = substr($code, 1, 6); // Left Inner
28420 $charRI = substr($code, 7, 6); // Right Inner
28421 if (!$supplement) {
28422 $charRO = '>'; // Right Outer
28424 } elseif ($btype == 'UPCA') {
28425 $outerfontsize = 2.3; // Inner fontsize = 3
28426 $outerp = $xres * 10;
28427 $innerp = $xres * 2.5;
28428 $textw = ($bcw * 0.5) - $outerp - $innerp;
28429 $chars = 5;
28430 $charLO = substr($code, 0, 1); // Left Outer
28431 $charLI = substr($code, 1, 5); // Left Inner
28432 $charRI = substr($code, 6, 5); // Right Inner
28433 $charRO = substr($code, 11, 1); // Right Outer
28434 } elseif ($btype == 'UPCE') {
28435 $outerfontsize = 2.3; // Inner fontsize = 3
28436 $outerp = $xres * 4;
28437 $innerp = 0;
28438 $textw = ($bcw * 0.5) - $outerp - $innerp;
28439 $chars = 3;
28440 $upce_code = $arrcode['code'];
28441 $charLO = substr($code, 0, 1); // Left Outer
28442 $charLI = substr($upce_code, 0, 3); // Left Inner
28443 $charRI = substr($upce_code, 3, 3); // Right Inner
28444 $charRO = substr($code, 11, 1); // Right Outer
28445 } elseif ($btype == 'EAN8') {
28446 $outerfontsize = 3; // Inner fontsize = 3
28447 $outerp = $xres * 4;
28448 $innerp = $xres * 2.5;
28449 $textw = ($bcw * 0.5) - $outerp - $innerp;
28450 $chars = 4;
28451 $charLO = '<'; // Left Outer
28452 $charLI = substr($code, 0, 4); // Left Inner
28453 $charRI = substr($code, 4, 4); // Right Inner
28454 if (!$supplement) {
28455 $charRO = '>'; // Right Outer
28459 $this->SetFontSize(($outerfontsize / 3) * 3 * $fh * $size * Mpdf::SCALE); // 3mm numerals (FontSize is larger to account for space above/below characters)
28461 if (!$this->usingCoreFont) {
28462 $cw = $this->_getCharWidth($this->CurrentFont['cw'], 32) * 3 * $fh * $size / 1000;
28463 } // character width at 3mm
28464 else {
28465 $cw = 600 * 3 * $fh * $size / 1000;
28466 } // mPDF 5.7.4
28467 // Outer left character
28468 $y_text = $y + $paddingT + $bch - ($num_height / 2);
28469 $y_text_outer = $y + $paddingT + $bch - ($num_height * ($outerfontsize / 3) / 2);
28471 $this->x = $x + $paddingL - ($cw * ($outerfontsize / 3) * 0.1); // 0.1 is correction as char does not fill full width;
28472 $this->y = $y_text_outer;
28473 $this->Cell($cw, $num_height, $charLO);
28475 // WORD SPACING for inner chars
28476 $xtra = $textw - ($cw * $chars);
28477 $charspacing = $xtra / ($chars - 1);
28478 if ($charspacing) {
28479 $this->_out(sprintf('BT %.3F Tc ET', $charspacing * Mpdf::SCALE));
28482 if ($bgcol) {
28483 $this->SetFColor($bgcol);
28484 } else {
28485 $this->SetFColor($this->colorConverter->convert(255, $this->PDFAXwarnings));
28488 $this->SetFontSize(3 * $fh * $size * Mpdf::SCALE); // 3mm numerals (FontSize is larger to account for space above/below characters)
28489 // Inner left half characters
28490 $this->x = $x + $paddingL + $llm + $outerp;
28491 $this->y = $y_text;
28492 $this->Cell($textw, $num_height, $charLI, 0, 0, '', 1);
28494 // Inner right half characters
28495 $this->x = $x + $paddingL + $llm + ($bcw * 0.5) + $innerp;
28496 $this->y = $y_text;
28497 $this->Cell($textw, $num_height, $charRI, 0, 0, '', 1);
28499 if ($charspacing) {
28500 $this->_out('BT 0 Tc ET');
28503 // Outer Right character
28504 $this->SetFontSize(($outerfontsize / 3) * 3 * $fh * $size * Mpdf::SCALE); // 3mm numerals (FontSize is larger to account for space above/below characters)
28506 $this->x = $x + $paddingL + $llm + $bcw + $rlm - ($cw * ($outerfontsize / 3) * 0.9); // 0.9 is correction as char does not fill full width
28507 $this->y = $y_text_outer;
28508 $this->Cell($cw * ($outerfontsize / 3), $num_height, $charRO, 0, 0, 'R');
28510 if ($supplement) { // EAN-2 or -5 Supplement
28511 // PRINT BARS
28512 $supparrcode = $this->barcode->getBarcodeArray($supplement_code, 'EAN' . $supplement);
28513 if ($supparrcode === false) {
28514 throw new \Mpdf\MpdfException('Error in barcode string (supplement): ' . $codestr . ' ' . $supplement_code);
28516 if (strlen($supplement_code) != $supplement) {
28517 throw new \Mpdf\MpdfException('Barcode supplement incorrect: ' . $supplement_code);
28519 $llm = $fbw - (($arrcode['lightmR'] - $supparrcode['sepM']) * $arrcode['nom-X'] * $size); // Left Light margin
28520 $rlm = $arrcode['lightmR'] * $arrcode['nom-X'] * $size; // Right Light margin
28522 $bcw = ($supparrcode["maxw"] * $xres); // Barcode width = Should always be 31.35mm * $size
28524 $fbw = $bcw + $llm + $rlm; // Full barcode width incl. light margins
28525 $ow = $fbw + $paddingL + $paddingR; // Full overall width incl. user-defined padding
28526 $bch = $fbh - (1.5 * $size) - ($num_height + 0.5); // Barcode height of bars (3mm for numerals)
28528 $xpos = $x + $paddingL + $llm;
28529 $ypos = $y + $paddingT + $num_height + 0.5;
28530 if ($col) {
28531 $this->SetFColor($col);
28532 } else {
28533 $this->SetFColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
28535 if ($supparrcode !== false) {
28536 foreach ($supparrcode["bcode"] as $v) {
28537 $bw = ($v["w"] * $xres);
28538 if ($v["t"]) {
28539 // draw a vertical bar
28540 $this->Rect($xpos, $ypos, $bw, $bch, 'F');
28542 $xpos += $bw;
28546 // Characters
28547 if ($bgcol) {
28548 $this->SetFColor($bgcol);
28549 } else {
28550 $this->SetFColor($this->colorConverter->convert(255, $this->PDFAXwarnings));
28552 $this->SetFontSize(3 * $fh * $size * Mpdf::SCALE); // 3mm numerals (FontSize is larger to account for space above/below characters)
28553 $this->x = $x + $paddingL + $llm;
28554 $this->y = $y + $paddingT;
28555 $this->Cell($bcw, $num_height, $supplement_code, 0, 0, 'C');
28557 // Outer Right character (light margin)
28558 $this->SetFontSize(($outerfontsize / 3) * 3 * $fh * $size * Mpdf::SCALE); // 3mm numerals (FontSize is larger to account for space above/below characters)
28559 $this->x = $x + $paddingL + $llm + $bcw + $rlm - ($cw * 0.9); // 0.9 is correction as char does not fill full width
28560 $this->y = $y + $paddingT;
28561 $this->Cell($cw * ($outerfontsize / 3), $num_height, '>', 0, 0, 'R');
28564 // Restore **************
28565 $this->SetFont($prevFontFamily, $prevFontStyle, $prevFontSizePt);
28566 $this->DrawColor = $prevDrawColor;
28567 $this->TextColor = $prevTextColor;
28568 $this->FillColor = $prevFillColor;
28569 $this->SetLineWidth($lw);
28570 $this->SetY($y);
28573 // ====================================================
28574 // POSTAL and OTHER barcodes
28575 function WriteBarcode2($code, $x = '', $y = '', $size = 1, $height = 1, $bgcol = false, $col = false, $btype = 'IMB', $print_ratio = '', $k = 1)
28577 if (empty($code)) {
28578 return;
28581 $this->barcode = new Barcode();
28582 $arrcode = $this->barcode->getBarcodeArray($code, $btype, $print_ratio);
28584 if (empty($x)) {
28585 $x = $this->x;
28587 if (empty($y)) {
28588 $y = $this->y;
28590 $prevDrawColor = $this->DrawColor;
28591 $prevTextColor = $this->TextColor;
28592 $prevFillColor = $this->FillColor;
28593 $lw = $this->LineWidth;
28594 $this->SetLineWidth(0.01);
28595 $size /= $k; // in case resized in a table
28596 $xres = $arrcode['nom-X'] * $size;
28598 if ($btype == 'IMB' || $btype == 'RM4SCC' || $btype == 'KIX' || $btype == 'POSTNET' || $btype == 'PLANET') {
28599 $llm = $arrcode['quietL'] / $k; // Left Quiet margin
28600 $rlm = $arrcode['quietR'] / $k; // Right Quiet margin
28601 $tlm = $blm = $arrcode['quietTB'] / $k;
28602 $height = 1; // Overrides
28603 } elseif (in_array($btype, ['C128A', 'C128B', 'C128C', 'EAN128A', 'EAN128B', 'EAN128C', 'C39', 'C39+', 'C39E', 'C39E+', 'S25', 'S25+', 'I25', 'I25+', 'I25B', 'I25B+', 'C93', 'MSI', 'MSI+', 'CODABAR', 'CODE11'])) {
28604 $llm = $arrcode['lightmL'] * $xres; // Left Quiet margin
28605 $rlm = $arrcode['lightmR'] * $xres; // Right Quiet margin
28606 $tlm = $blm = $arrcode['lightTB'] * $xres * $height;
28610 $bcw = ($arrcode["maxw"] * $xres);
28611 $fbw = $bcw + $llm + $rlm; // Full barcode width incl. light margins
28613 $bch = ($arrcode["nom-H"] * $size * $height);
28614 $fbh = $bch + $tlm + $blm; // Full barcode height
28615 // PRINT border background color
28616 $xpos = $x;
28617 $ypos = $y;
28618 if ($col) {
28619 $this->SetDColor($col);
28620 $this->SetTColor($col);
28621 } else {
28622 $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
28623 $this->SetTColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
28625 if ($bgcol) {
28626 $this->SetFColor($bgcol);
28627 } else {
28628 $this->SetFColor($this->colorConverter->convert(255, $this->PDFAXwarnings));
28631 // PRINT BARS
28632 if ($col) {
28633 $this->SetFColor($col);
28634 } else {
28635 $this->SetFColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
28637 $xpos = $x + $llm;
28639 if ($arrcode !== false) {
28640 foreach ($arrcode["bcode"] as $v) {
28641 $bw = ($v["w"] * $xres);
28642 if ($v["t"]) {
28643 $ypos = $y + $tlm + ($bch * $v['p'] / $arrcode['maxh']);
28644 $this->Rect($xpos, $ypos, $bw, ($v['h'] * $bch / $arrcode['maxh']), 'F');
28646 $xpos += $bw;
28650 // PRINT BEARER BARS
28651 if ($btype == 'I25B' || $btype == 'I25B+') {
28652 $this->Rect($x, $y, $fbw, ($arrcode['lightTB'] * $xres * $height), 'F');
28653 $this->Rect($x, $y + $tlm + $bch, $fbw, ($arrcode['lightTB'] * $xres * $height), 'F');
28656 // Restore **************
28657 $this->DrawColor = $prevDrawColor;
28658 $this->TextColor = $prevTextColor;
28659 $this->FillColor = $prevFillColor;
28660 $this->SetLineWidth($lw);
28661 $this->SetY($y);
28664 /* -- END BARCODES -- */
28666 // ====================================================
28667 // ====================================================
28669 function StartTransform($returnstring = false)
28671 if ($returnstring) {
28672 return('q');
28673 } else {
28674 $this->_out('q');
28678 function StopTransform($returnstring = false)
28680 if ($returnstring) {
28681 return('Q');
28682 } else {
28683 $this->_out('Q');
28687 function transformScale($s_x, $s_y, $x = '', $y = '', $returnstring = false)
28689 if ($x === '') {
28690 $x = $this->x;
28692 if ($y === '') {
28693 $y = $this->y;
28695 if (($s_x == 0) or ( $s_y == 0)) {
28696 throw new \Mpdf\MpdfException('Please do not use values equal to zero for scaling');
28698 $y = ($this->h - $y) * Mpdf::SCALE;
28699 $x *= Mpdf::SCALE;
28700 // calculate elements of transformation matrix
28701 $s_x /= 100;
28702 $s_y /= 100;
28703 $tm = [];
28704 $tm[0] = $s_x;
28705 $tm[1] = 0;
28706 $tm[2] = 0;
28707 $tm[3] = $s_y;
28708 $tm[4] = $x * (1 - $s_x);
28709 $tm[5] = $y * (1 - $s_y);
28710 // scale the coordinate system
28711 if ($returnstring) {
28712 return($this->_transform($tm, true));
28713 } else {
28714 $this->_transform($tm);
28718 function transformTranslate($t_x, $t_y, $returnstring = false)
28720 // calculate elements of transformation matrix
28721 $tm = [];
28722 $tm[0] = 1;
28723 $tm[1] = 0;
28724 $tm[2] = 0;
28725 $tm[3] = 1;
28726 $tm[4] = $t_x * Mpdf::SCALE;
28727 $tm[5] = -$t_y * Mpdf::SCALE;
28728 // translate the coordinate system
28729 if ($returnstring) {
28730 return($this->_transform($tm, true));
28731 } else {
28732 $this->_transform($tm);
28736 function transformRotate($angle, $x = '', $y = '', $returnstring = false)
28738 if ($x === '') {
28739 $x = $this->x;
28741 if ($y === '') {
28742 $y = $this->y;
28744 $angle = -$angle;
28745 $y = ($this->h - $y) * Mpdf::SCALE;
28746 $x *= Mpdf::SCALE;
28747 // calculate elements of transformation matrix
28748 $tm = [];
28749 $tm[0] = cos(deg2rad($angle));
28750 $tm[1] = sin(deg2rad($angle));
28751 $tm[2] = -$tm[1];
28752 $tm[3] = $tm[0];
28753 $tm[4] = $x + $tm[1] * $y - $tm[0] * $x;
28754 $tm[5] = $y - $tm[0] * $y - $tm[1] * $x;
28755 // rotate the coordinate system around ($x,$y)
28756 if ($returnstring) {
28757 return($this->_transform($tm, true));
28758 } else {
28759 $this->_transform($tm);
28763 // mPDF 5.7.3 TRANSFORMS
28764 function transformSkew($angle_x, $angle_y, $x = '', $y = '', $returnstring = false)
28766 if ($x === '') {
28767 $x = $this->x;
28769 if ($y === '') {
28770 $y = $this->y;
28772 $angle_x = -$angle_x;
28773 $angle_y = -$angle_y;
28774 $x *= Mpdf::SCALE;
28775 $y = ($this->h - $y) * Mpdf::SCALE;
28776 // calculate elements of transformation matrix
28777 $tm = [];
28778 $tm[0] = 1;
28779 $tm[1] = tan(deg2rad($angle_y));
28780 $tm[2] = tan(deg2rad($angle_x));
28781 $tm[3] = 1;
28782 $tm[4] = -$tm[2] * $y;
28783 $tm[5] = -$tm[1] * $x;
28784 // skew the coordinate system
28785 if ($returnstring) {
28786 return($this->_transform($tm, true));
28787 } else {
28788 $this->_transform($tm);
28792 function _transform($tm, $returnstring = false)
28794 if ($returnstring) {
28795 return(sprintf('%.4F %.4F %.4F %.4F %.4F %.4F cm', $tm[0], $tm[1], $tm[2], $tm[3], $tm[4], $tm[5]));
28796 } else {
28797 $this->_out(sprintf('%.4F %.4F %.4F %.4F %.4F %.4F cm', $tm[0], $tm[1], $tm[2], $tm[3], $tm[4], $tm[5]));
28801 // AUTOFONT =========================
28802 function markScriptToLang($html)
28804 if ($this->onlyCoreFonts) {
28805 return $html;
28808 $n = '';
28809 $a = preg_split('/<(.*?)>/ms', $html, -1, PREG_SPLIT_DELIM_CAPTURE);
28810 foreach ($a as $i => $e) {
28811 if ($i % 2 == 0) {
28812 // ignore if in Textarea
28813 if ($i > 0 && strtolower(substr($a[$i - 1], 1, 8)) == 'textarea') {
28814 $a[$i] = $e;
28815 continue;
28817 $e = UtfString::strcode2utf($e);
28818 $e = $this->lesser_entity_decode($e);
28820 $earr = $this->UTF8StringToArray($e, false);
28822 $scriptblock = 0;
28823 $scriptblocks = [];
28824 $scriptblocks[0] = 0;
28825 $chardata = [];
28826 $subchunk = 0;
28827 $charctr = 0;
28828 foreach ($earr as $char) {
28829 $ucd_record = Ucdn::get_ucd_record($char);
28830 $sbl = $ucd_record[6];
28832 if ($sbl && $sbl != 40 && $sbl != 102) {
28833 if ($scriptblock == 0) {
28834 $scriptblock = $sbl;
28835 $scriptblocks[$subchunk] = $scriptblock;
28836 } elseif ($scriptblock > 0 && $scriptblock != $sbl) {
28837 // NEW (non-common) Script encountered in this chunk.
28838 // Start a new subchunk
28839 $subchunk++;
28840 $scriptblock = $sbl;
28841 $charctr = 0;
28842 $scriptblocks[$subchunk] = $scriptblock;
28846 $chardata[$subchunk][$charctr]['script'] = $sbl;
28847 $chardata[$subchunk][$charctr]['uni'] = $char;
28848 $charctr++;
28851 // If scriptblock[x] = common & non-baseScript
28852 // and scriptblock[x+1] = baseScript
28853 // Move common script from end of x to start of x+1
28854 for ($sch = 0; $sch < $subchunk; $sch++) {
28855 if ($scriptblocks[$sch] > 0 && $scriptblocks[$sch] != $this->baseScript && $scriptblocks[$sch + 1] == $this->baseScript) {
28856 $end = count($chardata[$sch]) - 1;
28857 while ($chardata[$sch][$end]['script'] == 0 && $end > 1) { // common script
28858 $tmp = array_pop($chardata[$sch]);
28859 array_unshift($chardata[$sch + 1], $tmp);
28860 $end--;
28865 $o = '';
28866 for ($sch = 0; $sch <= $subchunk; $sch++) {
28867 if (isset($chardata[$sch])) {
28868 $s = '';
28869 for ($j = 0; $j < count($chardata[$sch]); $j++) {
28870 $s .= UtfString::code2utf($chardata[$sch][$j]['uni']);
28872 // ZZZ99 Undo lesser_entity_decode as above - but only for <>&
28873 $s = str_replace("&", "&amp;", $s);
28874 $s = str_replace("<", "&lt;", $s);
28875 $s = str_replace(">", "&gt;", $s);
28877 // Check Vietnamese if Latin script - even if Basescript
28878 if ($scriptblocks[$sch] == Ucdn::SCRIPT_LATIN && $this->autoVietnamese && preg_match("/([" . $this->scriptToLanguage->getLanguageDelimiters('viet') . "])/u", $s)) {
28879 $o .= '<span lang="vi" class="lang_vi">' . $s . '</span>';
28880 } // Check Arabic for different languages if Arabic script - even if Basescript
28881 elseif ($scriptblocks[$sch] == Ucdn::SCRIPT_ARABIC && $this->autoArabic) {
28882 if (preg_match("/[" . $this->scriptToLanguage->getLanguageDelimiters('sindhi') . "]/u", $s)) {
28883 $o .= '<span lang="sd" class="lang_sd">' . $s . '</span>';
28884 } elseif (preg_match("/[" . $this->scriptToLanguage->getLanguageDelimiters('urdu') . "]/u", $s)) {
28885 $o .= '<span lang="ur" class="lang_ur">' . $s . '</span>';
28886 } elseif (preg_match("/[" . $this->scriptToLanguage->getLanguageDelimiters('pashto') . "]/u", $s)) {
28887 $o .= '<span lang="ps" class="lang_ps">' . $s . '</span>';
28888 } elseif (preg_match("/[" . $this->scriptToLanguage->getLanguageDelimiters('persian') . "]/u", $s)) {
28889 $o .= '<span lang="fa" class="lang_fa">' . $s . '</span>';
28890 } elseif ($this->baseScript != Ucdn::SCRIPT_ARABIC && $this->scriptToLanguage->getLanguageByScript($scriptblocks[$sch])) {
28891 $o .= '<span lang="' . $this->scriptToLanguage->getLanguageByScript($scriptblocks[$sch]) . '" class="lang_' . $this->scriptToLanguage->getLanguageByScript($scriptblocks[$sch]) . '">' . $s . '</span>';
28892 } else {
28893 // Just output chars
28894 $o .= $s;
28896 } // Identify Script block if not Basescript, and mark up as language
28897 elseif ($scriptblocks[$sch] > 0 && $scriptblocks[$sch] != $this->baseScript && $this->scriptToLanguage->getLanguageByScript($scriptblocks[$sch])) {
28898 // Encase in <span>
28899 $o .= '<span lang="' . $this->scriptToLanguage->getLanguageByScript($scriptblocks[$sch]) . '" class="lang_' . $this->scriptToLanguage->getLanguageByScript($scriptblocks[$sch]) . '">';
28900 $o .= $s;
28901 $o .= '</span>';
28902 } else {
28903 // Just output chars
28904 $o .= $s;
28909 $a[$i] = $o;
28910 } else {
28911 $a[$i] = '<' . $e . '>';
28914 $n = implode('', $a);
28916 return $n;
28919 // ===========================
28920 // Functions
28921 // Call-back function Used for usort in fn _tableWrite
28923 function _cmpdom($a, $b)
28925 return ($a["dom"] < $b["dom"]) ? -1 : 1;
28928 function mb_strrev($str, $enc = 'utf-8')
28930 $ch = [];
28931 $ch = preg_split('//u', $str);
28932 $revch = array_reverse($ch);
28933 return implode('', $revch);
28936 /* -- COLUMNS -- */
28938 // Callback function from function printcolumnbuffer in mpdf
28939 function columnAdjustAdd($type, $k, $xadj, $yadj, $a, $b, $c = 0, $d = 0, $e = 0, $f = 0)
28941 if ($type == 'Td') { // xpos,ypos
28942 $a += ($xadj * $k);
28943 $b -= ($yadj * $k);
28944 return 'BT ' . sprintf('%.3F %.3F', $a, $b) . ' Td';
28945 } elseif ($type == 're') { // xpos,ypos,width,height
28946 $a += ($xadj * $k);
28947 $b -= ($yadj * $k);
28948 return sprintf('%.3F %.3F %.3F %.3F', $a, $b, $c, $d) . ' re';
28949 } elseif ($type == 'l') { // xpos,ypos,x2pos,y2pos
28950 $a += ($xadj * $k);
28951 $b -= ($yadj * $k);
28952 return sprintf('%.3F %.3F l', $a, $b);
28953 } elseif ($type == 'img') { // width,height,xpos,ypos
28954 $c += ($xadj * $k);
28955 $d -= ($yadj * $k);
28956 return sprintf('q %.3F 0 0 %.3F %.3F %.3F', $a, $b, $c, $d) . ' cm /' . $e;
28957 } elseif ($type == 'draw') { // xpos,ypos
28958 $a += ($xadj * $k);
28959 $b -= ($yadj * $k);
28960 return sprintf('%.3F %.3F m', $a, $b);
28961 } elseif ($type == 'bezier') { // xpos,ypos,x2pos,y2pos,x3pos,y3pos
28962 $a += ($xadj * $k);
28963 $b -= ($yadj * $k);
28964 $c += ($xadj * $k);
28965 $d -= ($yadj * $k);
28966 $e += ($xadj * $k);
28967 $f -= ($yadj * $k);
28968 return sprintf('%.3F %.3F %.3F %.3F %.3F %.3F', $a, $b, $c, $d, $e, $f) . ' c';
28972 /* -- END COLUMNS -- */
28974 // mPDF 5.7.3 TRANSFORMS
28975 function ConvertAngle($s, $makepositive = true)
28977 if (preg_match('/([\-]*[0-9\.]+)(deg|grad|rad)/i', $s, $m)) {
28978 $angle = $m[1] + 0;
28979 if (strtolower($m[2]) == 'deg') {
28980 $angle = $angle;
28981 } elseif (strtolower($m[2]) == 'grad') {
28982 $angle *= (360 / 400);
28983 } elseif (strtolower($m[2]) == 'rad') {
28984 $angle = rad2deg($angle);
28986 while ($angle >= 360) {
28987 $angle -= 360;
28989 while ($angle <= -360) {
28990 $angle += 360;
28992 if ($makepositive) { // always returns an angle between 0 and 360deg
28993 if ($angle < 0) {
28994 $angle += 360;
28997 } else {
28998 $angle = $s + 0;
29000 return $angle;
29003 function lesser_entity_decode($html)
29005 // supports the most used entity codes (only does ascii safe characters)
29006 $html = str_replace("&lt;", "<", $html);
29007 $html = str_replace("&gt;", ">", $html);
29009 $html = str_replace("&apos;", "'", $html);
29010 $html = str_replace("&quot;", '"', $html);
29011 $html = str_replace("&amp;", "&", $html);
29012 return $html;
29015 function AdjustHTML($html, $tabSpaces = 8)
29017 $limit = ini_get('pcre.backtrack_limit');
29018 if (strlen($html) > $limit) {
29019 throw new \Mpdf\MpdfException(sprintf(
29020 'The HTML code size is larger than pcre.backtrack_limit %d. You should use WriteHTML() with smaller string lengths.',
29021 $limit
29025 preg_match_all("/(<annotation.*?>)/si", $html, $m);
29026 if (count($m[1])) {
29027 for ($i = 0; $i < count($m[1]); $i++) {
29028 $sub = preg_replace("/\n/si", "\xbb\xa4\xac", $m[1][$i]);
29029 $html = preg_replace('/' . preg_quote($m[1][$i], '/') . '/si', $sub, $html);
29033 preg_match_all("/(<svg.*?<\/svg>)/si", $html, $svgi);
29034 if (count($svgi[0])) {
29035 for ($i = 0; $i < count($svgi[0]); $i++) {
29036 $file = $this->cache->write('/_tempSVG' . uniqid(random_int(1, 100000), true) . '_' . $i . '.svg', $svgi[0][$i]);
29037 $html = str_replace($svgi[0][$i], '<img src="' . $file . '" />', $html);
29041 // Remove javascript code from HTML (should not appear in the PDF file)
29042 $html = preg_replace('/<script.*?<\/script>/is', '', $html);
29044 // Remove special comments
29045 $html = preg_replace('/<!--mpdf/i', '', $html);
29046 $html = preg_replace('/mpdf-->/i', '', $html);
29048 // Remove comments from HTML (should not appear in the PDF file)
29049 $html = preg_replace('/<!--.*?-->/s', '', $html);
29051 $html = preg_replace('/\f/', '', $html); // replace formfeed by nothing
29052 $html = preg_replace('/\r/', '', $html); // replace carriage return by nothing
29053 // Well formed XHTML end tags
29054 $html = preg_replace('/<(br|hr)>/i', "<\\1 />", $html); // mPDF 6
29055 $html = preg_replace('/<(br|hr)\/>/i', "<\\1 />", $html);
29056 // Get rid of empty <thead></thead> etc
29057 $html = preg_replace('/<tr>\s*<\/tr>/i', '', $html);
29058 $html = preg_replace('/<thead>\s*<\/thead>/i', '', $html);
29059 $html = preg_replace('/<tfoot>\s*<\/tfoot>/i', '', $html);
29060 $html = preg_replace('/<table[^>]*>\s*<\/table>/i', '', $html);
29062 // Remove spaces at end of table cells
29063 $html = preg_replace("/[ \n\r]+<\/t(d|h)/", '</t\\1', $html);
29065 $html = preg_replace("/[ ]*<dottab\s*[\/]*>[ ]*/", '<dottab />', $html);
29067 // Concatenates any Substitute characters from symbols/dingbats
29068 $html = str_replace('</tts><tts>', '|', $html);
29069 $html = str_replace('</ttz><ttz>', '|', $html);
29070 $html = str_replace('</tta><tta>', '|', $html);
29072 $html = preg_replace('/<br \/>\s*/is', "<br />", $html);
29074 $html = preg_replace('/<wbr[ \/]*>\s*/is', "&#173;", $html);
29076 // Preserve '\n's in content between the tags <pre> and </pre>
29077 if (preg_match('/<pre/', $html)) {
29078 $html_a = preg_split('/(\<\/?pre[^\>]*\>)/', $html, -1, 2);
29079 $h = [];
29080 $c = 0;
29081 foreach ($html_a as $s) {
29082 if ($c > 1 && preg_match('/^<\/pre/i', $s)) {
29083 $c--;
29084 $s = preg_replace('/<\/pre/i', '</innerpre', $s);
29085 } elseif ($c > 0 && preg_match('/^<pre/i', $s)) {
29086 $c++;
29087 $s = preg_replace('/<pre/i', '<innerpre', $s);
29088 } elseif (preg_match('/^<pre/i', $s)) {
29089 $c++;
29090 } elseif (preg_match('/^<\/pre/i', $s)) {
29091 $c--;
29093 array_push($h, $s);
29095 $html = implode("", $h);
29097 $thereispre = preg_match_all('#<pre(.*?)>(.*?)</pre>#si', $html, $temp);
29098 // Preserve '\n's in content between the tags <textarea> and </textarea>
29099 $thereistextarea = preg_match_all('#<textarea(.*?)>(.*?)</textarea>#si', $html, $temp2);
29100 $html = preg_replace('/[\n]/', ' ', $html); // replace linefeed by spaces
29101 $html = preg_replace('/[\t]/', ' ', $html); // replace tabs by spaces
29102 // Converts < to &lt; when not a tag
29103 $html = preg_replace('/<([^!\/a-zA-Z_:])/i', '&lt;\\1', $html); // mPDF 5.7.3
29104 $html = preg_replace("/[ ]+/", ' ', $html);
29106 $html = preg_replace('/\/li>\s+<\/(u|o)l/i', '/li></\\1l', $html);
29107 $html = preg_replace('/\/(u|o)l>\s+<\/li/i', '/\\1l></li', $html);
29108 $html = preg_replace('/\/li>\s+<\/(u|o)l/i', '/li></\\1l', $html);
29109 $html = preg_replace('/\/li>\s+<li/i', '/li><li', $html);
29110 $html = preg_replace('/<(u|o)l([^>]*)>[ ]+/i', '<\\1l\\2>', $html);
29111 $html = preg_replace('/[ ]+<(u|o)l/i', '<\\1l', $html);
29113 // Make self closing tabs valid XHTML
29114 // Tags which are self-closing: 1) Replaceable and 2) Non-replaced items
29115 $selftabs = 'input|hr|img|br|barcode|dottab';
29116 $selftabs2 = 'indexentry|indexinsert|bookmark|watermarktext|watermarkimage|column_break|columnbreak|newcolumn|newpage|page_break|pagebreak|formfeed|columns|toc|tocpagebreak|setpageheader|setpagefooter|sethtmlpageheader|sethtmlpagefooter|annotation';
29118 // Fix self-closing tags which don't close themselves
29119 $html = preg_replace('/(<(' . $selftabs . '|' . $selftabs2 . ')[^>\/]*)>/i', '\\1 />', $html);
29121 // Fix self-closing tags that don't include a space between the tag name and the closing slash
29122 $html = preg_replace('/(<(' . $selftabs . '|' . $selftabs2 . '))\/>/i', '\\1 />', $html);
29124 $iterator = 0;
29125 while ($thereispre) { // Recover <pre attributes>content</pre>
29126 $temp[2][$iterator] = preg_replace('/<([^!\/a-zA-Z_:])/', '&lt;\\1', $temp[2][$iterator]); // mPDF 5.7.2 // mPDF 5.7.3
29128 $temp[2][$iterator] = preg_replace_callback("/^([^\n\t]*?)\t/m", [$this, 'tabs2spaces_callback'], $temp[2][$iterator]); // mPDF 5.7+
29129 $temp[2][$iterator] = preg_replace('/\t/', str_repeat(" ", $tabSpaces), $temp[2][$iterator]);
29131 $temp[2][$iterator] = preg_replace('/\n/', "<br />", $temp[2][$iterator]);
29132 $temp[2][$iterator] = str_replace('\\', "\\\\", $temp[2][$iterator]);
29133 // $html = preg_replace('#<pre(.*?)>(.*?)</pre>#si','<erp'.$temp[1][$iterator].'>'.$temp[2][$iterator].'</erp>',$html,1);
29134 $html = preg_replace('#<pre(.*?)>(.*?)</pre>#si', '<erp' . $temp[1][$iterator] . '>' . str_replace('$', '\$', $temp[2][$iterator]) . '</erp>', $html, 1); // mPDF 5.7+
29135 $thereispre--;
29136 $iterator++;
29138 $iterator = 0;
29139 while ($thereistextarea) { // Recover <textarea attributes>content</textarea>
29140 $temp2[2][$iterator] = preg_replace('/\t/', str_repeat(" ", $tabSpaces), $temp2[2][$iterator]);
29141 $temp2[2][$iterator] = str_replace('\\', "\\\\", $temp2[2][$iterator]);
29142 $html = preg_replace('#<textarea(.*?)>(.*?)</textarea>#si', '<aeratxet' . $temp2[1][$iterator] . '>' . trim($temp2[2][$iterator]) . '</aeratxet>', $html, 1);
29143 $thereistextarea--;
29144 $iterator++;
29146 // Restore original tag names
29147 $html = str_replace("<erp", "<pre", $html);
29148 $html = str_replace("</erp>", "</pre>", $html);
29149 $html = str_replace("<aeratxet", "<textarea", $html);
29150 $html = str_replace("</aeratxet>", "</textarea>", $html);
29151 $html = str_replace("</innerpre", "</pre", $html);
29152 $html = str_replace("<innerpre", "<pre", $html);
29154 $html = preg_replace('/<textarea([^>]*)><\/textarea>/si', '<textarea\\1> </textarea>', $html);
29155 $html = preg_replace('/(<table[^>]*>)\s*(<caption)(.*?<\/caption>)(.*?<\/table>)/si', '\\2 position="top"\\3\\1\\4\\2 position="bottom"\\3', $html); // *TABLES*
29156 $html = preg_replace('/<(h[1-6])([^>]*)(>(?:(?!h[1-6]).)*?<\/\\1>\s*<table)/si', '<\\1\\2 keep-with-table="1"\\3', $html); // *TABLES*
29157 $html = preg_replace("/\xbb\xa4\xac/", "\n", $html);
29159 // Fixes <p>&#8377</p> which browser copes with even though it is wrong!
29160 $html = preg_replace("/(&#[x]{0,1}[0-9a-f]{1,5})</i", "\\1;<", $html);
29161 return $html;
29164 // mPDF 5.7+
29165 function tabs2spaces_callback($matches)
29167 return (stripslashes($matches[1]) . str_repeat(' ', $this->tabSpaces - (mb_strlen(stripslashes($matches[1])) % $this->tabSpaces)));
29170 // mPDF 5.7+
29171 function date_callback($matches)
29173 return date($matches[1]);
29176 // ===========================
29177 /* -- IMPORTS -- */
29178 function SetImportUse()
29180 if (!class_exists('fpdi_pdf_parser')) {
29181 throw new \Mpdf\MpdfException('Class fpdi_pdf_parser not found. Please run composer update or require setasign/fpdi 1.6.* manually');
29184 $this->enableImports = true;
29187 // from mPDFI
29188 function hex2str($hex)
29190 return pack("H*", str_replace(["\r", "\n", " "], "", $hex));
29193 function str2hex($str)
29195 return current(unpack("H*", $str));
29199 * Un-escapes a PDF string
29201 * @param string $s
29202 * @return string
29204 function _unescape($s)
29206 $out = '';
29207 for ($count = 0, $n = strlen($s); $count < $n; $count++) {
29208 if ($s[$count] != '\\' || $count == $n-1) {
29209 $out .= $s[$count];
29210 } else {
29211 switch ($s[++$count]) {
29212 case ')':
29213 case '(':
29214 case '\\':
29215 $out .= $s[$count];
29216 break;
29217 case 'f':
29218 $out .= chr(0x0C);
29219 break;
29220 case 'b':
29221 $out .= chr(0x08);
29222 break;
29223 case 't':
29224 $out .= chr(0x09);
29225 break;
29226 case 'r':
29227 $out .= chr(0x0D);
29228 break;
29229 case 'n':
29230 $out .= chr(0x0A);
29231 break;
29232 case "\r":
29233 if ($count != $n-1 && $s[$count+1] == "\n") {
29234 $count++;
29236 break;
29237 case "\n":
29238 break;
29239 default:
29240 // Octal-Values
29241 if (ord($s[$count]) >= ord('0') &&
29242 ord($s[$count]) <= ord('9')) {
29243 $oct = ''. $s[$count];
29244 if (ord($s[$count+1]) >= ord('0') &&
29245 ord($s[$count+1]) <= ord('9')) {
29246 $oct .= $s[++$count];
29247 if (ord($s[$count+1]) >= ord('0') &&
29248 ord($s[$count+1]) <= ord('9')) {
29249 $oct .= $s[++$count];
29252 $out .= chr(octdec($oct));
29253 } else {
29254 $out .= $s[$count];
29259 return $out;
29262 function pdf_write_value(&$value)
29264 switch ($value[0]) {
29265 case pdf_parser::TYPE_TOKEN:
29266 $this->_out($value[1] . ' ', false);
29267 break;
29269 case pdf_parser::TYPE_NUMERIC:
29270 case pdf_parser::TYPE_REAL:
29271 if (is_float($value[1]) && $value[1] != 0) {
29272 $this->_out(rtrim(rtrim(sprintf('%F', $value[1]), '0'), '.') . ' ', false);
29273 } else {
29274 $this->_out($value[1] . ' ', false);
29276 break;
29278 case pdf_parser::TYPE_ARRAY:
29279 // An array. Output the proper
29280 // structure and move on.
29281 $this->_out("[", false);
29282 for ($i = 0; $i < count($value[1]); $i++) {
29283 $this->pdf_write_value($value[1][$i]);
29285 $this->_out("]");
29286 break;
29288 case pdf_parser::TYPE_DICTIONARY:
29289 // A dictionary.
29290 $this->_out("<<", false);
29292 foreach ($value[1] as $k => $v) {
29293 $this->_out($k . ' ', false);
29294 $this->pdf_write_value($v);
29297 $this->_out(">>");
29298 break;
29300 case pdf_parser::TYPE_OBJREF:
29301 // An indirect object reference
29302 // Fill the object stack if needed
29303 $cpfn = $this->current_parser->filename;
29304 if (!isset($this->_don_obj_stack[$cpfn][$value[1]])) {
29305 $this->_newobj(false, true);
29306 $this->_obj_stack[$cpfn][$value[1]] = [$this->n, $value];
29307 $this->_don_obj_stack[$cpfn][$value[1]] = [$this->n, $value];
29309 $objid = $this->_don_obj_stack[$cpfn][$value[1]][0];
29310 $this->_out("{$objid} 0 R"); // {$value[2]}
29311 break;
29313 case pdf_parser::TYPE_STRING:
29314 if ($this->encrypted) {
29315 $value[1] = $this->_unescape($value[1]);
29316 $value[1] = $this->protection->rc4($this->protection->objectKey($this->_current_obj_id), $value[1]);
29317 $value[1] = $this->_escape($value[1]);
29319 // A string.
29320 $this->_out('(' . $value[1] . ')');
29321 break;
29323 case pdf_parser::TYPE_STREAM:
29324 // A stream. First, output the
29325 // stream dictionary, then the
29326 // stream data itself.
29327 $this->pdf_write_value($value[1]);
29328 if ($this->encrypted) {
29329 $value[2][1] = $this->protection->rc4($this->protection->objectKey($this->_current_obj_id), $value[2][1]);
29331 $this->_out("stream");
29332 $this->_out($value[2][1]);
29333 $this->_out("endstream");
29334 break;
29336 case pdf_parser::TYPE_HEX:
29337 if ($this->encrypted) {
29338 $value[1] = $this->hex2str($value[1]);
29339 $value[1] = $this->protection->rc4($this->protection->objectKey($this->_current_obj_id), $value[1]);
29340 // remake hexstring of encrypted string
29341 $value[1] = $this->str2hex($value[1]);
29343 $this->_out("<" . $value[1] . ">");
29344 break;
29346 case pdf_parser::TYPE_BOOLEAN:
29347 $this->_out($value[1] ? 'true' : 'false');
29348 break;
29350 case pdf_parser::TYPE_NULL:
29351 // The null object.
29352 $this->_out("null");
29353 break;
29357 // ========== OVERWRITE SEARCH STRING IN A PDF FILE ================
29358 function OverWrite($file_in, $search, $replacement, $dest = Destination::DOWNLOAD, $file_out = "mpdf")
29360 $pdf = file_get_contents($file_in);
29362 if (!is_array($search)) {
29363 $x = $search;
29364 $search = [$x];
29366 if (!is_array($replacement)) {
29367 $x = $replacement;
29368 $replacement = [$x]; // mPDF 5.7.4
29371 if (!$this->onlyCoreFonts && !$this->usingCoreFont) {
29372 foreach ($search as $k => $val) {
29373 $search[$k] = $this->UTF8ToUTF16BE($search[$k], false);
29374 $search[$k] = $this->_escape($search[$k]);
29375 $replacement[$k] = $this->UTF8ToUTF16BE($replacement[$k], false);
29376 $replacement[$k] = $this->_escape($replacement[$k]);
29378 } else {
29379 foreach ($replacement as $k => $val) {
29380 $replacement[$k] = mb_convert_encoding($replacement[$k], $this->mb_enc, 'utf-8');
29381 $replacement[$k] = $this->_escape($replacement[$k]);
29385 // Get xref into array
29386 $xref = [];
29387 preg_match("/xref\n0 (\d+)\n(.*?)\ntrailer/s", $pdf, $m);
29388 $xref_objid = $m[1];
29389 preg_match_all('/(\d{10}) (\d{5}) (f|n)/', $m[2], $x);
29390 for ($i = 0; $i < count($x[0]); $i++) {
29391 $xref[] = [intval($x[1][$i]), $x[2][$i], $x[3][$i]];
29394 $changes = [];
29395 preg_match("/<<\s*\/Type\s*\/Pages\s*\/Kids\s*\[(.*?)\]\s*\/Count/s", $pdf, $m);
29396 preg_match_all("/(\d+) 0 R /s", $m[1], $o);
29397 $objlist = $o[1];
29399 foreach ($objlist as $obj) {
29400 if ($this->compress) {
29401 preg_match("/" . ($obj + 1) . " 0 obj\n<<\s*\/Filter\s*\/FlateDecode\s*\/Length (\d+)>>\nstream\n(.*?)\nendstream\n/s", $pdf, $m);
29402 } else {
29403 preg_match("/" . ($obj + 1) . " 0 obj\n<<\s*\/Length (\d+)>>\nstream\n(.*?)\nendstream\n/s", $pdf, $m);
29406 $s = $m[2];
29407 if (!$s) {
29408 continue;
29411 $oldlen = $m[1];
29413 if ($this->encrypted) {
29414 $s = $this->protection->rc4($this->protection->objectKey($obj + 1), $s);
29417 if ($this->compress) {
29418 $s = gzuncompress($s);
29421 foreach ($search as $k => $val) {
29422 $s = str_replace($search[$k], $replacement[$k], $s);
29425 if ($this->compress) {
29426 $s = gzcompress($s);
29429 if ($this->encrypted) {
29430 $s = $this->protection->rc4($this->protection->objectKey($obj + 1), $s);
29433 $newlen = strlen($s);
29435 $changes[($xref[$obj + 1][0])] = ($newlen - $oldlen) + (strlen($newlen) - strlen($oldlen));
29437 if ($this->compress) {
29438 $newstr = ($obj + 1) . " 0 obj\n<</Filter /FlateDecode /Length " . $newlen . ">>\nstream\n" . $s . "\nendstream\n";
29439 } else {
29440 $newstr = ($obj + 1) . " 0 obj\n<</Length " . $newlen . ">>\nstream\n" . $s . "\nendstream\n";
29443 $pdf = str_replace($m[0], $newstr, $pdf);
29446 // Update xref in PDF
29447 krsort($changes);
29448 $newxref = "xref\n0 " . $xref_objid . "\n";
29449 foreach ($xref as $v) {
29450 foreach ($changes as $ck => $cv) {
29451 if ($v[0] > $ck) {
29452 $v[0] += $cv;
29455 $newxref .= sprintf('%010d', $v[0]) . ' ' . $v[1] . ' ' . $v[2] . " \n";
29457 $newxref .= "trailer";
29458 $pdf = preg_replace("/xref\n0 \d+\n.*?\ntrailer/s", $newxref, $pdf);
29460 // Update startxref in PDF
29461 preg_match("/startxref\n(\d+)\n%%EOF/s", $pdf, $m);
29462 $startxref = $m[1];
29463 $startxref += array_sum($changes);
29464 $pdf = preg_replace("/startxref\n(\d+)\n%%EOF/s", "startxref\n" . $startxref . "\n%%EOF", $pdf);
29466 // OUTPUT
29467 switch ($dest) {
29468 case Destination::INLINE:
29469 if (isset($_SERVER['SERVER_NAME'])) {
29470 // We send to a browser
29471 header('Content-Type: application/pdf');
29472 header('Content-Length: ' . strlen($pdf));
29473 header('Content-disposition: inline; filename=' . $file_out);
29476 echo $pdf;
29478 break;
29480 case Destination::FILE:
29481 if (!$file_out) {
29482 $file_out = 'mpdf.pdf';
29485 $f = fopen($file_out, 'wb');
29487 if (!$f) {
29488 throw new \Mpdf\MpdfException('Unable to create output file: ' . $file_out);
29491 fwrite($f, $pdf, strlen($pdf));
29493 fclose($f);
29495 break;
29497 case Destination::STRING_RETURN:
29498 return $pdf;
29500 case Destination::DOWNLOAD: // Download file
29501 default:
29502 if (isset($_SERVER['HTTP_USER_AGENT']) and strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE')) {
29503 header('Content-Type: application/force-download');
29504 } else {
29505 header('Content-Type: application/octet-stream');
29508 header('Content-Length: ' . strlen($pdf));
29509 header('Content-disposition: attachment; filename=' . $file_out);
29511 echo $pdf;
29513 break;
29517 function GetTemplateSize($tplidx, $_w = 0, $_h = 0)
29519 if (!$this->tpls[$tplidx]) {
29520 return false;
29522 $w = $this->tpls[$tplidx]['box']['w'];
29523 $h = $this->tpls[$tplidx]['box']['h'];
29524 if ($_w == 0 and $_h == 0) {
29525 $_w = $w;
29526 $_h = $h;
29528 if ($_w == 0) {
29529 $_w = $_h * $w / $h;
29531 if ($_h == 0) {
29532 $_h = $_w * $h / $w;
29534 return ["w" => $_w, "h" => $_h];
29538 function Thumbnail($file, $npr = 3, $spacing = 10)
29540 // $npr = number per row
29541 $w = (($this->pgwidth + $spacing) / $npr) - $spacing;
29542 $oldlinewidth = $this->LineWidth;
29543 $this->SetLineWidth(0.02);
29544 $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings));
29545 $h = 0;
29546 $maxh = 0;
29547 $x = $_x = $this->lMargin;
29548 $_y = $this->tMargin;
29550 if ($this->y == 0) {
29551 $y = $_y;
29552 } else {
29553 $y = $this->y;
29556 $pagecount = $this->SetSourceFile($file);
29558 for ($n = 1; $n <= $pagecount; $n++) {
29559 $tplidx = $this->ImportPage($n);
29560 $size = $this->UseTemplate($tplidx, $x, $y, $w);
29561 $this->Rect($x, $y, $size['w'], $size['h']);
29562 $h = max($h, $size['h']);
29563 $maxh = max($h, $maxh);
29565 if ($n % $npr == 0) {
29566 if (($y + $h + $spacing + $maxh) > $this->PageBreakTrigger && $n != $pagecount) {
29567 $this->AddPage();
29568 $x = $_x;
29569 $y = $_y;
29570 } else {
29571 $y += $h + $spacing;
29572 $x = $_x;
29573 $h = 0;
29575 } else {
29576 $x += $w + $spacing;
29579 $this->SetLineWidth($oldlinewidth);
29582 function SetSourceFile($filename)
29584 $this->current_filename = $filename;
29585 $fn = $this->current_filename;
29586 if (!isset($this->parsers[$fn])) {
29587 try {
29588 $this->parsers[$fn] = new fpdi_pdf_parser($fn);
29589 } catch (\Exception $e) {
29590 throw new \Mpdf\MpdfException($e->getMessage());
29594 $this->current_parser = $this->parsers[$fn];
29595 return $this->parsers[$fn]->getPageCount();
29598 function ImportPage($pageno = 1, $crop_x = null, $crop_y = null, $crop_w = 0, $crop_h = 0, $boxName = '/CropBox')
29600 $fn = $this->current_filename;
29601 $parser = $this->parsers[$fn];
29602 $parser->setPageno($pageno);
29604 $this->tpl++;
29605 $this->tpls[$this->tpl] = [];
29606 $tpl = & $this->tpls[$this->tpl];
29607 $tpl['parser'] = $parser;
29608 $tpl['resources'] = $parser->getPageResources();
29609 $tpl['buffer'] = $parser->getContent();
29611 if (!in_array($boxName, $parser->availableBoxes)) {
29612 throw new \Mpdf\MpdfException(sprintf("Unknown box: %s", $boxName));
29615 $pageboxes = $parser->getPageBoxes($pageno, Mpdf::SCALE);
29618 * MediaBox
29619 * CropBox: Default -> MediaBox
29620 * BleedBox: Default -> CropBox
29621 * TrimBox: Default -> CropBox
29622 * ArtBox: Default -> CropBox
29624 if (!isset($pageboxes[$boxName]) && ($boxName == "/BleedBox" || $boxName == "/TrimBox" || $boxName == "/ArtBox")) {
29625 $boxName = "/CropBox";
29628 if (!isset($pageboxes[$boxName]) && $boxName == "/CropBox") {
29629 $boxName = "/MediaBox";
29632 if (!isset($pageboxes[$boxName])) {
29633 return false;
29636 $box = $pageboxes[$boxName];
29638 $tpl['box'] = $box;
29639 // To build an array that can be used by useTemplate()
29640 $this->tpls[$this->tpl] = array_merge($this->tpls[$this->tpl], $box);
29641 // An imported page will start at 0,0 everytime. Translation will be set in _putformxobjects()
29642 $tpl['x'] = 0;
29643 $tpl['y'] = 0;
29644 $tpl['w'] = $tpl['box']['w'];
29645 $tpl['h'] = $tpl['box']['h'];
29647 if ($crop_w) {
29648 $tpl['box']['w'] = $crop_w;
29650 if ($crop_h) {
29651 $tpl['box']['h'] = $crop_h;
29653 if (isset($crop_x)) {
29654 $tpl['box']['x'] = $crop_x;
29656 if (isset($crop_y)) {
29657 $tpl['box']['y'] = $tpl['h'] - $crop_y - $crop_h;
29660 // fix for rotated pages
29661 $rotation = $parser->getPageRotation($pageno);
29663 if (isset($rotation[1]) && ($angle = $rotation[1] % 360) != 0 && $tpl['box']['w'] == $tpl['w']) {
29664 $steps = $angle / 90;
29666 $_w = $tpl['w'];
29667 $_h = $tpl['h'];
29668 $tpl['w'] = $steps % 2 == 0 ? $_w : $_h;
29669 $tpl['h'] = $steps % 2 == 0 ? $_h : $_w;
29670 if ($steps % 2 != 0) {
29671 $x = $y = ($steps == 1 || $steps == -3) ? $tpl['h'] : $tpl['w'];
29672 } else {
29673 $x = $tpl['w'];
29674 $y = $tpl['h'];
29676 $cx = ($x / 2 + $tpl['box']['x']) * Mpdf::SCALE;
29677 $cy = ($y / 2 + $tpl['box']['y']) * Mpdf::SCALE;
29678 $angle*=-1;
29679 $angle*=M_PI / 180;
29680 $c = cos($angle);
29681 $s = sin($angle);
29682 $tpl['box']['w'] = $tpl['w'];
29683 $tpl['box']['h'] = $tpl['h'];
29684 $tpl['buffer'] = sprintf('q %.5F %.5F %.5F %.5F %.2F %.2F cm 1 0 0 1 %.2F %.2F cm %s Q', $c, $s, -$s, $c, $cx, $cy, -$cx, -$cy, $tpl['buffer']);
29687 return $this->tpl;
29690 function UseTemplate($tplidx, $_x = null, $_y = null, $_w = 0, $_h = 0)
29692 if (!isset($this->tpls[$tplidx])) {
29693 throw new \Mpdf\MpdfException("Template does not exist!");
29696 if ($this->state == 0) {
29697 $this->AddPage();
29700 $out = 'q 0 J 1 w 0 j 0 G' . "\n"; // reset standard values
29701 $x = $this->tpls[$tplidx]['x'];
29702 $y = $this->tpls[$tplidx]['y'];
29703 $w = $this->tpls[$tplidx]['w'];
29704 $h = $this->tpls[$tplidx]['h'];
29706 if ($_x == null) {
29707 $_x = $x;
29710 if ($_y == null) {
29711 $_y = $y;
29714 if ($_x === -1) {
29715 $_x = $this->x;
29718 if ($_y === -1) {
29719 $_y = $this->y;
29722 $wh = $this->GetTemplateSize($tplidx, $_w, $_h);
29723 $_w = $wh['w'];
29724 $_h = $wh['h'];
29725 $out .= sprintf("q %.4F 0 0 %.4F %.2F %.2F cm", ($_w / $this->tpls[$tplidx]['box']['w']), ($_h / $this->tpls[$tplidx]['box']['h']), $_x * Mpdf::SCALE, ($this->h - ($_y + $_h)) * Mpdf::SCALE) . "\n";
29726 $out .= $this->tplprefix . $tplidx . " Do Q\n";
29728 $s = ["w" => $_w, "h" => $_h];
29729 $out .= "Q\n";
29730 $this->pages[$this->page] = $out . $this->pages[$this->page];
29731 return $s;
29734 function SetPageTemplate($tplidx = '')
29736 if (!isset($this->tpls[$tplidx])) {
29737 $this->pageTemplate = '';
29738 return false;
29740 $this->pageTemplate = $tplidx;
29743 function SetDocTemplate($file = '', $continue = 0)
29745 $this->docTemplate = $file;
29746 $this->docTemplateContinue = $continue;
29749 /* -- END IMPORTS -- */
29751 // JAVASCRIPT
29752 function _set_object_javascript($string)
29754 $this->_newobj();
29755 $this->_out('<<');
29756 $this->_out('/S /JavaScript ');
29757 $this->_out('/JS ' . $this->_textstring($string));
29758 $this->_out('>>');
29759 $this->_out('endobj');
29762 function SetJS($script)
29764 $this->js = $script;
29768 * This function takes the last comma or dot (if any) to make a clean float, ignoring thousand separator, currency or any other letter
29770 * @param string $num
29771 * @see http://php.net/manual/de/function.floatval.php#114486
29772 * @return float
29774 public function toFloat($num)
29776 $dotPos = strrpos($num, '.');
29777 $commaPos = strrpos($num, ',');
29778 $sep = (($dotPos > $commaPos) && $dotPos) ? $dotPos : ((($commaPos > $dotPos) && $commaPos) ? $commaPos : false);
29780 if (!$sep) {
29781 return floatval(preg_replace('/[^0-9]/', '', $num));
29784 return floatval(
29785 preg_replace('/[^0-9]/', '', substr($num, 0, $sep)) . '.' .
29786 preg_replace('/[^0-9]/', '', substr($num, $sep+1, strlen($num)))
29790 public function getFontDescriptor()
29792 return $this->fontDescriptor;