From ebf5cf784f1f67e09304984486a7ec2faac8cac3 Mon Sep 17 00:00:00 2001 From: owen Date: Tue, 19 Nov 2019 14:24:29 -0800 Subject: [PATCH 1/5] Convert named constant colors to RGB in Shared/Converter. Otherwise, colors will not be as expected for RTF and ODT. --- src/PhpWord/Shared/Converter.php | 45 ++++++++++++++++++++++++++ tests/PhpWord/Shared/ConverterTest.php | 2 ++ 2 files changed, 47 insertions(+) diff --git a/src/PhpWord/Shared/Converter.php b/src/PhpWord/Shared/Converter.php index 7008ac5d..7c3048fc 100644 --- a/src/PhpWord/Shared/Converter.php +++ b/src/PhpWord/Shared/Converter.php @@ -272,6 +272,50 @@ class Converter return round($angle / self::DEGREE_TO_ANGLE); } + /** + * Convert colorname as string to RGB + * + * @param string $value color name + * @return string color as hex RGB string, or original value if unknown + */ + public static function stringToRgb($value) + { + switch ($value) { + case \PhpOffice\PhpWord\Style\Font::FGCOLOR_YELLOW: + return 'FFFF00'; + case \PhpOffice\PhpWord\Style\Font::FGCOLOR_LIGHTGREEN: + return '90EE90'; + case \PhpOffice\PhpWord\Style\Font::FGCOLOR_CYAN: + return '00FFFF'; + case \PhpOffice\PhpWord\Style\Font::FGCOLOR_MAGENTA: + return 'FF00FF'; + case \PhpOffice\PhpWord\Style\Font::FGCOLOR_BLUE: + return '0000FF'; + case \PhpOffice\PhpWord\Style\Font::FGCOLOR_RED: + return 'FF0000'; + case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKBLUE: + return '00008B'; + case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKCYAN: + return '008B8B'; + case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKGREEN: + return '006400'; + case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKMAGENTA: + return '8B008B'; + case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKRED: + return '8B0000'; + case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKYELLOW: + return '8B8B00'; + case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKGRAY: + return 'A9A9A9'; + case \PhpOffice\PhpWord\Style\Font::FGCOLOR_LIGHTGRAY: + return 'D3D3D3'; + case \PhpOffice\PhpWord\Style\Font::FGCOLOR_BLACK: + return '000000'; + } + + return $value; + } + /** * Convert HTML hexadecimal to RGB * @@ -283,6 +327,7 @@ class Converter if ($value[0] == '#') { $value = substr($value, 1); } + $value = self::stringToRgb($value); if (strlen($value) == 6) { list($red, $green, $blue) = array($value[0] . $value[1], $value[2] . $value[3], $value[4] . $value[5]); diff --git a/tests/PhpWord/Shared/ConverterTest.php b/tests/PhpWord/Shared/ConverterTest.php index 15be8ec1..122385a9 100644 --- a/tests/PhpWord/Shared/ConverterTest.php +++ b/tests/PhpWord/Shared/ConverterTest.php @@ -114,6 +114,8 @@ class ConverterTest extends \PHPUnit\Framework\TestCase $values[] = array('FF99DD', array(255, 153, 221)); // 6 characters $values[] = array('F9D', array(255, 153, 221)); // 3 characters $values[] = array('0F9D', false); // 4 characters + $values[] = array(\PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKMAGENTA, array(139, 0, 139)); + $values[] = array('unknow', array(0, 0, 0)); // 6 characters, invalid // Conduct test foreach ($values as $value) { $result = Converter::htmlToRgb($value[0]); From ecfafd757667c2857586d5841dffad6d67b65b70 Mon Sep 17 00:00:00 2001 From: owen Date: Tue, 3 Dec 2019 07:46:16 -0800 Subject: [PATCH 2/5] RTF Changes 1. Converter is currently expecting colors as strings of hex digits, but PhpWord allows specification of colors by named constant, so result is random when one of those is used. This change handles all the named colors. 2. Table needs \pard at end; formatting may be wrong without it. 3. RTF writer will no longer ignore paragraph style for TextRun. 4. RTF writer will no longer ignore paragraph and font style for Title. 5. Add support for RTF headers and footers. 6. Add support for right-to-left in font. 7. Add support for PageBreakBefore and LineHeight for paragraphs. 8. Add support for PageNumberingStart for sections. There are test cases for all of these changes. --- src/PhpWord/Shared/Converter.php | 3 +- .../Writer/RTF/Element/AbstractElement.php | 4 +- src/PhpWord/Writer/RTF/Element/Table.php | 1 + src/PhpWord/Writer/RTF/Element/TextRun.php | 1 + src/PhpWord/Writer/RTF/Element/Title.php | 67 ++++++++++++++++ src/PhpWord/Writer/RTF/Part/Document.php | 69 ++++++++++++++++ src/PhpWord/Writer/RTF/Style/Font.php | 1 + src/PhpWord/Writer/RTF/Style/Paragraph.php | 10 +++ src/PhpWord/Writer/RTF/Style/Section.php | 1 + tests/PhpWord/Shared/ConverterTest.php | 20 ++--- tests/PhpWord/Writer/RTF/ElementTest.php | 76 ++++++++++++++++++ tests/PhpWord/Writer/RTF/HeaderFooterTest.php | 78 +++++++++++++++++++ tests/PhpWord/Writer/RTF/StyleTest.php | 45 +++++++++++ 13 files changed, 360 insertions(+), 16 deletions(-) create mode 100644 tests/PhpWord/Writer/RTF/HeaderFooterTest.php diff --git a/src/PhpWord/Shared/Converter.php b/src/PhpWord/Shared/Converter.php index 7c3048fc..9206a3bc 100644 --- a/src/PhpWord/Shared/Converter.php +++ b/src/PhpWord/Shared/Converter.php @@ -326,8 +326,9 @@ class Converter { if ($value[0] == '#') { $value = substr($value, 1); + } else { + $value = self::stringToRgb($value); } - $value = self::stringToRgb($value); if (strlen($value) == 6) { list($red, $green, $blue) = array($value[0] . $value[1], $value[2] . $value[3], $value[4] . $value[5]); diff --git a/src/PhpWord/Writer/RTF/Element/AbstractElement.php b/src/PhpWord/Writer/RTF/Element/AbstractElement.php index cf1aa391..132890e6 100644 --- a/src/PhpWord/Writer/RTF/Element/AbstractElement.php +++ b/src/PhpWord/Writer/RTF/Element/AbstractElement.php @@ -41,14 +41,14 @@ abstract class AbstractElement extends HTMLAbstractElement * * @var \PhpOffice\PhpWord\Style\Font */ - private $fontStyle; + protected $fontStyle; /** * Paragraph style * * @var \PhpOffice\PhpWord\Style\Paragraph */ - private $paragraphStyle; + protected $paragraphStyle; public function __construct(AbstractWriter $parentWriter, Element $element, $withoutP = false) { diff --git a/src/PhpWord/Writer/RTF/Element/Table.php b/src/PhpWord/Writer/RTF/Element/Table.php index 8154aa7c..63c3e6a3 100644 --- a/src/PhpWord/Writer/RTF/Element/Table.php +++ b/src/PhpWord/Writer/RTF/Element/Table.php @@ -58,6 +58,7 @@ class Table extends AbstractElement $content .= $this->writeRow($rows[$i]); $content .= '\row' . PHP_EOL; } + $content .= '\pard' . PHP_EOL; } return $content; diff --git a/src/PhpWord/Writer/RTF/Element/TextRun.php b/src/PhpWord/Writer/RTF/Element/TextRun.php index bfd161f0..e2865d82 100644 --- a/src/PhpWord/Writer/RTF/Element/TextRun.php +++ b/src/PhpWord/Writer/RTF/Element/TextRun.php @@ -32,6 +32,7 @@ class TextRun extends AbstractElement public function write() { $writer = new Container($this->parentWriter, $this->element); + $this->getStyles(); $content = ''; $content .= $this->writeOpening(); diff --git a/src/PhpWord/Writer/RTF/Element/Title.php b/src/PhpWord/Writer/RTF/Element/Title.php index a9940ca9..3d5e08ca 100644 --- a/src/PhpWord/Writer/RTF/Element/Title.php +++ b/src/PhpWord/Writer/RTF/Element/Title.php @@ -24,4 +24,71 @@ namespace PhpOffice\PhpWord\Writer\RTF\Element; */ class Title extends Text { + protected function getStyles() + { + /** @var \PhpOffice\PhpWord\Element\Text $element Type hint */ + $element = $this->element; + $style = $element->getStyle(); + if (is_string($style)) { + $style = str_replace('Heading', 'Heading_', $style); + $style = \PhpOffice\PhpWord\Style::getStyle($style); + if ($style instanceof \PhpOffice\PhpWord\Style\Font) { + $this->fontStyle = $style; + $pstyle = $style->getParagraph(); + if ($pstyle instanceof \PhpOffice\PhpWord\Style\Paragraph && $pstyle->hasPageBreakBefore()) { + $sect = $element->getParent(); + if ($sect instanceof \PhpOffice\PhpWord\Element\Section) { + $elems = $sect->getElements(); + if ($elems[0] === $element) { + $pstyle = clone $pstyle; + $pstyle->setPageBreakBefore(false); + } + } + } + $this->paragraphStyle = $pstyle; + } + } + } + + /** + * Write element + * + * @return string + */ + public function write() + { + /** @var \PhpOffice\PhpWord\Element\Text $element Type hint */ + $element = $this->element; + $elementClass = str_replace('\\Writer\\RTF', '', get_class($this)); + if (!$element instanceof $elementClass || !is_string($element->getText())) { + return ''; + } + + $this->getStyles(); + + $content = ''; + + $content .= $this->writeOpening(); + $endout = ''; + $style = $element->getStyle(); + if (is_string($style)) { + $style = str_replace('Heading', '', $style); + if (is_numeric($style)) { + $style = (int) $style - 1; + if ($style >= 0 && $style <= 8) { + $content .= '{\\outlinelevel' . $style; + $endout = '}'; + } + } + } + + $content .= '{'; + $content .= $this->writeFontStyle(); + $content .= $this->writeText($element->getText()); + $content .= '}'; + $content .= $this->writeClosing(); + $content .= $endout; + + return $content; + } } diff --git a/src/PhpWord/Writer/RTF/Part/Document.php b/src/PhpWord/Writer/RTF/Part/Document.php index d4bfadb4..5d4d23d9 100644 --- a/src/PhpWord/Writer/RTF/Part/Document.php +++ b/src/PhpWord/Writer/RTF/Part/Document.php @@ -17,6 +17,7 @@ namespace PhpOffice\PhpWord\Writer\RTF\Part; +use PhpOffice\PhpWord\Element\Footer; use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Writer\RTF\Element\Container; use PhpOffice\PhpWord\Writer\RTF\Style\Section as SectionStyleWriter; @@ -105,11 +106,36 @@ class Document extends AbstractPart $content .= '\lang' . $langId; $content .= '\kerning1'; // Point size (in half-points) above which to kern character pairs $content .= '\fs' . (Settings::getDefaultFontSize() * 2); // Set the font size in half-points + if ($docSettings->hasEvenAndOddHeaders()) { + $content .= '\\facingp'; + } $content .= PHP_EOL; return $content; } + /** + * Write titlepg directive if any "f" headers or footers + * + * @param \PhpOffice\PhpWord\PhpWord\Element\Section $section + * @return string + */ + private static function writeTitlepg($section) + { + foreach ($section->getHeaders() as $header) { + if ($header->getType() === Footer::FIRST) { + return '\\titlepg' . PHP_EOL; + } + } + foreach ($section->getFooters() as $header) { + if ($header->getType() === Footer::FIRST) { + return '\\titlepg' . PHP_EOL; + } + } + + return ''; + } + /** * Write sections * @@ -120,10 +146,53 @@ class Document extends AbstractPart $content = ''; $sections = $this->getParentWriter()->getPhpWord()->getSections(); + $evenOdd = $this->getParentWriter()->getPhpWord()->getSettings()->hasEvenAndOddHeaders(); foreach ($sections as $section) { $styleWriter = new SectionStyleWriter($section->getStyle()); $styleWriter->setParentWriter($this->getParentWriter()); $content .= $styleWriter->write(); + $content .= self::writeTitlepg($section); + + foreach ($section->getHeaders() as $header) { + $type = $header->getType(); + if ($evenOdd || $type !== FOOTER::EVEN) { + $content .= '{\\header'; + if ($type === Footer::FIRST) { + $content .= 'f'; + } elseif ($evenOdd) { + $content .= ($type === FOOTER::EVEN) ? 'l' : 'r'; + } + foreach ($header->getElements() as $element) { + $cl = get_class($element); + $cl2 = str_replace('Element', 'Writer\\RTF\\Element', $cl); + if (class_exists($cl2)) { + $elementWriter = new $cl2($this->getParentWriter(), $element); + $content .= $elementWriter->write(); + } + } + $content .= '}' . PHP_EOL; + } + } + foreach ($section->getFooters() as $footer) { + $type = $footer->getType(); + if ($evenOdd || $type !== FOOTER::EVEN) { + $content .= '{\\footer'; + if ($type === Footer::FIRST) { + $content .= 'f'; + } elseif ($evenOdd) { + $content .= ($type === FOOTER::EVEN) ? 'l' : 'r'; + } + foreach ($footer->getElements() as $element) { + $cl = get_class($element); + $cl2 = str_replace('Element', 'Writer\\RTF\\Element', $cl); + if (class_exists($cl2)) { + $elementWriter = new $cl2($this->getParentWriter(), $element); + $content .= $elementWriter->write(); + } + } + $content .= '}' . PHP_EOL; + } + } $elementWriter = new Container($this->getParentWriter(), $section); $content .= $elementWriter->write(); diff --git a/src/PhpWord/Writer/RTF/Style/Font.php b/src/PhpWord/Writer/RTF/Style/Font.php index b9001ea0..34f6c1af 100644 --- a/src/PhpWord/Writer/RTF/Style/Font.php +++ b/src/PhpWord/Writer/RTF/Style/Font.php @@ -49,6 +49,7 @@ class Font extends AbstractStyle } $content = ''; + $content .= $this->getValueIf($style->isRTL(), '\rtlch'); $content .= '\cf' . $this->colorIndex; $content .= '\f' . $this->nameIndex; diff --git a/src/PhpWord/Writer/RTF/Style/Paragraph.php b/src/PhpWord/Writer/RTF/Style/Paragraph.php index 8ef3e146..3394f9d4 100644 --- a/src/PhpWord/Writer/RTF/Style/Paragraph.php +++ b/src/PhpWord/Writer/RTF/Style/Paragraph.php @@ -52,6 +52,8 @@ class Paragraph extends AbstractStyle Jc::END => '\qr', Jc::CENTER => '\qc', Jc::BOTH => '\qj', + Jc::LEFT => '\ql', + Jc::RIGHT => '\qr', ); $spaceAfter = $style->getSpaceAfter(); @@ -67,6 +69,14 @@ class Paragraph extends AbstractStyle $content .= $this->writeIndentation($style->getIndentation()); $content .= $this->getValueIf($spaceBefore !== null, '\sb' . round($spaceBefore)); $content .= $this->getValueIf($spaceAfter !== null, '\sa' . round($spaceAfter)); + $lineHeight = $style->getLineHeight(); + if ($lineHeight !== null) { + $lineHeightAdjusted = (int) ($lineHeight * 240); + $content .= "\\sl$lineHeightAdjusted\\slmult1"; + } + if ($style->getPageBreakBefore()) { + $content .= '\\page'; + } $styles = $style->getStyleValues(); $content .= $this->writeTabs($styles['tabs']); diff --git a/src/PhpWord/Writer/RTF/Style/Section.php b/src/PhpWord/Writer/RTF/Style/Section.php index 190bb670..c6801947 100644 --- a/src/PhpWord/Writer/RTF/Style/Section.php +++ b/src/PhpWord/Writer/RTF/Style/Section.php @@ -53,6 +53,7 @@ class Section extends AbstractStyle $content .= $this->getValueIf($style->getHeaderHeight() !== null, '\headery' . round($style->getHeaderHeight())); $content .= $this->getValueIf($style->getFooterHeight() !== null, '\footery' . round($style->getFooterHeight())); $content .= $this->getValueIf($style->getGutter() !== null, '\guttersxn' . round($style->getGutter())); + $content .= $this->getValueIf($style->getPageNumberingStart() !== null, '\pgnstarts' . $style->getPageNumberingStart() . '\pgnrestart'); $content .= ' '; // Borders diff --git a/tests/PhpWord/Shared/ConverterTest.php b/tests/PhpWord/Shared/ConverterTest.php index 122385a9..0d10dc49 100644 --- a/tests/PhpWord/Shared/ConverterTest.php +++ b/tests/PhpWord/Shared/ConverterTest.php @@ -108,19 +108,13 @@ class ConverterTest extends \PHPUnit\Framework\TestCase */ public function testHtmlToRGB() { - // Prepare test values [ original, expected ] - $values = array(); - $values[] = array('#FF99DD', array(255, 153, 221)); // With # - $values[] = array('FF99DD', array(255, 153, 221)); // 6 characters - $values[] = array('F9D', array(255, 153, 221)); // 3 characters - $values[] = array('0F9D', false); // 4 characters - $values[] = array(\PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKMAGENTA, array(139, 0, 139)); - $values[] = array('unknow', array(0, 0, 0)); // 6 characters, invalid - // Conduct test - foreach ($values as $value) { - $result = Converter::htmlToRgb($value[0]); - $this->assertEquals($value[1], $result); - } + $flse = false; + $this->assertEquals(array(255, 153, 221), Converter::htmlToRgb('#FF99DD')); // With # + $this->assertEquals(array(224, 170, 29), Converter::htmlToRgb('E0AA1D')); // 6 characters + $this->assertEquals(array(102, 119, 136), Converter::htmlToRgb('678')); // 3 characters + $this->assertEquals($flse, Converter::htmlToRgb('0F9D')); // 4 characters + $this->assertEquals(array(0, 0, 0), Converter::htmlToRgb('unknow')); // 6 characters, invalid + $this->assertEquals(array(139, 0, 139), Converter::htmlToRgb(\PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKMAGENTA)); // Constant } /** diff --git a/tests/PhpWord/Writer/RTF/ElementTest.php b/tests/PhpWord/Writer/RTF/ElementTest.php index 3e9c235d..44287d71 100644 --- a/tests/PhpWord/Writer/RTF/ElementTest.php +++ b/tests/PhpWord/Writer/RTF/ElementTest.php @@ -80,4 +80,80 @@ class ElementTest extends \PHPUnit\Framework\TestCase $this->assertEquals("{}\\par\n", $this->removeCr($field)); } + + public function testTable() + { + $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 \PhpOffice\PhpWord\Writer\RTF\Element\Table($parentWriter, $element); + $expect = implode("\n", array( + '\\pard', + "\\trowd \\cellx$width \\cellx$width2 ", + '\\intbl', + '{\\cf0\\f0 1}\\par', + '\\cell', + '\\intbl', + '{\\cf0\\f0 2}\\par', + '\\cell', + '\\row', + "\\trowd \\cellx$width \\cellx$width2 ", + '\\intbl', + '{\\cf0\\f0 3}\\par', + '\\cell', + '\\intbl', + '{\\cf0\\f0 4}\par', + '\\cell', + '\\row', + '\\pard', + '', + )); + + $this->assertEquals($expect, $this->removeCr($table)); + } + + public function testTextRun() + { + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\TextRun(); + $element->addText('Hello '); + $element->addText('there.'); + $textrun = new \PhpOffice\PhpWord\Writer\RTF\Element\TextRun($parentWriter, $element); + $expect = "\\pard\\nowidctlpar {{\\cf0\\f0 Hello }{\\cf0\\f0 there.}}\\par\n"; + $this->assertEquals($expect, $this->removeCr($textrun)); + } + + public function testTextRunParagraphStyle() + { + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\TextRun(array('spaceBefore' => 0, 'spaceAfter' => 0)); + $element->addText('Hello '); + $element->addText('there.'); + $textrun = new \PhpOffice\PhpWord\Writer\RTF\Element\TextRun($parentWriter, $element); + $expect = "\\pard\\nowidctlpar \\sb0\\sa0{{\\cf0\\f0 Hello }{\\cf0\\f0 there.}}\\par\n"; + $this->assertEquals($expect, $this->removeCr($textrun)); + } + + public function testTitle() + { + $parentWriter = new RTF(); + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $phpWord->addTitleStyle(1, array(), array('spaceBefore' => 0, 'spaceAfter' => 0)); + $section = $phpWord->addSection(); + $element = $section->addTitle('First Heading', 1); + $elwrite = new \PhpOffice\PhpWord\Writer\RTF\Element\Title($parentWriter, $element); + $expect = "\\pard\\nowidctlpar \\sb0\\sa0{\\outlinelevel0{\\cf0\\f0 First Heading}\\par\n}"; + $this->assertEquals($expect, $this->removeCr($elwrite)); + } } diff --git a/tests/PhpWord/Writer/RTF/HeaderFooterTest.php b/tests/PhpWord/Writer/RTF/HeaderFooterTest.php new file mode 100644 index 00000000..81c589f4 --- /dev/null +++ b/tests/PhpWord/Writer/RTF/HeaderFooterTest.php @@ -0,0 +1,78 @@ +addSection(); + $section->addText('Doc without header or footer'); + $contents = $parentWriter->getWriterPart('Document')->write(); + $this->assertEquals(0, preg_match('/\\\\header[rlf]?\\b/', $contents)); + $this->assertEquals(0, preg_match('/\\\\footer[rlf]?\\b/', $contents)); + $this->assertEquals(0, preg_match('/\\\\titlepg\\b/', $contents)); + $this->assertEquals(0, preg_match('/\\\\facingp\\b/', $contents)); + } + + public function testNoHeaderYesFooter() + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $parentWriter = new RTF($phpWord); + $section = $phpWord->addSection(); + $footer = $section->addFooter(); + $footer->addText('Auto footer'); + $section->addText('Doc without header but with footer'); + $contents = $parentWriter->getWriterPart('Document')->write(); + $this->assertEquals(0, preg_match('/\\\\header[rlf]?\\b/', $contents)); + $this->assertEquals(1, preg_match('/\\\\footer[rlf]?\\b/', $contents)); + $this->assertEquals(0, preg_match('/\\\\titlepg\\b/', $contents)); + $this->assertEquals(0, preg_match('/\\\\facingp\\b/', $contents)); + } + + public function testEvenHeaderFirstFooter() + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $phpWord->getSettings()->setEvenAndOddHeaders(true); + $parentWriter = new RTF($phpWord); + $section = $phpWord->addSection(); + $footer = $section->addFooter(Footer::FIRST); + $footer->addText('First footer'); + $footer = $section->addHeader(Footer::EVEN); + $footer->addText('Even footer'); + $footer = $section->addHeader(Footer::AUTO); + $footer->addText('Odd footer'); + $section->addText('Doc with even/odd header and first footer'); + $contents = $parentWriter->getWriterPart('Document')->write(); + $this->assertEquals(1, preg_match('/\\\\headerr\\b/', $contents)); + $this->assertEquals(1, preg_match('/\\\\headerl\\b/', $contents)); + $this->assertEquals(0, preg_match('/\\\\header[f]?\\b/', $contents)); + $this->assertEquals(1, preg_match('/\\\\footerf\\b/', $contents)); + $this->assertEquals(0, preg_match('/\\\\footer[rl]?\\b/', $contents)); + $this->assertEquals(1, preg_match('/\\\\titlepg\\b/', $contents)); + $this->assertEquals(1, preg_match('/\\\\facingp\\b/', $contents)); + } +} diff --git a/tests/PhpWord/Writer/RTF/StyleTest.php b/tests/PhpWord/Writer/RTF/StyleTest.php index 317014c6..6624f5ee 100644 --- a/tests/PhpWord/Writer/RTF/StyleTest.php +++ b/tests/PhpWord/Writer/RTF/StyleTest.php @@ -26,6 +26,11 @@ use PHPUnit\Framework\Assert; */ class StyleTest extends \PHPUnit\Framework\TestCase { + public function removeCr($field) + { + return str_replace("\r\n", "\n", $field->write()); + } + /** * Test empty styles */ @@ -108,4 +113,44 @@ class StyleTest extends \PHPUnit\Framework\TestCase Assert::assertEquals('\tqdec\tx0', $result); } + + public function testRTL() + { + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\Text('אב גד', array('RTL'=> true)); + $text = new \PhpOffice\PhpWord\Writer\RTF\Element\Text($parentWriter, $element); + $expect = "\\pard\\nowidctlpar {\\rtlch\\cf0\\f0 \\uc0{\\u1488}\\uc0{\\u1489} \\uc0{\\u1490}\\uc0{\\u1491}}\\par\n"; + $this->assertEquals($expect, $this->removeCr($text)); + } + + public function testPageBreakLineHeight() + { + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\Text('New page', null, array('lineHeight' => 1.08, 'pageBreakBefore' => true)); + $text = new \PhpOffice\PhpWord\Writer\RTF\Element\Text($parentWriter, $element); + $expect = "\\pard\\nowidctlpar \\sl259\\slmult1\\page{\\cf0\\f0 New page}\\par\n"; + $this->assertEquals($expect, $this->removeCr($text)); + } + + public function testPageNumberRestart() + { + //$parentWriter = new RTF(); + $phpword = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpword->addSection(array('pageNumberingStart' => 5)); + $styleWriter = new \PhpOffice\PhpWord\Writer\RTF\Style\Section($section->getStyle()); + $wstyle = $this->removeCr($styleWriter); + // following have default values which might change so don't use them + $wstyle = preg_replace('/\\\\pgwsxn\\d+/', '', $wstyle); + $wstyle = preg_replace('/\\\\pghsxn\\d+/', '', $wstyle); + $wstyle = preg_replace('/\\\\margtsxn\\d+/', '', $wstyle); + $wstyle = preg_replace('/\\\\margrsxn\\d+/', '', $wstyle); + $wstyle = preg_replace('/\\\\margbsxn\\d+/', '', $wstyle); + $wstyle = preg_replace('/\\\\marglsxn\\d+/', '', $wstyle); + $wstyle = preg_replace('/\\\\headery\\d+/', '', $wstyle); + $wstyle = preg_replace('/\\\\footery\\d+/', '', $wstyle); + $wstyle = preg_replace('/\\\\guttersxn\\d+/', '', $wstyle); + $wstyle = preg_replace('/ +/', ' ', $wstyle); + $expect = "\\sectd \\pgnstarts5\\pgnrestart \n"; + $this->assertEquals($expect, $wstyle); + } } From 7657992a83a272c44ec6f50e868173a8a52c233d Mon Sep 17 00:00:00 2001 From: owen Date: Thu, 5 Dec 2019 22:51:00 -0800 Subject: [PATCH 3/5] Scrutinizer-suggested changes Changes to doc-blocks and code suggested by Scrutinizer. --- src/PhpWord/Writer/RTF/Element/Title.php | 2 +- src/PhpWord/Writer/RTF/Part/Document.php | 2 +- src/PhpWord/Writer/RTF/Style/Paragraph.php | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/PhpWord/Writer/RTF/Element/Title.php b/src/PhpWord/Writer/RTF/Element/Title.php index 3d5e08ca..d5c8d455 100644 --- a/src/PhpWord/Writer/RTF/Element/Title.php +++ b/src/PhpWord/Writer/RTF/Element/Title.php @@ -26,7 +26,7 @@ class Title extends Text { protected function getStyles() { - /** @var \PhpOffice\PhpWord\Element\Text $element Type hint */ + /** @var \PhpOffice\PhpWord\Element\Title $element Type hint */ $element = $this->element; $style = $element->getStyle(); if (is_string($style)) { diff --git a/src/PhpWord/Writer/RTF/Part/Document.php b/src/PhpWord/Writer/RTF/Part/Document.php index 5d4d23d9..14d90094 100644 --- a/src/PhpWord/Writer/RTF/Part/Document.php +++ b/src/PhpWord/Writer/RTF/Part/Document.php @@ -117,7 +117,7 @@ class Document extends AbstractPart /** * Write titlepg directive if any "f" headers or footers * - * @param \PhpOffice\PhpWord\PhpWord\Element\Section $section + * @param \PhpOffice\PhpWord\Element\Section $section * @return string */ private static function writeTitlepg($section) diff --git a/src/PhpWord/Writer/RTF/Style/Paragraph.php b/src/PhpWord/Writer/RTF/Style/Paragraph.php index 3394f9d4..a9c060ac 100644 --- a/src/PhpWord/Writer/RTF/Style/Paragraph.php +++ b/src/PhpWord/Writer/RTF/Style/Paragraph.php @@ -52,8 +52,8 @@ class Paragraph extends AbstractStyle Jc::END => '\qr', Jc::CENTER => '\qc', Jc::BOTH => '\qj', - Jc::LEFT => '\ql', - Jc::RIGHT => '\qr', + "left" => '\ql', + "right" => '\qr', ); $spaceAfter = $style->getSpaceAfter(); @@ -70,11 +70,11 @@ class Paragraph extends AbstractStyle $content .= $this->getValueIf($spaceBefore !== null, '\sb' . round($spaceBefore)); $content .= $this->getValueIf($spaceAfter !== null, '\sa' . round($spaceAfter)); $lineHeight = $style->getLineHeight(); - if ($lineHeight !== null) { + if ($lineHeight) { $lineHeightAdjusted = (int) ($lineHeight * 240); $content .= "\\sl$lineHeightAdjusted\\slmult1"; } - if ($style->getPageBreakBefore()) { + if ($style->hasPageBreakBefore()) { $content .= '\\page'; } From 5e64b264512265f8bdd3510b86f758be9cffd210 Mon Sep 17 00:00:00 2001 From: owen Date: Thu, 5 Dec 2019 23:24:03 -0800 Subject: [PATCH 4/5] Additional Scrutinizer Recommendations Some more editorial changes. --- src/PhpWord/Writer/RTF/Element/Title.php | 30 ++++++++++------------ src/PhpWord/Writer/RTF/Style/Paragraph.php | 2 -- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/PhpWord/Writer/RTF/Element/Title.php b/src/PhpWord/Writer/RTF/Element/Title.php index d5c8d455..77ebff17 100644 --- a/src/PhpWord/Writer/RTF/Element/Title.php +++ b/src/PhpWord/Writer/RTF/Element/Title.php @@ -29,24 +29,22 @@ class Title extends Text /** @var \PhpOffice\PhpWord\Element\Title $element Type hint */ $element = $this->element; $style = $element->getStyle(); - if (is_string($style)) { - $style = str_replace('Heading', 'Heading_', $style); - $style = \PhpOffice\PhpWord\Style::getStyle($style); - if ($style instanceof \PhpOffice\PhpWord\Style\Font) { - $this->fontStyle = $style; - $pstyle = $style->getParagraph(); - if ($pstyle instanceof \PhpOffice\PhpWord\Style\Paragraph && $pstyle->hasPageBreakBefore()) { - $sect = $element->getParent(); - if ($sect instanceof \PhpOffice\PhpWord\Element\Section) { - $elems = $sect->getElements(); - if ($elems[0] === $element) { - $pstyle = clone $pstyle; - $pstyle->setPageBreakBefore(false); - } + $style = str_replace('Heading', 'Heading_', $style); + $style = \PhpOffice\PhpWord\Style::getStyle($style); + if ($style instanceof \PhpOffice\PhpWord\Style\Font) { + $this->fontStyle = $style; + $pstyle = $style->getParagraph(); + if ($pstyle instanceof \PhpOffice\PhpWord\Style\Paragraph && $pstyle->hasPageBreakBefore()) { + $sect = $element->getParent(); + if ($sect instanceof \PhpOffice\PhpWord\Element\Section) { + $elems = $sect->getElements(); + if ($elems[0] === $element) { + $pstyle = clone $pstyle; + $pstyle->setPageBreakBefore(false); } } - $this->paragraphStyle = $pstyle; } + $this->paragraphStyle = $pstyle; } } @@ -57,7 +55,7 @@ class Title extends Text */ public function write() { - /** @var \PhpOffice\PhpWord\Element\Text $element Type hint */ + /** @var \PhpOffice\PhpWord\Element\Title $element Type hint */ $element = $this->element; $elementClass = str_replace('\\Writer\\RTF', '', get_class($this)); if (!$element instanceof $elementClass || !is_string($element->getText())) { diff --git a/src/PhpWord/Writer/RTF/Style/Paragraph.php b/src/PhpWord/Writer/RTF/Style/Paragraph.php index a9c060ac..9f8cf9df 100644 --- a/src/PhpWord/Writer/RTF/Style/Paragraph.php +++ b/src/PhpWord/Writer/RTF/Style/Paragraph.php @@ -52,8 +52,6 @@ class Paragraph extends AbstractStyle Jc::END => '\qr', Jc::CENTER => '\qc', Jc::BOTH => '\qj', - "left" => '\ql', - "right" => '\qr', ); $spaceAfter = $style->getSpaceAfter(); From 122aaf17b11cbc69afffbfb8a91d79fb4480f79b Mon Sep 17 00:00:00 2001 From: owen Date: Thu, 5 Dec 2019 23:48:44 -0800 Subject: [PATCH 5/5] Incorporate Pull Request 1771 Fix PHPUnit tests on develop branch --- .../AbstractWebServerEmbeddedTest.php | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/PhpWord/_includes/AbstractWebServerEmbeddedTest.php b/tests/PhpWord/_includes/AbstractWebServerEmbeddedTest.php index 9316a9fe..5d1e3c12 100644 --- a/tests/PhpWord/_includes/AbstractWebServerEmbeddedTest.php +++ b/tests/PhpWord/_includes/AbstractWebServerEmbeddedTest.php @@ -26,7 +26,25 @@ abstract class AbstractWebServerEmbeddedTest extends \PHPUnit\Framework\TestCase public static function setUpBeforeClass() { if (self::isBuiltinServerSupported()) { - self::$httpServer = new Process('php -S localhost:8080 -t tests/PhpWord/_files'); + $commandLine = 'php -S localhost:8080 -t tests/PhpWord/_files'; + /* + * Make sure to invoke \Symfony\Component\Process\Process correctly + * regardless of PHP version used. + * + * In Process version >= 5 / PHP >= 7.2.5, the constructor requires + * an array, while in version < 3.3 / PHP < 5.5.9 it requires a string. + * In between, it can accept both. + * + * Process::fromShellCommandLine() was introduced in version 4.2.0, + * to enable recent versions of Process to parse a command string, + * so if it is not available it means it is still possible to pass + * a string to the constructor. + */ + if (method_exists('Symfony\Component\Process\Process', 'fromShellCommandLine')) { + self::$httpServer = Process::fromShellCommandline($commandLine); + } else { + self::$httpServer = new Process($commandLine); + } self::$httpServer->start(); while (!self::$httpServer->isRunning()) { usleep(1000);