Skip to content

Commit ecfafd7

Browse files
committed
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.
1 parent ebf5cf7 commit ecfafd7

File tree

13 files changed

+360
-16
lines changed

13 files changed

+360
-16
lines changed

src/PhpWord/Shared/Converter.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,8 +326,9 @@ public static function htmlToRgb($value)
326326
{
327327
if ($value[0] == '#') {
328328
$value = substr($value, 1);
329+
} else {
330+
$value = self::stringToRgb($value);
329331
}
330-
$value = self::stringToRgb($value);
331332

332333
if (strlen($value) == 6) {
333334
list($red, $green, $blue) = array($value[0] . $value[1], $value[2] . $value[3], $value[4] . $value[5]);

src/PhpWord/Writer/RTF/Element/AbstractElement.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,14 @@ abstract class AbstractElement extends HTMLAbstractElement
4141
*
4242
* @var \PhpOffice\PhpWord\Style\Font
4343
*/
44-
private $fontStyle;
44+
protected $fontStyle;
4545

4646
/**
4747
* Paragraph style
4848
*
4949
* @var \PhpOffice\PhpWord\Style\Paragraph
5050
*/
51-
private $paragraphStyle;
51+
protected $paragraphStyle;
5252

5353
public function __construct(AbstractWriter $parentWriter, Element $element, $withoutP = false)
5454
{

src/PhpWord/Writer/RTF/Element/Table.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ public function write()
5858
$content .= $this->writeRow($rows[$i]);
5959
$content .= '\row' . PHP_EOL;
6060
}
61+
$content .= '\pard' . PHP_EOL;
6162
}
6263

6364
return $content;

src/PhpWord/Writer/RTF/Element/TextRun.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class TextRun extends AbstractElement
3232
public function write()
3333
{
3434
$writer = new Container($this->parentWriter, $this->element);
35+
$this->getStyles();
3536

3637
$content = '';
3738
$content .= $this->writeOpening();

src/PhpWord/Writer/RTF/Element/Title.php

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,71 @@
2424
*/
2525
class Title extends Text
2626
{
27+
protected function getStyles()
28+
{
29+
/** @var \PhpOffice\PhpWord\Element\Text $element Type hint */
30+
$element = $this->element;
31+
$style = $element->getStyle();
32+
if (is_string($style)) {
33+
$style = str_replace('Heading', 'Heading_', $style);
34+
$style = \PhpOffice\PhpWord\Style::getStyle($style);
35+
if ($style instanceof \PhpOffice\PhpWord\Style\Font) {
36+
$this->fontStyle = $style;
37+
$pstyle = $style->getParagraph();
38+
if ($pstyle instanceof \PhpOffice\PhpWord\Style\Paragraph && $pstyle->hasPageBreakBefore()) {
39+
$sect = $element->getParent();
40+
if ($sect instanceof \PhpOffice\PhpWord\Element\Section) {
41+
$elems = $sect->getElements();
42+
if ($elems[0] === $element) {
43+
$pstyle = clone $pstyle;
44+
$pstyle->setPageBreakBefore(false);
45+
}
46+
}
47+
}
48+
$this->paragraphStyle = $pstyle;
49+
}
50+
}
51+
}
52+
53+
/**
54+
* Write element
55+
*
56+
* @return string
57+
*/
58+
public function write()
59+
{
60+
/** @var \PhpOffice\PhpWord\Element\Text $element Type hint */
61+
$element = $this->element;
62+
$elementClass = str_replace('\\Writer\\RTF', '', get_class($this));
63+
if (!$element instanceof $elementClass || !is_string($element->getText())) {
64+
return '';
65+
}
66+
67+
$this->getStyles();
68+
69+
$content = '';
70+
71+
$content .= $this->writeOpening();
72+
$endout = '';
73+
$style = $element->getStyle();
74+
if (is_string($style)) {
75+
$style = str_replace('Heading', '', $style);
76+
if (is_numeric($style)) {
77+
$style = (int) $style - 1;
78+
if ($style >= 0 && $style <= 8) {
79+
$content .= '{\\outlinelevel' . $style;
80+
$endout = '}';
81+
}
82+
}
83+
}
84+
85+
$content .= '{';
86+
$content .= $this->writeFontStyle();
87+
$content .= $this->writeText($element->getText());
88+
$content .= '}';
89+
$content .= $this->writeClosing();
90+
$content .= $endout;
91+
92+
return $content;
93+
}
2794
}

src/PhpWord/Writer/RTF/Part/Document.php

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
namespace PhpOffice\PhpWord\Writer\RTF\Part;
1919

20+
use PhpOffice\PhpWord\Element\Footer;
2021
use PhpOffice\PhpWord\Settings;
2122
use PhpOffice\PhpWord\Writer\RTF\Element\Container;
2223
use PhpOffice\PhpWord\Writer\RTF\Style\Section as SectionStyleWriter;
@@ -105,11 +106,36 @@ private function writeFormatting()
105106
$content .= '\lang' . $langId;
106107
$content .= '\kerning1'; // Point size (in half-points) above which to kern character pairs
107108
$content .= '\fs' . (Settings::getDefaultFontSize() * 2); // Set the font size in half-points
109+
if ($docSettings->hasEvenAndOddHeaders()) {
110+
$content .= '\\facingp';
111+
}
108112
$content .= PHP_EOL;
109113

110114
return $content;
111115
}
112116

117+
/**
118+
* Write titlepg directive if any "f" headers or footers
119+
*
120+
* @param \PhpOffice\PhpWord\PhpWord\Element\Section $section
121+
* @return string
122+
*/
123+
private static function writeTitlepg($section)
124+
{
125+
foreach ($section->getHeaders() as $header) {
126+
if ($header->getType() === Footer::FIRST) {
127+
return '\\titlepg' . PHP_EOL;
128+
}
129+
}
130+
foreach ($section->getFooters() as $header) {
131+
if ($header->getType() === Footer::FIRST) {
132+
return '\\titlepg' . PHP_EOL;
133+
}
134+
}
135+
136+
return '';
137+
}
138+
113139
/**
114140
* Write sections
115141
*
@@ -120,10 +146,53 @@ private function writeSections()
120146
$content = '';
121147

122148
$sections = $this->getParentWriter()->getPhpWord()->getSections();
149+
$evenOdd = $this->getParentWriter()->getPhpWord()->getSettings()->hasEvenAndOddHeaders();
123150
foreach ($sections as $section) {
124151
$styleWriter = new SectionStyleWriter($section->getStyle());
125152
$styleWriter->setParentWriter($this->getParentWriter());
126153
$content .= $styleWriter->write();
154+
$content .= self::writeTitlepg($section);
155+
156+
foreach ($section->getHeaders() as $header) {
157+
$type = $header->getType();
158+
if ($evenOdd || $type !== FOOTER::EVEN) {
159+
$content .= '{\\header';
160+
if ($type === Footer::FIRST) {
161+
$content .= 'f';
162+
} elseif ($evenOdd) {
163+
$content .= ($type === FOOTER::EVEN) ? 'l' : 'r';
164+
}
165+
foreach ($header->getElements() as $element) {
166+
$cl = get_class($element);
167+
$cl2 = str_replace('Element', 'Writer\\RTF\\Element', $cl);
168+
if (class_exists($cl2)) {
169+
$elementWriter = new $cl2($this->getParentWriter(), $element);
170+
$content .= $elementWriter->write();
171+
}
172+
}
173+
$content .= '}' . PHP_EOL;
174+
}
175+
}
176+
foreach ($section->getFooters() as $footer) {
177+
$type = $footer->getType();
178+
if ($evenOdd || $type !== FOOTER::EVEN) {
179+
$content .= '{\\footer';
180+
if ($type === Footer::FIRST) {
181+
$content .= 'f';
182+
} elseif ($evenOdd) {
183+
$content .= ($type === FOOTER::EVEN) ? 'l' : 'r';
184+
}
185+
foreach ($footer->getElements() as $element) {
186+
$cl = get_class($element);
187+
$cl2 = str_replace('Element', 'Writer\\RTF\\Element', $cl);
188+
if (class_exists($cl2)) {
189+
$elementWriter = new $cl2($this->getParentWriter(), $element);
190+
$content .= $elementWriter->write();
191+
}
192+
}
193+
$content .= '}' . PHP_EOL;
194+
}
195+
}
127196

128197
$elementWriter = new Container($this->getParentWriter(), $section);
129198
$content .= $elementWriter->write();

src/PhpWord/Writer/RTF/Style/Font.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public function write()
4949
}
5050

5151
$content = '';
52+
$content .= $this->getValueIf($style->isRTL(), '\rtlch');
5253
$content .= '\cf' . $this->colorIndex;
5354
$content .= '\f' . $this->nameIndex;
5455

src/PhpWord/Writer/RTF/Style/Paragraph.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ public function write()
5252
Jc::END => '\qr',
5353
Jc::CENTER => '\qc',
5454
Jc::BOTH => '\qj',
55+
Jc::LEFT => '\ql',
56+
Jc::RIGHT => '\qr',
5557
);
5658

5759
$spaceAfter = $style->getSpaceAfter();
@@ -67,6 +69,14 @@ public function write()
6769
$content .= $this->writeIndentation($style->getIndentation());
6870
$content .= $this->getValueIf($spaceBefore !== null, '\sb' . round($spaceBefore));
6971
$content .= $this->getValueIf($spaceAfter !== null, '\sa' . round($spaceAfter));
72+
$lineHeight = $style->getLineHeight();
73+
if ($lineHeight !== null) {
74+
$lineHeightAdjusted = (int) ($lineHeight * 240);
75+
$content .= "\\sl$lineHeightAdjusted\\slmult1";
76+
}
77+
if ($style->getPageBreakBefore()) {
78+
$content .= '\\page';
79+
}
7080

7181
$styles = $style->getStyleValues();
7282
$content .= $this->writeTabs($styles['tabs']);

src/PhpWord/Writer/RTF/Style/Section.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public function write()
5353
$content .= $this->getValueIf($style->getHeaderHeight() !== null, '\headery' . round($style->getHeaderHeight()));
5454
$content .= $this->getValueIf($style->getFooterHeight() !== null, '\footery' . round($style->getFooterHeight()));
5555
$content .= $this->getValueIf($style->getGutter() !== null, '\guttersxn' . round($style->getGutter()));
56+
$content .= $this->getValueIf($style->getPageNumberingStart() !== null, '\pgnstarts' . $style->getPageNumberingStart() . '\pgnrestart');
5657
$content .= ' ';
5758

5859
// Borders

tests/PhpWord/Shared/ConverterTest.php

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -108,19 +108,13 @@ public function testUnitConversions()
108108
*/
109109
public function testHtmlToRGB()
110110
{
111-
// Prepare test values [ original, expected ]
112-
$values = array();
113-
$values[] = array('#FF99DD', array(255, 153, 221)); // With #
114-
$values[] = array('FF99DD', array(255, 153, 221)); // 6 characters
115-
$values[] = array('F9D', array(255, 153, 221)); // 3 characters
116-
$values[] = array('0F9D', false); // 4 characters
117-
$values[] = array(\PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKMAGENTA, array(139, 0, 139));
118-
$values[] = array('unknow', array(0, 0, 0)); // 6 characters, invalid
119-
// Conduct test
120-
foreach ($values as $value) {
121-
$result = Converter::htmlToRgb($value[0]);
122-
$this->assertEquals($value[1], $result);
123-
}
111+
$flse = false;
112+
$this->assertEquals(array(255, 153, 221), Converter::htmlToRgb('#FF99DD')); // With #
113+
$this->assertEquals(array(224, 170, 29), Converter::htmlToRgb('E0AA1D')); // 6 characters
114+
$this->assertEquals(array(102, 119, 136), Converter::htmlToRgb('678')); // 3 characters
115+
$this->assertEquals($flse, Converter::htmlToRgb('0F9D')); // 4 characters
116+
$this->assertEquals(array(0, 0, 0), Converter::htmlToRgb('unknow')); // 6 characters, invalid
117+
$this->assertEquals(array(139, 0, 139), Converter::htmlToRgb(\PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKMAGENTA)); // Constant
124118
}
125119

126120
/**

0 commit comments

Comments
 (0)