From 0f7535b57a59b5faeb5158d56e11cbbc6c0ef3c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Po=C5=9Bpiech?= Date: Fri, 8 Mar 2019 17:19:35 +0100 Subject: [PATCH] initial class names support! --- .editorconfig | 5 + composer.json | 3 +- composer.lock | 58 ++++++- lib/Document.php | 53 +++++++ lib/Html/Element.php | 30 ++++ lib/Layout/BlockBox.php | 48 +++++- lib/Layout/Box.php | 16 +- lib/Layout/ElementBox.php | 51 ++++-- lib/Layout/StyleBox.php | 33 ++++ lib/Style/Style.php | 321 +++++++++++++++++++++++++++----------- 10 files changed, 497 insertions(+), 121 deletions(-) create mode 100644 .editorconfig create mode 100644 lib/Layout/StyleBox.php diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..33703af --- /dev/null +++ b/.editorconfig @@ -0,0 +1,5 @@ +root = true +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/composer.json b/composer.json index 93f791e..f8fa273 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,8 @@ "phenx/php-font-lib": "^0.5.1", "guzzlehttp/guzzle": "^6.3", "composer/ca-bundle": "^1.1", - "milon/barcode": "^5.3" + "milon/barcode": "^5.3", + "sabberworm/php-css-parser": "^8.3" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index ecdbd36..ec5a2d3 100644 --- a/composer.lock +++ b/composer.lock @@ -1,10 +1,10 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1328c86140066b7fea4b990d92c4256b", + "content-hash": "4f2910298fcc0b9688b2966e3b385009", "packages": [ { "name": "composer/ca-bundle", @@ -704,6 +704,51 @@ ], "time": "2017-10-23T01:57:42+00:00" }, + { + "name": "sabberworm/php-css-parser", + "version": "8.3.0", + "source": { + "type": "git", + "url": "https://github.com/sabberworm/PHP-CSS-Parser.git", + "reference": "91bcc3e3fdb7386c9a2e0e0aa09ca75cc43f121f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sabberworm/PHP-CSS-Parser/zipball/91bcc3e3fdb7386c9a2e0e0aa09ca75cc43f121f", + "reference": "91bcc3e3fdb7386c9a2e0e0aa09ca75cc43f121f", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "codacy/coverage": "^1.4", + "phpunit/phpunit": "~4.8" + }, + "type": "library", + "autoload": { + "psr-0": { + "Sabberworm\\CSS": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Raphael Schweikert" + } + ], + "description": "Parser for CSS Files written in PHP", + "homepage": "http://www.sabberworm.com/blog/2010/6/10/php-css-parser", + "keywords": [ + "css", + "parser", + "stylesheet" + ], + "time": "2019-02-22T07:42:52+00:00" + }, { "name": "symfony/contracts", "version": "v1.0.2", @@ -912,7 +957,14 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=7.1" + "php": ">=7.1", + "ext-mbstring": "*", + "ext-dom": "*", + "ext-libxml": "*", + "ext-bcmath": "*", + "ext-zlib": "*", + "ext-gd": "*", + "ext-filter": "*" }, "platform-dev": [] } diff --git a/lib/Document.php b/lib/Document.php index 9e6abdd..9120ed3 100644 --- a/lib/Document.php +++ b/lib/Document.php @@ -140,6 +140,12 @@ class Document * @var array */ protected $ordCache = []; + /** + * Css selectors like classes ids. + * + * @var array + */ + protected $cssSelectors = []; /** * Are we debugging? @@ -877,4 +883,51 @@ public function render(): string return $this->buffer; } + + /** + * Get css selector rules. + * + * @param string $selector + * + * @return array + */ + public function getCssSelectorRules(string $selector) + { + $rules = []; + foreach(explode(' ',$selector) as $className){ + if ($className && isset($this->cssSelectors[$className])) { + $rules = array_merge($rules,$this->cssSelectors[$className]); + } + } + + return $rules; + } + + /** + * Get css selectors. + * + * @return array + */ + public function getCssSelectors() + { + return $this->cssSelectors; + } + + /** + * Add css selector rules. + * + * @param string $selector .className or #id + * @param array $rules + * + * @return $this + */ + public function addCssSelectorRules(string $selector, array $rules): self + { + if (isset($this->cssSelectors[$selector])) { + $this->cssSelectors[$selector] = array_merge($this->cssSelectors[$selector], $rules); + } else { + $this->cssSelectors[$selector] = $rules; + } + return $this; + } } diff --git a/lib/Html/Element.php b/lib/Html/Element.php index a50b035..52293c4 100644 --- a/lib/Html/Element.php +++ b/lib/Html/Element.php @@ -40,6 +40,12 @@ class Element extends \YetiForcePDF\Base * @var \DOMElement */ protected $domElement; + /** + * Class names for element + * + * @var string + */ + protected $classNames = ''; /** * Initialisation. @@ -170,4 +176,28 @@ public function isTextNode() } return false; } + + /** + * Set class names for element + * + * @param string $classNames Class names for element + * + * @return self + */ + public function setClassNames(string $classNames) + { + $this->classNames = $classNames; + + return $this; + } + + /** + * Get class names for element + * + * @return string[] + */ + public function getClassNames() + { + return $this->classNames; + } } diff --git a/lib/Layout/BlockBox.php b/lib/Layout/BlockBox.php index 6747bf3..1bef450 100644 --- a/lib/Layout/BlockBox.php +++ b/lib/Layout/BlockBox.php @@ -59,6 +59,7 @@ public function init() ->setDocument($this->document) ->setBox($this) ->init(); + return $this; } @@ -83,6 +84,7 @@ public function setElement(Element $element) { $this->element = $element; $element->setBox($this); + return $this; } @@ -119,13 +121,14 @@ public function getNewLineBox($before = null) ->setBox($this->currentLineBox); $this->currentLineBox->setStyle($style); $this->currentLineBox->getStyle()->init(); + return $this->currentLineBox; } /** * Close line box. * - * @param \YetiForcePDF\Layout\LineBox|null $lineBox + * @param null|\YetiForcePDF\Layout\LineBox $lineBox * @param bool $createNew * * @return \YetiForcePDF\Layout\LineBox @@ -134,6 +137,7 @@ public function closeLine() { $this->saveSourceLine($this->currentLineBox); $this->currentLineBox = null; + return $this->currentLineBox; } @@ -164,6 +168,7 @@ public function appendBlockBox($childDomElement, $element, $style, $parentBlock) $this->appendChild($box); $box->getStyle()->init(); $box->buildTree($box); + return $box; } @@ -194,6 +199,7 @@ public function appendHeaderBox($childDomElement, $element, $style, $parentBlock $box->getStyle()->init(); $box->buildTree($box); $box->setDisplayable(false); + return $box; } @@ -224,6 +230,7 @@ public function appendFooterBox($childDomElement, $element, $style, $parentBlock $box->getStyle()->init(); $box->buildTree($box); $box->setDisplayable(false); + return $box; } @@ -254,6 +261,7 @@ public function appendWatermarkBox($childDomElement, $element, $style, $parentBl $box->getStyle()->init(); $box->buildTree($box); $box->setDisplayable(false); + return $box; } @@ -289,6 +297,20 @@ public function appendFontBox($childDomElement, $element, $style, $parentBlock) $box->loadFont(); $box->buildTree($box); $box->setDisplayable(false)->setRenderable(false)->setForMeasurement(false); + + return $box; + } + + public function appendStyleBox($childDomElement, $element, $style, $parentBlock) + { + $box = (new StyleBox()) + ->setDocument($this->document) + ->setParent($this) + ->setElement($element) + ->setStyle($style) + ->init(); + $this->appendChild($box); + $box->setDisplayable(false)->setRenderable(false)->setForMeasurement(false); return $box; } @@ -311,6 +333,7 @@ public function appendBarcodeBox($childDomElement, $element, $style, $parentBloc } else { $currentLineBox = $this->getNewLineBox(); } + return $currentLineBox->appendBarcode($childDomElement, $element, $style, $this); } @@ -344,6 +367,7 @@ public function appendInlineBlockBox($childDomElement, $element, $style, $parent } else { $currentLineBox = $this->getNewLineBox(); } + return $currentLineBox->appendInlineBlock($childDomElement, $element, $style, $this); } @@ -357,6 +381,7 @@ public function appendInlineBox($childDomElement, $element, $style, $parentBlock } else { $currentLineBox = $this->getNewLineBox(); } + return $currentLineBox->appendInline($childDomElement, $element, $style, $this); } @@ -384,6 +409,7 @@ public function measureWidth() } $this->divideLines(); } + return $this; } // if parent doesn't have a width specified @@ -400,10 +426,11 @@ public function measureWidth() $maxWidth = Math::sub($maxWidth, $style->getHorizontalMarginsWidth()); $dimensions->setWidth($maxWidth); $this->applyStyleWidth(); + return $this; } $width = $this->document->getCurrentPage()->getDimensions()->getWidth(); - if ($this->getDimensions()->getWidth()!==$width) { + if ($this->getDimensions()->getWidth() !== $width) { $dimensions->setWidth($width); $this->applyStyleWidth(); foreach ($this->getChildren() as $child) { @@ -411,6 +438,7 @@ public function measureWidth() } $this->divideLines(); } + return $this; } @@ -428,10 +456,11 @@ public function groupLines() $lineGroups[$currentGroup][] = $child; } else { if (isset($lineGroups[$currentGroup])) { - $currentGroup++; + ++$currentGroup; } } } + return $lineGroups; } @@ -456,6 +485,7 @@ public function mergeLineGroups(array $lineGroups) $lines[] = $currentLine; } } + return $lines; } @@ -475,6 +505,7 @@ protected function hideEmptyLines() } } } + return $this; } @@ -488,6 +519,7 @@ protected function hideEmptyLines() protected function saveSourceLine(LineBox $line) { $this->sourceLines[] = $line->clone(); + return $this; } @@ -525,6 +557,7 @@ public function divideLines() } $this->hideEmptyLines(); unset($lines); + return $this; } @@ -552,6 +585,7 @@ public function measureHeight(bool $afterPageDividing = false) $height = Math::add($height, $style->getVerticalPaddingsWidth(), $style->getVerticalBordersWidth()); $this->getDimensions()->setHeight($height); $this->applyStyleHeight(); + return $this; } @@ -590,6 +624,7 @@ public function measureOffset() foreach ($this->getChildren() as $child) { $child->measureOffset(); } + return $this; } @@ -614,6 +649,7 @@ public function measurePosition() foreach ($this->getChildren() as $child) { $child->measurePosition(); } + return $this; } @@ -633,6 +669,7 @@ public function layout(bool $afterPageDividing = false) $this->measureOffset(); $this->alignText(); $this->measurePosition(); + return $this; } @@ -660,6 +697,7 @@ public function replacePageNumbers() } } unset($allChildren); + return $this; } @@ -694,6 +732,7 @@ public function addBackgroundColorInstructions(array $element, $pdfX, $pdfY, $wi ]; $element = array_merge($element, $bgColor); } + return $element; } @@ -727,6 +766,7 @@ public function addBackgroundImageInstructions(array $element, $pdfX, $pdfY, $wi ]; $element = array_merge($element, $bgColor); } + return $element; } @@ -748,6 +788,7 @@ public function breakPageAfter() foreach ($breakAfter as $breakBox) { $this->document->getCurrentPage()->breakAfter($breakBox); } + return $this; } @@ -771,6 +812,7 @@ public function getInstructions(): string $element = $this->addBackgroundColorInstructions($element, $pdfX, $pdfY, $width, $height); $element = $this->addBackgroundImageInstructions($element, $pdfX, $pdfY, $width, $height); $element = $this->addBorderInstructions($element, $pdfX, $pdfY, $width, $height); + return implode("\n", $element); } } diff --git a/lib/Layout/Box.php b/lib/Layout/Box.php index 0f19176..dbffc31 100644 --- a/lib/Layout/Box.php +++ b/lib/Layout/Box.php @@ -1101,8 +1101,8 @@ protected function addBorderInstructions(array $element, string $pdfX, string $p 'q', $graphicStateStr, "{$rules['border-top-color'][0]} {$rules['border-top-color'][1]} {$rules['border-top-color'][2]} rg", - "1 0 0 1 ${pdfX} ${pdfY} cm", - "${x1} ${y1} m", // move to start point + "1 0 0 1 $pdfX $pdfY cm", + "$x1 $y1 m", // move to start point $path . ' l h', 'f', 'Q' @@ -1119,9 +1119,9 @@ protected function addBorderInstructions(array $element, string $pdfX, string $p $borderTop = [ 'q', $graphicStateStr, - "1 0 0 1 ${pdfX} ${pdfY} cm", + "1 0 0 1 $pdfX $pdfY cm", "{$rules['border-right-color'][0]} {$rules['border-right-color'][1]} {$rules['border-right-color'][2]} rg", - "${x2} ${y1} m", + "$x2 $y1 m", $path . ' l h', 'f', 'Q' @@ -1138,9 +1138,9 @@ protected function addBorderInstructions(array $element, string $pdfX, string $p $borderTop = [ 'q', $graphicStateStr, - "1 0 0 1 ${pdfX} ${pdfY} cm", + "1 0 0 1 $pdfX $pdfY cm", "{$rules['border-bottom-color'][0]} {$rules['border-bottom-color'][1]} {$rules['border-bottom-color'][2]} rg", - "${x1} ${y2} m", + "$x1 $y2 m", $path . ' l h', 'f', 'Q' @@ -1157,9 +1157,9 @@ protected function addBorderInstructions(array $element, string $pdfX, string $p $borderTop = [ 'q', $graphicStateStr, - "1 0 0 1 ${pdfX} ${pdfY} cm", + "1 0 0 1 $pdfX $pdfY cm", "{$rules['border-left-color'][0]} {$rules['border-left-color'][1]} {$rules['border-left-color'][2]} rg", - "${x1} ${y1} m", + "$x1 $y1 m", $path . ' l h', 'f', 'Q' diff --git a/lib/Layout/ElementBox.php b/lib/Layout/ElementBox.php index df8d301..c1f83c0 100644 --- a/lib/Layout/ElementBox.php +++ b/lib/Layout/ElementBox.php @@ -46,6 +46,7 @@ public function setElement(Element $element) { $this->element = $element; $element->setBox($this); + return $this; } @@ -71,6 +72,7 @@ public function getBoxesByTagName(string $tagName) } } } + return $boxes; } @@ -116,7 +118,7 @@ public function fixTables() foreach ($columns as $columnIndex => $column) { if ($column->getRowSpan() > 1) { $rowSpans = $column->getRowSpan(); - for ($i = 1; $i < $rowSpans; $i++) { + for ($i = 1; $i < $rowSpans; ++$i) { $nextRow = $rowGroup->getChildren()[$rowIndex + $i]; $rowChildren = $nextRow->getChildren(); $insertColumn = $nextRow->removeChild($nextRow->createColumnBox()); @@ -138,7 +140,7 @@ public function fixTables() foreach ($rowGroup->getChildren() as $row) { $columns = $row->getChildren(); $missing = $columnsCount - count($columns); - for ($i = 0; $i < $missing; $i++) { + for ($i = 0; $i < $missing; ++$i) { $column = $row->createColumnBox(); $column->createCellBox(); } @@ -157,7 +159,7 @@ public function fixTables() } else { if (isset($rowSpans[$columnIndex]) && $rowSpans[$columnIndex] > 1) { if ($rowSpansUp[$columnIndex] < $rowSpans[$columnIndex]) { - $rowSpansUp[$columnIndex]++; + ++$rowSpansUp[$columnIndex]; $column->setRowSpanUp($rowSpansUp[$columnIndex]); $row->setRowSpanUp(max($row->getRowSpanUp(), $rowSpansUp[$columnIndex])); $row->setRowSpan(max($row->getRowSpan(), $column->getRowSpan())); @@ -172,6 +174,7 @@ public function fixTables() } } } + return $this; } @@ -186,6 +189,7 @@ public function spanAllRows() foreach ($tablesBoxes as $tableBox) { $tableBox->spanRows(); } + return $this; } @@ -209,20 +213,31 @@ public function buildTree($parentBlock = null) if ($childDomElement instanceof \DOMComment) { continue; } - $styleStr = ''; - if ($childDomElement instanceof \DOMElement && $childDomElement->hasAttribute('style')) { - $styleStr = $childDomElement->getAttribute('style'); - } + $element = (new Element()) ->setDocument($this->document) ->setDOMElement($childDomElement) ->init(); - // for now only basic style is used - from current element only (with defaults) $style = (new \YetiForcePDF\Style\Style()) ->setDocument($this->document) - ->setElement($element) - ->setContent($styleStr) - ->parseInline(); + ->setElement($element); + if ($childDomElement instanceof \DOMElement) { + if ($childDomElement->hasAttribute('style')) { + // for now only basic style is used - from current element only (with defaults) + $style->setContent($childDomElement->getAttribute('style')); + } elseif ($childDomElement->nodeName === 'style') { + $style->parseCss($childDomElement->nodeValue); + }elseif ($childDomElement->hasAttribute('class')) { + $classNames = []; + foreach (explode(' ', $childDomElement->getAttribute('class')) as $className) { + if (trim($className)) { + $classNames[] = '.' . $className; + } + } + $element->setClassNames(implode(' ', $classNames)); + } + } + $style = $style->parseInline(); $display = $style->getRules('display'); switch ($display) { case 'block': @@ -239,34 +254,48 @@ public function buildTree($parentBlock = null) } else { $this->appendBlockBox($childDomElement, $element, $style, $parentBlock); } + break; case 'table': $tableWrapper = $this->appendTableWrapperBox($childDomElement, $element, $style, $parentBlock); $tableWrapper->appendTableBox($childDomElement, $element, $style, $parentBlock); + break; case 'table-row-group': case 'table-header-group': case 'table-footer-group': $this->appendTableRowGroupBox($childDomElement, $element, $style, $parentBlock, $display); + break; case 'table-row': $this->appendTableRowBox($childDomElement, $element, $style, $parentBlock); + break; case 'table-cell': $this->appendTableCellBox($childDomElement, $element, $style, $parentBlock); + break; case 'inline': $inline = $this->appendInlineBox($childDomElement, $element, $style, $parentBlock); if (isset($inline) && $childDomElement instanceof \DOMText) { $inline->setAnonymous(true)->appendText($childDomElement, null, null, $parentBlock); } + break; case 'inline-block': $this->appendInlineBlockBox($childDomElement, $element, $style, $parentBlock); + + break; + case 'none': + if ($childDomElement->nodeName === 'style') { + $this->appendStyleBox($childDomElement, $element, $style, $parentBlock); + } + break; } } } + return $this; } } diff --git a/lib/Layout/StyleBox.php b/lib/Layout/StyleBox.php new file mode 100644 index 0000000..861d9b1 --- /dev/null +++ b/lib/Layout/StyleBox.php @@ -0,0 +1,33 @@ + + */ + +namespace YetiForcePDF\Layout; + +/** + * Class StyleBox. + */ +class StyleBox extends BlockBox +{ + /** + * @var bool do we need to measure this box ? + */ + protected $forMeasurement = false; + /** + * @var bool is this element show up in view? take space? + */ + protected $renderable = false; + /** + * @var bool + */ + protected $displayable = false; +} diff --git a/lib/Style/Style.php b/lib/Style/Style.php index a9bdc3f..0966309 100644 --- a/lib/Style/Style.php +++ b/lib/Style/Style.php @@ -13,6 +13,7 @@ namespace YetiForcePDF\Style; +use Sabberworm\CSS\Parser as CSSParser; use YetiForcePDF\Html\Element; use YetiForcePDF\Layout\Box; use YetiForcePDF\Layout\InlineBox; @@ -35,7 +36,7 @@ class Style extends \YetiForcePDF\Base /** * CSS text to parse. * - * @var string|null + * @var null|string */ protected $content; /** @@ -74,6 +75,7 @@ class Style extends \YetiForcePDF\Base * @var string */ protected $maxLineHeight; + /** * Css properties that are inherited by default. * @@ -206,72 +208,99 @@ class Style extends \YetiForcePDF\Base 'color' => 'blue', ], 'a:visited:active' => [ - 'color' => 'blue', ], + 'color' => 'blue', + ], 'address' => [ 'display' => 'block', - 'font-style' => 'italic', ], + 'font-style' => 'italic', + ], 'area' => [ - 'display' => 'none', ], + 'display' => 'none', + ], 'article' => [ - 'display' => 'block', ], + 'display' => 'block', + ], 'aside' => [ - 'display' => 'block', ], + 'display' => 'block', + ], 'b' => [ - 'font-weight' => 'bold', ], + 'font-weight' => 'bold', + ], 'bdo' => [ - 'unicode-bidi' => 'bidi-override', ], + 'unicode-bidi' => 'bidi-override', + ], 'blockquote' => [ 'display' => 'block', 'margin-top' => '1em', 'margin-bottom' => '1em', 'margin-left' => '40px', - 'margin-right' => '40px', ], + 'margin-right' => '40px', + ], 'body' => [ 'display' => 'block', - 'margin' => '8px', ], + 'margin' => '8px', + ], 'body:focus' => [ - 'outline' => 'none', ], + 'outline' => 'none', + ], 'br' => [ - 'display' => 'block', ], + 'display' => 'block', + ], 'button' => [ 'display' => 'inline-block', - 'padding' => '10px', ], + 'padding' => '10px', + ], 'caption' => [ 'display' => 'table-caption', - 'text-align' => 'center', ], + 'text-align' => 'center', + ], 'cite' => [ - 'font-style' => 'italic', ], + 'font-style' => 'italic', + ], 'code' => [ - 'font-family' => 'monospace', ], + 'font-family' => 'monospace', + ], 'col' => [ - 'display' => 'table-column', ], + 'display' => 'table-column', + ], 'colgroup' => [ - 'display:table-column-group', ], + 'display:table-column-group', + ], 'datalist' => [ - 'display' => 'none', ], + 'display' => 'none', + ], 'dd' => [ 'display' => 'block', - 'margin-left' => '40px', ], + 'margin-left' => '40px', + ], 'del' => [ - 'text-decoration' => 'line-through', ], + 'text-decoration' => 'line-through', + ], 'details' => [ - 'display' => 'block', ], + 'display' => 'block', + ], 'dfn' => [ - 'font-style' => 'italic', ], + 'font-style' => 'italic', + ], 'div' => [ - 'display' => 'block', ], + 'display' => 'block', + ], 'dl' => [ 'display' => 'block', 'margin-top' => '1em', 'margin-bottom' => '1em', 'margin-left' => '0', - 'margin-right' => '0', ], + 'margin-right' => '0', + ], 'dt' => [ - 'display' => 'block', ], + 'display' => 'block', + ], 'em' => [ - 'font-style' => 'italic', ], + 'font-style' => 'italic', + ], 'embed:focus' => [ - 'outline' => 'none', ], + 'outline' => 'none', + ], 'fieldset' => [ 'display' => 'block', 'margin-left' => '2px', @@ -282,18 +311,22 @@ class Style extends \YetiForcePDF\Base 'padding-right' => '0.75em', ], 'figcaption' => [ - 'display' => 'block', ], + 'display' => 'block', + ], 'figure' => [ 'display' => 'block', 'margin-top' => '1em', 'margin-bottom' => '1em', 'margin-left' => '40px', - 'margin-right' => '40px', ], + 'margin-right' => '40px', + ], 'footer' => [ - 'display' => 'block', ], + 'display' => 'block', + ], 'form' => [ 'display' => 'block', - 'margin-top' => '0em', ], + 'margin-top' => '0em', + ], 'h1' => [ 'display' => 'block', 'font-size' => '2em', @@ -301,7 +334,8 @@ class Style extends \YetiForcePDF\Base 'margin-bottom' => '0.67em', 'margin-left' => '0', 'margin-right' => '0', - 'font-weight' => 'bold', ], + 'font-weight' => 'bold', + ], 'h2' => [ 'display' => 'block', 'font-size' => '1.5em', @@ -309,7 +343,8 @@ class Style extends \YetiForcePDF\Base 'margin-bottom' => '0.83em', 'margin-left' => '0', 'margin-right' => '0', - 'font-weight' => 'bold', ], + 'font-weight' => 'bold', + ], 'h3' => [ 'display' => 'block', 'font-size' => '1.17em', @@ -317,14 +352,16 @@ class Style extends \YetiForcePDF\Base 'margin-bottom' => '1em', 'margin-left' => '0', 'margin-right' => '0', - 'font-weight' => 'bold', ], + 'font-weight' => 'bold', + ], 'h4' => [ 'display' => 'block', 'margin-top' => '1.33em', 'margin-bottom' => '1.33em', 'margin-left' => '0', 'margin-right' => '0', - 'font-weight' => 'bold', ], + 'font-weight' => 'bold', + ], 'h5' => [ 'display' => 'block', 'font-size' => '.83em', @@ -332,7 +369,8 @@ class Style extends \YetiForcePDF\Base 'margin-bottom' => '1.67em', 'margin-left' => '0', 'margin-right' => '0', - 'font-weight' => 'bold', ], + 'font-weight' => 'bold', + ], 'h6' => [ 'display' => 'block', 'font-size' => '.67em', @@ -340,11 +378,14 @@ class Style extends \YetiForcePDF\Base 'margin-bottom' => '2.33em', 'margin-left' => '0', 'margin-right' => '0', - 'font-weight' => 'bold', ], + 'font-weight' => 'bold', + ], 'head' => [ - 'display' => 'none', ], + 'display' => 'none', + ], 'header' => [ - 'display' => 'block', ], + 'display' => 'block', + ], 'hr' => [ 'display' => 'block', 'margin-top' => '0.5em', @@ -353,39 +394,54 @@ class Style extends \YetiForcePDF\Base 'margin-right' => '0px', 'border-style' => 'solid', 'border-color' => 'lightgray', - 'border-top-width' => '1px', ], + 'border-top-width' => '1px', + ], 'html' => [ - 'display' => 'block', ], + 'display' => 'block', + ], 'html:focus' => [ - 'outline' => 'none', ], + 'outline' => 'none', + ], 'i' => [ - 'font-style' => 'italic', ], + 'font-style' => 'italic', + ], 'iframe:focus' => [ - 'outline' => 'none', ], + 'outline' => 'none', + ], 'iframe[seamless]' => [ - 'display' => 'block', ], + 'display' => 'block', + ], 'img' => [ - 'display' => 'inline-block', ], + 'display' => 'inline-block', + ], 'ins' => [ - 'text-decoration' => 'underline', ], + 'text-decoration' => 'underline', + ], 'kbd' => [ - 'font-family' => 'monospace', ], + 'font-family' => 'monospace', + ], 'label' => [ - 'cursor' => 'default', ], + 'cursor' => 'default', + ], 'legend' => [ 'display' => 'block', 'padding-left' => '2px', 'padding-right' => '2px', - 'border' => 'none', ], + 'border' => 'none', + ], 'li' => [ - 'display' => 'list-item', ], + 'display' => 'list-item', + ], 'link' => [ - 'display' => 'none', ], + 'display' => 'none', + ], 'map' => [ - 'display' => 'inline', ], + 'display' => 'inline', + ], 'mark' => [ 'background-color' => 'yellow', - 'color' => 'black', ], + 'color' => 'black', + ], 'menu' => [ 'display' => 'block', 'list-style-type' => 'disc', @@ -393,11 +449,14 @@ class Style extends \YetiForcePDF\Base 'margin-bottom' => '1em', 'margin-left' => '0', 'margin-right' => '0', - 'padding-left' => '40px', ], + 'padding-left' => '40px', + ], 'nav' => [ - 'display' => 'block', ], + 'display' => 'block', + ], 'object:focus' => [ - 'outline' => 'none', ], + 'outline' => 'none', + ], 'ol' => [ 'display' => 'block', 'list-style-type' => 'decimal', @@ -405,71 +464,95 @@ class Style extends \YetiForcePDF\Base 'margin-bottom' => '1em', 'margin-left' => '0', 'margin-right' => '0', - 'padding-left' => '40px', ], + 'padding-left' => '40px', + ], 'output' => [ - 'display' => 'inline', ], + 'display' => 'inline', + ], 'p' => [ 'display' => 'block', 'margin-top' => '1em', 'margin-bottom' => '1em', 'margin-left' => '0', - 'margin-right' => '0', ], + 'margin-right' => '0', + ], 'param' => [ - 'display' => 'none', ], + 'display' => 'none', + ], 'pre' => [ 'display' => 'block', 'font-family' => 'monospace', 'white-space' => 'pre', - 'margin' => '1em0', ], + 'margin' => '1em0', + ], 'q' => [ - 'display' => 'inline', ], + 'display' => 'inline', + ], 'q::before' => [ - 'content' => 'open-quote', ], + 'content' => 'open-quote', + ], 'q::after' => [ - 'content' => 'close-quote', ], + 'content' => 'close-quote', + ], 'rt' => [ - 'line-height' => 'normal', ], + 'line-height' => 'normal', + ], 's' => [ - 'text-decoration' => 'line-through', ], + 'text-decoration' => 'line-through', + ], 'samp' => [ - 'font-family' => 'monospace', ], + 'font-family' => 'monospace', + ], 'script' => [ - 'display' => 'none', ], + 'display' => 'none', + ], 'section' => [ - 'display' => 'block', ], + 'display' => 'block', + ], 'small' => [ - 'font-size' => 'smaller', ], + 'font-size' => 'smaller', + ], 'strike' => [ - 'text-decoration' => 'line-through', ], + 'text-decoration' => 'line-through', + ], 'strong' => [ - 'font-weight' => 'bold', ], + 'font-weight' => 'bold', + ], 'style' => [ - 'display' => 'none', ], + 'display' => 'none', + ], 'sub' => [ 'vertical-align' => 'sub', - 'font-size' => 'smaller', ], + 'font-size' => 'smaller', + ], 'summary' => [ - 'display' => 'block', ], + 'display' => 'block', + ], 'sup' => [ 'vertical-align' => 'super', - 'font-size' => 'smaller', ], + 'font-size' => 'smaller', + ], 'table' => [ 'display' => 'table', 'border-collapse' => 'separate', 'border-spacing' => '2px', - 'border-color' => 'gray', ], + 'border-color' => 'gray', + ], 'tbody' => [ 'display' => 'table-row-group', 'vertical-align' => 'middle', - 'border-color' => 'inherit', ], + 'border-color' => 'inherit', + ], 'td' => [ 'display' => 'table-cell', 'vertical-align' => 'inherit', - 'padding' => '1px', ], + 'padding' => '1px', + ], 'tfoot' => [ 'display' => 'table-footer-group', 'vertical-align' => 'middle', - 'border-color' => 'inherit', ], + 'border-color' => 'inherit', + ], 'th' => [ 'display' => 'table-cell', 'vertical-align' => 'inherit', @@ -481,15 +564,19 @@ class Style extends \YetiForcePDF\Base 'thead' => [ 'display' => 'table-header-group', 'vertical-align' => 'middle', - 'border-color' => 'inherit', ], + 'border-color' => 'inherit', + ], 'title' => [ - 'display' => 'none', ], + 'display' => 'none', + ], 'tr' => [ 'display' => 'table-row', 'vertical-align' => 'inherit', - 'border-color' => 'inherit', ], + 'border-color' => 'inherit', + ], 'u' => [ - 'text-decoration' => 'underline', ], + 'text-decoration' => 'underline', + ], 'ul' => [ 'display' => 'block', 'list-style-type' => 'disc', @@ -497,9 +584,11 @@ class Style extends \YetiForcePDF\Base 'margin-bottom' => '1em', 'margin-left' => '0', 'margin-right' => '0', - 'padding-left' => '40px', ], + 'padding-left' => '40px', + ], 'var' => [ - 'font-style' => 'italic', ], + 'font-style' => 'italic', + ], ]; /** @@ -634,7 +723,7 @@ public function setMargins(string $top = null, string $right = null, string $bot /** * Get parent style. * - * @return Style|null + * @return null|Style */ public function getParent() { @@ -1235,7 +1324,7 @@ public function applyBorderSpacing($rulesParsed) * * @param string $ruleName * - * @return string|null + * @return null|string */ public function getParentOriginalValue(string $ruleName) { @@ -1249,6 +1338,20 @@ public function getParentOriginalValue(string $ruleName) } } + /** + * Import selector rules if exists. + * + * @param array $parsed rules + */ + protected function importSelectors() + { + $element = $this->getElement(); + if($element && !empty($element->getClassNames())){ + return $this->document->getCssSelectorRules($element->getClassNames()); + } + return []; + } + /** * Parse css style. * @@ -1303,6 +1406,7 @@ protected function parse() } $rulesParsed = $this->parseImage($rulesParsed); $rulesParsed = $this->parseGraphicState($rulesParsed, $inherited); + $selectorRules = $this->importSelectors(); $finalRules = []; foreach ($rulesParsed as $ruleName => $ruleValue) { if (is_string($ruleValue) && strtolower($ruleValue) === 'inherit') { @@ -1312,7 +1416,10 @@ protected function parse() } } $this->originalRules[$ruleName] = $ruleValue; - if (!isset($inherited[$ruleName])) { + if(isset($selectorRules[$ruleName])){ + $ruleValue = $selectorRules[$ruleName]; + } + if (!isset($inherited[$ruleName]) || isset($selectorRules[$ruleName])) { $normalizerName = \YetiForcePDF\Style\Normalizer\Normalizer::getNormalizerClassName($ruleName); $normalizer = (new $normalizerName()) ->setDocument($this->document) @@ -1330,7 +1437,8 @@ protected function parse() $finalRules['margin-bottom'] = '0'; } elseif (in_array($finalRules['display'], [ 'table-cell', 'table-row', 'table-row-group', 'table-column', - 'table-column-group', 'table-header-group', 'table-footer-group'])) { + 'table-column-group', 'table-header-group', 'table-footer-group' + ])) { $finalRules['margin-top'] = '0'; $finalRules['margin-bottom'] = '0'; $finalRules['margin-left'] = '0'; @@ -1338,7 +1446,7 @@ protected function parse() } $this->rules = $finalRules; $this->parsed = true; - unset($finalRules,$rules,$parsed,$rulesParsed,$ruleParsed, $defaultRules, $inherited); + unset($finalRules, $rules, $parsed, $rulesParsed, $ruleParsed, $defaultRules, $inherited); return $this; } @@ -1457,7 +1565,7 @@ public function fixTables(bool $removeBottomBorders) } } } - unset($rowGroup,$boxes,$rows,$columns); + unset($rowGroup, $boxes, $rows, $columns); return $this; } @@ -1573,6 +1681,29 @@ public function clearMiddleInline() return $this; } + /** + * Parse css rules. + * + * @param string $cssString + * + * @return $this + */ + public function parseCss(string $cssString): self + { + $css = (new CSSParser($cssString))->parse(); + foreach ($css->getAllRuleSets() as $ruleSet) { + $selector = implode(' ', $ruleSet->getSelector()); + $selectorRules = (array) $ruleSet->getRules(); + $rules = []; + foreach ($selectorRules as $rule) { + $rules[$rule->getRule()] = trim((string) $rule->getValue(), '" \''); + } + $this->document->addCssSelectorRules($selector, $rules); + } + + return $this; + } + /** * Get transformations. * @@ -1602,7 +1733,7 @@ public function __clone() { $this->font = clone $this->font; if ($this->element) { - $this->elemet = clone $this->element; + $this->element = clone $this->element; } } }