diff --git a/README.MD b/README.MD index 499d426..82f41f8 100644 --- a/README.MD +++ b/README.MD @@ -2,12 +2,14 @@ [![Latest Stable Version](https://poser.pugx.org/yetiforce/yetiforcepdf/v/stable)](https://packagist.org/packages/yetiforce/yetiforcepdf) [![Build Status](https://travis-ci.org/YetiForceCompany/YetiForcePDF.svg?branch=developer)](https://travis-ci.org/YetiForceCompany/YetiForcePDF) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/YetiForceCompany/YetiForcePDF/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/YetiForceCompany/YetiForcePDF/?branch=master) -[![Codacy Badge](https://api.codacy.com/project/badge/Grade/b2e8645f5091496089ed203d05a05d61)](https://app.codacy.com/app/mariuszkrzaczkowski/YetiForcePDF?utm_source=github.com&utm_medium=referral&utm_content=YetiForceCompany/YetiForcePDF&utm_campaign=Badge_Grade_Settings) [![Maintainability](https://api.codeclimate.com/v1/badges/af478ddd07cf7278841a/maintainability)](https://codeclimate.com/github/YetiForceCompany/YetiForcePDF/maintainability) ## PDF generation library for PHP The best library in the world to generate PDF from HTML +## Issues & bugs +Report errors related to PDF in https://github.com/YetiForceCompany/YetiForceCRM/issues + ## Basic usage (for more take a look at examples folder) ```php @@ -196,3 +198,12 @@ When you want to place page number (in header or footer for example) you can do ## License Distributed under the MIT license. See LICENSE for details. + +## 👥 Contributors + +This project exists thanks to all the people who contribute. + + + + + diff --git a/composer.json b/composer.json index 1ab74c6..f6b4fc1 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "phenx/php-font-lib": "^0.5", "guzzlehttp/guzzle": "^7", "composer/ca-bundle": "^1", - "milon/barcode": "^9", + "milon/barcode": "^11", "sabberworm/php-css-parser": "^8" }, "autoload": { diff --git a/lib/Document.php b/lib/Document.php index 4e8da32..5ae821e 100644 --- a/lib/Document.php +++ b/lib/Document.php @@ -14,6 +14,8 @@ namespace YetiForcePDF; +use Exception; +use YetiForcePDF\Html\Parser; use YetiForcePDF\Layout\FooterBox; use YetiForcePDF\Layout\HeaderBox; use YetiForcePDF\Layout\WatermarkBox; @@ -31,34 +33,40 @@ class Document * @var int */ protected $actualId = 0; + /** * Main output buffer / content for pdf file. * * @var string */ protected $buffer = ''; + /** * Main entry point - root element. * * @var \YetiForcePDF\Catalog */ protected $catalog; + /** * Pages dictionary. * * @var Pages */ protected $pagesObject; + /** * Current page object. * * @var Page */ protected $currentPageObject; + /** * @var string default page format */ protected $defaultFormat = 'A4'; + /** * @var string default page orientation */ @@ -68,6 +76,7 @@ class Document * @var Page[] all pages in the document */ protected $pages = []; + /** * Default page margins. * @@ -79,68 +88,82 @@ class Document 'right' => 40, 'bottom' => 40, ]; + /** * All objects inside document. * * @var \YetiForcePDF\Objects\PdfObject[] */ protected $objects = []; + /** - * @var \YetiForcePDF\Html\Parser + * @var Parser */ protected $htmlParser; + /** * Fonts data. * * @var array */ protected $fontsData = []; + /** * @var array */ protected $fontInstances = []; + /** * Actual font id. * * @var int */ protected $actualFontId = 0; + /** * Actual graphic state id. * * @var int */ protected $actualGraphicStateId = 0; + /** * @var bool */ protected $debugMode = false; + /** * @var HeaderBox|null */ protected $header; + /** * @var FooterBox|null */ protected $footer; + /** * @var WatermarkBox|null */ protected $watermark; + /** * @var Meta */ protected $meta; + /** * @var bool */ protected $parsed = false; + /** * Characters int values cache for fonts. * * @var array */ - protected $ordCache = []; + public $ordCache = []; + /** * Css selectors like classes ids. * @@ -444,7 +467,7 @@ public function getFontData(string $family, string $weight, string $style) */ public static function addFonts(array $fonts) { - return \YetiForcePDF\Objects\Font::loadFromArray($fonts); + \YetiForcePDF\Objects\Font::loadFromArray($fonts); } /** @@ -765,14 +788,19 @@ public function removeObject(PdfObject $object): self * Load html string. * * @param string $html - * @param string $inputEncoding + * @param string $fromEncoding * * @return $this + * @throws Exception */ - public function loadHtml(string $html, string $inputEncoding = 'UTF-8') + public function loadHtml(string $html, string $fromEncoding = 'UTF-8'): self { - $this->htmlParser = (new \YetiForcePDF\Html\Parser())->setDocument($this)->init(); - $this->htmlParser->loadHtml($html, $inputEncoding); + if ($fromEncoding === '') { + throw new Exception('Encoding can not be empty'); + } + + $this->htmlParser = (new Parser())->setDocument($this)->init(); + $this->htmlParser->loadHtml($html, $fromEncoding); return $this; } @@ -872,10 +900,13 @@ public function parse() */ public function render(): string { - $xref = $this->buffer = ''; + $xref = ''; + $this->buffer = ''; + $this->buffer .= $this->getDocumentHeader(); $this->parse(); $objectSize = 0; + foreach ($this->objects as $object) { if (\in_array($object->getBasicType(), ['Dictionary', 'Stream', 'Array'])) { $xref .= sprintf("%010d 00000 n \n", \strlen($this->buffer)); @@ -883,23 +914,27 @@ public function render(): string ++$objectSize; } } + $offset = \strlen($this->buffer); $this->buffer .= implode("\n", [ 'xref', - '0 ' . ($objectSize + 1), + '0 ' . $objectSize, '0000000000 65535 f ', $xref, ]); + $trailer = (new \YetiForcePDF\Objects\Trailer()) ->setDocument($this)->setRootObject($this->catalog)->setSize($objectSize); + $this->buffer .= $trailer->render() . "\n"; - // $this->buffer .= implode("\n", [ - // 'startxref', - // $offset, - // '', - // ]); + $this->buffer .= implode("\n", [ + 'startxref', + $offset, + '', + ]); $this->buffer .= $this->getDocumentFooter(); $this->removeObject($trailer); + return $this->buffer; } @@ -910,7 +945,7 @@ public function render(): string * * @return array */ - public function getCssSelectorRules(string $selector) + public function getCssSelectorRules(string $selector): array { $rules = []; foreach (explode(' ', $selector) as $className) { diff --git a/lib/Html/Parser.php b/lib/Html/Parser.php index 7b38e7d..537b763 100644 --- a/lib/Html/Parser.php +++ b/lib/Html/Parser.php @@ -1,6 +1,7 @@ html = htmlspecialchars_decode($html, ENT_HTML5); $this->html = $this->cleanUpHtml($html); - $this->html = mb_convert_encoding($this->html, 'HTML-ENTITIES', $fromEncoding); + + // 0x80 - start of unicode range + // 0x10FFFF - end of unicode range + // 0 - do not ommit any unicode char + // ~0 - negated 0 - convert negation of nothing (so convert all) + $this->html = mb_encode_numericentity($this->html, [0x80, 0x10FFFF, 0, ~0], $fromEncoding); + return $this; } @@ -72,7 +83,7 @@ public function loadHtml(string $html, string $fromEncoding = ''): self * * @return string */ - public function getHtml() + public function getHtml(): string { return $this->html; } diff --git a/lib/Layout/TextBox.php b/lib/Layout/TextBox.php index 22690ee..080417b 100644 --- a/lib/Layout/TextBox.php +++ b/lib/Layout/TextBox.php @@ -1,6 +1,7 @@ style = (new \YetiForcePDF\Style\Style()) ->setDocument($this->document) ->setBox($this) ->init(); + return $this; } @@ -45,7 +50,7 @@ public function init() * * @return $this */ - public function setText(string $text) + public function setText(string $text): self { $this->text = $text; return $this; @@ -56,7 +61,7 @@ public function setText(string $text) * * @return string */ - public function getText() + public function getText(): string { return $this->text; } @@ -66,9 +71,10 @@ public function getText() * * @return $this */ - public function measureWidth() + public function measureWidth(): self { $this->getDimensions()->setWidth($this->getStyle()->getFont()->getTextWidth($this->getText())); + return $this; } @@ -77,9 +83,10 @@ public function measureWidth() * * @return $this */ - public function measureHeight() + public function measureHeight(): self { $this->getDimensions()->setHeight($this->getStyle()->getFont()->getTextHeight($this->getText())); + return $this; } @@ -88,10 +95,11 @@ public function measureHeight() * * @return $this */ - public function measureOffset() + public function measureOffset(): self { $this->getOffset()->setLeft('0'); $this->getOffset()->setTop('0'); + return $this; } @@ -100,11 +108,12 @@ public function measureOffset() * * @return $this */ - public function measurePosition() + public function measurePosition(): self { $parent = $this->getParent(); $this->getCoordinates()->setX(Math::add($parent->getCoordinates()->getX(), $this->getOffset()->getLeft())); $this->getCoordinates()->setY(Math::add($parent->getCoordinates()->getY(), $this->getOffset()->getTop())); + return $this; } @@ -139,6 +148,7 @@ public function getInstructions(): string $textHeight = $style->getFont()->getTextHeight(); $textContent = $this->document->filterText($this->getText()); $transform = $style->getTransformations($pdfX, $baseLineY); + $element = [ 'q', $graphicStateStr, @@ -150,7 +160,7 @@ public function getInstructions(): string 'ET', 'Q', ]; - $this->drawTextOutline = false; + if ($this->drawTextOutline) { $element = array_merge($element, [ 'q', @@ -162,6 +172,7 @@ public function getInstructions(): string 'Q', ]); } + return implode("\n", $element); } } diff --git a/lib/Page.php b/lib/Page.php index 8dd63f5..efcb9f8 100644 --- a/lib/Page.php +++ b/lib/Page.php @@ -1147,7 +1147,7 @@ protected function divideTable(Box $tableChild, string $yPos, Box $cloned) if ($row->getRowSpanUp() > 0) { $move = []; // copy spanned rows too - for ($i = $row->getRowSpanUp(); $i >= 0; --$i) { + for ($i = $row->getRowSpanUp(); $i > 0; --$i) { $spannedRowIndex = $rowIndex - $i; $move[] = $tableRowGroup->getChildren()[$spannedRowIndex]; } diff --git a/lib/Style/Normalizer/Border.php b/lib/Style/Normalizer/Border.php index 8f18cea..13d8759 100644 --- a/lib/Style/Normalizer/Border.php +++ b/lib/Style/Normalizer/Border.php @@ -25,8 +25,8 @@ public function normalize($ruleValue, string $ruleName = ''): array } $matches = []; preg_match('/([0-9]+)([a-z]+)\s+(solid|dashed|dotted|none)\s+(.+)?/ui', $ruleValue, $matches); - $originalSize = $matches[1]; - $originalUnit = $matches[2]; + $originalSize = $matches[1] ?? 0; + $originalUnit = $matches[2] ?? 0; if (isset($matches[3])) { $style = $matches[3]; } else {