diff --git a/composer.json b/composer.json index 4a0a5789f1..7e0deaa092 100644 --- a/composer.json +++ b/composer.json @@ -68,7 +68,6 @@ "ext-dom": "*", "ext-json": "*", "ext-xml": "*", - "laminas/laminas-escaper": ">=2.6", "phpoffice/math": "^0.1" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 23452781b4..0f140f1e5a 100644 --- a/composer.lock +++ b/composer.lock @@ -6,68 +6,6 @@ ], "content-hash": "23680170abecc52de95d0833296ced64", "packages": [ - { - "name": "laminas/laminas-escaper", - "version": "2.12.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-escaper.git", - "reference": "ee7a4c37bf3d0e8c03635d5bddb5bb3184ead490" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/ee7a4c37bf3d0e8c03635d5bddb5bb3184ead490", - "reference": "ee7a4c37bf3d0e8c03635d5bddb5bb3184ead490", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "ext-mbstring": "*", - "php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0" - }, - "conflict": { - "zendframework/zend-escaper": "*" - }, - "require-dev": { - "infection/infection": "^0.26.6", - "laminas/laminas-coding-standard": "~2.4.0", - "maglnet/composer-require-checker": "^3.8.0", - "phpunit/phpunit": "^9.5.18", - "psalm/plugin-phpunit": "^0.17.0", - "vimeo/psalm": "^4.22.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Laminas\\Escaper\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "Securely and safely escape HTML, HTML attributes, JavaScript, CSS, and URLs", - "homepage": "https://laminas.dev", - "keywords": [ - "escaper", - "laminas" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-escaper/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-escaper/issues", - "rss": "https://github.com/laminas/laminas-escaper/releases.atom", - "source": "https://github.com/laminas/laminas-escaper" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2022-10-10T10:11:09+00:00" - }, { "name": "phpoffice/math", "version": "0.1.0", diff --git a/docs/usage/styles/font.md b/docs/usage/styles/font.md index 90e492bf0e..94b59b6500 100644 --- a/docs/usage/styles/font.md +++ b/docs/usage/styles/font.md @@ -23,4 +23,6 @@ Available Font style options: - ``lang``. Language, either a language code like *en-US*, *fr-BE*, etc. or an object (or as an array) if you need to set eastAsian or bidirectional languages See ``\PhpOffice\PhpWord\Style\Language`` class for some language codes. - ``position``. The text position, raised or lowered, in half points -- ``hidden``. Hidden text, *true* or *false*. \ No newline at end of file +- ``hidden``. Hidden text, *true* or *false*. +`htmlWhiteSpace``. How white space is handled when generating html/pdf. Possible values are *pre-wrap* and *normal* (other css values for white space are accepted, but are not expected to be useful). +- ``htmlGenericFont``. Fallback generic font for html/pdf. Possible values are *sans-serif*, *serif*, and *monospace* (other css values for generic fonts are accepted). diff --git a/docs/usage/writers.md b/docs/usage/writers.md index 684abeeeac..f68008bf87 100644 --- a/docs/usage/writers.md +++ b/docs/usage/writers.md @@ -10,6 +10,15 @@ $writer = IOFactory::createWriter($oPhpWord, 'HTML'); $writer->save(__DIR__ . '/sample.html'); ``` + +When generating html/pdf, you can alter the default handling of white space (normal), +and/or supply a fallback generic font as follows: + +```php + $phpWord->setDefaultHtmlGenericFont('serif'); + $phpWord->setDefaultHtmlWhiteSpace('pre-wrap'); +``` + ## ODText The name of the writer is `ODText`. diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 088d4c5427..8c75ddf8a3 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -520,16 +520,6 @@ parameters: count: 1 path: src/PhpWord/Shared/Microsoft/PasswordEncoder.php - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLReader\\:\\:getElements\\(\\) should return DOMNodeList\\ but returns DOMNodeList\\\\.$#" - count: 1 - path: src/PhpWord/Shared/XMLReader.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLReader\\:\\:getElements\\(\\) should return DOMNodeList\\ but returns DOMNodeList\\\\|false\\.$#" - count: 2 - path: src/PhpWord/Shared/XMLReader.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:getData\\(\\) should return string but returns string\\|false\\.$#" count: 1 @@ -1180,46 +1170,11 @@ parameters: count: 1 path: src/PhpWord/Writer/HTML/Element/AbstractElement.php - - - message: "#^Call to an undefined method Laminas\\\\Escaper\\\\Escaper\\|PhpOffice\\\\PhpWord\\\\Escaper\\\\AbstractEscaper\\:\\:escapeHtml\\(\\)\\.$#" - count: 1 - path: src/PhpWord/Writer/HTML/Element/Link.php - - - - message: "#^Call to an undefined method Laminas\\\\Escaper\\\\Escaper\\|PhpOffice\\\\PhpWord\\\\Escaper\\\\AbstractEscaper\\:\\:escapeHtmlAttr\\(\\)\\.$#" - count: 1 - path: src/PhpWord/Writer/HTML/Element/Link.php - - - - message: "#^Call to an undefined method Laminas\\\\Escaper\\\\Escaper\\|PhpOffice\\\\PhpWord\\\\Escaper\\\\AbstractEscaper\\:\\:escapeHtml\\(\\)\\.$#" - count: 1 - path: src/PhpWord/Writer/HTML/Element/ListItem.php - - message: "#^Variable \\$row in PHPDoc tag @var does not match assigned variable \\$rowStyle\\.$#" count: 1 path: src/PhpWord/Writer/HTML/Element/Table.php - - - message: "#^Call to an undefined method Laminas\\\\Escaper\\\\Escaper\\|PhpOffice\\\\PhpWord\\\\Escaper\\\\AbstractEscaper\\:\\:escapeHtml\\(\\)\\.$#" - count: 2 - path: src/PhpWord/Writer/HTML/Element/Text.php - - - - message: "#^Call to an undefined method Laminas\\\\Escaper\\\\Escaper\\|PhpOffice\\\\PhpWord\\\\Escaper\\\\AbstractEscaper\\:\\:escapeHtml\\(\\)\\.$#" - count: 1 - path: src/PhpWord/Writer/HTML/Element/Title.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\HTML\\\\Style\\\\AbstractStyle\\:\\:write\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Writer/HTML/Style/AbstractStyle.php - - - - message: "#^Else branch is unreachable because previous condition is always true\\.$#" - count: 1 - path: src/PhpWord/Writer/HTML/Style/Paragraph.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Element\\\\Field\\:\\:writeDefault\\(\\) has parameter \\$type with no type specified\\.$#" count: 1 @@ -1275,11 +1230,6 @@ parameters: count: 1 path: src/PhpWord/Writer/ODText/Part/Styles.php - - - message: "#^Variable \\$indent in empty\\(\\) always exists and is not falsy\\.$#" - count: 1 - path: src/PhpWord/Writer/ODText/Style/Paragraph.php - - message: "#^Parameter \\#1 \\$callback of function call_user_func_array expects callable\\(\\)\\: mixed, array\\{PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\AbstractRenderer, string\\} given\\.$#" count: 1 @@ -1330,11 +1280,6 @@ parameters: count: 1 path: src/PhpWord/Writer/RTF/Element/AbstractElement.php - - - message: "#^Call to an undefined method Laminas\\\\Escaper\\\\Escaper\\|PhpOffice\\\\PhpWord\\\\Escaper\\\\AbstractEscaper\\:\\:escape\\(\\)\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/AbstractElement.php - - message: "#^Parameter \\#1 \\$value of method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Style\\\\Font\\:\\:setNameIndex\\(\\) expects int, int\\|string given\\.$#" count: 1 @@ -1415,21 +1360,6 @@ parameters: count: 1 path: src/PhpWord/Writer/RTF/Style/Border.php - - - message: "#^Variable \\$spaceAfter on left side of \\?\\? always exists and is not nullable\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Style/Paragraph.php - - - - message: "#^Variable \\$spaceBefore on left side of \\?\\? always exists and is not nullable\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Style/Paragraph.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Style\\\\Tab\\:\\:write\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Style/Tab.php - - message: "#^PHPDoc tag @param has invalid value \\(\\\\PhpOffice\\\\PhpWord\\\\PhpWord\\)\\: Unexpected token \"\\\\n \", expected variable at offset 86$#" count: 1 @@ -1480,16 +1410,6 @@ parameters: count: 1 path: src/PhpWord/Writer/Word2007/Element/SDT.php - - - message: "#^Parameter \\#1 \\$content of method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\AbstractElement\\:\\:writeText\\(\\) expects string, PhpOffice\\\\PhpWord\\\\Element\\\\TextRun\\|string given\\.$#" - count: 1 - path: src/PhpWord/Writer/Word2007/Element/TOC.php - - - - message: "#^Parameter \\#3 \\$indent of method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\TOC\\:\\:writeStyle\\(\\) expects int, float\\|int given\\.$#" - count: 1 - path: src/PhpWord/Writer/Word2007/Element/TOC.php - - message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\TableAlignment\\:\\:\\$attributes has no type specified\\.$#" count: 1 @@ -1540,11 +1460,6 @@ parameters: count: 1 path: src/PhpWord/Writer/Word2007/Style/Font.php - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\WriterInterface\\:\\:save\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Writer/WriterInterface.php - - message: "#^Call to an undefined method object\\:\\:read\\(\\)\\.$#" count: 1 @@ -1905,21 +1820,6 @@ parameters: count: 2 path: tests/PhpWordTests/TemplateProcessorTest.php - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\TemplateProcessorTest\\:\\:testTemplateCanBeSavedInTemporaryLocation\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/PhpWordTests/TemplateProcessorTest.php - - - - message: "#^Parameter \\#1 \\$expectedXml of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertXmlStringEqualsXmlString\\(\\) expects DOMDocument\\|string, string\\|false given\\.$#" - count: 3 - path: tests/PhpWordTests/TemplateProcessorTest.php - - - - message: "#^Parameter \\#2 \\$actualXml of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertXmlStringEqualsXmlString\\(\\) expects DOMDocument\\|string, string\\|false given\\.$#" - count: 3 - path: tests/PhpWordTests/TemplateProcessorTest.php - - message: "#^Parameter \\#2 \\$haystack of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertStringContainsString\\(\\) expects string, string\\|false given\\.$#" count: 6 @@ -2000,11 +1900,6 @@ parameters: count: 1 path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:save\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setFont\\(\\)\\.$#" count: 1 @@ -2030,26 +1925,11 @@ parameters: count: 3 path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:save\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/MPDFTest.php - - - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" - count: 2 - path: tests/PhpWordTests/Writer/PDF/MPDFTest.php - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getFont\\(\\)\\.$#" count: 1 path: tests/PhpWordTests/Writer/PDF/MPDFTest.php - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:save\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/TCPDFTest.php - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" count: 2 @@ -2060,16 +1940,6 @@ parameters: count: 1 path: tests/PhpWordTests/Writer/PDF/TCPDFTest.php - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:save\\(\\)\\.$#" - count: 2 - path: tests/PhpWordTests/Writer/PDFTest.php - - - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDFTest.php - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\ElementTest\\:\\:removeCr\\(\\) has no return type specified\\.$#" count: 1 diff --git a/phpunit.xml.dist b/phpunit.xml.dist index a81487036d..6f1f5445ab 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,27 +1,24 @@ - - - - ./tests/PhpWordTests - - - - - ./src - - ./src/PhpWordTests/Shared/PCLZip - - - - - - - + + + + + ./src + + + ./src/PhpWord/Shared/PCLZip + + + + + + + + + + + + ./tests/PhpWordTests + + + diff --git a/samples/Sample_36_RTL.php b/samples/Sample_36_RTL.php index 4b3e760465..b3baca90cb 100644 --- a/samples/Sample_36_RTL.php +++ b/samples/Sample_36_RTL.php @@ -2,9 +2,15 @@ include_once 'Sample_Header.php'; +use PhpOffice\PhpWord\Settings; + // New Word document echo date('H:i:s'), ' Create new PhpWord object', EOL; $phpWord = new \PhpOffice\PhpWord\PhpWord(); +$phpWord->setDefaultFontName('DejaVu Sans'); // for good rendition of PDF +$rendererName = Settings::PDF_RENDERER_MPDF; +$rendererLibraryPath = $vendorDirPath . '/mpdf/mpdf'; +Settings::setPdfRenderer($rendererName, $rendererLibraryPath); // New section $section = $phpWord->addSection(); @@ -26,15 +32,15 @@ //Vidually bidirectinal table $table->addRow(); -$cell = $table->addCell(500, $cellVCentered); +$cell = $table->addCell(1500, $cellVCentered); $textrun = $cell->addTextRun($cellHCentered); $textrun->addText('ردیف', $style); -$cell = $table->addCell(11000); +$cell = $table->addCell(2000); $textrun = $cell->addTextRun($cellHEnd); $textrun->addText('سوالات', $style); -$cell = $table->addCell(500, $cellVCentered); +$cell = $table->addCell(1000, $cellVCentered); $textrun = $cell->addTextRun($cellHCentered); $textrun->addText('بارم', $style); diff --git a/samples/Sample_52_RTLDefault.php b/samples/Sample_52_RTLDefault.php new file mode 100644 index 0000000000..080df5571b --- /dev/null +++ b/samples/Sample_52_RTLDefault.php @@ -0,0 +1,34 @@ +setDefaultFontName('DejaVu Sans'); // for good rendition of PDF +$rendererName = Settings::PDF_RENDERER_MPDF; +$rendererLibraryPath = $vendorDirPath . '/mpdf/mpdf'; +Settings::setPdfRenderer($rendererName, $rendererLibraryPath); + +// New section +$section = $phpWord->addSection(); +$arabic = '

الألم الذي ربما تنجم عنه بعض ا.

'; +$english = '

LTR in RTL document.

'; +SharedHtml::addHtml($section, $arabic, false, false); +SharedHtml::addHtml($section, $english, false, false); +SharedHtml::addHtml($section, $english, false, false); +SharedHtml::addHtml($section, $arabic, false, false); +SharedHtml::addHtml($section, $arabic, false, false); + +// Save file +echo write($phpWord, basename(__FILE__, '.php'), $writers); +if (!CLI) { + include_once 'Sample_Footer.php'; +} +Style::setDefaultRtl(null); diff --git a/src/PhpWord/Element/Cell.php b/src/PhpWord/Element/Cell.php index 7f2a189b83..b0aa0c4ed4 100644 --- a/src/PhpWord/Element/Cell.php +++ b/src/PhpWord/Element/Cell.php @@ -46,7 +46,7 @@ class Cell extends AbstractContainer /** * Create new instance. * - * @param int $width + * @param null|int $width * @param array|\PhpOffice\PhpWord\Style\Cell $style */ public function __construct($width = null, $style = null) diff --git a/src/PhpWord/Element/Image.php b/src/PhpWord/Element/Image.php index f92f4bd572..1f1a62500a 100644 --- a/src/PhpWord/Element/Image.php +++ b/src/PhpWord/Element/Image.php @@ -417,10 +417,10 @@ public function getImageStringData($base64 = false) } if ($base64) { - return chunk_split(base64_encode($imageBinary)); + return base64_encode($imageBinary); } - return chunk_split(bin2hex($imageBinary)); + return bin2hex($imageBinary); } /** diff --git a/src/PhpWord/PhpWord.php b/src/PhpWord/PhpWord.php index da57f38d29..489119052b 100644 --- a/src/PhpWord/PhpWord.php +++ b/src/PhpWord/PhpWord.php @@ -256,6 +256,68 @@ public function setDefaultFontName($fontName): void Settings::setDefaultFontName($fontName); } + /** + * Default generic name for default font for html. + * + * @var string + */ + private $defaultHtmlGenericFont = ''; + + /** + * Get generic name for default font for html. + * + * @return string + */ + public function getDefaultHtmlGenericFont() + { + return $this->defaultHtmlGenericFont; + } + + /** + * Set generic name for default font for html. + * + * @param string $value + * + * @return bool + */ + public function setDefaultHtmlGenericFont($value) + { + $this->defaultHtmlGenericFont = \PhpOffice\PhpWord\Style\Font::validateGenericFont($value); + + return '' !== $this->defaultHtmlGenericFont; + } + + /** + * Default white space style for html. + * + * @var string + */ + private $defaultHtmlWhiteSpace = ''; + + /** + * Get default white space style for html. + * + * @return string + */ + public function getDefaultHtmlWhiteSpace() + { + return $this->defaultHtmlWhiteSpace; + } + + /** + * Set default white space style for html. + * + * @param string $value + * + * @return bool + */ + public function setDefaultHtmlWhiteSpace($value) + { + $this->defaultHtmlWhiteSpace = \PhpOffice\PhpWord\Style\Font::validateWhiteSpace($value); + + return '' !== $this->defaultHtmlWhiteSpace; + } + /** * Get default font size. * @@ -325,52 +387,4 @@ public function save($filename, $format = 'Word2007', $download = false) return true; } - - /** - * Create new section. - * - * @deprecated 0.10.0 - * - * @param array $settings - * - * @return \PhpOffice\PhpWord\Element\Section - * - * @codeCoverageIgnore - */ - public function createSection($settings = null) - { - return $this->addSection($settings); - } - - /** - * Get document properties object. - * - * @deprecated 0.12.0 - * - * @return \PhpOffice\PhpWord\Metadata\DocInfo - * - * @codeCoverageIgnore - */ - public function getDocumentProperties() - { - return $this->getDocInfo(); - } - - /** - * Set document properties object. - * - * @deprecated 0.12.0 - * - * @param \PhpOffice\PhpWord\Metadata\DocInfo $documentProperties - * - * @return self - * - * @codeCoverageIgnore - */ - public function setDocumentProperties($documentProperties) - { - $this->metadata['Document'] = $documentProperties; - - return $this; - } } diff --git a/src/PhpWord/Reader/MsDoc.php b/src/PhpWord/Reader/MsDoc.php index fed6c99188..842ba75ba8 100644 --- a/src/PhpWord/Reader/MsDoc.php +++ b/src/PhpWord/Reader/MsDoc.php @@ -57,16 +57,6 @@ class MsDoc extends AbstractReader implements ReaderInterface */ private $dataObjectPool; - /** - * Object Stream. - */ - private $_SummaryInformation; - - /** - * Object Stream. - */ - private $_DocumentSummaryInformation; - /** * @var stdClass[] */ @@ -92,6 +82,12 @@ class MsDoc extends AbstractReader implements ReaderInterface */ private $arraySections = []; + /** @var string */ + private $summaryInformation; + + /** @var string */ + private $documentSummaryInformation; + const VERSION_97 = '97'; const VERSION_2000 = '2000'; const VERSION_2002 = '2002'; @@ -160,9 +156,9 @@ private function loadOLE($filename): void // Get Data stream $this->dataObjectPool = $ole->getStream($ole->wrkObjectPool); // Get Summary Information data - $this->_SummaryInformation = $ole->getStream($ole->summaryInformation); + $this->summaryInformation = $ole->getStream($ole->summaryInformation); // Get Document Summary Information data - $this->_DocumentSummaryInformation = $ole->getStream($ole->docSummaryInfos); + $this->documentSummaryInformation = $ole->getStream($ole->docSummaryInfos); } private function getNumInLcb($lcb, $iSize) @@ -1141,7 +1137,7 @@ private function readFibContent(): void /** * Section and information about them. * - * @see http://msdn.microsoft.com/en-us/library/dd924458%28v=office.12%29.aspx + * @see : http://msdn.microsoft.com/en-us/library/dd924458%28v=office.12%29.aspx */ private function readRecordPlcfSed(): void { @@ -1187,7 +1183,7 @@ private function readRecordPlcfSed(): void /** * Specifies the fonts that are used in the document. * - * @see http://msdn.microsoft.com/en-us/library/dd943880%28v=office.12%29.aspx + * @see : http://msdn.microsoft.com/en-us/library/dd943880%28v=office.12%29.aspx */ private function readRecordSttbfFfn(): void { @@ -1271,7 +1267,7 @@ private function readRecordPlcfBtePapx(): void } $arrayRGB = []; for ($inc = 1; $inc <= $numRun; ++$inc) { - //@see http://msdn.microsoft.com/en-us/library/dd925804(v=office.12).aspx + // @see http://msdn.microsoft.com/en-us/library/dd925804(v=office.12).aspx $arrayRGB[$inc] = self::getInt1d($this->dataWorkDocument, $offset); ++$offset; // reserved @@ -1478,7 +1474,7 @@ private function readRecordPlcfBteChpx(): void $offset = $offsetBase; // ChpxFkp - //@see : http://msdn.microsoft.com/en-us/library/dd910989%28v=office.12%29.aspx + // @see : http://msdn.microsoft.com/en-us/library/dd910989%28v=office.12%29.aspx $numRGFC = self::getInt1d($this->dataWorkDocument, $offset + 511); $arrayRGFC = []; for ($inc = 0; $inc <= $numRGFC; ++$inc) { @@ -1501,7 +1497,7 @@ private function readRecordPlcfBteChpx(): void if ($rgb > 0) { // Chp Structure - //@see : http://msdn.microsoft.com/en-us/library/dd772849%28v=office.12%29.aspx + // @see : http://msdn.microsoft.com/en-us/library/dd772849%28v=office.12%29.aspx $posRGB = $offsetBase + $rgb * 2; $cb = self::getInt1d($this->dataWorkDocument, $posRGB); diff --git a/src/PhpWord/Shared/Handler.php b/src/PhpWord/Shared/Handler.php new file mode 100644 index 0000000000..72232cc9e6 --- /dev/null +++ b/src/PhpWord/Shared/Handler.php @@ -0,0 +1,46 @@ +nodeType) { $attributes = $node->attributes; // get all the attributes(eg: id, class) + $bidi = ($attributes['dir'] ?? '') === 'rtl'; foreach ($attributes as $attribute) { $val = $attribute->value; switch (strtolower($attribute->name)) { case 'align': - $styles['alignment'] = self::mapAlign(trim($val)); + $styles['alignment'] = self::mapAlign(trim($val), $bidi); break; case 'lang': @@ -680,6 +681,7 @@ protected static function parseStyle($attribute, $styles) protected static function parseStyleDeclarations(array $selectors, array $styles) { + $bidi = ($selectors['direction'] ?? '') === 'rtl'; foreach ($selectors as $property => $value) { switch ($property) { case 'text-decoration': @@ -696,7 +698,7 @@ protected static function parseStyleDeclarations(array $selectors, array $styles break; case 'text-align': - $styles['alignment'] = self::mapAlign($value); + $styles['alignment'] = self::mapAlign($value, $bidi); break; case 'display': @@ -705,6 +707,7 @@ protected static function parseStyleDeclarations(array $selectors, array $styles break; case 'direction': $styles['rtl'] = $value === 'rtl'; + $styles['bidi'] = $value === 'rtl'; break; case 'font-size': @@ -1026,20 +1029,21 @@ protected static function mapBorderColor(&$styles, $cssBorderColor): void * Transforms a HTML/CSS alignment into a \PhpOffice\PhpWord\SimpleType\Jc. * * @param string $cssAlignment + * @param bool $bidi * * @return null|string */ - protected static function mapAlign($cssAlignment) + protected static function mapAlign($cssAlignment, $bidi) { switch ($cssAlignment) { case 'right': - return Jc::END; + return $bidi ? Jc::START : Jc::END; case 'center': return Jc::CENTER; case 'justify': return Jc::BOTH; default: - return Jc::START; + return $bidi ? Jc::END : Jc::START; } } diff --git a/src/PhpWord/Shared/Text.php b/src/PhpWord/Shared/Text.php index 4a530b2e10..967fce3f95 100644 --- a/src/PhpWord/Shared/Text.php +++ b/src/PhpWord/Shared/Text.php @@ -138,21 +138,24 @@ public static function isUTF8($value = '') /** * Return UTF8 encoded value. * - * @param string $value + * @param ?string $value * - * @return string + * @return ?string */ public static function toUTF8($value = '') { if (null !== $value && !self::isUTF8($value)) { - if (PHP_VERSION_ID < 80200) { - $value = utf8_encode($value); - } else { - $value = mb_convert_encoding($value, 'UTF-8', mb_list_encodings()); - } + // utf8_encode deprecated in php8.2, but mb_convert_encoding always usable + $value = (function_exists('mb_convert_encoding')) ? mb_convert_encoding($value, 'UTF-8', 'ISO-8859-1') : utf8_encode($value); } - return $value; + return self::ensureStringOrNull($value); + } + + /** @param null|array|string $value */ + private static function ensureStringOrNull($value): ?string + { + return is_array($value) ? '' : $value; } /** diff --git a/src/PhpWord/Shared/XMLReader.php b/src/PhpWord/Shared/XMLReader.php index 99c59931e9..9acde47d1f 100644 --- a/src/PhpWord/Shared/XMLReader.php +++ b/src/PhpWord/Shared/XMLReader.php @@ -104,17 +104,15 @@ public function getDomFromString($content) public function getElements($path, ?DOMElement $contextNode = null) { if ($this->dom === null) { - return new DOMNodeList(); + return new DOMNodeList(); // @phpstan-ignore-line } if ($this->xpath === null) { $this->xpath = new DOMXpath($this->dom); } - if (null === $contextNode) { - return $this->xpath->query($path); - } + $result = @$this->xpath->query($path, $contextNode); - return $this->xpath->query($path, $contextNode); + return empty($result) ? new DOMNodeList() : $result; // @phpstan-ignore-line } /** diff --git a/src/PhpWord/Style.php b/src/PhpWord/Style.php index cd27bffec6..f280c9cc2a 100644 --- a/src/PhpWord/Style.php +++ b/src/PhpWord/Style.php @@ -35,6 +35,9 @@ class Style */ private static $styles = []; + /** @var ?bool */ + private static $defaultRtl; + /** * Add paragraph style. * @@ -144,6 +147,7 @@ public static function countStyles() public static function resetStyles(): void { self::$styles = []; + self::$defaultRtl = null; } /** @@ -214,4 +218,16 @@ private static function setStyleValues($name, $style, $value = null) return self::getStyle($name); } + + /** @param ?bool $defaultRtl */ + public static function setDefaultRtl($defaultRtl): void + { + self::$defaultRtl = $defaultRtl; + } + + /** @return ?bool */ + public static function getDefaultRtl() + { + return self::$defaultRtl; + } } diff --git a/src/PhpWord/Style/Font.php b/src/PhpWord/Style/Font.php index d0cc2503e1..6c81b003c6 100644 --- a/src/PhpWord/Style/Font.php +++ b/src/PhpWord/Style/Font.php @@ -17,6 +17,8 @@ namespace PhpOffice\PhpWord\Style; +use PhpOffice\PhpWord\Style; + /** * Font style. */ @@ -230,7 +232,7 @@ class Font extends AbstractStyle /** * Right to left languages. * - * @var bool + * @var ?bool */ private $rtl; @@ -245,7 +247,7 @@ class Font extends AbstractStyle /** * Languages. * - * @var \PhpOffice\PhpWord\Style\Language + * @var null|\PhpOffice\PhpWord\Style\Language */ private $lang; @@ -288,6 +290,8 @@ public function __construct($type = 'text', $paragraph = null) */ public function getStyleValues() { + $hws = 'htmlWhiteSpace'; + $hgf = 'htmlGenericFont'; $styles = [ 'name' => $this->getStyleName(), 'basic' => [ @@ -319,6 +323,8 @@ public function getStyleValues() 'rtl' => $this->isRTL(), 'shading' => $this->getShading(), 'lang' => $this->getLang(), + $hws => $this->getHtmlWhiteSpace(), + $hgf => $this->getHtmlGenericFont(), ]; return $styles; @@ -827,17 +833,17 @@ public function setParagraph($value = null) /** * Get rtl. * - * @return bool + * @return ?bool */ public function isRTL() { - return $this->rtl; + return $this->rtl ?? Style::getDefaultRtl(); } /** * Set rtl. * - * @param bool $value + * @param ?bool $value * * @return self */ @@ -875,7 +881,7 @@ public function setShading($value = null) /** * Get language. * - * @return \PhpOffice\PhpWord\Style\Language + * @return null|\PhpOffice\PhpWord\Style\Language */ public function getLang() { @@ -946,4 +952,114 @@ public function setPosition($value = null) return $this; } + + /** + * Preservation of white space in html. + * + * @var string Value used for css white-space + */ + private $htmlWhiteSpace = ''; + + /** + * Validate html css white-space value. It is expected that only pre-wrap and normal (default) are useful. + * + * @param string $value Should be one of pre-wrap, normal, nowrap, pre, pre-line, initial, inherit + * + * @return string value if valid, null string if not + */ + public static function validateWhiteSpace($value) + { + switch ($value) { + case 'pre-wrap': + case 'normal': + case 'nowrap': + case 'pre': + case 'pre-line': + case 'initial': + case 'inherit': + return $value; + default: + return ''; + } + } + + /** + * Set html css white-space value. It is expected that only pre-wrap and normal (default) are useful. + * + * @param string $value Should be one of pre-wrap, normal, nowrap, pre, pre-line, initial, inherit + * + * @return self + */ + public function setHtmlWhiteSpace($value) + { + $this->htmlWhiteSpace = self::validateWhiteSpace($value); + + return $this; + } + + /** + * Get html css white-space value. + * + * @return string + */ + public function getHtmlWhiteSpace() + { + return $this->htmlWhiteSpace; + } + + /** + * Generic font as fallback for html. + * + * @var string generic font name + */ + private $htmlGenericFont = ''; + + /** + * Validate generic font for fallback for html. + * + * @param string $value generic font name + * + * @return string value if legitimate, null string if not + */ + public static function validateGenericFont($value) + { + switch ($value) { + case 'serif': + case 'sans-serif': + case 'monospace': + case 'cursive': + case 'fantasy': + case 'system-ui': + case 'math': + case 'emoji': + case 'fangsong': + return $value; + default: + return ''; + } + } + + /** + * Set generic font for fallback for html. + * + * @param string $value generic font name + * + * @return self + */ + public function setHtmlGenericFont($value) + { + $this->htmlGenericFont = self::validateGenericFont($value); + + return $this; + } + + /** + * Get html fallback generic font. + * + * @return string + */ + public function getHtmlGenericFont() + { + return $this->htmlGenericFont; + } } diff --git a/src/PhpWord/Style/Indentation.php b/src/PhpWord/Style/Indentation.php index 87277b4b81..42c6e11845 100644 --- a/src/PhpWord/Style/Indentation.php +++ b/src/PhpWord/Style/Indentation.php @@ -44,7 +44,7 @@ class Indentation extends AbstractStyle * * @var float|int */ - private $firstLine; + private $firstLine = 0; /** * Indentation removed from first line (twip). @@ -80,7 +80,7 @@ public function getLeft() * * @return self */ - public function setLeft($value = null) + public function setLeft($value) { $this->left = $this->setNumericVal($value, $this->left); @@ -104,7 +104,7 @@ public function getRight() * * @return self */ - public function setRight($value = null) + public function setRight($value) { $this->right = $this->setNumericVal($value, $this->right); @@ -128,7 +128,7 @@ public function getFirstLine() * * @return self */ - public function setFirstLine($value = null) + public function setFirstLine($value) { $this->firstLine = $this->setNumericVal($value, $this->firstLine); diff --git a/src/PhpWord/Style/Paragraph.php b/src/PhpWord/Style/Paragraph.php index e7b97afb1b..cfd1b5b974 100644 --- a/src/PhpWord/Style/Paragraph.php +++ b/src/PhpWord/Style/Paragraph.php @@ -21,6 +21,7 @@ use PhpOffice\PhpWord\Shared\Text; use PhpOffice\PhpWord\SimpleType\Jc; use PhpOffice\PhpWord\SimpleType\TextAlignment; +use PhpOffice\PhpWord\Style; /** * Paragraph style. @@ -99,7 +100,7 @@ class Paragraph extends Border /** * Text line height. * - * @var float|int + * @var null|float|int */ private $lineHeight; @@ -169,9 +170,9 @@ class Paragraph extends Border /** * Right to Left Paragraph Layout. * - * @var bool + * @var ?bool */ - private $bidi = false; + private $bidi; /** * Vertical Character Alignment on Line. @@ -321,9 +322,9 @@ public function setNext($value = null) } /** - * Get shading. + * Get indentation. * - * @return \PhpOffice\PhpWord\Style\Indentation + * @return null|\PhpOffice\PhpWord\Style\Indentation */ public function getIndentation() { @@ -419,7 +420,7 @@ public function setSpace($value = null) /** * Get space before paragraph. * - * @return int + * @return null|float|int */ public function getSpaceBefore() { @@ -429,7 +430,7 @@ public function getSpaceBefore() /** * Set space before paragraph. * - * @param int $value + * @param null|float|int $value * * @return self */ @@ -441,7 +442,7 @@ public function setSpaceBefore($value = null) /** * Get space after paragraph. * - * @return int + * @return null|float|int */ public function getSpaceAfter() { @@ -451,7 +452,7 @@ public function getSpaceAfter() /** * Set space after paragraph. * - * @param int $value + * @param null|float|int $value * * @return self */ @@ -463,7 +464,7 @@ public function setSpaceAfter($value = null) /** * Get spacing between lines. * - * @return float|int + * @return null|float|int */ public function getSpacing() { @@ -473,7 +474,7 @@ public function getSpacing() /** * Set spacing between lines. * - * @param float|int $value + * @param null|float|int $value * * @return self */ @@ -507,7 +508,7 @@ public function setSpacingLineRule($value) /** * Get line height. * - * @return float|int + * @return null|float|int */ public function getLineHeight() { @@ -759,17 +760,17 @@ public function setContextualSpacing($contextualSpacing) /** * Get bidirectional. * - * @return bool + * @return ?bool */ public function isBidi() { - return $this->bidi; + return $this->bidi ?? Style::getDefaultRtl(); } /** * Set bidi. * - * @param bool $bidi + * @param ?bool $bidi * Set to true to write from right to left * * @return self diff --git a/src/PhpWord/Style/Spacing.php b/src/PhpWord/Style/Spacing.php index 7807065dd4..196ad8daa1 100644 --- a/src/PhpWord/Style/Spacing.php +++ b/src/PhpWord/Style/Spacing.php @@ -30,21 +30,21 @@ class Spacing extends AbstractStyle /** * Spacing above paragraph (twip). * - * @var float|int + * @var null|float|int */ private $before; /** * Spacing below paragraph (twip). * - * @var float|int + * @var null|float|int */ private $after; /** * Spacing between lines in paragraph (twip). * - * @var float|int + * @var null|float|int */ private $line; @@ -68,7 +68,7 @@ public function __construct($style = []) /** * Get before. * - * @return float|int + * @return null|float|int */ public function getBefore() { @@ -78,7 +78,7 @@ public function getBefore() /** * Set before. * - * @param float|int $value + * @param null|float|int $value * * @return self */ @@ -92,7 +92,7 @@ public function setBefore($value = null) /** * Get after. * - * @return float|int + * @return null|float|int */ public function getAfter() { @@ -102,7 +102,7 @@ public function getAfter() /** * Set after. * - * @param float|int $value + * @param null|float|int $value * * @return self */ @@ -116,7 +116,7 @@ public function setAfter($value = null) /** * Get line. * - * @return float|int + * @return null|float|int */ public function getLine() { @@ -126,7 +126,7 @@ public function getLine() /** * Set distance. * - * @param float|int $value + * @param null|float|int $value * * @return self */ diff --git a/src/PhpWord/Style/Table.php b/src/PhpWord/Style/Table.php index 1833024d14..d1ad6d8a3e 100644 --- a/src/PhpWord/Style/Table.php +++ b/src/PhpWord/Style/Table.php @@ -21,6 +21,7 @@ use PhpOffice\PhpWord\SimpleType\Jc; use PhpOffice\PhpWord\SimpleType\JcTable; use PhpOffice\PhpWord\SimpleType\TblWidth; +use PhpOffice\PhpWord\Style; class Table extends Border { @@ -162,9 +163,9 @@ class Table extends Border * * @see http://www.datypic.com/sc/ooxml/e-w_bidiVisual-1.html * - * @var bool + * @var ?bool */ - private $bidiVisual = false; + private $bidiVisual; /** * Create new table style. @@ -768,17 +769,17 @@ public function setColumnWidths(?array $value = null): void /** * Get bidiVisual. * - * @return bool + * @return ?bool */ public function isBidiVisual() { - return $this->bidiVisual; + return $this->bidiVisual ?? Style::getDefaultRtl(); } /** * Set bidiVisual. * - * @param bool $bidi + * @param ?bool $bidi * Set to true to visually present table as Right to Left * * @return self diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index ea90ef1d5e..5198ff1229 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -27,6 +27,7 @@ use PhpOffice\PhpWord\Shared\Text; use PhpOffice\PhpWord\Shared\XMLWriter; use PhpOffice\PhpWord\Shared\ZipArchive; +use Throwable; use XSLTProcessor; class TemplateProcessor @@ -135,6 +136,28 @@ public function __construct($documentTemplate) $this->tempDocumentContentTypes = $this->zipClass->getFromName($this->getDocumentContentTypesName()); } + public function __destruct() + { + if ($this->zipClass !== null) { + try { + $this->zipClass->close(); + } catch (Throwable $e) { + // Nothing to do here. + } + } + if ($this->tempDocumentFilename && file_exists($this->tempDocumentFilename)) { + unlink($this->tempDocumentFilename); + } + } + + public function __wakeup(): void + { + $this->tempDocumentFilename = ''; + $this->zipClass = null; + + throw new Exception('unserialize not permitted for this class'); + } + /** * Expose zip class. * @@ -256,15 +279,7 @@ protected static function ensureMacroCompleted($macro) */ protected static function ensureUtf8Encoded($subject) { - if (!Text::isUTF8($subject) && null !== $subject) { - if (PHP_VERSION_ID < 80200) { - $subject = utf8_encode($subject); - } else { - $subject = mb_convert_encoding($subject, 'UTF-8', mb_list_encodings()); - } - } - - return (null !== $subject) ? $subject : ''; + return (null !== $subject) ? Text::toUTF8($subject) : ''; } /** @@ -441,7 +456,7 @@ private function chooseImageDimension($baseValue, $inlineValue, $defaultValue) if (null === $value && isset($inlineValue)) { $value = $inlineValue; } - if (!preg_match('/^([0-9.]*(cm|mm|in|pt|pc|px|%|em|ex|)|auto)$/i', $value ?? '')) { + if (!preg_match('/^([0-9]*(cm|mm|in|pt|pc|px|%|em|ex|)|auto)$/i', $value ?? '')) { $value = null; } if (null === $value) { diff --git a/src/PhpWord/Writer/HTML.php b/src/PhpWord/Writer/HTML.php index ea0d654e5d..ca9784a83b 100644 --- a/src/PhpWord/Writer/HTML.php +++ b/src/PhpWord/Writer/HTML.php @@ -35,6 +35,13 @@ class HTML extends AbstractWriter implements WriterInterface */ protected $isPdf = false; + /** + * Is the current writer creating TCPDF? + * + * @var bool + */ + protected $isTcpdf = false; + /** * Footnotes and endnotes collection. * @@ -42,6 +49,13 @@ class HTML extends AbstractWriter implements WriterInterface */ protected $notes = []; + /** + * Callback for editing generated html. + * + * @var null|callable + */ + private $editHtmlCallback; + /** * Create new instance. */ @@ -63,10 +77,8 @@ public function __construct(?PhpWord $phpWord = null) /** * Save PhpWord to file. - * - * @param string $filename */ - public function save($filename = null): void + public function save(string $filename): void { $this->writeFile($this->openFile($filename), $this->getContent()); } @@ -84,14 +96,44 @@ public function getContent() $content .= '' . PHP_EOL; $content .= '' . PHP_EOL; - $content .= '' . PHP_EOL; + $langtext = ''; + $phpWord = $this->getPhpWord(); + $lang = $phpWord->getSettings()->getThemeFontLang(); + if (!empty($lang)) { // @phpstan-ignore-line + $lang2 = $lang->getLatin(); + if (!$lang2) { + $lang2 = $lang->getEastAsia(); + } + if (!$lang2) { + $lang2 = $lang->getBidirectional(); + } + if ($lang2) { + $langtext = " lang='" . $lang2 . "'"; + } + } + $content .= "" . PHP_EOL; $content .= $this->getWriterPart('Head')->write(); $content .= $this->getWriterPart('Body')->write(); $content .= '' . PHP_EOL; + $callback = $this->editHtmlCallback; + if ($callback !== null) { + $content = $callback($content); + } return $content; } + /** + * Set a callback to edit the entire HTML. + * + * The callback must accept the HTML as string as first parameter, + * and it must return the edited HTML as string. + */ + public function setEditHtmlCallback(?callable $callback): void + { + $this->editHtmlCallback = $callback; + } + /** * Get is PDF. * @@ -102,6 +144,16 @@ public function isPdf() return $this->isPdf; } + /** + * Get is TCPDF. + * + * @return bool + */ + public function isTcpdf() + { + return $this->isTcpdf; + } + /** * Get notes. * @@ -122,4 +174,18 @@ public function addNote($noteId, $noteMark): void { $this->notes[$noteId] = $noteMark; } + + /** + * Escape string or not depending on setting. + * + * @param string $txt + */ + public static function escapeOrNot($txt): string + { + if (\PhpOffice\PhpWord\Settings::isOutputEscapingEnabled()) { + return htmlspecialchars($txt, ENT_QUOTES | (defined('ENT_SUBSTITUTE') ? ENT_SUBSTITUTE : 0), 'UTF-8'); + } + + return $txt; + } } diff --git a/src/PhpWord/Writer/HTML/Element/AbstractElement.php b/src/PhpWord/Writer/HTML/Element/AbstractElement.php index f5b0e91719..ae203d70d2 100644 --- a/src/PhpWord/Writer/HTML/Element/AbstractElement.php +++ b/src/PhpWord/Writer/HTML/Element/AbstractElement.php @@ -17,7 +17,6 @@ namespace PhpOffice\PhpWord\Writer\HTML\Element; -use Laminas\Escaper\Escaper; use PhpOffice\PhpWord\Element\AbstractElement as Element; use PhpOffice\PhpWord\Writer\AbstractWriter; @@ -49,11 +48,6 @@ abstract class AbstractElement */ protected $withoutP = false; - /** - * @var \Laminas\Escaper\Escaper|\PhpOffice\PhpWord\Escaper\AbstractEscaper - */ - protected $escaper; - /** * Write element. */ @@ -69,7 +63,6 @@ public function __construct(AbstractWriter $parentWriter, Element $element, $wit $this->parentWriter = $parentWriter; $this->element = $element; $this->withoutP = $withoutP; - $this->escaper = new Escaper(); } /** diff --git a/src/PhpWord/Writer/HTML/Element/Link.php b/src/PhpWord/Writer/HTML/Element/Link.php index 7d302c1f85..5ff7030118 100644 --- a/src/PhpWord/Writer/HTML/Element/Link.php +++ b/src/PhpWord/Writer/HTML/Element/Link.php @@ -17,7 +17,7 @@ namespace PhpOffice\PhpWord\Writer\HTML\Element; -use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\Writer\HTML; /** * Link element HTML writer. @@ -39,11 +39,11 @@ public function write() $prefix = $this->element->isInternal() ? '#' : ''; $content = $this->writeOpening(); - if (Settings::isOutputEscapingEnabled()) { - $content .= "escaper->escapeHtmlAttr($this->element->getSource())}\">{$this->escaper->escapeHtml($this->element->getText())}"; - } else { - $content .= "element->getSource()}\">{$this->element->getText()}"; - } + $content .= "element->getSource()) + . '">' + . HTML::escapeOrNot($this->element->getText()) + . ''; $content .= $this->writeClosing(); return $content; diff --git a/src/PhpWord/Writer/HTML/Element/ListItem.php b/src/PhpWord/Writer/HTML/Element/ListItem.php index d04798684f..4dd61ff321 100644 --- a/src/PhpWord/Writer/HTML/Element/ListItem.php +++ b/src/PhpWord/Writer/HTML/Element/ListItem.php @@ -17,7 +17,7 @@ namespace PhpOffice\PhpWord\Writer\HTML\Element; -use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\Writer\HTML; /** * ListItem element HTML writer. @@ -37,11 +37,7 @@ public function write() return ''; } - if (Settings::isOutputEscapingEnabled()) { - $content = '

' . $this->escaper->escapeHtml($this->element->getTextObject()->getText()) . '

' . PHP_EOL; - } else { - $content = '

' . $this->element->getTextObject()->getText() . '

' . PHP_EOL; - } + $content = '

' . HTML::escapeOrNot($this->element->getTextObject()->getText()) . '

' . PHP_EOL; return $content; } diff --git a/src/PhpWord/Writer/HTML/Element/PageBreak.php b/src/PhpWord/Writer/HTML/Element/PageBreak.php index 762426bf26..8e3971781c 100644 --- a/src/PhpWord/Writer/HTML/Element/PageBreak.php +++ b/src/PhpWord/Writer/HTML/Element/PageBreak.php @@ -35,10 +35,13 @@ public function write() { /** @var \PhpOffice\PhpWord\Writer\HTML $parentWriter Type hint */ $parentWriter = $this->parentWriter; + if ($parentWriter->isTcpdf()) { + return '
'; + } if ($parentWriter->isPdf()) { return ''; } - return ''; + return '
 
' . PHP_EOL; } } diff --git a/src/PhpWord/Writer/HTML/Element/Table.php b/src/PhpWord/Writer/HTML/Element/Table.php index b1a2ee9667..43320ea8fb 100644 --- a/src/PhpWord/Writer/HTML/Element/Table.php +++ b/src/PhpWord/Writer/HTML/Element/Table.php @@ -51,10 +51,10 @@ public function write() $rowCellCount = count($rowCells); for ($j = 0; $j < $rowCellCount; ++$j) { $cellStyle = $rowCells[$j]->getStyle(); + $cellStyleCss = self::getTableStyle($cellStyle); $cellBgColor = $cellStyle->getBgColor(); - $cellBgColor === 'auto' && $cellBgColor = null; // auto cannot be parsed to hexadecimal number $cellFgColor = null; - if ($cellBgColor) { + if ($cellBgColor && $cellBgColor !== 'auto') { $red = hexdec(substr($cellBgColor, 0, 2)); $green = hexdec(substr($cellBgColor, 2, 2)); $blue = hexdec(substr($cellBgColor, 4, 2)); @@ -67,12 +67,8 @@ public function write() if ($cellVMerge === 'restart') { for ($k = $i + 1; $k < $rowCount; ++$k) { $kRowCells = $rows[$k]->getCells(); - if (isset($kRowCells[$j])) { - if ($kRowCells[$j]->getStyle()->getVMerge() === 'continue') { - ++$cellRowSpan; - } else { - break; - } + if (isset($kRowCells[$j]) && $kRowCells[$j]->getStyle()->getVMerge() === 'continue') { + ++$cellRowSpan; } else { break; } @@ -83,22 +79,18 @@ public function write() $cellTag = $tblHeader ? 'th' : 'td'; $cellColSpanAttr = (is_numeric($cellColSpan) && ($cellColSpan > 1) ? " colspan=\"{$cellColSpan}\"" : ''); $cellRowSpanAttr = ($cellRowSpan > 1 ? " rowspan=\"{$cellRowSpan}\"" : ''); - $cellBgColorAttr = (null === $cellBgColor ? '' : " bgcolor=\"#{$cellBgColor}\""); - $cellFgColorAttr = (null === $cellFgColor ? '' : " color=\"#{$cellFgColor}\""); - $content .= "<{$cellTag}{$cellColSpanAttr}{$cellRowSpanAttr}{$cellBgColorAttr}{$cellFgColorAttr}>" . PHP_EOL; + $cellBgColorAttr = (empty($cellBgColor) ? '' : " bgcolor=\"#{$cellBgColor}\""); + $cellFgColorAttr = (empty($cellFgColor) ? '' : " color=\"#{$cellFgColor}\""); + $content .= "<{$cellTag}{$cellStyleCss}{$cellColSpanAttr}{$cellRowSpanAttr}{$cellBgColorAttr}{$cellFgColorAttr}>" . PHP_EOL; $writer = new Container($this->parentWriter, $rowCells[$j]); $content .= $writer->write(); if ($cellRowSpan > 1) { // There shouldn't be any content in the subsequent merged cells, but lets check anyway for ($k = $i + 1; $k < $rowCount; ++$k) { $kRowCells = $rows[$k]->getCells(); - if (isset($kRowCells[$j])) { - if ($kRowCells[$j]->getStyle()->getVMerge() === 'continue') { - $writer = new Container($this->parentWriter, $kRowCells[$j]); - $content .= $writer->write(); - } else { - break; - } + if (isset($kRowCells[$j]) && $kRowCells[$j]->getStyle()->getVMerge() === 'continue') { + $writer = new Container($this->parentWriter, $kRowCells[$j]); + $content .= $writer->write(); } else { break; } @@ -118,26 +110,82 @@ public function write() /** * Translates Table style in CSS equivalent. * - * @param null|\PhpOffice\PhpWord\Style\Table|string $tableStyle + * @param null|\PhpOffice\PhpWord\Style\Cell|\PhpOffice\PhpWord\Style\Table|string $tableStyle * * @return string */ - private function getTableStyle($tableStyle = null) + private static function getTableStyle($tableStyle = null) { if ($tableStyle == null) { return ''; } if (is_string($tableStyle)) { $style = ' class="' . $tableStyle; - } else { - $style = ' style="'; + + return $style . '"'; + } + + $style = self::getTableStyleString($tableStyle); + if ($style === '') { + return ''; + } + + return ' style="' . $style . '"'; + } + + /** + * Translates Table style in CSS equivalent. + * + * @param \PhpOffice\PhpWord\Style\Cell|\PhpOffice\PhpWord\Style\Table|string $tableStyle + * + * @return string + */ + public static function getTableStyleString($tableStyle) + { + $style = ''; + if (is_object($tableStyle) && method_exists($tableStyle, 'getLayout')) { if ($tableStyle->getLayout() == \PhpOffice\PhpWord\Style\Table::LAYOUT_FIXED) { $style .= 'table-layout: fixed;'; } elseif ($tableStyle->getLayout() == \PhpOffice\PhpWord\Style\Table::LAYOUT_AUTO) { $style .= 'table-layout: auto;'; } } + if (is_object($tableStyle) && method_exists($tableStyle, 'isBidiVisual')) { + if ($tableStyle->isBidiVisual()) { + $style .= ' direction: rtl;'; + } + } + + $dirs = ['Top', 'Left', 'Bottom', 'Right']; + $testmethprefix = 'getBorder'; + foreach ($dirs as $dir) { + $testmeth = $testmethprefix . $dir . 'Style'; + if (method_exists($tableStyle, $testmeth)) { + $outval = $tableStyle->{$testmeth}(); + if ($outval === 'single') { + $outval = 'solid'; + } + if (is_string($outval) && 1 == preg_match('/^[a-z]+$/', $outval)) { + $style .= ' border-' . lcfirst($dir) . '-style: ' . $outval . ';'; + } + } + $testmeth = $testmethprefix . $dir . 'Color'; + if (method_exists($tableStyle, $testmeth)) { + $outval = $tableStyle->{$testmeth}(); + if (is_string($outval) && 1 == preg_match('/^[a-z]+$/', $outval)) { + $style .= ' border-' . lcfirst($dir) . '-color: ' . $outval . ';'; + } + } + $testmeth = $testmethprefix . $dir . 'Size'; + if (method_exists($tableStyle, $testmeth)) { + $outval = $tableStyle->{$testmeth}(); + if (is_numeric($outval)) { + // size is in twips - divide by 20 to get points + $style .= ' border-' . lcfirst($dir) . '-width: ' . ((string) ($outval / 20)) . 'pt;'; + } + } + } - return $style . '"'; + return $style; } } diff --git a/src/PhpWord/Writer/HTML/Element/Text.php b/src/PhpWord/Writer/HTML/Element/Text.php index a360f0922b..e6c9d00134 100644 --- a/src/PhpWord/Writer/HTML/Element/Text.php +++ b/src/PhpWord/Writer/HTML/Element/Text.php @@ -18,9 +18,10 @@ namespace PhpOffice\PhpWord\Writer\HTML\Element; use PhpOffice\PhpWord\Element\TrackChange; -use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWord\Style\Paragraph; +use PhpOffice\PhpWord\Writer\HTML; use PhpOffice\PhpWord\Writer\HTML\Style\Font as FontStyleWriter; use PhpOffice\PhpWord\Writer\HTML\Style\Paragraph as ParagraphStyleWriter; @@ -74,11 +75,11 @@ public function write() $content .= $this->writeOpening(); $content .= $this->openingText; $content .= $this->openingTags; - if (Settings::isOutputEscapingEnabled()) { - $content .= $this->escaper->escapeHtml($element->getText()); - } else { - $content .= $element->getText(); + $contenx = HTML::escapeOrNot($element->getText()); + if (!$this->withoutP && !trim(/** @scrutinizer ignore-type */ $contenx)) { + $contenx = ' '; } + $content .= $contenx; $content .= $this->closingTags; $content .= $this->closingText; $content .= $this->writeClosing(); @@ -115,10 +116,7 @@ protected function writeOpening() { $content = ''; if (!$this->withoutP) { - $style = ''; - if (method_exists($this->element, 'getParagraphStyle')) { - $style = $this->getParagraphStyle(); - } + $style = $this->getParagraphStyle(); $content .= ""; } @@ -141,12 +139,7 @@ protected function writeClosing() $content .= $this->writeTrackChangeClosing(); if (!$this->withoutP) { - if (Settings::isOutputEscapingEnabled()) { - $content .= $this->escaper->escapeHtml($this->closingText); - } else { - $content .= $this->closingText; - } - + $content .= HTML::escapeOrNot($this->closingText); $content .= '

' . PHP_EOL; } @@ -248,17 +241,39 @@ private function getFontStyle(): void /** @var \PhpOffice\PhpWord\Element\Text $element Type hint */ $element = $this->element; $style = ''; + $langtext = ''; + $lang = null; $fontStyle = $element->getFontStyle(); $fStyleIsObject = ($fontStyle instanceof Font); if ($fStyleIsObject) { $styleWriter = new FontStyleWriter($fontStyle); - $style = $styleWriter->write(); - } elseif (is_string($fontStyle)) { - $style = $fontStyle; + $styl2 = $styleWriter->write(); + if ($styl2) { + $style = " style=\"$styl2\""; + } + $lang = $fontStyle->getLang(); + } elseif (!empty($fontStyle)) { + $style = " class=\"$fontStyle\""; + /** @var \PhpOffice\PhpWord\Style\Font $styl3 Type hint */ + $styl3 = Style::getStyle($fontStyle); + if (!empty($styl3) && method_exists($styl3, 'getLang')) { // @phpstan-ignore-line + $lang = $styl3->getLang(); + } } - if ($style) { - $attribute = $fStyleIsObject ? 'style' : 'class'; - $this->openingTags = ""; + if ($lang) { + $langtext = $lang->getLatin(); + if (!$langtext) { + $langtext = $lang->getEastAsia(); + } + if (!$langtext) { + $langtext = $lang->getBidirectional(); + } + if ($langtext) { + $langtext = " lang='$langtext'"; + } + } + if ($style || $langtext) { + $this->openingTags = ""; $this->closingTags = ''; } } diff --git a/src/PhpWord/Writer/HTML/Element/Title.php b/src/PhpWord/Writer/HTML/Element/Title.php index 3749505013..c46249f373 100644 --- a/src/PhpWord/Writer/HTML/Element/Title.php +++ b/src/PhpWord/Writer/HTML/Element/Title.php @@ -17,7 +17,7 @@ namespace PhpOffice\PhpWord\Writer\HTML\Element; -use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\Writer\HTML; /** * TextRun element HTML writer. @@ -41,10 +41,8 @@ public function write() $text = $this->element->getText(); if (is_string($text)) { - if (Settings::isOutputEscapingEnabled()) { - $text = $this->escaper->escapeHtml($text); - } - } elseif ($text instanceof \PhpOffice\PhpWord\Element\AbstractContainer) { + $text = HTML::escapeOrNot($text); + } else { $writer = new Container($this->parentWriter, $text); $text = $writer->write(); } diff --git a/src/PhpWord/Writer/HTML/Part/AbstractPart.php b/src/PhpWord/Writer/HTML/Part/AbstractPart.php index 8612e28451..0fd9a40940 100644 --- a/src/PhpWord/Writer/HTML/Part/AbstractPart.php +++ b/src/PhpWord/Writer/HTML/Part/AbstractPart.php @@ -17,9 +17,8 @@ namespace PhpOffice\PhpWord\Writer\HTML\Part; -use Laminas\Escaper\Escaper; use PhpOffice\PhpWord\Exception\Exception; -use PhpOffice\PhpWord\Writer\AbstractWriter; +use PhpOffice\PhpWord\Writer\HTML; /** * @since 0.11.0 @@ -27,35 +26,22 @@ abstract class AbstractPart { /** - * @var \PhpOffice\PhpWord\Writer\AbstractWriter + * @var ?HTML */ private $parentWriter; - /** - * @var \Laminas\Escaper\Escaper - */ - protected $escaper; - - public function __construct() - { - $this->escaper = new Escaper(); - } - /** * @return string */ abstract public function write(); - /** - * @param \PhpOffice\PhpWord\Writer\AbstractWriter $writer - */ - public function setParentWriter(?AbstractWriter $writer = null): void + public function setParentWriter(?HTML $writer = null): void { $this->parentWriter = $writer; } /** - * @return \PhpOffice\PhpWord\Writer\AbstractWriter + * @return HTML */ public function getParentWriter() { diff --git a/src/PhpWord/Writer/HTML/Part/Body.php b/src/PhpWord/Writer/HTML/Part/Body.php index 19aae8aa1f..fe25df5f90 100644 --- a/src/PhpWord/Writer/HTML/Part/Body.php +++ b/src/PhpWord/Writer/HTML/Part/Body.php @@ -40,9 +40,18 @@ public function write() $content .= '' . PHP_EOL; $sections = $phpWord->getSections(); + $secno = 0; + $tcpdf = $this->getParentWriter()->isTcpdf(); foreach ($sections as $section) { + ++$secno; + if ($tcpdf && $secno > 1) { + $content .= "
" . PHP_EOL; + } else { + $content .= "
" . PHP_EOL; + } $writer = new Container($this->getParentWriter(), $section); $content .= $writer->write(); + $content .= '
' . PHP_EOL; } $content .= $this->writeNotes(); diff --git a/src/PhpWord/Writer/HTML/Part/Head.php b/src/PhpWord/Writer/HTML/Part/Head.php index 6117f736e3..b8daf4dab2 100644 --- a/src/PhpWord/Writer/HTML/Part/Head.php +++ b/src/PhpWord/Writer/HTML/Part/Head.php @@ -21,6 +21,9 @@ use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWord\Style\Paragraph; +use PhpOffice\PhpWord\Style\Table; +use PhpOffice\PhpWord\Writer\HTML; +use PhpOffice\PhpWord\Writer\HTML\Element\Table as TableStyleWriter; use PhpOffice\PhpWord\Writer\HTML\Style\Font as FontStyleWriter; use PhpOffice\PhpWord\Writer\HTML\Style\Generic as GenericStyleWriter; use PhpOffice\PhpWord\Writer\HTML\Style\Paragraph as ParagraphStyleWriter; @@ -63,8 +66,10 @@ public function write() $method = 'get' . $key; if ($docProps->$method() != '') { $content .= '' . PHP_EOL; + . ' content="' + . HTML::escapeOrNot($docProps->$method()) + . '"' + . ' />' . PHP_EOL; } } $content .= $this->writeStyles(); @@ -83,11 +88,19 @@ private function writeStyles() $css = '' . PHP_EOL; diff --git a/src/PhpWord/Writer/HTML/Style/AbstractStyle.php b/src/PhpWord/Writer/HTML/Style/AbstractStyle.php index 5a5fcacd79..a650786769 100644 --- a/src/PhpWord/Writer/HTML/Style/AbstractStyle.php +++ b/src/PhpWord/Writer/HTML/Style/AbstractStyle.php @@ -17,7 +17,8 @@ namespace PhpOffice\PhpWord\Writer\HTML\Style; -use PhpOffice\PhpWord\Style\AbstractStyle as Style; +use PhpOffice\PhpWord\Style\AbstractStyle as StyleAbstract; +use PhpOffice\PhpWord\Writer\HTML; /** * Style writer. @@ -29,26 +30,28 @@ abstract class AbstractStyle /** * Parent writer. * - * @var \PhpOffice\PhpWord\Writer\AbstractWriter + * @var HTML */ private $parentWriter; /** * Style. * - * @var null|array|\PhpOffice\PhpWord\Style\AbstractStyle + * @var null|array|StyleAbstract */ private $style; /** * Write style. + * + * @return mixed */ abstract public function write(); /** * Create new instance. * - * @param array|\PhpOffice\PhpWord\Style\AbstractStyle $style + * @param array|StyleAbstract $style */ public function __construct($style = null) { @@ -58,7 +61,7 @@ public function __construct($style = null) /** * Set parent writer. * - * @param \PhpOffice\PhpWord\Writer\AbstractWriter $writer + * @param HTML $writer */ public function setParentWriter($writer): void { @@ -68,7 +71,7 @@ public function setParentWriter($writer): void /** * Get parent writer. * - * @return \PhpOffice\PhpWord\Writer\AbstractWriter + * @return HTML */ public function getParentWriter() { @@ -78,11 +81,11 @@ public function getParentWriter() /** * Get style. * - * @return null|array|\PhpOffice\PhpWord\Style\AbstractStyle|string + * @return null|array|string|StyleAbstract */ public function getStyle() { - if (!$this->style instanceof Style && !is_array($this->style)) { + if (!$this->style instanceof StyleAbstract && !is_array($this->style)) { return ''; } diff --git a/src/PhpWord/Writer/HTML/Style/Font.php b/src/PhpWord/Writer/HTML/Style/Font.php index 1fd88f5e8e..5aead50c07 100644 --- a/src/PhpWord/Writer/HTML/Style/Font.php +++ b/src/PhpWord/Writer/HTML/Style/Font.php @@ -39,14 +39,14 @@ public function write() } $css = []; - $font = $style->getName(); + $font = self::getFontFamily($style->getName(), $style->getHtmlGenericFont()); $size = $style->getSize(); $color = $style->getColor(); $fgColor = $style->getFgColor(); $underline = $style->getUnderline() != FontStyle::UNDERLINE_NONE; $lineThrough = $style->isStrikethrough() || $style->isDoubleStrikethrough(); - $css['font-family'] = $this->getValueIf($font !== null, "'{$font}'"); + $css['font-family'] = $this->getValueIf(!empty($font), "{$font}"); $css['font-size'] = $this->getValueIf($size !== null, "{$size}pt"); $css['color'] = $this->getValueIf($color !== null, "#{$color}"); $css['background'] = $this->getValueIf($fgColor != '', $fgColor); @@ -61,10 +61,40 @@ public function write() $css['text-transform'] = $this->getValueIf($style->isAllCaps(), 'uppercase'); $css['font-variant'] = $this->getValueIf($style->isSmallCaps(), 'small-caps'); $css['display'] = $this->getValueIf($style->isHidden(), 'none'); + $whitespace = $style->getHtmlWhiteSpace(); + if ($whitespace) { + $css['white-space'] = $whitespace; + } $spacing = $style->getSpacing(); $css['letter-spacing'] = $this->getValueIf(null !== $spacing, ($spacing / 20) . 'pt'); + if ($style->isRTL()) { + $css['direction'] = 'rtl'; + } elseif ($style->isRTL() === false) { + $css['direction'] = 'ltr'; + } return $this->assembleCss($css); } + + /** + * Set font and alternates for css font-family. + * + * @param string $font + * @param string $genericFont + * + * @return string + */ + public static function getFontFamily($font, $genericFont) + { + if (empty($font)) { + return ''; + } + $fontfamily = "'" . htmlspecialchars($font, ENT_QUOTES, 'UTF-8') . "'"; + if (!empty($genericFont)) { + $fontfamily .= ", $genericFont"; + } + + return $fontfamily; + } } diff --git a/src/PhpWord/Writer/HTML/Style/Paragraph.php b/src/PhpWord/Writer/HTML/Style/Paragraph.php index 865114ff58..73286bda8b 100644 --- a/src/PhpWord/Writer/HTML/Style/Paragraph.php +++ b/src/PhpWord/Writer/HTML/Style/Paragraph.php @@ -49,23 +49,31 @@ public function write() break; case Jc::END: + $textAlign = ($style->isBidi()) ? 'left' : 'right'; + + break; case Jc::MEDIUM_KASHIDA: case Jc::HIGH_KASHIDA: case Jc::LOW_KASHIDA: - case Jc::RIGHT: + case /** @scrutinizer ignore-deprecated */ Jc::RIGHT: $textAlign = 'right'; break; case Jc::BOTH: case Jc::DISTRIBUTE: case Jc::THAI_DISTRIBUTE: - case Jc::JUSTIFY: + case /** @scrutinizer ignore-deprecated */ Jc::JUSTIFY: $textAlign = 'justify'; break; - default: //all others, align left + case /** @scrutinizer ignore-deprecated */ Jc::LEFT: $textAlign = 'left'; + break; + + default: //all others, including Jc::START + $textAlign = ($style->isBidi()) ? 'right' : 'left'; + break; } @@ -79,9 +87,27 @@ public function write() $after = $spacing->getAfter(); $css['margin-top'] = $this->getValueIf(null !== $before, ($before / 20) . 'pt'); $css['margin-bottom'] = $this->getValueIf(null !== $after, ($after / 20) . 'pt'); - } else { - $css['margin-top'] = '0'; - $css['margin-bottom'] = '0'; + } + + $lht = $style->getLineHeight(); + if (!empty($lht)) { + $css['line-height'] = $lht; + } + $ind = $style->getIndentation(); + if ($ind != null) { + $tcpdf = $this->getParentWriter()->isTcpdf(); + $left = $ind->getLeft(); + $inches = $left * 1.0 / \PhpOffice\PhpWord\Shared\Converter::INCH_TO_TWIP; + $css[$tcpdf ? 'text-indent' : 'margin-left'] = ((string) $inches) . 'in'; + $left = $ind->getRight(); + $inches = $left * 1.0 / \PhpOffice\PhpWord\Shared\Converter::INCH_TO_TWIP; + $css['margin-right'] = ((string) $inches) . 'in'; + } + if ($style->hasPageBreakBefore()) { + $css['page-break-before'] = 'always'; + } + if ($style->isBidi()) { + $css['direction'] = 'rtl'; } return $this->assembleCss($css); diff --git a/src/PhpWord/Writer/ODText/Element/Table.php b/src/PhpWord/Writer/ODText/Element/Table.php index 783bb21232..e12ae24b58 100644 --- a/src/PhpWord/Writer/ODText/Element/Table.php +++ b/src/PhpWord/Writer/ODText/Element/Table.php @@ -44,7 +44,7 @@ public function write(): void if ($rowCount > 0) { $xmlWriter->startElement('table:table'); $xmlWriter->writeAttribute('table:name', $element->getElementId()); - $xmlWriter->writeAttribute('table:style', $element->getElementId()); + $xmlWriter->writeAttribute('table:style-name', $element->getElementId()); // Write columns $this->writeColumns($xmlWriter, $element); diff --git a/src/PhpWord/Writer/ODText/Element/Title.php b/src/PhpWord/Writer/ODText/Element/Title.php index 45fe65c7f5..ebe7dc4d5f 100644 --- a/src/PhpWord/Writer/ODText/Element/Title.php +++ b/src/PhpWord/Writer/ODText/Element/Title.php @@ -55,7 +55,8 @@ public function write(): void $text = $element->getText(); if (is_string($text)) { $this->writeText($text); - } elseif ($text instanceof \PhpOffice\PhpWord\Element\AbstractContainer) { + } + if ($text instanceof \PhpOffice\PhpWord\Element\AbstractContainer) { $containerWriter = new Container($xmlWriter, $text); $containerWriter->write(); } diff --git a/src/PhpWord/Writer/ODText/Part/Content.php b/src/PhpWord/Writer/ODText/Part/Content.php index 0c0607a06f..00871d9c52 100644 --- a/src/PhpWord/Writer/ODText/Part/Content.php +++ b/src/PhpWord/Writer/ODText/Part/Content.php @@ -201,7 +201,7 @@ private function writeTextStyles(XMLWriter $xmlWriter): void } foreach ($styles as $style) { - $sty = $style->getStyleName(); + $sty = (string) $style->getStyleName(); if (substr($sty, 0, 8) === 'Heading_') { $style = new Paragraph(); $style->setStyleName('HD' . substr($sty, 8)); diff --git a/src/PhpWord/Writer/ODText/Style/Paragraph.php b/src/PhpWord/Writer/ODText/Style/Paragraph.php index 330043e116..666294efee 100644 --- a/src/PhpWord/Writer/ODText/Style/Paragraph.php +++ b/src/PhpWord/Writer/ODText/Style/Paragraph.php @@ -18,6 +18,8 @@ namespace PhpOffice\PhpWord\Writer\ODText\Style; use PhpOffice\PhpWord\Shared\Converter; +use PhpOffice\PhpWord\SimpleType\Jc; +use PhpOffice\PhpWord\Style; /** * Font style writer. @@ -26,6 +28,16 @@ */ class Paragraph extends AbstractStyle { + private const BIDI_MAP = [ + Jc::END => Jc::LEFT, + Jc::START => Jc::RIGHT, + ]; + + private const NON_BIDI_MAP = [ + Jc::START => Jc::LEFT, + Jc::END => Jc::RIGHT, + ]; + /** * Write style. */ @@ -42,7 +54,7 @@ public function write(): void $xmlWriter->startElement('style:style'); - $styleName = $style->getStyleName(); + $styleName = (string) $style->getStyleName(); $styleAuto = false; $mpm = ''; $psm = ''; @@ -111,8 +123,18 @@ public function write(): void $xmlWriter->writeAttributeIf($marginTop !== null, 'fo:margin-top', ($marginTop / $twipToPoint) . 'pt'); $xmlWriter->writeAttributeIf($marginBottom !== null, 'fo:margin-bottom', ($marginBottom / $twipToPoint) . 'pt'); } - $temp = $style->getAlignment(); - $xmlWriter->writeAttributeIf($temp !== '', 'fo:text-align', $temp); + $alignment = $style->getAlignment(); + $bidi = $style->isBidi(); + $defaultRtl = Style::getDefaultRtl(); + if ($alignment === '' && $bidi !== null) { + $alignment = Jc::START; + } + if ($bidi) { + $alignment = self::BIDI_MAP[$alignment] ?? $alignment; + } elseif ($defaultRtl !== null) { + $alignment = self::NON_BIDI_MAP[$alignment] ?? $alignment; + } + $xmlWriter->writeAttributeIf($alignment !== '', 'fo:text-align', $alignment); $temp = $style->getLineHeight(); $xmlWriter->writeAttributeIf($temp !== null, 'fo:line-height', ((string) ($temp * 100) . '%')); $xmlWriter->writeAttributeIf($style->hasPageBreakBefore() === true, 'fo:break-before', 'page'); diff --git a/src/PhpWord/Writer/PDF.php b/src/PhpWord/Writer/PDF.php index 9a8cd6ac25..b24e66efb8 100644 --- a/src/PhpWord/Writer/PDF.php +++ b/src/PhpWord/Writer/PDF.php @@ -73,4 +73,9 @@ public function __call($name, $arguments) return call_user_func_array([$this->renderer, $name], $arguments); } + + public function save(string $filename): void + { + $this->renderer->save($filename); + } } diff --git a/src/PhpWord/Writer/PDF/AbstractRenderer.php b/src/PhpWord/Writer/PDF/AbstractRenderer.php index 6ab6535f4c..c143a6cb5c 100644 --- a/src/PhpWord/Writer/PDF/AbstractRenderer.php +++ b/src/PhpWord/Writer/PDF/AbstractRenderer.php @@ -81,7 +81,7 @@ abstract class AbstractRenderer extends HTML public function __construct(PhpWord $phpWord) { parent::__construct($phpWord); - + $this->isPdf = true; if ($this->includeFile != null) { $includeFile = Settings::getPdfRendererPath() . '/' . $this->includeFile; if (file_exists($includeFile)) { @@ -194,7 +194,6 @@ protected function prepareForSave($filename = null) throw new Exception("Could not open file $filename for writing."); } // @codeCoverageIgnoreEnd - $this->isPdf = true; return $fileHandle; } diff --git a/src/PhpWord/Writer/PDF/DomPDF.php b/src/PhpWord/Writer/PDF/DomPDF.php index ea167b4d0a..8e5b4054d5 100644 --- a/src/PhpWord/Writer/PDF/DomPDF.php +++ b/src/PhpWord/Writer/PDF/DomPDF.php @@ -53,10 +53,8 @@ protected function createExternalWriterInstance() /** * Save PhpWord to file. - * - * @param string $filename Name of the file to save as */ - public function save($filename = null): void + public function save(string $filename): void { $fileHandle = parent::prepareForSave($filename); diff --git a/src/PhpWord/Writer/PDF/MPDF.php b/src/PhpWord/Writer/PDF/MPDF.php index 481d4629b3..311f743d6d 100644 --- a/src/PhpWord/Writer/PDF/MPDF.php +++ b/src/PhpWord/Writer/PDF/MPDF.php @@ -29,6 +29,9 @@ */ class MPDF extends AbstractRenderer implements WriterInterface { + public const SIMULATED_BODY_START = ''; + private const BODY_TAG = ''; + /** * Overridden to set the correct includefile, only needed for MPDF 5. * @@ -46,7 +49,7 @@ public function __construct(PhpWord $phpWord) /** * Gets the implementation of external PDF library that should be used. * - * @return Mpdf implementation + * @return \Mpdf\Mpdf implementation */ protected function createExternalWriterInstance() { @@ -62,10 +65,8 @@ protected function createExternalWriterInstance() /** * Save PhpWord to file. - * - * @param string $filename Name of the file to save as */ - public function save($filename = null): void + public function save(string $filename): void { $fileHandle = parent::prepareForSave($filename); @@ -87,7 +88,24 @@ public function save($filename = null): void $pdf->setKeywords($docProps->getKeywords()); $pdf->setCreator($docProps->getCreator()); - $pdf->writeHTML($this->getContent()); + $html = $this->getContent(); + $bodyLocation = strpos($html, self::SIMULATED_BODY_START); + if ($bodyLocation === false) { + $bodyLocation = strpos($html, self::BODY_TAG); + if ($bodyLocation !== false) { + $bodyLocation += strlen(self::BODY_TAG); + } + } + // Make sure first data presented to Mpdf includes body tag + // (and any htmlpageheader/htmlpagefooter tags) + // so that Mpdf doesn't parse it as content. Issue 2432. + if ($bodyLocation !== false) { + $pdf->WriteHTML(substr($html, 0, $bodyLocation)); + $html = substr($html, $bodyLocation); + } + foreach (explode("\n", $html) as $line) { + $pdf->WriteHTML("$line\n"); + } // Write to file fwrite($fileHandle, $pdf->output($filename, 'S')); diff --git a/src/PhpWord/Writer/PDF/TCPDF.php b/src/PhpWord/Writer/PDF/TCPDF.php index 0847a0c3e8..5ef92f451b 100644 --- a/src/PhpWord/Writer/PDF/TCPDF.php +++ b/src/PhpWord/Writer/PDF/TCPDF.php @@ -17,6 +17,9 @@ namespace PhpOffice\PhpWord\Writer\PDF; +use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Writer\WriterInterface; /** @@ -35,6 +38,17 @@ class TCPDF extends AbstractRenderer implements WriterInterface */ protected $includeFile = 'tcpdf.php'; + /** + * Overridden to set isTcpdf. + * + * @codeCoverageIgnore + */ + public function __construct(PhpWord $phpWord) + { + parent::__construct($phpWord); + $this->isTcpdf = true; + } + /** * Gets the implementation of external PDF library that should be used. * @@ -55,17 +69,41 @@ protected function createExternalWriterInstance($orientation, $unit, $paperSize) return $instance; } + /** + * Overwriteable function to allow user to extend TCPDF. + * There should always be an AddPage call, preceded or followed + * by code to customize TCPDF configuration. + * The customization below sets vertical spacing + * between paragaraphs when the user has + * explicitly set those values to numeric in default style. + */ + protected function prepareToWrite(\TCPDF $pdf): void + { + $pdf->AddPage(); + $customStyles = Style::getStyles(); + $normal = $customStyles['Normal'] ?? null; + if ($normal instanceof Style\Paragraph) { + $before = $normal->getSpaceBefore(); + $after = $normal->getSpaceAfter(); + $height = $normal->getLineHeight() ?? ''; + if (is_numeric($before) && is_numeric($after)) { + $tagvs = [ + 'p' => [['n' => $before, 'h' => $height], ['n' => $after, 'h' => $height]], + ]; + $pdf->setHtmlVSpace($tagvs); + } + } + } + /** * Save PhpWord to file. - * - * @param string $filename Name of the file to save as */ - public function save($filename = null): void + public function save(string $filename): void { $fileHandle = parent::prepareForSave($filename); // PDF settings - $paperSize = 'A4'; + $paperSize = strtoupper(Settings::getDefaultPaper()); $orientation = 'P'; // Create PDF @@ -73,8 +111,8 @@ public function save($filename = null): void $pdf->setFontSubsetting(false); $pdf->setPrintHeader(false); $pdf->setPrintFooter(false); - $pdf->AddPage(); $pdf->SetFont($this->getFont()); + $this->prepareToWrite($pdf); $pdf->writeHTML($this->getContent()); // Write document properties diff --git a/src/PhpWord/Writer/RTF/Element/AbstractElement.php b/src/PhpWord/Writer/RTF/Element/AbstractElement.php index a12469ad4c..dedbc8bfa1 100644 --- a/src/PhpWord/Writer/RTF/Element/AbstractElement.php +++ b/src/PhpWord/Writer/RTF/Element/AbstractElement.php @@ -25,7 +25,6 @@ use PhpOffice\PhpWord\Style\Font as FontStyle; use PhpOffice\PhpWord\Style\Paragraph as ParagraphStyle; use PhpOffice\PhpWord\Writer\AbstractWriter; -use PhpOffice\PhpWord\Writer\HTML\Element\AbstractElement as HTMLAbstractElement; use PhpOffice\PhpWord\Writer\RTF\Style\Font as FontStyleWriter; use PhpOffice\PhpWord\Writer\RTF\Style\Paragraph as ParagraphStyleWriter; @@ -34,8 +33,36 @@ * * @since 0.11.0 */ -abstract class AbstractElement extends HTMLAbstractElement +abstract class AbstractElement { + /** + * Parent writer. + * + * @var \PhpOffice\PhpWord\Writer\AbstractWriter + */ + protected $parentWriter; + + /** + * Element. + * + * @var \PhpOffice\PhpWord\Element\AbstractElement + */ + protected $element; + + /** + * Without paragraph. + * + * @var bool + */ + protected $withoutP = false; + + /** + * Write element. + * + * @return string + */ + abstract public function write(); + /** * Font style. * @@ -50,10 +77,16 @@ abstract class AbstractElement extends HTMLAbstractElement */ protected $paragraphStyle; - public function __construct(AbstractWriter $parentWriter, Element $element, $withoutP = false) - { - parent::__construct($parentWriter, $element, $withoutP); + /** + * @var \PhpOffice\PhpWord\Escaper\EscaperInterface + */ + protected $escaper; + public function __construct(AbstractWriter $parentWriter, Element $element, bool $withoutP = false) + { + $this->parentWriter = $parentWriter; + $this->element = $element; + $this->withoutP = $withoutP; $this->escaper = new Rtf(); } diff --git a/src/PhpWord/Writer/RTF/Element/Container.php b/src/PhpWord/Writer/RTF/Element/Container.php index 7f43cb6aaa..5e198aec86 100644 --- a/src/PhpWord/Writer/RTF/Element/Container.php +++ b/src/PhpWord/Writer/RTF/Element/Container.php @@ -17,14 +17,14 @@ namespace PhpOffice\PhpWord\Writer\RTF\Element; -use PhpOffice\PhpWord\Writer\HTML\Element\Container as HTMLContainer; +use PhpOffice\PhpWord\Element\AbstractContainer as ContainerElement; /** * Container element RTF writer. * * @since 0.11.0 */ -class Container extends HTMLContainer +class Container extends AbstractElement { /** * Namespace; Can't use __NAMESPACE__ in inherited class (RTF). @@ -32,4 +32,33 @@ class Container extends HTMLContainer * @var string */ protected $namespace = 'PhpOffice\\PhpWord\\Writer\\RTF\\Element'; + + /** + * Write container. + * + * @return string + */ + public function write() + { + $container = $this->element; + if (!$container instanceof ContainerElement) { + return ''; + } + $containerClass = substr(get_class($container), strrpos(get_class($container), '\\') + 1); + $withoutP = in_array($containerClass, ['TextRun', 'Footnote', 'Endnote']) ? true : false; + $content = ''; + + $elements = $container->getElements(); + foreach ($elements as $element) { + $elementClass = get_class($element); + $writerClass = str_replace('PhpOffice\\PhpWord\\Element', $this->namespace, $elementClass); + if (class_exists($writerClass)) { + /** @var AbstractElement $writer Type hint */ + $writer = new $writerClass($this->parentWriter, $element, $withoutP); + $content .= $writer->write(); + } + } + + return $content; + } } diff --git a/src/PhpWord/Writer/RTF/Element/Table.php b/src/PhpWord/Writer/RTF/Element/Table.php index 5d0755c931..45211a9202 100644 --- a/src/PhpWord/Writer/RTF/Element/Table.php +++ b/src/PhpWord/Writer/RTF/Element/Table.php @@ -20,6 +20,7 @@ use PhpOffice\PhpWord\Element\Cell as CellElement; use PhpOffice\PhpWord\Element\Row as RowElement; use PhpOffice\PhpWord\Element\Table as TableElement; +use PhpOffice\PhpWord\Style; /** * Table element RTF writer. @@ -45,6 +46,9 @@ public function write() } $content = ''; + $style = $this->element->getStyle(); + $bidiStyle = (is_object($style) && method_exists($style, 'isBidiVisual')) ? $style->isBidiVisual() : Style::getDefaultRtl(); + $bidi = $bidiStyle ? '\rtlrow' : ''; $rows = $element->getRows(); $rowCount = count($rows); @@ -52,7 +56,7 @@ public function write() $content .= '\pard' . PHP_EOL; for ($i = 0; $i < $rowCount; ++$i) { - $content .= '\trowd '; + $content .= "\\trowd$bidi "; $content .= $this->writeRowDef($rows[$i]); $content .= PHP_EOL; $content .= $this->writeRow($rows[$i]); diff --git a/src/PhpWord/Writer/RTF/Element/Title.php b/src/PhpWord/Writer/RTF/Element/Title.php index 653ba93df7..a387f273ca 100644 --- a/src/PhpWord/Writer/RTF/Element/Title.php +++ b/src/PhpWord/Writer/RTF/Element/Title.php @@ -38,7 +38,7 @@ protected function getStyles(): void $sect = $element->getParent(); if ($sect instanceof \PhpOffice\PhpWord\Element\Section) { $elems = $sect->getElements(); - if ($elems[0] === $element) { + if (self::isEqual($elems[0], $element)) { $pstyle = clone $pstyle; $pstyle->setPageBreakBefore(false); } @@ -48,6 +48,15 @@ protected function getStyles(): void } } + /** + * @param mixed $comparand1 + * @param mixed $comparand2 + */ + private static function isEqual($comparand1, $comparand2): bool + { + return $comparand1 === $comparand2; + } + /** * Write element. * @@ -71,7 +80,7 @@ public function write() $style = $element->getStyle(); if (is_string($style)) { $style = str_replace('Heading', '', $style); - if (is_numeric($style)) { + if ("$style" !== '') { $style = (int) $style - 1; if ($style >= 0 && $style <= 8) { $content .= '{\\outlinelevel' . $style; diff --git a/src/PhpWord/Writer/RTF/Part/AbstractPart.php b/src/PhpWord/Writer/RTF/Part/AbstractPart.php index d1bca0a4e2..be772b93a0 100644 --- a/src/PhpWord/Writer/RTF/Part/AbstractPart.php +++ b/src/PhpWord/Writer/RTF/Part/AbstractPart.php @@ -27,7 +27,7 @@ abstract class AbstractPart { /** - * @var \PhpOffice\PhpWord\Writer\AbstractWriter + * @var \PhpOffice\PhpWord\Writer\RTF */ private $parentWriter; @@ -47,7 +47,7 @@ public function __construct() abstract public function write(); /** - * @param \PhpOffice\PhpWord\Writer\AbstractWriter $writer + * @param \PhpOffice\PhpWord\Writer\RTF $writer */ public function setParentWriter(?AbstractWriter $writer = null): void { @@ -55,7 +55,7 @@ public function setParentWriter(?AbstractWriter $writer = null): void } /** - * @return \PhpOffice\PhpWord\Writer\AbstractWriter + * @return \PhpOffice\PhpWord\Writer\RTF */ public function getParentWriter() { diff --git a/src/PhpWord/Writer/RTF/Part/Document.php b/src/PhpWord/Writer/RTF/Part/Document.php index b452b9e937..1d55d43fa1 100644 --- a/src/PhpWord/Writer/RTF/Part/Document.php +++ b/src/PhpWord/Writer/RTF/Part/Document.php @@ -148,7 +148,13 @@ private function writeSections() $sections = $this->getParentWriter()->getPhpWord()->getSections(); $evenOdd = $this->getParentWriter()->getPhpWord()->getSettings()->hasEvenAndOddHeaders(); + $sectOwed = false; foreach ($sections as $section) { + if ($sectOwed) { + $content .= '\sect' . PHP_EOL; + } else { + $sectOwed = true; + } $styleWriter = new SectionStyleWriter($section->getStyle()); $styleWriter->setParentWriter($this->getParentWriter()); $content .= $styleWriter->write(); @@ -198,7 +204,7 @@ private function writeSections() $elementWriter = new Container($this->getParentWriter(), $section); $content .= $elementWriter->write(); - $content .= '\sect' . PHP_EOL; + //$content .= '\sect' . PHP_EOL; } return $content; diff --git a/src/PhpWord/Writer/RTF/Style/AbstractStyle.php b/src/PhpWord/Writer/RTF/Style/AbstractStyle.php index e7117d3261..355e384440 100644 --- a/src/PhpWord/Writer/RTF/Style/AbstractStyle.php +++ b/src/PhpWord/Writer/RTF/Style/AbstractStyle.php @@ -17,13 +17,91 @@ namespace PhpOffice\PhpWord\Writer\RTF\Style; -use PhpOffice\PhpWord\Writer\HTML\Style\AbstractStyle as HTMLAbstractStyle; +use PhpOffice\PhpWord\Style\AbstractStyle as StyleAbstract; +use PhpOffice\PhpWord\Writer\RTF; /** * Abstract RTF style writer. * * @since 0.11.0 */ -abstract class AbstractStyle extends HTMLAbstractStyle +abstract class AbstractStyle { + /** + * Parent writer. + * + * @var RTF + */ + private $parentWriter; + + /** + * Style. + * + * @var null|array|StyleAbstract + */ + private $style; + + /** + * Write style. + * + * @return mixed + */ + abstract public function write(); + + /** + * Create new instance. + * + * @param array|StyleAbstract $style + */ + public function __construct($style = null) + { + $this->style = $style; + } + + /** + * Set parent writer. + * + * @param RTF $writer + */ + public function setParentWriter($writer): void + { + $this->parentWriter = $writer; + } + + /** + * Get parent writer. + * + * @return RTF + */ + public function getParentWriter() + { + return $this->parentWriter; + } + + /** + * Get style. + * + * @return null|array|string|StyleAbstract + */ + public function getStyle() + { + if (!$this->style instanceof StyleAbstract && !is_array($this->style)) { + return ''; + } + + return $this->style; + } + + /** + * Get value if ... + * + * @param null|bool $condition + * @param string $value + * + * @return string + */ + protected function getValueIf($condition, $value) + { + return $condition == true ? $value : ''; + } } diff --git a/src/PhpWord/Writer/RTF/Style/Paragraph.php b/src/PhpWord/Writer/RTF/Style/Paragraph.php index c61f3ab23d..333975da03 100644 --- a/src/PhpWord/Writer/RTF/Style/Paragraph.php +++ b/src/PhpWord/Writer/RTF/Style/Paragraph.php @@ -35,6 +35,10 @@ class Paragraph extends AbstractStyle */ private $nestedLevel = 0; + private const LEFT = /** @scrutinizer ignore-deprecated */ Jc::LEFT; + private const RIGHT = /** @scrutinizer ignore-deprecated */ Jc::RIGHT; + private const JUSTIFY = /** @scrutinizer ignore-deprecated */ Jc::JUSTIFY; + /** * Write style. * @@ -52,6 +56,18 @@ public function write() Jc::END => '\qr', Jc::CENTER => '\qc', Jc::BOTH => '\qj', + self::LEFT => '\ql', + self::RIGHT => '\qr', + self::JUSTIFY => '\qj', + ]; + $bidiAlignments = [ + Jc::START => '\qr', + Jc::END => '\ql', + Jc::CENTER => '\qc', + Jc::BOTH => '\qj', + self::LEFT => '\ql', + self::RIGHT => '\qr', + self::JUSTIFY => '\qj', ]; $spaceAfter = $style->getSpaceAfter(); @@ -61,8 +77,13 @@ public function write() if ($this->nestedLevel == 0) { $content .= '\pard\nowidctlpar '; } - if (isset($alignments[$style->getAlignment()])) { - $content .= $alignments[$style->getAlignment()]; + $alignment = $style->getAlignment(); + $bidi = $style->isBidi(); + if ($alignment === '' && $bidi !== null) { + $alignment = Jc::START; + } + if (isset($alignments[$alignment])) { + $content .= $bidi ? $bidiAlignments[$alignment] : $alignments[$alignment]; } $content .= $this->writeIndentation($style->getIndentation()); $content .= $this->getValueIf($spaceBefore !== null, '\sb' . round($spaceBefore ?? 0)); diff --git a/src/PhpWord/Writer/Word2007/Element/TOC.php b/src/PhpWord/Writer/Word2007/Element/TOC.php index 7c5d089775..f7d00c14c8 100644 --- a/src/PhpWord/Writer/Word2007/Element/TOC.php +++ b/src/PhpWord/Writer/Word2007/Element/TOC.php @@ -93,7 +93,8 @@ private function writeTitle(XMLWriter $xmlWriter, TOCElement $element, Title $ti $styleWriter->write(); } $xmlWriter->startElement('w:t'); - $this->writeText($title->getText()); + $titleText = $title->getText(); + $this->writeText(is_string($titleText) ? $titleText : ''); $xmlWriter->endElement(); // w:t $xmlWriter->endElement(); // w:r @@ -141,8 +142,10 @@ private function writeTitle(XMLWriter $xmlWriter, TOCElement $element, Title $ti /** * Write style. + * + * @param float|int $indent */ - private function writeStyle(XMLWriter $xmlWriter, TOCElement $element, int $indent): void + private function writeStyle(XMLWriter $xmlWriter, TOCElement $element, $indent): void { $tocStyle = $element->getStyleTOC(); $fontStyle = $element->getStyleFont(); diff --git a/src/PhpWord/Writer/Word2007/Element/TOC.php.bak b/src/PhpWord/Writer/Word2007/Element/TOC.php.bak new file mode 100644 index 0000000000..a86199bf23 --- /dev/null +++ b/src/PhpWord/Writer/Word2007/Element/TOC.php.bak @@ -0,0 +1,202 @@ +getXmlWriter(); + $element = $this->getElement(); + if (!$element instanceof TOCElement) { + return; + } + + $titles = $element->getTitles(); + $writeFieldMark = true; + + foreach ($titles as $title) { + $this->writeTitle($xmlWriter, $element, $title, $writeFieldMark); + if ($writeFieldMark) { + $writeFieldMark = false; + } + } + + $xmlWriter->startElement('w:p'); + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:fldChar'); + $xmlWriter->writeAttribute('w:fldCharType', 'end'); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + } + + /** + * Write title. + * + * @param \PhpOffice\PhpWord\Element\Title $title + * @param bool $writeFieldMark + */ + private function writeTitle(XMLWriter $xmlWriter, TOCElement $element, $title, $writeFieldMark): void + {echo "here2\n"; + $tocStyle = $element->getStyleTOC(); + $fontStyle = $element->getStyleFont(); + $isObject = ($fontStyle instanceof Font) ? true : false; + $rId = $title->getRelationId(); + $indent = ($title->getDepth() - 1) * $tocStyle->getIndent(); + + $xmlWriter->startElement('w:p'); + + // Write style and field mark + $this->writeStyle($xmlWriter, $element, $indent); + if ($writeFieldMark) { + $this->writeFieldMark($xmlWriter, $element); + } + + // Hyperlink + $xmlWriter->startElement('w:hyperlink'); + $xmlWriter->writeAttribute('w:anchor', "_Toc{$rId}"); + $xmlWriter->writeAttribute('w:history', '1'); + + // Title text + $xmlWriter->startElement('w:r'); + if ($isObject) { + $styleWriter = new FontStyleWriter($xmlWriter, $fontStyle); + $styleWriter->write(); + } + $xmlWriter->startElement('w:t'); + $titleText = $title->getText(); + $this->writeText(is_string($titleText) ? $titleText : ''); + $xmlWriter->endElement(); // w:t + $xmlWriter->endElement(); // w:r + + $xmlWriter->startElement('w:r'); + $xmlWriter->writeElement('w:tab', null); + $xmlWriter->endElement(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:fldChar'); + $xmlWriter->writeAttribute('w:fldCharType', 'begin'); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:instrText'); + $xmlWriter->writeAttribute('xml:space', 'preserve'); + $xmlWriter->text("PAGEREF _Toc{$rId} \\h"); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:fldChar'); + $xmlWriter->writeAttribute('w:fldCharType', 'end'); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + + $xmlWriter->endElement(); // w:hyperlink + + $xmlWriter->endElement(); // w:p + } + + /** + * Write style. + * + * @param float|int $indent + */ + private function writeStyle(XMLWriter $xmlWriter, TOCElement $element, $indent): void + {echo "here3\n"; + $tocStyle = $element->getStyleTOC(); + $fontStyle = $element->getStyleFont(); + $isObject = ($fontStyle instanceof Font) ? true : false; + + $xmlWriter->startElement('w:pPr'); + + // Paragraph + if ($isObject && null !== $fontStyle->getParagraph()) { + $styleWriter = new ParagraphStyleWriter($xmlWriter, $fontStyle->getParagraph()); + $styleWriter->write(); + } + + // Font + if (!empty($fontStyle) && !$isObject) { + $xmlWriter->startElement('w:rPr'); + $xmlWriter->startElement('w:rStyle'); + $xmlWriter->writeAttribute('w:val', $fontStyle); + $xmlWriter->endElement(); + $xmlWriter->endElement(); // w:rPr + } + + // Tab + $xmlWriter->startElement('w:tabs'); + $styleWriter = new TabStyleWriter($xmlWriter, $tocStyle); + $styleWriter->write(); + $xmlWriter->endElement(); + + // Indent + if ($indent > 0) { + $xmlWriter->startElement('w:ind'); + $xmlWriter->writeAttribute('w:left', $indent); + $xmlWriter->endElement(); + } + + $xmlWriter->endElement(); // w:pPr + } + + /** + * Write TOC Field. + */ + private function writeFieldMark(XMLWriter $xmlWriter, TOCElement $element): void + {echo "here4\n"; + $minDepth = $element->getMinDepth(); + $maxDepth = $element->getMaxDepth(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:fldChar'); + $xmlWriter->writeAttribute('w:fldCharType', 'begin'); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:instrText'); + $xmlWriter->writeAttribute('xml:space', 'preserve'); + $xmlWriter->text("TOC \\o {$minDepth}-{$maxDepth} \\h \\z \\u"); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:fldChar'); + $xmlWriter->writeAttribute('w:fldCharType', 'separate'); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + } +} diff --git a/src/PhpWord/Writer/Word2007/Element/Title.php b/src/PhpWord/Writer/Word2007/Element/Title.php index dd46d755e3..072dcc8d84 100644 --- a/src/PhpWord/Writer/Word2007/Element/Title.php +++ b/src/PhpWord/Writer/Word2007/Element/Title.php @@ -67,7 +67,8 @@ public function write(): void $this->writeText($text); $xmlWriter->endElement(); // w:t $xmlWriter->endElement(); // w:r - } elseif ($text instanceof \PhpOffice\PhpWord\Element\AbstractContainer) { + } + if ($text instanceof \PhpOffice\PhpWord\Element\AbstractContainer) { $containerWriter = new Container($xmlWriter, $text); $containerWriter->write(); } diff --git a/src/PhpWord/Writer/WriterInterface.php b/src/PhpWord/Writer/WriterInterface.php index 58bd455756..f205eed019 100644 --- a/src/PhpWord/Writer/WriterInterface.php +++ b/src/PhpWord/Writer/WriterInterface.php @@ -24,8 +24,6 @@ interface WriterInterface { /** * Save PhpWord to file. - * - * @param string $filename */ - public function save($filename = null); + public function save(string $filename): void; } diff --git a/tests/PhpWordTests/Collection/CollectionTest.php b/tests/PhpWordTests/Collection/CollectionTest.php index 9a18a2a75f..55425a333a 100644 --- a/tests/PhpWordTests/Collection/CollectionTest.php +++ b/tests/PhpWordTests/Collection/CollectionTest.php @@ -45,9 +45,6 @@ public function testCollection(): void self::assertNull($object->getItem(2)); // Check if it's null } - /** - * @covers ::setItem - */ public function testCollectionSetItem(): void { $object = new Footnotes(); diff --git a/tests/PhpWordTests/Element/CellTest.php b/tests/PhpWordTests/Element/CellTest.php index a6affafe7f..a80328edea 100644 --- a/tests/PhpWordTests/Element/CellTest.php +++ b/tests/PhpWordTests/Element/CellTest.php @@ -68,7 +68,7 @@ public function testAddText(): void public function testAddTextNotUTF8(): void { $oCell = new Cell(); - $element = $oCell->addText(utf8_decode('ééé')); + $element = $oCell->addText(Utf8Decode::decode('ééé')); self::assertCount(1, $oCell->getElements()); self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); @@ -81,7 +81,7 @@ public function testAddTextNotUTF8(): void public function testAddLink(): void { $oCell = new Cell(); - $element = $oCell->addLink(utf8_decode('ééé'), utf8_decode('ééé')); + $element = $oCell->addLink(Utf8Decode::decode('ééé'), Utf8Decode::decode('ééé')); self::assertCount(1, $oCell->getElements()); self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Link', $element); @@ -117,7 +117,7 @@ public function testAddListItem(): void public function testAddListItemNotUTF8(): void { $oCell = new Cell(); - $element = $oCell->addListItem(utf8_decode('ééé')); + $element = $oCell->addListItem(Utf8Decode::decode('ééé')); self::assertCount(1, $oCell->getElements()); self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\ListItem', $element); @@ -219,7 +219,7 @@ public function testAddPreserveTextNotUTF8(): void { $oCell = new Cell(); $oCell->setDocPart('Header', 1); - $element = $oCell->addPreserveText(utf8_decode('ééé')); + $element = $oCell->addPreserveText(Utf8Decode::decode('ééé')); self::assertCount(1, $oCell->getElements()); self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\PreserveText', $element); @@ -255,7 +255,7 @@ public function testCreateTextRun(): void public function testAddCheckBox(): void { $oCell = new Cell(); - $element = $oCell->addCheckBox(utf8_decode('ééé'), utf8_decode('ééé')); + $element = $oCell->addCheckBox(Utf8Decode::decode('ééé'), Utf8Decode::decode('ééé')); self::assertCount(1, $oCell->getElements()); self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\CheckBox', $element); diff --git a/tests/PhpWordTests/Element/FooterTest.php b/tests/PhpWordTests/Element/FooterTest.php index 672f3db75a..fcd2448611 100644 --- a/tests/PhpWordTests/Element/FooterTest.php +++ b/tests/PhpWordTests/Element/FooterTest.php @@ -57,7 +57,7 @@ public function testAddText(): void public function testAddTextNotUTF8(): void { $oFooter = new Footer(1); - $element = $oFooter->addText(utf8_decode('ééé')); + $element = $oFooter->addText(Utf8Decode::decode('ééé')); self::assertCount(1, $oFooter->getElements()); self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); @@ -143,7 +143,7 @@ public function testAddPreserveText(): void public function testAddPreserveTextNotUTF8(): void { $oFooter = new Footer(1); - $element = $oFooter->addPreserveText(utf8_decode('ééé')); + $element = $oFooter->addPreserveText(Utf8Decode::decode('ééé')); self::assertCount(1, $oFooter->getElements()); self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\PreserveText', $element); diff --git a/tests/PhpWordTests/Element/HeaderTest.php b/tests/PhpWordTests/Element/HeaderTest.php index 74e7c12685..5a6f63ed9d 100644 --- a/tests/PhpWordTests/Element/HeaderTest.php +++ b/tests/PhpWordTests/Element/HeaderTest.php @@ -60,7 +60,7 @@ public function testAddText(): void public function testAddTextNotUTF8(): void { $oHeader = new Header(1); - $element = $oHeader->addText(utf8_decode('ééé')); + $element = $oHeader->addText(Utf8Decode::decode('ééé')); self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); self::assertCount(1, $oHeader->getElements()); @@ -153,7 +153,7 @@ public function testAddPreserveText(): void public function testAddPreserveTextNotUTF8(): void { $oHeader = new Header(1); - $element = $oHeader->addPreserveText(utf8_decode('ééé')); + $element = $oHeader->addPreserveText(Utf8Decode::decode('ééé')); self::assertCount(1, $oHeader->getElements()); self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\PreserveText', $element); diff --git a/tests/PhpWordTests/Element/ImageTest.php b/tests/PhpWordTests/Element/ImageTest.php index 9f1773eb4e..725331a2df 100644 --- a/tests/PhpWordTests/Element/ImageTest.php +++ b/tests/PhpWordTests/Element/ImageTest.php @@ -88,7 +88,7 @@ public function testImages($source, $type, $extension, $createFunction, $imageFu self::assertNotNull($image->getImageStringData()); } - public function providerImages(): array + public static function providerImages(): array { return [ ['mars.jpg', 'image/jpeg', 'jpg', 'imagecreatefromjpeg', true, 100], diff --git a/tests/PhpWordTests/Element/ListItemRunTest.php b/tests/PhpWordTests/Element/ListItemRunTest.php index e438d186d2..387725cb31 100644 --- a/tests/PhpWordTests/Element/ListItemRunTest.php +++ b/tests/PhpWordTests/Element/ListItemRunTest.php @@ -114,7 +114,7 @@ public function testAddText(): void public function testAddTextNotUTF8(): void { $oListItemRun = new ListItemRun(); - $element = $oListItemRun->addText(utf8_decode('ééé')); + $element = $oListItemRun->addText(Utf8Decode::decode('ééé')); self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); self::assertCount(1, $oListItemRun->getElements()); diff --git a/tests/PhpWordTests/Element/SectionTest.php b/tests/PhpWordTests/Element/SectionTest.php index 72aada10d6..7bcc3bb9a6 100644 --- a/tests/PhpWordTests/Element/SectionTest.php +++ b/tests/PhpWordTests/Element/SectionTest.php @@ -75,18 +75,18 @@ public function testAddElements(): void $section = new Section(0); $section->setPhpWord(new PhpWord()); - $section->addText(utf8_decode('ä')); - $section->addLink(utf8_decode('http://äää.com'), utf8_decode('ä')); + $section->addText(Utf8Decode::decode('ä')); + $section->addLink(Utf8Decode::decode('http://äää.com'), Utf8Decode::decode('ä')); $section->addTextBreak(); $section->addPageBreak(); $section->addTable(); - $section->addListItem(utf8_decode('ä')); + $section->addListItem(Utf8Decode::decode('ä')); $section->addObject($objectSource); $section->addImage($imageSource); - $section->addTitle(utf8_decode('ä'), 1); + $section->addTitle(Utf8Decode::decode('ä'), 1); $section->addTextRun(); $section->addFootnote(); - $section->addCheckBox(utf8_decode('chkä'), utf8_decode('Contentä')); + $section->addCheckBox(Utf8Decode::decode('chkä'), Utf8Decode::decode('Contentä')); $section->addTOC(); $elementCollection = $section->getElements(); diff --git a/tests/PhpWordTests/Element/TextRunTest.php b/tests/PhpWordTests/Element/TextRunTest.php index 2a1ad6efcd..f3c50624ec 100644 --- a/tests/PhpWordTests/Element/TextRunTest.php +++ b/tests/PhpWordTests/Element/TextRunTest.php @@ -99,7 +99,7 @@ public function testAddText(): void public function testAddTextNotUTF8(): void { $oTextRun = new TextRun(); - $element = $oTextRun->addText(utf8_decode('ééé')); + $element = $oTextRun->addText(Utf8Decode::decode('ééé')); self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); self::assertCount(1, $oTextRun->getElements()); diff --git a/tests/PhpWordTests/Element/Utf8Decode.php b/tests/PhpWordTests/Element/Utf8Decode.php new file mode 100644 index 0000000000..8f3a1cb1ad --- /dev/null +++ b/tests/PhpWordTests/Element/Utf8Decode.php @@ -0,0 +1,29 @@ +write()); + + return $txt2; + } + + public function expect(string $str, bool $rtl = false): string + { + return ($rtl ? self:: HEADER_RTL : self::HEADER) . $str . self::TRAILER; + } + + /** + * Test special characters which require escaping. + */ + public function testSpecial(): void + { + Style::setDefaultRtl(false); + $str = 'Special characters { open brace } close brace \\ backslash'; + $expect = $this->expect('Special characters \\{ open brace \\} close brace \\\\ backslash'); + self::assertEquals($expect, $this->escapestring($str)); + } + + /** + * Test accented character. + */ + public function testAccent(): void + { + Style::setDefaultRtl(false); + $str = 'Voilà - string with accented char'; + $expect = $this->expect('Voil\\uc0{\\u224} - string with accented char'); + self::assertEquals($expect, $this->escapestring($str)); + } + + /** + * Test Hebrew. + */ + public function testHebrew(): void + { + Style::setDefaultRtl(true); + $str = 'Hebrew - שלום'; + $expect = $this->expect('Hebrew - \\uc0{\\u1513}\\uc0{\\u1500}\\uc0{\\u1493}\\uc0{\\u1501}', true); + self::assertEquals($expect, $this->escapestring($str)); + } + + /** + * Test tab. + */ + public function testTab(): void + { + Style::setDefaultRtl(false); + $str = "Here's a tab\tfollowed by more characters."; + $expect = $this->expect("Here's a tab{\\tab}followed by more characters."); + self::assertEquals($expect, $this->escapestring($str)); + } +} diff --git a/tests/PhpWordTests/IOFactoryTest.php b/tests/PhpWordTests/IOFactoryTest.php index 890f45c78e..ee8d7fe180 100644 --- a/tests/PhpWordTests/IOFactoryTest.php +++ b/tests/PhpWordTests/IOFactoryTest.php @@ -54,7 +54,7 @@ public function testCreateWriter(string $name, string $expected): void self::assertInstanceOf($expected, $actual); } - public function providerCreateWriter(): iterable + public static function providerCreateWriter(): iterable { return [ ['ODText', ODText::class], diff --git a/tests/PhpWordTests/PhpWordTest.php b/tests/PhpWordTests/PhpWordTest.php index 19e141f49e..7b756e7082 100644 --- a/tests/PhpWordTests/PhpWordTest.php +++ b/tests/PhpWordTests/PhpWordTest.php @@ -122,13 +122,14 @@ public function testAddTitleStyle(): void */ public function testSave(): void { - $this->setOutputCallback(function (): void { - }); $phpWord = new PhpWord(); $section = $phpWord->addSection(); $section->addText('Hello world!'); - + ob_start(); self::assertTrue($phpWord->save('test.docx', 'Word2007', true)); + $contents = ob_get_contents(); + self::assertTrue(ob_end_clean()); + self::assertNotEmpty($contents); } /** diff --git a/tests/PhpWordTests/Reader/Word2007Test.php b/tests/PhpWordTests/Reader/Word2007Test.php index 1d8674d729..fac5b6b59a 100644 --- a/tests/PhpWordTests/Reader/Word2007Test.php +++ b/tests/PhpWordTests/Reader/Word2007Test.php @@ -40,6 +40,14 @@ */ class Word2007Test extends \PHPUnit\Framework\TestCase { + /** + * Tear down after each test. + */ + protected function tearDown(): void + { + TestHelperDOCX::clear(); + } + /** * Test canRead() method. */ diff --git a/tests/PhpWordTests/Shared/HandlerTest.php b/tests/PhpWordTests/Shared/HandlerTest.php new file mode 100644 index 0000000000..4aa051d3ce --- /dev/null +++ b/tests/PhpWordTests/Shared/HandlerTest.php @@ -0,0 +1,79 @@ +getMessage()); + } + } + } + + public function testNotice(): void + { + try { + Handler::notice('invalidtz'); + self::fail('Expected error/exception did not happen'); + } catch (Throwable $e) { + self::assertStringContainsString('Timezone', $e->getMessage()); + } + } + + public function testWarning(): void + { + try { + Handler::warning(); + self::fail('Expected error/exception did not happen'); + } catch (Throwable $e) { + self::assertStringContainsString('ailed to open stream', $e->getMessage()); + } + } + + public function testUserDeprecated(): void + { + try { + Handler::userDeprecated(); + self::fail('Expected error/exception did not happen'); + } catch (Throwable $e) { + self::assertStringContainsString('hello', $e->getMessage()); + } + } + + public function testUserNotice(): void + { + try { + Handler::userNotice(); + self::fail('Expected error/exception did not happen'); + } catch (Throwable $e) { + self::assertStringContainsString('userNotice', $e->getMessage()); + } + } + + public function testUserWarning(): void + { + try { + Handler::userWarning(); + self::fail('Expected error/exception did not happen'); + } catch (Throwable $e) { + self::assertStringContainsString('userWarning', $e->getMessage()); + } + } +} diff --git a/tests/PhpWordTests/Shared/HtmlTest.php b/tests/PhpWordTests/Shared/HtmlTest.php index b0ab159150..c8640509de 100644 --- a/tests/PhpWordTests/Shared/HtmlTest.php +++ b/tests/PhpWordTests/Shared/HtmlTest.php @@ -35,6 +35,14 @@ */ class HtmlTest extends AbstractWebServerEmbeddedTest { + /** + * Tear down after each test. + */ + protected function tearDown(): void + { + TestHelperDOCX::clear(); + } + /** * Test unit conversion functions with various numbers. */ @@ -900,7 +908,10 @@ public function testParseLink(): void self::assertEquals('link text', $doc->getElement('/w:document/w:body/w:p/w:hyperlink/w:r/w:t')->nodeValue); self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:hyperlink/w:r/w:rPr/w:u')); self::assertEquals('single', $doc->getElementAttribute('/w:document/w:body/w:p/w:hyperlink/w:r/w:rPr/w:u', 'w:val')); + } + public function testParseLink2(): void + { $phpWord = new PhpWord(); $section = $phpWord->addSection(); $section->addBookmark('bookmark'); diff --git a/tests/PhpWordTests/Shared/TextTest.php b/tests/PhpWordTests/Shared/TextTest.php index fdd9e9df7a..56d7681bcd 100644 --- a/tests/PhpWordTests/Shared/TextTest.php +++ b/tests/PhpWordTests/Shared/TextTest.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWordTests\Shared; use PhpOffice\PhpWord\Shared\Text; +use PhpOffice\PhpWordTests\Element\Utf8Decode; /** * Test class for Text. @@ -66,7 +67,7 @@ public function testIsUTF8(): void { self::assertTrue(Text::isUTF8('')); self::assertTrue(Text::isUTF8('éééé')); - self::assertFalse(Text::isUTF8(utf8_decode('éééé'))); + self::assertFalse(Text::isUTF8(Utf8Decode::decode('éééé'))); } /** diff --git a/tests/PhpWordTests/Style/FontTest.php b/tests/PhpWordTests/Style/FontTest.php index 854a4b5951..e2d7d4f582 100644 --- a/tests/PhpWordTests/Style/FontTest.php +++ b/tests/PhpWordTests/Style/FontTest.php @@ -79,6 +79,8 @@ public function testSetStyleValueWithNullOrEmpty(): void 'kerning' => null, 'lang' => null, 'hidden' => false, + 'htmlWhiteSpace' => '', + 'htmlGenericFont' => '', ]; foreach ($attributes as $key => $default) { $get = is_bool($default) ? "is{$key}" : "get{$key}"; @@ -121,6 +123,8 @@ public function testSetStyleValueNormal(): void 'noProof' => true, 'lang' => new Language(Language::EN_US), 'hidden' => true, + 'htmlWhiteSpace' => 'pre-wrap', + 'htmlGenericFont' => 'serif', ]; $object->setStyleByArray($attributes); foreach ($attributes as $key => $value) { @@ -150,6 +154,7 @@ public function testLineHeight(): void self::assertEquals('auto', $lineRule); // Test setter + TestHelperDOCX::clear(); $text->getFontStyle()->setLineHeight(3.0); $doc = TestHelperDOCX::getDocument($phpWord); $element = $doc->getElement('/w:document/w:body/w:p/w:pPr/w:spacing'); diff --git a/tests/PhpWordTests/Style/ParagraphTest.php b/tests/PhpWordTests/Style/ParagraphTest.php index 124b8829c8..2263e9938e 100644 --- a/tests/PhpWordTests/Style/ParagraphTest.php +++ b/tests/PhpWordTests/Style/ParagraphTest.php @@ -157,6 +157,7 @@ public function testLineHeight(): void // Test setter $text->getParagraphStyle()->setLineHeight(3.0); + TestHelperDOCX::clear(); $doc = TestHelperDOCX::getDocument($phpWord); $element = $doc->getElement('/w:document/w:body/w:p/w:pPr/w:spacing'); diff --git a/tests/PhpWordTests/TemplateProcessorTest.php b/tests/PhpWordTests/TemplateProcessorTest.php index d8ff9ace9e..843ac798e2 100644 --- a/tests/PhpWordTests/TemplateProcessorTest.php +++ b/tests/PhpWordTests/TemplateProcessorTest.php @@ -21,10 +21,12 @@ use Exception; use PhpOffice\PhpWord\Element\Text; use PhpOffice\PhpWord\Element\TextRun; +use PhpOffice\PhpWord\Exception\Exception as WordException; use PhpOffice\PhpWord\IOFactory; use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\TemplateProcessor; +use TypeError; use ZipArchive; /** @@ -54,11 +56,8 @@ public function testTheConstruct(): void * @covers ::save * @covers ::zip */ - public function testTemplateCanBeSavedInTemporaryLocation() + public function xtestTemplateCanBeSavedInTemporaryLocation(string $templateFqfn, TemplateProcessor $templateProcessor): string { - $templateFqfn = __DIR__ . '/_files/templates/with_table_macros.docx'; - - $templateProcessor = new TemplateProcessor($templateFqfn); $xslDomDocument = new DOMDocument(); $xslDomDocument->load(__DIR__ . '/_files/xsl/remove_tables_by_needle.xsl'); foreach (['${employee.', '${scoreboard.', '${reference.'] as $needle) { @@ -103,13 +102,13 @@ public function testTemplateCanBeSavedInTemporaryLocation() * XSL stylesheet can be applied. * * @covers ::applyXslStyleSheet - * - * @depends testTemplateCanBeSavedInTemporaryLocation - * - * @param string $actualDocumentFqfn */ - public function testXslStyleSheetCanBeApplied($actualDocumentFqfn): void + public function testXslStyleSheetCanBeApplied(): void { + $templateFqfn = __DIR__ . '/_files/templates/with_table_macros.docx'; + $templateProcessor = new TemplateProcessor($templateFqfn); + + $actualDocumentFqfn = $this->xtestTemplateCanBeSavedInTemporaryLocation($templateFqfn, $templateProcessor); $expectedDocumentFqfn = __DIR__ . '/_files/documents/without_table_macros.docx'; $actualDocumentZip = new ZipArchive(); @@ -130,9 +129,9 @@ public function testXslStyleSheetCanBeApplied($actualDocumentFqfn): void throw new Exception("Could not close zip file \"{$expectedDocumentFqfn}\"."); } - self::assertXmlStringEqualsXmlString($expectedHeaderXml, $actualHeaderXml); - self::assertXmlStringEqualsXmlString($expectedMainPartXml, $actualMainPartXml); - self::assertXmlStringEqualsXmlString($expectedFooterXml, $actualFooterXml); + self::assertSame($expectedHeaderXml, $actualHeaderXml); + self::assertSame($expectedMainPartXml, $actualMainPartXml); + self::assertSame($expectedFooterXml, $actualFooterXml); } /** @@ -142,11 +141,13 @@ public function testXslStyleSheetCanBeApplied($actualDocumentFqfn): void */ public function testXslStyleSheetCanNotBeAppliedOnFailureOfSettingParameterValue(): void { - $this->expectException(\PhpOffice\PhpWord\Exception\Exception::class); - $this->expectExceptionMessage('Could not set values for the given XSL style sheet parameters.'); - // Test is not needed for PHP 8.0, because internally validation throws TypeError exception. if (\PHP_VERSION_ID >= 80000) { - self::markTestSkipped('not needed for PHP 8.0'); + // PHP 8+ internal validation throws TypeError. + $this->expectException(TypeError::class); + $this->expectExceptionMessage('must contain only string keys'); + } else { + $this->expectException(\PhpOffice\PhpWord\Exception\Exception::class); + $this->expectExceptionMessage('Could not set values for the given XSL style sheet parameters.'); } $templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/blank.docx'); @@ -223,7 +224,7 @@ public function testCloneRow(): void ); $docName = 'clone-test-result.docx'; - $templateProcessor->setValue('tableHeader', utf8_decode('ééé')); + $templateProcessor->setValue('tableHeader', 'ééé'); $templateProcessor->cloneRow('userId', 1); $templateProcessor->setValue('userId#1', 'Test'); $templateProcessor->saveAs($docName); @@ -250,7 +251,7 @@ public function testCloneRowWithCustomMacro(): void ); $docName = 'clone-test-result.docx'; - $templateProcessor->setValue('tableHeader', utf8_decode('ééé')); + $templateProcessor->setValue('tableHeader', 'ééé'); $templateProcessor->cloneRow('userId', 1); $templateProcessor->setValue('userId#1', 'Test'); $templateProcessor->saveAs($docName); @@ -1423,6 +1424,18 @@ public function testShouldMakeFieldsUpdateOnOpen(): void self::assertStringContainsString('', $templateProcessor->getSettingsPart()); } + /** + * Should not allow unserialize to avoid malware. + */ + public function testUnserialize(): void + { + $this->expectException(WordException::class); + $this->expectExceptionMessage('unserialize not permitted'); + $object = new TemplateProcessor(__DIR__ . '/_files/templates/blank.docx'); + $serialized = serialize($object); + $object2 = unserialize($serialized); + } + public function testShouldMakeFieldsUpdateOnOpenWithCustomMacro(): void { $settingsPart = ' diff --git a/tests/PhpWordTests/TestHelperDOCX.php b/tests/PhpWordTests/TestHelperDOCX.php index 44766bedaa..de9f1a81b7 100644 --- a/tests/PhpWordTests/TestHelperDOCX.php +++ b/tests/PhpWordTests/TestHelperDOCX.php @@ -80,6 +80,7 @@ public static function clear(): void { if (self::$file && file_exists(self::$file)) { unlink(self::$file); + self::$file = ''; } if (is_dir(Settings::getTempDir() . '/PhpWord_Unit_Test/')) { self::deleteDir(Settings::getTempDir() . '/PhpWord_Unit_Test/'); @@ -103,7 +104,7 @@ public static function deleteDir($dir): void } } - rmdir($dir); + @rmdir($dir); } /** diff --git a/tests/PhpWordTests/Writer/HTML/DirectionTest.php b/tests/PhpWordTests/Writer/HTML/DirectionTest.php new file mode 100644 index 0000000000..d14c0cfad8 --- /dev/null +++ b/tests/PhpWordTests/Writer/HTML/DirectionTest.php @@ -0,0 +1,57 @@ +addSection(); + $html = '

الألم الذي ربما تنجم عنه بعض ا.

'; + SharedHtml::addHtml($section, $html, false, false); + $english = '

LTR in RTL document.

'; + SharedHtml::addHtml($section, $english, false, false); + SharedHtml::addHtml($section, $english, false, false); + SharedHtml::addHtml($section, $html, false, false); + SharedHtml::addHtml($section, $html, false, false); + $writer = new HTML($doc); + $content = $writer->getContent(); + self::assertSame(3, substr_count($content, '')); + self::assertSame(2, substr_count($content, '')); + self::assertSame(3, substr_count($content, '

')); + self::assertSame(2, substr_count($content, '

')); + } +} diff --git a/tests/PhpWordTests/Writer/HTML/ElementTest.php b/tests/PhpWordTests/Writer/HTML/ElementTest.php index ccdd5eaf06..64fa5a31ff 100644 --- a/tests/PhpWordTests/Writer/HTML/ElementTest.php +++ b/tests/PhpWordTests/Writer/HTML/ElementTest.php @@ -76,8 +76,8 @@ public function testWriteTrackChanges(): void $dom = $this->getAsHTML($phpWord); $xpath = new DOMXPath($dom); - self::assertEquals(1, $xpath->query('/html/body/p[1]/ins')->length); - self::assertEquals(1, $xpath->query('/html/body/p[2]/del')->length); + self::assertEquals(1, $xpath->query('/html/body/div/p[1]/ins')->length); + self::assertEquals(1, $xpath->query('/html/body/div/p[2]/del')->length); } /** @@ -100,14 +100,14 @@ public function testWriteColSpan(): void $dom = $this->getAsHTML($phpWord); $xpath = new DOMXPath($dom); - self::assertEquals(1, $xpath->query('/html/body/table/tr[1]/td')->length); - self::assertEquals('2', $xpath->query('/html/body/table/tr/td[1]')->item(0)->attributes->getNamedItem('colspan')->textContent); - self::assertEquals(2, $xpath->query('/html/body/table/tr[2]/td')->length); + self::assertEquals(1, $xpath->query('/html/body/div/table/tr[1]/td')->length); + self::assertEquals('2', $xpath->query('/html/body/div/table/tr/td[1]')->item(0)->attributes->getNamedItem('colspan')->textContent); + self::assertEquals(2, $xpath->query('/html/body/div/table/tr[2]/td')->length); - self::assertEquals('#6086B8', $xpath->query('/html/body/table/tr[1]/td')->item(0)->attributes->getNamedItem('bgcolor')->textContent); - self::assertEquals('#ffffff', $xpath->query('/html/body/table/tr[1]/td')->item(0)->attributes->getNamedItem('color')->textContent); - self::assertEquals('#ffffff', $xpath->query('/html/body/table/tr[2]/td')->item(0)->attributes->getNamedItem('bgcolor')->textContent); - self::assertNull($xpath->query('/html/body/table/tr[2]/td')->item(0)->attributes->getNamedItem('color')); + self::assertEquals('#6086B8', $xpath->query('/html/body/div/table/tr[1]/td')->item(0)->attributes->getNamedItem('bgcolor')->textContent); + self::assertEquals('#ffffff', $xpath->query('/html/body/div/table/tr[1]/td')->item(0)->attributes->getNamedItem('color')->textContent); + self::assertEquals('#ffffff', $xpath->query('/html/body/div/table/tr[2]/td')->item(0)->attributes->getNamedItem('bgcolor')->textContent); + self::assertNull($xpath->query('/html/body/div/table/tr[2]/td')->item(0)->attributes->getNamedItem('color')); } /** @@ -134,9 +134,9 @@ public function testWriteRowSpan(): void $dom = $this->getAsHTML($phpWord); $xpath = new DOMXPath($dom); - self::assertEquals(2, $xpath->query('/html/body/table/tr[1]/td')->length); - self::assertEquals('3', $xpath->query('/html/body/table/tr[1]/td[1]')->item(0)->attributes->getNamedItem('rowspan')->textContent); - self::assertEquals(1, $xpath->query('/html/body/table/tr[2]/td')->length); + self::assertEquals(2, $xpath->query('/html/body/div/table/tr[1]/td')->length); + self::assertEquals('3', $xpath->query('/html/body/div/table/tr[1]/td[1]')->item(0)->attributes->getNamedItem('rowspan')->textContent); + self::assertEquals(1, $xpath->query('/html/body/div/table/tr[2]/td')->length); } private function getAsHTML(PhpWord $phpWord) @@ -211,7 +211,7 @@ public function testWriteTableLayout(): void $dom = $this->getAsHTML($phpWord); $xpath = new DOMXPath($dom); - self::assertEquals('table-layout: fixed;', $xpath->query('/html/body/table[1]')->item(0)->attributes->getNamedItem('style')->textContent); - self::assertEquals('table-layout: auto;', $xpath->query('/html/body/table[2]')->item(0)->attributes->getNamedItem('style')->textContent); + self::assertEquals('table-layout: fixed;', $xpath->query('/html/body/div/table[1]')->item(0)->attributes->getNamedItem('style')->textContent); + self::assertEquals('table-layout: auto;', $xpath->query('/html/body/div/table[2]')->item(0)->attributes->getNamedItem('style')->textContent); } } diff --git a/tests/PhpWordTests/Writer/HTML/FontTest.php b/tests/PhpWordTests/Writer/HTML/FontTest.php new file mode 100644 index 0000000000..cffcd6c7de --- /dev/null +++ b/tests/PhpWordTests/Writer/HTML/FontTest.php @@ -0,0 +1,301 @@ +defaultFontName = Settings::getDefaultFontName(); + $this->defaultFontSize = Settings::getDefaultFontSize(); + } + + /** + * Executed after each method of the class. + */ + protected function tearDown(): void + { + Settings::setDefaultFontName($this->defaultFontName); + Settings::setDefaultFontSize($this->defaultFontSize); + } + + /** + * Tests font names - without generics. + */ + public function testFontNames1(): void + { + $phpWord = new PhpWord(); + $phpWord->setDefaultFontName('Courier New'); + $phpWord->setDefaultFontSize(12); + $phpWord->addFontStyle('style1', ['name' => 'Tahoma', 'size' => 10, 'color' => '1B2232', 'bold' => true]); + $phpWord->addFontStyle('style2', ['name' => 'Arial', 'size' => 10]); + $phpWord->addFontStyle('style3', ['name' => 'hack attempt\'}; display:none', 'size' => 10]); + $phpWord->addFontStyle('style4', ['name' => 'padmaa 1.1', 'size' => 10, 'bold' => true]); + $phpWord->addFontStyle('style5', ['name' => 'MingLiU-ExtB', 'size' => 10, 'bold' => true]); + $section1 = $phpWord->addSection(); + $section1->addText('Default font'); + $section1->addText('Tahoma', 'style1'); + $section1->addText('Arial', 'style2'); + $section1->addText('hack attempt', 'style3'); + $section1->addText('padmaa 1.1 bold', 'style4'); + $section1->addText('MingLiu-ExtB bold', 'style5'); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/p[1]', 'class')); + self::assertEquals(0, Helper::getLength($xpath, '/html/body/div/p[1]/span')); + self::assertEquals('style1', Helper::getTextContent($xpath, '/html/body/div/p[2]/span', 'class')); + self::assertEquals('style2', Helper::getTextContent($xpath, '/html/body/div/p[3]/span', 'class')); + self::assertEquals('style3', Helper::getTextContent($xpath, '/html/body/div/p[4]/span', 'class')); + self::assertEquals('style4', Helper::getTextContent($xpath, '/html/body/div/p[5]/span', 'class')); + self::assertEquals('style5', Helper::getTextContent($xpath, '/html/body/div/p[6]/span', 'class')); + + $style = Helper::getTextContent($xpath, '/html/head/style'); + $prg = preg_match('/^[*][^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('* {font-family: \'Courier New\'; font-size: 12pt;}', $matches[0]); + $prg = preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style1 {font-family: \'Tahoma\'; font-size: 10pt; color: #1B2232; font-weight: bold;}', $matches[0]); + $prg = preg_match('/^[.]style2[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style2 {font-family: \'Arial\'; font-size: 10pt;}', $matches[0]); + $prg = preg_match('/^[.]style3[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style3 {font-family: \'hack attempt'}; display:none\'; font-size: 10pt;}', $matches[0]); + $prg = preg_match('/^[.]style4[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style4 {font-family: \'padmaa 1.1\'; font-size: 10pt; font-weight: bold;}', $matches[0]); + $prg = preg_match('/^[.]style5[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style5 {font-family: \'MingLiU-ExtB\'; font-size: 10pt; font-weight: bold;}', $matches[0]); + } + + /** + * Tests font names - with generics. + */ + public function testFontNames2(): void + { + $phpWord = new PhpWord(); + $phpWord->setDefaultFontName('Courier New'); + $phpWord->setDefaultFontSize(12); + $phpWord->addFontStyle('style1', ['name' => 'Tahoma', 'size' => 10, 'color' => '1B2232', 'bold' => true]); + $phpWord->addFontStyle('style2', ['name' => 'Arial', 'size' => 10, 'htmlGenericFont' => 'sans-serif']); + $phpWord->addFontStyle('style3', ['name' => 'DejaVu Sans Monospace', 'size' => 10, 'htmlGenericFont' => 'monospace']); + $phpWord->addFontStyle('style4', ['name' => 'Arial', 'size' => 10, 'htmlGenericFont' => 'invalid']); + $section1 = $phpWord->addSection(); + $section1->addText('Default font'); + $section1->addText('Tahoma', 'style1'); + $section1->addText('Arial', 'style2'); + $section1->addText('DejaVu Sans Monospace', 'style3'); + $section1->addText('Arial with invalid fallback', 'style4'); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/p[1]', 'class')); + self::assertEquals(0, Helper::getLength($xpath, '/html/body/div/p[1]/span')); + self::assertEquals('style1', Helper::getTextContent($xpath, '/html/body/div/p[2]/span', 'class')); + self::assertEquals('style2', Helper::getTextContent($xpath, '/html/body/div/p[3]/span', 'class')); + self::assertEquals('style3', Helper::getTextContent($xpath, '/html/body/div/p[4]/span', 'class')); + self::assertEquals('style4', Helper::getTextContent($xpath, '/html/body/div/p[5]/span', 'class')); + + $style = Helper::getTextContent($xpath, '/html/head/style'); + $prg = preg_match('/^[*][^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('* {font-family: \'Courier New\'; font-size: 12pt;}', $matches[0]); + $prg = preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style1 {font-family: \'Tahoma\'; font-size: 10pt; color: #1B2232; font-weight: bold;}', $matches[0]); + $prg = preg_match('/^[.]style2[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style2 {font-family: \'Arial\', sans-serif; font-size: 10pt;}', $matches[0]); + $prg = preg_match('/^[.]style3[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style3 {font-family: \'DejaVu Sans Monospace\', monospace; font-size: 10pt;}', $matches[0]); + $prg = preg_match('/^[.]style4[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style4 {font-family: \'Arial\'; font-size: 10pt;}', $matches[0]); + } + + /** + * Tests font names - with generics including for default font. + */ + public function testFontNames3(): void + { + $phpWord = new PhpWord(); + $phpWord->setDefaultFontName('Courier New'); + $phpWord->setDefaultFontSize(12); + $phpWord->setDefaultHtmlGenericFont('monospace'); + $phpWord->addFontStyle('style1', ['name' => 'Tahoma', 'size' => 10, 'color' => '1B2232', 'bold' => true]); + $phpWord->addFontStyle('style2', ['name' => 'Arial', 'size' => 10, 'htmlGenericFont' => 'sans-serif']); + $phpWord->addFontStyle('style3', ['name' => 'DejaVu Sans Monospace', 'size' => 10, 'htmlGenericFont' => 'monospace']); + $phpWord->addFontStyle('style4', ['name' => 'Arial', 'size' => 10, 'htmlGenericFont' => 'invalid']); + $section1 = $phpWord->addSection(); + $section1->addText('Default font'); + $section1->addText('Tahoma', 'style1'); + $section1->addText('Arial', 'style2'); + $section1->addText('DejaVu Sans Monospace', 'style3'); + $section1->addText('Arial with invalid fallback', 'style4'); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/p[1]', 'class')); + self::assertEquals(0, Helper::getLength($xpath, '/html/body/div/p[1]/span')); + self::assertEquals('style1', Helper::getTextContent($xpath, '/html/body/div/p[2]/span', 'class')); + self::assertEquals('style2', Helper::getTextContent($xpath, '/html/body/div/p[3]/span', 'class')); + self::assertEquals('style3', Helper::getTextContent($xpath, '/html/body/div/p[4]/span', 'class')); + self::assertEquals('style4', Helper::getTextContent($xpath, '/html/body/div/p[5]/span', 'class')); + + $style = Helper::getTextContent($xpath, '/html/head/style'); + $prg = preg_match('/^[*][^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('* {font-family: \'Courier New\', monospace; font-size: 12pt;}', $matches[0]); + $prg = preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style1 {font-family: \'Tahoma\'; font-size: 10pt; color: #1B2232; font-weight: bold;}', $matches[0]); + $prg = preg_match('/^[.]style2[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style2 {font-family: \'Arial\', sans-serif; font-size: 10pt;}', $matches[0]); + $prg = preg_match('/^[.]style3[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style3 {font-family: \'DejaVu Sans Monospace\', monospace; font-size: 10pt;}', $matches[0]); + $prg = preg_match('/^[.]style4[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style4 {font-family: \'Arial\'; font-size: 10pt;}', $matches[0]); + } + + /** + * Tests white space. + */ + public function testWhiteSpace(): void + { + $phpWord = new PhpWord(); + $phpWord->setDefaultHtmlWhiteSpace('pre-wrap'); + $phpWord->setDefaultFontSize(12); + $phpWord->addFontStyle('style1', ['name' => 'Courier New', 'size' => 10, 'htmlWhiteSpace' => 'pre-wrap']); + $phpWord->addFontStyle('style2', ['name' => 'Courier New', 'size' => 10, 'htmlWhiteSpace' => 'invalid']); + $phpWord->addFontStyle('style3', ['name' => 'Courier New', 'size' => 10, 'htmlWhiteSpace' => 'normal']); + $phpWord->addFontStyle('style4', ['name' => 'Courier New', 'size' => 10, 'htmlWhiteSpace' => 'invalid']); + $text = 'This is a long line which will be split over 2 lines with pre-wrap'; + $section1 = $phpWord->addSection(); + $section1->addText($text); + $section1->addText($text, 'style1'); + $section1->addText($text, 'style2'); + $section1->addText($text, 'style3'); + $section1->addText($text, 'style4'); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + $style = Helper::getTextContent($xpath, '/html/head/style'); + self::assertNotFalse(preg_match('/^[*][^\\r\\n]*/m', $style, $matches)); + self::assertEquals('* {font-family: \'Arial\'; font-size: 12pt; white-space: pre-wrap;}', $matches[0]); + $prg = preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style1 {font-family: \'Courier New\'; font-size: 10pt; white-space: pre-wrap;}', $matches[0]); + $prg = preg_match('/^[.]style2[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style2 {font-family: \'Courier New\'; font-size: 10pt;}', $matches[0]); + $prg = preg_match('/^[.]style3[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style3 {font-family: \'Courier New\'; font-size: 10pt; white-space: normal;}', $matches[0]); + $prg = preg_match('/^[.]style4[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style4 {font-family: \'Courier New\'; font-size: 10pt;}', $matches[0]); + } + + /** + * Tests inline font style. + */ + public function testInline(): void + { + $phpWord = new PhpWord(); + $style1 = ['name' => 'Courier New', 'size' => 10, 'htmlWhiteSpace' => 'pre-wrap']; + $style2 = ['name' => 'Verdana', 'size' => 8.5]; + $text = 'This is a paragraph.'; + $section1 = $phpWord->addSection(); + $section1->addText($text, $style1); + $section1->addText($text, $style2); + $section1->addText($text); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + self::assertEquals('font-family: \'Courier New\'; font-size: 10pt; white-space: pre-wrap;', Helper::getTextContent($xpath, '/html/body/div/p[1]/span', 'style')); + self::assertEquals('font-family: \'Verdana\'; font-size: 8.5pt;', Helper::getTextContent($xpath, '/html/body/div/p[2]/span', 'style')); + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/p[3]', 'class')); + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/p[3]', 'style')); + self::assertEquals(0, Helper::getLength($xpath, '/html/body/div/p[3]/span')); + } + + /** + * Tests languages. + */ + public function testLanguages(): void + { + $phpWord = new PhpWord(); + $langarabic = new Language('', '', 'ar-DZ'); + $phpWord->addFontStyle('arabic', ['lang' => $langarabic]); + $langhindi = new Language('', 'hi-IN'); + $phpWord->addFontStyle('hindi', ['lang' => $langhindi, 'name' => 'Arial']); + $phpWord->addFontStyle('nolang', ['name' => 'Verdana', 'size' => '10']); + $section = $phpWord->addSection(); + $textrun = $section->addTextRun(); + $textrun->addText('سلام این یک پاراگراف راست به چپ است', ['rtl' => true, 'lang' => $langarabic]); + $section->addText('Ce texte-ci est en français.', ['lang' => 'fr-BE']); + $section->addText('Ce texte-ci aussi.', ['lang' => 'fr-BE', 'name' => 'Verdana']); + $section->addText('Text with no language'); + $section->addText('पाठ हिंदी में', 'hindi'); + $section->addText('Non-existent style', 'nonexistent'); + $section->addText('Style without language', 'nolang'); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + self::assertEquals('ar-DZ', Helper::getTextContent($xpath, '/html/body/div/p[1]/span', 'lang')); + self::assertEquals('fr-BE', Helper::getTextContent($xpath, '/html/body/div/p[2]/span', 'lang')); + self::assertEquals('fr-BE', Helper::getTextContent($xpath, '/html/body/div/p[3]/span', 'lang')); + self::assertEquals('font-family: \'Verdana\';', Helper::getTextContent($xpath, '/html/body/div/p[3]/span', 'style')); + self::assertEquals(0, Helper::getLength($xpath, '/html/body/div/p[4]/span')); + self::assertEquals('hi-IN', Helper::getTextContent($xpath, '/html/body/div/p[5]/span', 'lang')); + self::assertEquals('hindi', Helper::getTextContent($xpath, '/html/body/div/p[5]/span', 'class')); + self::assertEquals('nonexistent', Helper::getTextContent($xpath, '/html/body/div/p[6]/span', 'class')); + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/p[6]/span', 'lang')); + self::assertEquals('nolang', Helper::getTextContent($xpath, '/html/body/div/p[7]/span', 'class')); + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/p[7]/span', 'lang')); + } +} diff --git a/tests/PhpWordTests/Writer/HTML/Helper.php b/tests/PhpWordTests/Writer/HTML/Helper.php new file mode 100644 index 0000000000..ccd542a1c6 --- /dev/null +++ b/tests/PhpWordTests/Writer/HTML/Helper.php @@ -0,0 +1,95 @@ +query($query); + if ($item === false) { + self::fail('Unexpected false return from xpath query'); + } else { + $item2 = $item->item($itemNumber); + if ($item2 === null) { + self::fail('Unexpected null return requesting item'); + } elseif ($namedItem !== '') { + $item3 = $item2->attributes->getNamedItem($namedItem); + if ($item3 === null) { + self::fail('Unexpected null return requesting namedItem'); + } else { + $returnVal = $item3->textContent; + } + } else { + $returnVal = $item2->textContent; + } + } + + return $returnVal; + } + + /** @return mixed */ + public static function getNamedItem(DOMXPath $xpath, string $query, string $namedItem, int $itemNumber = 0) + { + $returnVal = ''; + $item = $xpath->query($query); + if ($item === false) { + self::fail('Unexpected false return from xpath query'); + } else { + $item2 = $item->item($itemNumber); + if ($item2 === null) { + self::fail('Unexpected null return requesting item'); + } else { + $returnValue = $item2->attributes->getNamedItem($namedItem); + } + } + + return $returnVal; + } + + public static function getLength(DOMXPath $xpath, string $query): int + { + $returnVal = 0; + $item = $xpath->query($query); + if ($item === false) { + self::fail('Unexpected false return from xpath query'); + } else { + $returnVal = $item->length; + } + + return $returnVal; + } + + public static function getAsHTML(PhpWord $phpWord): DOMDocument + { + $htmlWriter = new HTML($phpWord); + $dom = new DOMDocument(); + $dom->loadHTML($htmlWriter->getContent()); + + return $dom; + } +} diff --git a/tests/PhpWordTests/Writer/HTML/ParagraphTest.php b/tests/PhpWordTests/Writer/HTML/ParagraphTest.php new file mode 100644 index 0000000000..42c338cdb8 --- /dev/null +++ b/tests/PhpWordTests/Writer/HTML/ParagraphTest.php @@ -0,0 +1,137 @@ + 0, 'spaceAfter' => 0, 'lineHeight' => 1.08]; + $phpWord->addParagraphStyle('indented', [ + 'indentation' => ['left' => 0.50 * Converter::INCH_TO_TWIP, 'right' => 0.60 * Converter::INCH_TO_TWIP], + ]); + $text = 'This is a paragraph. It should be long enough to show the effects of indentation on both the right and left sides.'; + $section1 = $phpWord->addSection(); + $section1->addText($text, null, $pstyle1); + $section1->addText($text, null, 'indented'); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + self::assertEquals(0, Helper::getLength($xpath, '/html/body/div/p[1]/span')); + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/p[1]', 'class')); + self::assertEquals('margin-top: 0pt; margin-bottom: 0pt; line-height: 1.08;', Helper::getTextContent($xpath, '/html/body/div/p[1]', 'style')); + self::assertEquals(0, Helper::getLength($xpath, '/html/body/div/p[2]/span')); + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/p[2]', 'style')); + self::assertEquals('indented', Helper::getTextContent($xpath, '/html/body/div/p[2]', 'class')); + + $style = Helper::getTextContent($xpath, '/html/head/style'); + self::assertNotFalse(preg_match('/^[.]indented[^\\r\\n]*/m', $style, $matches)); + self::assertEquals('.indented {margin-left: 0.5in; margin-right: 0.6in;}', $matches[0]); + } + + /** + * Tests paragraph and font styles specified togeter, both inline and named. + */ + public function testParagraphAndFontStyles(): void + { + $phpWord = new PhpWord(); + $pstyle1 = ['spaceBefore' => 0, 'spaceAfter' => 0, 'lineHeight' => 1.08]; + $phpWord->addParagraphStyle('indented', [ + 'indentation' => ['left' => 0.50 * Converter::INCH_TO_TWIP, 'right' => 0.60 * Converter::INCH_TO_TWIP], + ]); + $phpWord->addFontStyle('style1', ['name' => 'Courier New', 'size' => 10, 'htmlWhiteSpace' => 'pre-wrap', 'htmlGenericFont' => 'monospace']); + $text = 'This is a paragraph. It should be long enough to show the effects of indentation on both the right and left sides.'; + $section1 = $phpWord->addSection(); + $section1->addText($text, 'style1', $pstyle1); + $section1->addText($text, ['name' => 'Verdana', 'size' => '12'], 'indented'); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + self::assertEquals(1, Helper::getLength($xpath, '/html/body/div/p[1]/span')); + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/p[1]', 'class')); + self::assertEquals('margin-top: 0pt; margin-bottom: 0pt; line-height: 1.08;', Helper::getTextContent($xpath, '/html/body/div/p[1]', 'style')); + self::assertEquals('style1', Helper::getTextContent($xpath, '/html/body/div/p[1]/span', 'class')); + self::assertEquals(1, Helper::getLength($xpath, '/html/body/div/p[2]/span')); + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/p[2]', 'style')); + self::assertEquals('indented', Helper::getTextContent($xpath, '/html/body/div/p[2]', 'class')); + self::assertEquals('font-family: \'Verdana\'; font-size: 12pt;', Helper::getTextContent($xpath, '/html/body/div/p[2]/span', 'style')); + + $style = Helper::getTextContent($xpath, '/html/head/style'); + self::assertNotFalse(preg_match('/^[.]indented[^\\r\\n]*/m', $style, $matches)); + self::assertEquals('.indented {margin-left: 0.5in; margin-right: 0.6in;}', $matches[0]); + self::assertNotFalse(preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches)); + self::assertEquals('.style1 {font-family: \'Courier New\', monospace; font-size: 10pt; white-space: pre-wrap;}', $matches[0]); + } + + /** + * Tests page break before. + */ + public function testPageBreakBefore(): void + { + $phpWord = new PhpWord(); + $pstyle1 = ['lineHeight' => 1.08]; + $pstyle2 = ['lineHeight' => 1.08, 'pageBreakBefore' => true]; + + $section1 = $phpWord->addSection(); + $section1->addText('1st paragraph 1st page', null, $pstyle1); + $section1->addText('2nd paragraph 1st page', null, $pstyle1); + $section1->addText('1st paragraph 2nd page', null, $pstyle2); + $section1->addText('2nd paragraph 2nd page', null, $pstyle1); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + self::assertEquals('line-height: 1.08;', Helper::getTextContent($xpath, '/html/body/div/p[1]', 'style')); + self::assertEquals('line-height: 1.08;', Helper::getTextContent($xpath, '/html/body/div/p[2]', 'style')); + self::assertEquals('line-height: 1.08; page-break-before: always;', Helper::getTextContent($xpath, '/html/body/div/p[3]', 'style')); + self::assertEquals('line-height: 1.08;', Helper::getTextContent($xpath, '/html/body/div/p[4]', 'style')); + } + + /** + * Tests blank paragraph. + */ + public function testBlankParagraph(): void + { + $phpWord = new PhpWord(); + + $section1 = $phpWord->addSection(); + $section1->addText('Text before blank text'); + $section1->addText(''); + $section1->addText('Text after blank text'); + + $htmlWriter = new HTML($phpWord); + $body = $htmlWriter->getWriterPart('Body')->write(); + $bodylines = explode(PHP_EOL, $body); + self::assertEquals('

Text before blank text

', $bodylines[2]); + self::assertEquals('

 

', $bodylines[3]); + self::assertEquals('

Text after blank text

', $bodylines[4]); + } +} diff --git a/tests/PhpWordTests/Writer/HTML/PartTest.php b/tests/PhpWordTests/Writer/HTML/PartTest.php index 19f1147467..9515932ac8 100644 --- a/tests/PhpWordTests/Writer/HTML/PartTest.php +++ b/tests/PhpWordTests/Writer/HTML/PartTest.php @@ -17,6 +17,9 @@ namespace PhpOffice\PhpWordTests\Writer\HTML; +use DOMXPath; +use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Shared\Converter; use PhpOffice\PhpWord\Writer\HTML\Part\Body; /** @@ -33,4 +36,153 @@ public function testGetParentWriterException(): void $object = new Body(); $object->getParentWriter(); } + + /** + * Tests writing multiple sections. + */ + public function testWriteSections(): void + { + $phpWord = new PhpWord(); + $phpWord->getSettings()->setThemeFontLang(new \PhpOffice\PhpWord\Style\Language('en-US')); + $section1 = $phpWord->addSection(); + $mtop = 0.5 * Converter::INCH_TO_TWIP; + $mbot = 0.5 * Converter::INCH_TO_TWIP; + $mrig = 0.75 * Converter::INCH_TO_TWIP; + $mlef = 0.75 * Converter::INCH_TO_TWIP; + $section1 + ->getStyle() + ->setPaperSize('Letter') + ->setMarginTop($mtop) + ->setMarginBottom($mbot) + ->setMarginLeft($mlef) + ->setMarginRight($mrig); + $section1->getStyle()->setPortrait(); + $section1->addText('In theory, this will be printed portrait on letter paper'); + + $section2 = $phpWord->addSection(); + $mtop = 0.6 * Converter::INCH_TO_TWIP; + $mbot = 0.6 * Converter::INCH_TO_TWIP; + $mrig = 0.65 * Converter::INCH_TO_TWIP; + $mlef = 0.65 * Converter::INCH_TO_TWIP; + $section2 + ->getStyle() + ->setPaperSize('A4') + ->setMarginTop($mtop) + ->setMarginBottom($mbot) + ->setMarginLeft($mlef) + ->setMarginRight($mrig); + $section2->getStyle()->setLandscape(); + $section2->addText('In theory, this will be printed landscape on A4 paper'); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + self::assertEquals('en-US', Helper::getTextContent($xpath, '/html', 'lang')); + self::assertEquals(2, Helper::getLength($xpath, '/html/body/div')); + self::assertEquals('page: page1', Helper::getTextContent($xpath, '/html/body/div[1]', 'style')); + self::assertEquals('page: page2', Helper::getTextContent($xpath, '/html/body/div[2]', 'style')); + + $style = Helper::getTextContent($xpath, '/html/head/style'); + self::assertNotFalse(strpos($style, 'body > div + div {page-break-before: always;}')); + self::assertNotFalse(strpos($style, 'div > *:first-child {page-break-before: auto;}')); + self::assertNotFalse(strpos($style, '@page page1 {size: Letter portrait; margin-right: 0.75in; margin-left: 0.75in; margin-top: 0.5in; margin-bottom: 0.5in; }')); + self::assertNotFalse(strpos($style, '@page page2 {size: A4 landscape; margin-right: 0.65in; margin-left: 0.65in; margin-top: 0.6in; margin-bottom: 0.6in; }')); + } + + /** + * Tests theme font East Asian. + */ + public function testThemeFontEastAsian(): void + { + $phpWord = new PhpWord(); + $phpWord->getSettings()->setThemeFontLang(new \PhpOffice\PhpWord\Style\Language('', 'hi-IN')); + $section1 = $phpWord->addSection(); + $section1->addText('??? ????? ???'); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + self::assertEquals('hi-IN', Helper::getTextContent($xpath, '/html', 'lang')); + } + + /** + * Tests theme font bidirectional. + */ + public function testThemeBidirecional(): void + { + $phpWord = new PhpWord(); + $phpWord->getSettings()->setThemeFontLang(new \PhpOffice\PhpWord\Style\Language('', '', 'he-IL')); + $section1 = $phpWord->addSection(); + $section1->addText('????'); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + self::assertEquals('he-IL', Helper::getTextContent($xpath, '/html', 'lang')); + } + + /** + * Tests writing when default paragraph style is specified. + */ + public function testDefaultParagraphStyle(): void + { + $phpWord = new PhpWord(); + $nospacebeforeafter = ['spaceBefore' => 0, 'spaceAfter' => 0]; + $phpWord->setDefaultParagraphStyle($nospacebeforeafter); + $section1 = $phpWord->addSection(); + $section1->addText('First paragraph with no space before or after'); + $section1->addText('Second paragraph with no space before or after'); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + self::assertEmpty(Helper::getNamedItem($xpath, '/html', 'lang')); + $style = Helper::getTextContent($xpath, '/html/head/style'); + self::assertNotFalse(strpos($style, 'p, .Normal {margin-top: 0pt; margin-bottom: 0pt;}')); + } + + /** + * Tests writing when default paragraph style is omitted. + */ + public function testNoDefaultParagraphStyle(): void + { + $phpWord = new PhpWord(); + $section1 = $phpWord->addSection(); + $section1->addText('First paragraph with no space before or after'); + $section1->addText('Second paragraph with no space before or after'); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + $style = Helper::getTextContent($xpath, '/html/head/style'); + self::assertFalse(strpos($style, 'Normal')); + } + + /** + * Tests title styles. + */ + public function testTitleStyles(): void + { + $phpWord = new PhpWord(); + $phpWord->setDefaultParagraphStyle(['spaceBefore' => 0, 'spaceAfter' => 0]); + $phpWord->addTitleStyle(1, ['bold' => true, 'name' => 'Calibri'], ['spaceBefore' => 10, 'spaceAfter' => 10]); + $phpWord->addTitleStyle(2, ['italic' => true, 'name' => 'Times New Roman'], ['spaceBefore' => 5, 'spaceAfter' => 5]); + $section1 = $phpWord->addSection(); + $section1->addTitle('Header 1 #1', 1); + $section1->addTitle('Header 2 #1', 2); + $section1->addText('Paragraph under header 2 #1'); + $section1->addTitle('Header 2 #2', 2); + $section1->addText('Paragraph under header 2 #2'); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + $style = Helper::getTextContent($xpath, '/html/head/style'); + self::assertNotFalse(strpos($style, 'h1 {font-family: \'Calibri\'; font-weight: bold;}')); + self::assertNotFalse(strpos($style, 'h1 {margin-top: 0.5pt; margin-bottom: 0.5pt;}')); + self::assertNotFalse(strpos($style, 'h2 {font-family: \'Times New Roman\'; font-style: italic;}')); + self::assertNotFalse(strpos($style, 'h2 {margin-top: 0.25pt; margin-bottom: 0.25pt;}')); + self::assertEquals(1, Helper::getLength($xpath, '/html/body/div/h1')); + self::assertEquals(2, Helper::getLength($xpath, '/html/body/div/h2')); + } } diff --git a/tests/PhpWordTests/Writer/HTML/StyleTest.php b/tests/PhpWordTests/Writer/HTML/StyleTest.php index 7c360e1a6b..0a6d15d3f6 100644 --- a/tests/PhpWordTests/Writer/HTML/StyleTest.php +++ b/tests/PhpWordTests/Writer/HTML/StyleTest.php @@ -17,6 +17,9 @@ namespace PhpOffice\PhpWordTests\Writer\HTML; +use DOMXPath; +use PhpOffice\PhpWord\PhpWord; + /** * Test class for PhpOffice\PhpWord\Writer\HTML\Style subnamespace. */ @@ -35,4 +38,142 @@ public function testEmptyStyles(): void self::assertEquals('', $object->write()); } } + + /** + * Tests writing table with border styles. + */ + public function testWriteTableBorders(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + $bsnone = ['borderStyle' => 'none']; + $table1 = $section->addTable($bsnone); + $row1 = $table1->addRow(); + $row1->addCell(null, $bsnone)->addText('Row 1 Cell 1'); + $row1->addCell(null, $bsnone)->addText('Row 1 Cell 2'); + $row2 = $table1->addRow(); + $row2->addCell(null, $bsnone)->addText('Row 2 Cell 1'); + $row2->addCell(null, $bsnone)->addText('Row 2 Cell 2'); + + $table1 = $section->addTable(); + $row1 = $table1->addRow(); + $row1->addCell()->addText('Row 1 Cell 1'); + $row1->addCell()->addText('Row 1 Cell 2'); + $row2 = $table1->addRow(); + $row2->addCell()->addText('Row 2 Cell 1'); + $row2->addCell()->addText('Row 2 Cell 2'); + + $bstyle = ['borderStyle' => 'dashed', 'borderColor' => 'red']; + $table1 = $section->addTable($bstyle); + $row1 = $table1->addRow(); + $row1->addCell(null, $bstyle)->addText('Row 1 Cell 1'); + $row1->addCell(null, $bstyle)->addText('Row 1 Cell 2'); + $row2 = $table1->addRow(); + $row2->addCell(null, $bstyle)->addText('Row 2 Cell 1'); + $row2->addCell(null, $bstyle)->addText('Row 2 Cell 2'); + + $bstyle = [ + 'borderTopStyle' => 'dotted', + 'borderLeftStyle' => 'dashed', + 'borderRightStyle' => 'dashed', + 'borderBottomStyle' => 'dotted', + 'borderTopColor' => 'blue', + 'borderLeftColor' => 'green', + 'borderRightColor' => 'green', + 'borderBottomColor' => 'blue', + ]; + $table1 = $section->addTable($bstyle); + $row1 = $table1->addRow(); + $row1->addCell(null, $bstyle)->addText('Row 1 Cell 1'); + $row1->addCell(null, $bstyle)->addText('Row 1 Cell 2'); + $row2 = $table1->addRow(); + $row2->addCell(null, $bstyle)->addText('Row 2 Cell 1'); + $row2->addCell(null, $bstyle)->addText('Row 2 Cell 2'); + + $bstyle = ['borderStyle' => 'solid', 'borderSize' => 5]; + $table1 = $section->addTable($bstyle); + $row1 = $table1->addRow(); + $row1->addCell(null, $bstyle)->addText('Row 1 Cell 1'); + $row1->addCell(null, $bstyle)->addText('Row 1 Cell 2'); + $row2 = $table1->addRow(); + $row2->addCell(null, $bstyle)->addText('Row 2 Cell 1'); + $row2->addCell(null, $bstyle)->addText('Row 2 Cell 2'); + + $phpWord->addTableStyle('tstyle', ['borderStyle' => 'solid', 'borderSize' => 5]); + $table1 = $section->addTable('tstyle'); + $row1 = $table1->addRow(); + $row1->addCell(null, 'tstyle')->addText('Row 1 Cell 1'); + $row1->addCell(null, 'tstyle')->addText('Row 1 Cell 2'); + $row2 = $table1->addRow(); + $row2->addCell(null, 'tstyle')->addText('Row 2 Cell 1'); + $row2->addCell(null, 'tstyle')->addText('Row 2 Cell 2'); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + $cssnone = ' border-top-style: none;' + . ' border-left-style: none; ' + . 'border-bottom-style: none; ' + . 'border-right-style: none;'; + self::assertEquals("table-layout: auto;$cssnone", Helper::getTextContent($xpath, '/html/body/div/table[1]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[1]/tr[1]/td[1]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[1]/tr[1]/td[2]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[1]/tr[2]/td[1]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[1]/tr[2]/td[2]', 'style')); + + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/table[2]', 'style')); + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/table[2]/tr[1]/td[1]', 'style')); + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/table[2]/tr[1]/td[2]', 'style')); + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/table[2]/tr[2]/td[1]', 'style')); + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/table[2]/tr[2]/td[2]', 'style')); + + $cssnone = ' border-top-style: dashed;' + . ' border-top-color: red;' + . ' border-left-style: dashed;' + . ' border-left-color: red;' + . ' border-bottom-style: dashed;' + . ' border-bottom-color: red;' + . ' border-right-style: dashed;' + . ' border-right-color: red;'; + self::assertEquals("table-layout: auto;$cssnone", Helper::getTextContent($xpath, '/html/body/div/table[3]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[3]/tr[1]/td[1]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[3]/tr[1]/td[2]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[3]/tr[2]/td[1]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[3]/tr[2]/td[2]', 'style')); + + $cssnone = ' border-top-style: dotted;' + . ' border-top-color: blue;' + . ' border-left-style: dashed;' + . ' border-left-color: green;' + . ' border-bottom-style: dotted;' + . ' border-bottom-color: blue;' + . ' border-right-style: dashed;' + . ' border-right-color: green;'; + self::assertEquals("table-layout: auto;$cssnone", Helper::getTextContent($xpath, '/html/body/div/table[4]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[4]/tr[1]/td[1]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[4]/tr[1]/td[2]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[4]/tr[2]/td[1]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[4]/tr[2]/td[2]', 'style')); + + $cssnone = ' border-top-style: solid;' + . ' border-top-width: 0.25pt;' + . ' border-left-style: solid;' + . ' border-left-width: 0.25pt;' + . ' border-bottom-style: solid;' + . ' border-bottom-width: 0.25pt;' + . ' border-right-style: solid;' + . ' border-right-width: 0.25pt;'; + self::assertEquals("table-layout: auto;$cssnone", Helper::getTextContent($xpath, '/html/body/div/table[5]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[5]/tr[1]/td[1]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[5]/tr[1]/td[2]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[5]/tr[2]/td[1]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[5]/tr[2]/td[2]', 'style')); + + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/table[6]', 'style')); + self::assertEquals('tstyle', Helper::getTextContent($xpath, '/html/body/div/table[6]', 'class')); + $style = Helper::getTextContent($xpath, '/html/head/style'); + self::assertNotFalse(preg_match('/^[.]tstyle[^\\r\\n]*/m', $style, $matches)); + self::assertEquals(".tstyle {table-layout: auto;$cssnone}", $matches[0]); + } } diff --git a/tests/PhpWordTests/Writer/ODText/Element/ImageTest.php b/tests/PhpWordTests/Writer/ODText/Element/ImageTest.php index e150418c9f..671fabc4d0 100644 --- a/tests/PhpWordTests/Writer/ODText/Element/ImageTest.php +++ b/tests/PhpWordTests/Writer/ODText/Element/ImageTest.php @@ -17,6 +17,7 @@ namespace PhpOffice\PhpWordTests\Writer\ODText\Style; +use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Style\Image; use PhpOffice\PhpWordTests\TestHelperDOCX; @@ -32,6 +33,7 @@ class ImageTest extends \PHPUnit\Framework\TestCase */ protected function tearDown(): void { + Style::setDefaultRtl(null); TestHelperDOCX::clear(); } @@ -62,4 +64,33 @@ public function testImage1(): void self::assertTrue($doc->elementExists($path)); self::assertEquals('IM2', $doc->getElementAttribute($path, 'text:style-name')); } + + /** + * Test writing image, with non-default bidi. + */ + public function testImage2(): void + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + Style::setDefaultRtl(false); + $section = $phpWord->addSection(); + $section->addImage(__DIR__ . '/../../../_files/images/earth.jpg'); + $section->addImage(__DIR__ . '/../../../_files/images/mario.gif', ['align' => 'end']); + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:automatic-styles'; + $element = "$s2a/style:style[3]"; + self::assertEquals('IM1', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:paragraph-properties'; + self::assertEquals('left', $doc->getElementAttribute($element, 'fo:text-align')); + $element = "$s2a/style:style[4]"; + self::assertEquals('IM2', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:paragraph-properties'; + self::assertEquals('right', $doc->getElementAttribute($element, 'fo:text-align')); + + $path = '/office:document-content/office:body/office:text/text:section/text:p[2]'; + self::assertTrue($doc->elementExists($path)); + self::assertEquals('IM1', $doc->getElementAttribute($path, 'text:style-name')); + $path = '/office:document-content/office:body/office:text/text:section/text:p[3]'; + self::assertTrue($doc->elementExists($path)); + self::assertEquals('IM2', $doc->getElementAttribute($path, 'text:style-name')); + } } diff --git a/tests/PhpWordTests/Writer/ODText/ElementTest.php b/tests/PhpWordTests/Writer/ODText/ElementTest.php index 45f542b3b1..0ad0325656 100644 --- a/tests/PhpWordTests/Writer/ODText/ElementTest.php +++ b/tests/PhpWordTests/Writer/ODText/ElementTest.php @@ -130,7 +130,7 @@ public function testTableElements(): void $p2t = '/office:document-content/office:body/office:text/text:section'; $tableRootElement = "$p2t/table:table"; self::assertTrue($doc->elementExists($tableRootElement)); - self::assertEquals($tableStyleName, $doc->getElementAttribute($tableRootElement, 'table:style')); + self::assertEquals($tableStyleName, $doc->getElementAttribute($tableRootElement, 'table:style-name')); self::assertTrue($doc->elementExists($tableRootElement . '/table:table-column[4]')); } diff --git a/tests/PhpWordTests/Writer/ODText/Style/FontTest.php b/tests/PhpWordTests/Writer/ODText/Style/FontTest.php index dda0110af5..34e5e506b4 100644 --- a/tests/PhpWordTests/Writer/ODText/Style/FontTest.php +++ b/tests/PhpWordTests/Writer/ODText/Style/FontTest.php @@ -74,7 +74,7 @@ public function testColors(): void self::assertEquals('This should be dark green (FGCOLOR_DARKGREEN)', $doc->getElement($span)->nodeValue); } - public function providerAllNamedColors() + public static function providerAllNamedColors() { return [ [Font::FGCOLOR_YELLOW, 'FFFF00'], diff --git a/tests/PhpWordTests/Writer/ODText/Style/Paragraph2Test.php b/tests/PhpWordTests/Writer/ODText/Style/Paragraph2Test.php new file mode 100644 index 0000000000..7098035eee --- /dev/null +++ b/tests/PhpWordTests/Writer/ODText/Style/Paragraph2Test.php @@ -0,0 +1,153 @@ + 'end']; + $align2 = ['alignment' => 'start']; + $phpWord->setDefaultParagraphStyle($align1); + $section = $phpWord->addSection(); + $section->addText('Should use default alignment (right for this doc)'); + $section->addText('Explicit right alignment', null, $align2); + $section->addText('Explicit left alignment', null, $align1); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:automatic-styles'; + self::assertTrue($doc->elementExists($s2a)); + + $element = "$s2a/style:style[4]"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('Normal', $doc->getElementAttribute($element, 'style:parent-style-name')); + $element .= '/style:paragraph-properties'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('right', $doc->getElementAttribute($element, 'fo:text-align')); + + $element = "$s2a/style:style[6]/style:paragraph-properties"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('right', $doc->getElementAttribute($element, 'fo:text-align')); + + $element = "$s2a/style:style[8]/style:paragraph-properties"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('left', $doc->getElementAttribute($element, 'fo:text-align')); + + $doc->setDefaultFile('styles.xml'); + $element = '/office:document-styles/office:styles/style:style'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('Normal', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:paragraph-properties'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('left', $doc->getElementAttribute($element, 'fo:text-align')); + } + + /** + * Test text run paragraph style using named style. + */ + public function testTextRun(): void + { + $phpWord = new PhpWord(); + Style::setDefaultRtl(false); + $phpWord->addParagraphStyle('parstyle1', ['align' => 'start']); + $phpWord->addParagraphStyle('parstyle2', ['align' => 'end']); + $section = $phpWord->addSection(); + $trx = $section->addTextRun('parstyle1'); + $trx->addText('First text in textrun. '); + $trx->addText('Second text - paragraph style is specified but ignored.', null, 'parstyle2'); + $section->addText('Third text added to section not textrun - paragraph style is specified and used.', null, 'parstyle2'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:automatic-styles'; + $element = "$s2a/style:style[3]"; + self::assertEquals('P1_parstyle1', $doc->getElementAttribute($element, 'style:name')); + self::assertEquals('parstyle1', $doc->getElementAttribute($element, 'style:parent-style-name')); + $element = "$s2a/style:style[9]"; + self::assertEquals('P4_parstyle2', $doc->getElementAttribute($element, 'style:name')); + self::assertEquals('parstyle2', $doc->getElementAttribute($element, 'style:parent-style-name')); + + $s2a = '/office:document-content/office:body/office:text/text:section'; + $element = "$s2a/text:p[2]"; + self::assertEquals('P1_parstyle1', $doc->getElementAttribute($element, 'text:style-name')); + $element = "$s2a/text:p[3]"; + self::assertEquals('P4_parstyle2', $doc->getElementAttribute($element, 'text:style-name')); + + $doc->setDefaultFile('styles.xml'); + $element = '/office:document-styles/office:styles/style:style[1]'; + self::assertEquals('parstyle1', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:paragraph-properties'; + self::assertEquals('left', $doc->getElementAttribute($element, 'fo:text-align')); + $element = '/office:document-styles/office:styles/style:style[2]'; + self::assertEquals('parstyle2', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:paragraph-properties'; + self::assertEquals('right', $doc->getElementAttribute($element, 'fo:text-align')); + } + + /** + * Test text run paragraph style using unnamed style. + */ + public function testTextRunUnnamed(): void + { + $phpWord = new PhpWord(); + Style::setDefaultRtl(false); + $parstyle1 = ['align' => 'start']; + $parstyle2 = ['align' => 'end']; + $section = $phpWord->addSection(); + $trx = $section->addTextRun($parstyle1); + $trx->addText('First text in textrun. '); + $trx->addText('Second text - paragraph style is specified but ignored.', null, $parstyle2); + $section->addText('Third text added to section not textrun - paragraph style is specified and used.', null, $parstyle2); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:automatic-styles'; + $element = "$s2a/style:style[3]"; + self::assertEquals('P1', $doc->getElementAttribute($element, 'style:name')); + self::assertEquals('Normal', $doc->getElementAttribute($element, 'style:parent-style-name')); + $element .= '/style:paragraph-properties'; + self::assertEquals('left', $doc->getElementAttribute($element, 'fo:text-align')); + $element = "$s2a/style:style[9]"; + self::assertEquals('P4', $doc->getElementAttribute($element, 'style:name')); + self::assertEquals('Normal', $doc->getElementAttribute($element, 'style:parent-style-name')); + $element .= '/style:paragraph-properties'; + self::assertEquals('right', $doc->getElementAttribute($element, 'fo:text-align')); + + $s2a = '/office:document-content/office:body/office:text/text:section'; + $element = "$s2a/text:p[2]"; + self::assertEquals('P1', $doc->getElementAttribute($element, 'text:style-name')); + $element = "$s2a/text:p[3]"; + self::assertEquals('P4', $doc->getElementAttribute($element, 'text:style-name')); + } + + public function testWhenNullifed(): void + { + $dflt1 = Style::getDefaultRtl(); + self::assertFalse($dflt1); + $phpWord = new PhpWord(); + $dflt2 = Style::getDefaultRtl(); + self::assertNull($dflt2); + } +} diff --git a/tests/PhpWordTests/Writer/ODTextTest.php b/tests/PhpWordTests/Writer/ODTextTest.php index 060fda9e45..2405f9db7f 100644 --- a/tests/PhpWordTests/Writer/ODTextTest.php +++ b/tests/PhpWordTests/Writer/ODTextTest.php @@ -103,14 +103,15 @@ public function testSave(): void */ public function testSavePhpOutput(): void { - $this->setOutputCallback(function (): void { - }); $phpWord = new PhpWord(); $section = $phpWord->addSection(); $section->addText('Test'); $writer = new ODText($phpWord); + ob_start(); $writer->save('php://output'); - self::assertNotNull($this->getActualOutput()); + $contents = ob_get_contents(); + self::assertTrue(ob_end_clean()); + self::assertNotEmpty($contents); } /** diff --git a/tests/PhpWordTests/Writer/PDF/MPDFTest.php b/tests/PhpWordTests/Writer/PDF/MPDFTest.php index 5905fa4eaf..9dbbf9d5ac 100644 --- a/tests/PhpWordTests/Writer/PDF/MPDFTest.php +++ b/tests/PhpWordTests/Writer/PDF/MPDFTest.php @@ -20,6 +20,7 @@ use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Writer\PDF; +use PhpOffice\PhpWord\Writer\PDF\MPDF; /** * Test class for PhpOffice\PhpWord\Writer\PDF\MPDF. @@ -39,11 +40,16 @@ public function testConstruct(): void $section = $phpWord->addSection(); $section->addText('Test 1'); $section->addPageBreak(); + $section->addText('Test 2'); + $oSettings = new \PhpOffice\PhpWord\Style\Section(); + $oSettings->setSettingValue('orientation', 'landscape'); + $section = $phpWord->addSection($oSettings); // @phpstan-ignore-line + $section->addText('Section 2 - landscape'); - $rendererName = Settings::PDF_RENDERER_MPDF; - $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/mpdf/mpdf'); - Settings::setPdfRenderer($rendererName, $rendererLibraryPath); - $writer = new PDF($phpWord); + $writer = new MPDF($phpWord); + /** @var callable */ + $callback = [self::class, 'editContent']; + $writer->setEditHtmlCallback($callback); $writer->save($file); self::assertFileExists($file); @@ -58,11 +64,30 @@ public function testSetGetAbstractRendererOptions(): void { $rendererName = Settings::PDF_RENDERER_MPDF; $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/mpdf/mpdf'); - Settings::setPdfRenderer($rendererName, $rendererLibraryPath); + Settings::setPdfRenderer($rendererName, (string) $rendererLibraryPath); Settings::setPdfRendererOptions([ 'font' => 'Arial', ]); $writer = new PDF(new PhpWord()); self::assertEquals('Arial', $writer->getFont()); } + + // add a footer + public static function editContent(string $html): string + { + $afterBody = '
{PAGENO}
' . MPDF::SIMULATED_BODY_START; + $beforeBody = ''; + $needle = ''; + $pos = strpos($html, $needle); + if ($pos !== false) { + $html = (string) substr_replace($html, "$beforeBody\n$needle", $pos, strlen($needle)); + } + $needle = ''; + $pos = strpos($html, $needle); + if ($pos !== false) { + $html = (string) substr_replace($html, "$needle\n$afterBody", $pos, strlen($needle)); + } + + return $html; + } } diff --git a/tests/PhpWordTests/Writer/PDF/TCPDFTest.php b/tests/PhpWordTests/Writer/PDF/TCPDFTest.php index c3f05b2b16..00587cb7f3 100644 --- a/tests/PhpWordTests/Writer/PDF/TCPDFTest.php +++ b/tests/PhpWordTests/Writer/PDF/TCPDFTest.php @@ -36,6 +36,7 @@ public function testConstruct(): void $file = __DIR__ . '/../../_files/tcpdf.pdf'; $phpWord = new PhpWord(); + $phpWord->setDefaultParagraphStyle(['spaceBefore' => 0, 'spaceAfter' => 0]); $section = $phpWord->addSection(); $section->addText('Test 1'); diff --git a/tests/PhpWordTests/Writer/PDFTest.php b/tests/PhpWordTests/Writer/PDFTest.php index 72fae65e7f..5a2883feed 100644 --- a/tests/PhpWordTests/Writer/PDFTest.php +++ b/tests/PhpWordTests/Writer/PDFTest.php @@ -38,7 +38,7 @@ public function testConstruct(): void $rendererName = Settings::PDF_RENDERER_DOMPDF; $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/dompdf/dompdf'); - Settings::setPdfRenderer($rendererName, $rendererLibraryPath); + Settings::setPdfRenderer($rendererName, (string) $rendererLibraryPath); $writer = new PDF(new PhpWord()); $writer->save($file); @@ -55,6 +55,6 @@ public function testConstructException(): void $this->expectException(\PhpOffice\PhpWord\Exception\Exception::class); $this->expectExceptionMessage('PDF rendering library or library path has not been defined.'); $writer = new PDF(new PhpWord()); - $writer->save(); + $writer->save('unknown.file'); } } diff --git a/tests/PhpWordTests/Writer/RTF/Element2Test.php b/tests/PhpWordTests/Writer/RTF/Element2Test.php new file mode 100644 index 0000000000..5798b4b1bf --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Element2Test.php @@ -0,0 +1,122 @@ +write()); + } + + public function testTable(): void + { + Style::setDefaultRtl(false); + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\Table(); + $width = 100; + $width2 = 2 * $width; + $element->addRow(); + $tce = $element->addCell($width); + $tce->addText('1'); + $tce = $element->addCell($width); + $tce->addText('2'); + $element->addRow(); + $tce = $element->addCell($width); + $tce->addText('3'); + $tce = $element->addCell($width); + $tce->addText('4'); + $table = new WriterTable($parentWriter, $element); + $expect = implode("\n", [ + '\\pard', + "\\trowd \\cellx$width \\cellx$width2 ", + '\\intbl', + '\\ql{\\cf0\\f0 1}\\par', + '\\cell', + '\\intbl', + '{\\cf0\\f0 2}\\par', + '\\cell', + '\\row', + "\\trowd \\cellx$width \\cellx$width2 ", + '\\intbl', + '\\ql{\\cf0\\f0 3}\\par', + '\\cell', + '\\intbl', + '{\\cf0\\f0 4}\par', + '\\cell', + '\\row', + '\\pard', + '', + ]); + + self::assertEquals($expect, $this->removeCr($table)); + } + + public function testTextRun(): void + { + Style::setDefaultRtl(false); + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\TextRun(); + $element->addText('Hello '); + $element->addText('there.'); + $textrun = new WriterTextRun($parentWriter, $element); + $expect = "\\pard\\nowidctlpar \\ql{{\\cf0\\f0 Hello }{\\cf0\\f0 there.}}\\par\n"; + self::assertEquals($expect, $this->removeCr($textrun)); + } + + public function testTextRunParagraphStyle(): void + { + Style::setDefaultRtl(false); + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\TextRun(['spaceBefore' => 0, 'spaceAfter' => 0]); + $element->addText('Hello '); + $element->addText('there.'); + $textrun = new WriterTextRun($parentWriter, $element); + $expect = "\\pard\\nowidctlpar \\ql\\sb0\\sa0{{\\cf0\\f0 Hello }{\\cf0\\f0 there.}}\\par\n"; + self::assertEquals($expect, $this->removeCr($textrun)); + } + + public function testTitle(): void + { + $parentWriter = new RTF(); + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + Style::setDefaultRtl(false); + $phpWord->addTitleStyle(1, [], ['spaceBefore' => 0, 'spaceAfter' => 0]); + $section = $phpWord->addSection(); + $element = $section->addTitle('First Heading', 1); + $elwrite = new WriterTitle($parentWriter, $element); + $expect = "\\pard\\nowidctlpar \\ql\\sb0\\sa0{\\outlinelevel0{\\cf0\\f0 First Heading}\\par\n}"; + self::assertEquals($expect, $this->removeCr($elwrite)); + Style::setDefaultRtl(null); + } +} diff --git a/tests/PhpWordTests/Writer/RTF/StyleTest.php b/tests/PhpWordTests/Writer/RTF/StyleTest.php index 0605e104fd..3f75f77f28 100644 --- a/tests/PhpWordTests/Writer/RTF/StyleTest.php +++ b/tests/PhpWordTests/Writer/RTF/StyleTest.php @@ -17,6 +17,7 @@ namespace PhpOffice\PhpWordTests\Writer\RTF; +use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Writer\RTF; use PhpOffice\PhpWord\Writer\RTF\Style\Border; use PHPUnit\Framework\Assert; @@ -26,6 +27,11 @@ */ class StyleTest extends \PHPUnit\Framework\TestCase { + protected function tearDown(): void + { + Style::setDefaultRtl(null); + } + public function removeCr($field) { return str_replace("\r\n", "\n", $field->write()); @@ -123,6 +129,16 @@ public function testRTL(): void self::assertEquals($expect, $this->removeCr($text)); } + public function testRTL2(): void + { + Style::setDefaultRtl(true); + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\Text('אב גד'); + $text = new \PhpOffice\PhpWord\Writer\RTF\Element\Text($parentWriter, $element); + $expect = "\\pard\\nowidctlpar \\qr{\\rtlch\\cf0\\f0 \\uc0{\\u1488}\\uc0{\\u1489} \\uc0{\\u1490}\\uc0{\\u1491}}\\par\n"; + self::assertEquals($expect, $this->removeCr($text)); + } + public function testPageBreakLineHeight(): void { $parentWriter = new RTF(); @@ -132,6 +148,16 @@ public function testPageBreakLineHeight(): void self::assertEquals($expect, $this->removeCr($text)); } + public function testPageBreakLineHeight2(): void + { + Style::setDefaultRtl(false); + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\Text('New page', null, ['lineHeight' => 1.08, 'pageBreakBefore' => true]); + $text = new \PhpOffice\PhpWord\Writer\RTF\Element\Text($parentWriter, $element); + $expect = "\\pard\\nowidctlpar \\ql\\sl259\\slmult1\\page{\\cf0\\f0 New page}\\par\n"; + self::assertEquals($expect, $this->removeCr($text)); + } + public function testPageNumberRestart(): void { //$parentWriter = new RTF(); diff --git a/tests/PhpWordTests/Writer/RTFTest.php b/tests/PhpWordTests/Writer/RTFTest.php index 9b49eaf758..c56bc9f3dc 100644 --- a/tests/PhpWordTests/Writer/RTFTest.php +++ b/tests/PhpWordTests/Writer/RTFTest.php @@ -104,13 +104,14 @@ public function testSave(): void */ public function testSavePhpOutput(): void { - $this->setOutputCallback(function (): void { - }); $phpWord = new PhpWord(); $section = $phpWord->addSection(); $section->addText(htmlspecialchars('Test', ENT_COMPAT, 'UTF-8')); $writer = new RTF($phpWord); + ob_start(); $writer->save('php://output'); - self::assertNotNull($this->getActualOutput()); + $contents = ob_get_contents(); + self::assertTrue(ob_end_clean()); + self::assertNotEmpty($contents); } } diff --git a/tests/PhpWordTests/Writer/Word2007/Element/TOCTest.php b/tests/PhpWordTests/Writer/Word2007/Element/TOCTest.php index 537fb93d1a..c87379678d 100644 --- a/tests/PhpWordTests/Writer/Word2007/Element/TOCTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Element/TOCTest.php @@ -31,7 +31,7 @@ class TOCTest extends \PHPUnit\Framework\TestCase */ protected function tearDown(): void { - TestHelperDOCX::clear(); + //TestHelperDOCX::clear(); } public function testWriteTitlePageNumber(): void diff --git a/tests/PhpWordTests/Writer/Word2007/Part/DocumentTest.php b/tests/PhpWordTests/Writer/Word2007/Part/DocumentTest.php index afb22f17b7..58aaf6df6d 100644 --- a/tests/PhpWordTests/Writer/Word2007/Part/DocumentTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/DocumentTest.php @@ -406,12 +406,12 @@ public function testWriteImage(): void // behind $element = $doc->getElement('/w:document/w:body/w:p[2]/w:r/w:pict/v:shape'); $style = $element->getAttribute('style'); - - // Try to address CI coverage issue for PHP 7.1 and 7.2 when using regex match assertions - if (method_exists(static::class, 'assertMatchesRegularExpression')) { + if (method_exists(self::class, 'assertMatchesRegularExpression')) { self::assertMatchesRegularExpression('/z\-index:\-[0-9]*/', $style); - } else { + } elseif (method_exists(self::class, 'assertRegExp')) { self::assertRegExp('/z\-index:\-[0-9]*/', $style); + } else { + self::fail('Unsure how to test regexp'); } // square @@ -436,6 +436,20 @@ public function testWriteWatermark(): void self::assertStringStartsWith('rId', $element->getAttribute('r:id')); } + /** + * covers ::_writeTitle. + */ + public function testWriteTitle(): void + { + $phpWord = new PhpWord(); + $phpWord->addTitleStyle(1, ['bold' => true], ['spaceAfter' => 240]); + $phpWord->addSection()->addTitle('Test', 1); + $doc = TestHelperDOCX::getDocument($phpWord); + + $element = '/w:document/w:body/w:p/w:pPr/w:pStyle'; + self::assertEquals('Heading1', $doc->getElementAttribute($element, 'w:val')); + } + /** * covers ::_writeCheckbox. */ @@ -641,44 +655,6 @@ public function testWriteCellStyleCellGridSpan(): void self::assertEquals(5, $element->getAttribute('w:val')); } - /** - * covers ::_writeCellStyle. - */ - public function testWriteCellStyleCellNoWrapEnabled(): void - { - $phpWord = new PhpWord(); - $section = $phpWord->addSection(); - - $table = $section->addTable(); - $table->addRow(); - - $cell = $table->addCell(200); - $cell->getStyle()->setNoWrap(true); - - $doc = TestHelperDOCX::getDocument($phpWord); - - self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr/w:tc/w:tcPr/w:noWrap')); - } - - /** - * covers ::_writeCellStyle. - */ - public function testWriteCellStyleCellNoWrapDisabled(): void - { - $phpWord = new PhpWord(); - $section = $phpWord->addSection(); - - $table = $section->addTable(); - $table->addRow(); - - $cell = $table->addCell(200); - $cell->getStyle()->setNoWrap(false); - - $doc = TestHelperDOCX::getDocument($phpWord); - - self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr/w:tc/w:tcPr/w:noWrap')); - } - /** * Test write gutter and line numbering. */ diff --git a/tests/PhpWordTests/Writer/Word2007/Part/FooterTest.php b/tests/PhpWordTests/Writer/Word2007/Part/FooterTest.php index 97e8a646fc..0dc99e7e37 100644 --- a/tests/PhpWordTests/Writer/Word2007/Part/FooterTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/FooterTest.php @@ -17,8 +17,10 @@ namespace PhpOffice\PhpWordTests\Writer\Word2007\Part; +use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Writer\Word2007; use PhpOffice\PhpWord\Writer\Word2007\Part\Footer; +use PhpOffice\PhpWordTests\TestHelperDOCX; /** * Test class for PhpOffice\PhpWord\Writer\Word2007\Part\Footer. @@ -44,12 +46,17 @@ public function testWriteFooter(): void $container->addImage($imageSrc); $writer = new Word2007(); - $writer->setUseDiskCaching(true); + $dir = Settings::getTempDir() . DIRECTORY_SEPARATOR . 'phpwordcachefooter'; + if (!is_dir($dir) && !mkdir($dir)) { + self::fail('Unable to create temp directory'); + } + $writer->setUseDiskCaching(true, $dir); $object = new Footer(); $object->setParentWriter($writer); $object->setElement($container); $xml = simplexml_load_string($object->write()); self::assertInstanceOf('SimpleXMLElement', $xml); + TestHelperDOCX::deleteDir($dir); } } diff --git a/tests/PhpWordTests/Writer/Word2007/Part/HeaderTest.php b/tests/PhpWordTests/Writer/Word2007/Part/HeaderTest.php index 6832643b5e..27e9bcb9ba 100644 --- a/tests/PhpWordTests/Writer/Word2007/Part/HeaderTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/HeaderTest.php @@ -17,8 +17,10 @@ namespace PhpOffice\PhpWordTests\Writer\Word2007\Part; +use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Writer\Word2007; use PhpOffice\PhpWord\Writer\Word2007\Part\Header; +use PhpOffice\PhpWordTests\TestHelperDOCX; /** * Test class for PhpOffice\PhpWord\Writer\Word2007\Part\Header. @@ -44,12 +46,17 @@ public function testWriteHeader(): void $container->addWatermark($imageSrc); $writer = new Word2007(); - $writer->setUseDiskCaching(true); + $dir = Settings::getTempDir() . DIRECTORY_SEPARATOR . 'phpwordcachefooter'; + if (!is_dir($dir) && !mkdir($dir)) { + self::fail('Unable to create temp directory'); + } + $writer->setUseDiskCaching(true, $dir); $object = new Header(); $object->setParentWriter($writer); $object->setElement($container); $xml = simplexml_load_string($object->write()); self::assertInstanceOf('SimpleXMLElement', $xml); + TestHelperDOCX::deleteDir($dir); } } diff --git a/tests/PhpWordTests/Writer/Word2007/Style/DirectionTest.php b/tests/PhpWordTests/Writer/Word2007/Style/DirectionTest.php new file mode 100644 index 0000000000..a91de6590e --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Style/DirectionTest.php @@ -0,0 +1,61 @@ +addSection(); + $html = '

الألم الذي ربما تنجم عنه بعض ا.

'; + SharedHtml::addHtml($section, $html, false, false); + $english = '

LTR in RTL document.

'; + SharedHtml::addHtml($section, $english, false, false); + $doc = TestHelperDOCX::getDocument($word, 'Word2007'); + + $path = '/w:document/w:body/w:p[1]/w:pPr/w:bidi'; + self::assertTrue($doc->elementExists($path)); + $path = '/w:document/w:body/w:p[2]/w:pPr/w:bidi'; + self::assertFalse($doc->elementExists($path)); + + $path = '/w:document/w:body/w:p[1]/w:pPr/w:jc'; + self::assertFalse($doc->elementExists($path)); + $path = '/w:document/w:body/w:p[2]/w:pPr/w:jc'; + self::assertTrue($doc->elementExists($path)); + self::assertSame('start', $doc->getElementAttribute($path, 'w:val')); + } +} diff --git a/tests/PhpWordTests/Writer/Word2007Test.php b/tests/PhpWordTests/Writer/Word2007Test.php index 94f9caf8a1..0965222fb3 100644 --- a/tests/PhpWordTests/Writer/Word2007Test.php +++ b/tests/PhpWordTests/Writer/Word2007Test.php @@ -19,6 +19,7 @@ use finfo; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\SimpleType\Jc; use PhpOffice\PhpWord\Writer\Word2007; use PhpOffice\PhpWordTests\AbstractWebServerEmbeddedTest; @@ -117,13 +118,18 @@ public function testSaveUseDiskCaching(): void $footnote->addText('Test'); $writer = new Word2007($phpWord); - $writer->setUseDiskCaching(true); + $dir = Settings::getTempDir() . DIRECTORY_SEPARATOR . 'phpwordcachefooter'; + if (!is_dir($dir) && !mkdir($dir)) { + self::fail('Unable to create temp directory'); + } + $writer->setUseDiskCaching(true, $dir); $file = __DIR__ . '/../_files/temp.docx'; $writer->save($file); self::assertFileExists($file); unlink($file); + TestHelperDOCX::deleteDir($dir); } /** @@ -170,16 +176,17 @@ public function testGetWriterPartNull(): void */ public function testSetGetUseDiskCaching(): void { - $this->setOutputCallback(function (): void { - }); $phpWord = new PhpWord(); $phpWord->addSection(); $object = new Word2007($phpWord); $object->setUseDiskCaching(true, PHPWORD_TESTS_BASE_DIR); $writer = new Word2007($phpWord); + ob_start(); $writer->save('php://output'); - + $contents = ob_get_contents(); + self::assertTrue(ob_end_clean()); self::assertTrue($object->isUseDiskCaching()); + self::assertNotEmpty($contents); } /** diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 330abe133c..a79ff25e66 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -22,3 +22,35 @@ if (!defined('PHPWORD_TESTS_BASE_DIR')) { define('PHPWORD_TESTS_BASE_DIR', realpath(__DIR__)); } + +function phpunit10ErrorHandler(int $errno, string $errstr, string $filename, int $lineno): bool +{ + $x = error_reporting() & $errno; + if ( + in_array( + $errno, + [ + E_DEPRECATED, + E_WARNING, + E_NOTICE, + E_USER_DEPRECATED, + E_USER_NOTICE, + E_USER_WARNING, + ], + true + ) + ) { + if (0 === $x) { + return true; // message suppressed - stop error handling + } + + throw new \Exception("$errstr $filename $lineno"); + } + + return false; // continue error handling +} + +if (!method_exists(\PHPUnit\Framework\TestCase::class, 'setOutputCallback')) { + ini_set('error_reporting', (string) E_ALL); + set_error_handler('phpunit10ErrorHandler'); +}