Merge pull request #1775 from oleibman/rtfchanges

Add support for several features for the RTF writer
This commit is contained in:
troosan 2020-07-06 08:36:38 +02:00 committed by GitHub
commit 250e206b59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 400 additions and 13 deletions

View File

@ -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
*
@ -282,6 +326,8 @@ class Converter
{
if ($value[0] == '#') {
$value = substr($value, 1);
} else {
$value = self::stringToRgb($value);
}
if (strlen($value) == 6) {

View File

@ -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)
{

View File

@ -58,6 +58,7 @@ class Table extends AbstractElement
$content .= $this->writeRow($rows[$i]);
$content .= '\row' . PHP_EOL;
}
$content .= '\pard' . PHP_EOL;
}
return $content;

View File

@ -32,6 +32,7 @@ class TextRun extends AbstractElement
public function write()
{
$writer = new Container($this->parentWriter, $this->element);
$this->getStyles();
$content = '';
$content .= $this->writeOpening();

View File

@ -24,4 +24,69 @@ namespace PhpOffice\PhpWord\Writer\RTF\Element;
*/
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;
}
}

View File

@ -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\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();

View File

@ -49,6 +49,7 @@ class Font extends AbstractStyle
}
$content = '';
$content .= $this->getValueIf($style->isRTL(), '\rtlch');
$content .= '\cf' . $this->colorIndex;
$content .= '\f' . $this->nameIndex;

View File

@ -67,6 +67,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) {
$lineHeightAdjusted = (int) ($lineHeight * 240);
$content .= "\\sl$lineHeightAdjusted\\slmult1";
}
if ($style->hasPageBreakBefore()) {
$content .= '\\page';
}
$styles = $style->getStyleValues();
$content .= $this->writeTabs($styles['tabs']);

View File

@ -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

View File

@ -108,17 +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
// 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
}
/**

View File

@ -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));
}
}

View File

@ -0,0 +1,78 @@
<?php
/**
* This file is part of PHPWord - A pure PHP library for reading and writing
* word processing documents.
*
* PHPWord is free software distributed under the terms of the GNU Lesser
* General Public License version 3 as published by the Free Software Foundation.
*
* For the full copyright and license information, please read the LICENSE
* file that was distributed with this source code. For the full list of
* contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
*
* @see https://github.com/PHPOffice/PHPWord
* @copyright 2010-2018 PHPWord contributors
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
*/
namespace PhpOffice\PhpWord\Writer\RTF;
use PhpOffice\PhpWord\Element\Footer;
use PhpOffice\PhpWord\Writer\RTF;
/**
* Test class for PhpOffice\PhpWord\Writer\RTF\Element subnamespace
*/
class HeaderFooterTest extends \PHPUnit\Framework\TestCase
{
public function testNoHeaderNoFooter()
{
$phpWord = new \PhpOffice\PhpWord\PhpWord();
$parentWriter = new RTF($phpWord);
$section = $phpWord->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));
}
}

View File

@ -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);
}
}