diff --git a/src/PhpWord/Shared/Converter.php b/src/PhpWord/Shared/Converter.php index 7008ac5d1d..9206a3bcd3 100644 --- a/src/PhpWord/Shared/Converter.php +++ b/src/PhpWord/Shared/Converter.php @@ -272,6 +272,50 @@ public static function angleToDegree($angle = 1) 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 * @@ -282,6 +326,8 @@ public static function htmlToRgb($value) { if ($value[0] == '#') { $value = substr($value, 1); + } else { + $value = self::stringToRgb($value); } if (strlen($value) == 6) { diff --git a/src/PhpWord/Writer/RTF/Element/AbstractElement.php b/src/PhpWord/Writer/RTF/Element/AbstractElement.php index cf1aa391c5..132890e6ec 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 8154aa7cf5..63c3e6a31f 100644 --- a/src/PhpWord/Writer/RTF/Element/Table.php +++ b/src/PhpWord/Writer/RTF/Element/Table.php @@ -58,6 +58,7 @@ public function write() $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 bfd161f08a..e2865d827e 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 a9940ca99a..77ebff1769 100644 --- a/src/PhpWord/Writer/RTF/Element/Title.php +++ b/src/PhpWord/Writer/RTF/Element/Title.php @@ -24,4 +24,69 @@ */ class Title extends Text { + protected function getStyles() + { + /** @var \PhpOffice\PhpWord\Element\Title $element Type hint */ + $element = $this->element; + $style = $element->getStyle(); + $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\Title $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 d4bfadb4c5..14d900944e 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 @@ private function writeFormatting() $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\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 @@ private function writeSections() $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 b9001ea0ea..34f6c1afae 100644 --- a/src/PhpWord/Writer/RTF/Style/Font.php +++ b/src/PhpWord/Writer/RTF/Style/Font.php @@ -49,6 +49,7 @@ public function write() } $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 8ef3e146d9..9f8cf9dff0 100644 --- a/src/PhpWord/Writer/RTF/Style/Paragraph.php +++ b/src/PhpWord/Writer/RTF/Style/Paragraph.php @@ -67,6 +67,14 @@ public function write() $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) { + $lineHeightAdjusted = (int) ($lineHeight * 240); + $content .= "\\sl$lineHeightAdjusted\\slmult1"; + } + if ($style->hasPageBreakBefore()) { + $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 190bb67036..c680194763 100644 --- a/src/PhpWord/Writer/RTF/Style/Section.php +++ b/src/PhpWord/Writer/RTF/Style/Section.php @@ -53,6 +53,7 @@ public function write() $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 15be8ec12a..0d10dc4936 100644 --- a/tests/PhpWord/Shared/ConverterTest.php +++ b/tests/PhpWord/Shared/ConverterTest.php @@ -108,17 +108,13 @@ public function testUnitConversions() */ 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 - // 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 3e9c235d13..44287d7171 100644 --- a/tests/PhpWord/Writer/RTF/ElementTest.php +++ b/tests/PhpWord/Writer/RTF/ElementTest.php @@ -80,4 +80,80 @@ public function testIndexField() $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 0000000000..81c589f4b3 --- /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 317014c610..6624f5ee4d 100644 --- a/tests/PhpWord/Writer/RTF/StyleTest.php +++ b/tests/PhpWord/Writer/RTF/StyleTest.php @@ -26,6 +26,11 @@ */ 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 @@ public function testDecimalTab() 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); + } }