Skip to content

Commit 7d3e009

Browse files
committed
Rasamassen PR's 2824 and 2826
I made RTF usable some time ago. @rasamassen has recently done a lot of useful work to cover many missing areas. Since who knows when those changes will be merged, I intend to incorporate much of that work. I will do this with several pushes, each based on one or more of those changes. This one is based on PR PHPOffice#2824 (fixes issue PHPOffice#344), and PR PHPOffice#2826. Widow-orphan control may be affected by this change; based on comments in 2824, I have added a document-level widow-control used only by Rtf, but do not, as yet, use that value to do anything other than read/write an appropriate instruction in the file header.
1 parent ba408a8 commit 7d3e009

23 files changed

+475
-116
lines changed

phpstan-baseline.neon

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -105,26 +105,6 @@ parameters:
105105
count: 1
106106
path: src/PhpWord/Reader/ODText/Content.php
107107

108-
-
109-
message: "#^Property PhpOffice\\\\PhpWord\\\\Reader\\\\RTF\\\\Document\\:\\:\\$rtf \\(string\\) does not accept string\\|false\\.$#"
110-
count: 1
111-
path: src/PhpWord/Reader/RTF.php
112-
113-
-
114-
message: "#^Cannot call method setStyleByArray\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\.$#"
115-
count: 1
116-
path: src/PhpWord/Reader/RTF/Document.php
117-
118-
-
119-
message: "#^Property PhpOffice\\\\PhpWord\\\\Reader\\\\RTF\\\\Document\\:\\:\\$phpWord is never read, only written\\.$#"
120-
count: 1
121-
path: src/PhpWord/Reader/RTF/Document.php
122-
123-
-
124-
message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\ReaderInterface\\:\\:load\\(\\) has no return type specified\\.$#"
125-
count: 1
126-
path: src/PhpWord/Reader/ReaderInterface.php
127-
128108
-
129109
message: "#^Binary operation \"/\" between string\\|null and 2 results in an error\\.$#"
130110
count: 1

phpstan-baseline.php73.neon

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -95,26 +95,6 @@ parameters:
9595
count: 1
9696
path: src/PhpWord/Reader/ODText/Content.php
9797

98-
-
99-
message: "#^Property PhpOffice\\\\PhpWord\\\\Reader\\\\RTF\\\\Document\\:\\:\\$rtf \\(string\\) does not accept string\\|false\\.$#"
100-
count: 1
101-
path: src/PhpWord/Reader/RTF.php
102-
103-
-
104-
message: "#^Cannot call method setStyleByArray\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\.$#"
105-
count: 1
106-
path: src/PhpWord/Reader/RTF/Document.php
107-
108-
-
109-
message: "#^Property PhpOffice\\\\PhpWord\\\\Reader\\\\RTF\\\\Document\\:\\:\\$phpWord is never read, only written\\.$#"
110-
count: 1
111-
path: src/PhpWord/Reader/RTF/Document.php
112-
113-
-
114-
message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\ReaderInterface\\:\\:load\\(\\) has no return type specified\\.$#"
115-
count: 1
116-
path: src/PhpWord/Reader/ReaderInterface.php
117-
11898
-
11999
message: "#^Binary operation \"/\" between string\\|null and 2 results in an error\\.$#"
120100
count: 1

src/PhpWord/Metadata/Settings.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,14 @@ class Settings
168168
*/
169169
private $bookFoldPrinting = false;
170170

171+
/**
172+
* RTF, unlike other formats, sets widow/orphan control to off by default.
173+
* This setting allows changing that default for a whole document.
174+
*
175+
* @var bool
176+
*/
177+
private $rtfWidowControl = false;
178+
171179
/**
172180
* @return Protection
173181
*/
@@ -497,4 +505,16 @@ public function setBookFoldPrinting(bool $bookFoldPrinting): self
497505

498506
return $this;
499507
}
508+
509+
public function hasRtfWidowControl(): bool
510+
{
511+
return $this->rtfWidowControl;
512+
}
513+
514+
public function setRtfWidowControl(bool $rtfWidowControl): self
515+
{
516+
$this->rtfWidowControl = $rtfWidowControl;
517+
518+
return $this;
519+
}
500520
}

src/PhpWord/Reader/RTF.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public function load($docFile)
4242

4343
if ($this->canRead($docFile)) {
4444
$doc = new Document();
45-
$doc->rtf = file_get_contents($docFile);
45+
$doc->rtf = (string) file_get_contents($docFile);
4646
$doc->read($phpWord);
4747
} else {
4848
throw new Exception("Cannot read {$docFile}.");

src/PhpWord/Reader/RTF/Document.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,8 @@ private function parseControl($control, $parameter): void
344344
array_shift($directives); // remove the function variable; we won't need it
345345
$this->$function($directives);
346346
}
347+
} elseif ($control === 'widowctrl') {
348+
$this->phpWord->getSettings()->setRtfWidowControl(true);
347349
}
348350
}
349351

@@ -389,7 +391,9 @@ private function readText(): void
389391
{
390392
$text = $this->textrun->addText($this->text);
391393
if (isset($this->flags['styles']['font'])) {
392-
$text->getFontStyle()->setStyleByArray($this->flags['styles']['font']);
394+
/** @var \PhpOffice\PhpWord\Style\Font */
395+
$temp = $text->getFontStyle();
396+
$temp->setStyleByArray($this->flags['styles']['font']);
393397
}
394398
}
395399
}

src/PhpWord/Reader/ReaderInterface.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
namespace PhpOffice\PhpWord\Reader;
2020

21+
use PhpOffice\PhpWord\PhpWord;
22+
2123
/**
2224
* Reader interface.
2325
*
@@ -38,6 +40,8 @@ public function canRead($filename);
3840
* Loads PhpWord from file.
3941
*
4042
* @param string $filename
43+
*
44+
* @return PhpWord
4145
*/
4246
public function load($filename);
4347
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
/**
4+
* This file is part of PHPWord - A pure PHP library for reading and writing
5+
* word processing documents.
6+
*
7+
* PHPWord is free software distributed under the terms of the GNU Lesser
8+
* General Public License version 3 as published by the Free Software Foundation.
9+
*
10+
* For the full copyright and license information, please read the LICENSE
11+
* file that was distributed with this source code. For the full list of
12+
* contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
13+
*
14+
* @see https://github.com/PHPOffice/PHPWord
15+
*
16+
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
17+
*/
18+
19+
namespace PhpOffice\PhpWord\Writer\RTF\Element;
20+
21+
/**
22+
* Text element RTF writer.
23+
*
24+
* @since 0.10.0
25+
*/
26+
class PreserveText extends AbstractElement
27+
{
28+
/**
29+
* Write element.
30+
*
31+
* @return string
32+
*/
33+
public function write()
34+
{
35+
/** @var \PhpOffice\PhpWord\Element\PreserveText $element Type hint */
36+
$element = $this->element;
37+
if (!$element instanceof \PhpOffice\PhpWord\Element\PreserveText) {
38+
return '';
39+
}
40+
41+
$this->getStyles();
42+
43+
$content = '';
44+
$content .= $this->writeOpening();
45+
$content .= '{';
46+
$content .= $this->writeFontStyle();
47+
if (is_array($element->getText())) {
48+
foreach ($element->getText() as $text) {
49+
if (preg_match('/[{}]/', $text) == 1) {
50+
$text = str_replace(['{', '}'], '', $text);
51+
$content .= '{\field {\*\fldinst {' . $text . '}}{\\fldrslt {}}}';
52+
} else {
53+
$content .= $this->writeText($text);
54+
}
55+
}
56+
} else {
57+
$content .= $this->writeText($element->getText());
58+
}
59+
$content .= '}';
60+
$content .= $this->writeClosing();
61+
62+
return $content;
63+
}
64+
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,9 @@ private function writeFormatting()
110110
$content .= '\viewkind1'; // Set the view mode of the document
111111

112112
$content .= '\uc1'; // Set the numberof bytes that follows a unicode character
113-
$content .= '\pard'; // Resets to default paragraph properties.
114-
$content .= '\nowidctlpar'; // No widow/orphan control
113+
if ($docSettings->hasRtfWidowControl()) {
114+
$content .= '\widowctrl';
115+
}
115116
$content .= '\lang' . $langId;
116117
$content .= '\kerning1'; // Point size (in half-points) above which to kern character pairs
117118
$content .= '\fs' . (Settings::getDefaultFontSize() * 2); // Set the font size in half-points

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,27 @@ protected function getValueIf($condition, $value)
105105
{
106106
return $condition == true ? $value : '';
107107
}
108+
109+
/**
110+
* Write child style.
111+
*
112+
* @param string $name
113+
* @param mixed $style
114+
*
115+
* @return string
116+
*/
117+
protected function writeChildStyle($name, $style = null)
118+
{
119+
$stylePath = 'PhpOffice\\PhpWord\\Style\\' . ucfirst($name);
120+
if (isset($style) && $style instanceof $stylePath) {
121+
$writerPath = 'PhpOffice\\PhpWord\\Writer\\RTF\\Style\\' . ucfirst($name);
122+
$writer = new $writerPath($style);
123+
124+
if (method_exists($writer, 'write')) {
125+
return $writer->write();
126+
}
127+
}
128+
129+
return '';
130+
}
108131
}

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

Lines changed: 43 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,6 @@ class Paragraph extends AbstractStyle
3636
*/
3737
private $nestedLevel = 0;
3838

39-
private const LEFT = Jc::LEFT;
40-
private const RIGHT = Jc::RIGHT;
41-
private const JUSTIFY = Jc::JUSTIFY;
42-
4339
/**
4440
* Write style.
4541
*
@@ -57,69 +53,75 @@ public function write()
5753
Jc::END => '\qr',
5854
Jc::CENTER => '\qc',
5955
Jc::BOTH => '\qj',
60-
self::LEFT => '\ql',
61-
self::RIGHT => '\qr',
62-
self::JUSTIFY => '\qj',
56+
Jc::LEFT => '\ql',
57+
Jc::RIGHT => '\qr',
58+
Jc::JUSTIFY => '\qj',
59+
Jc::DISTRIBUTE => '\qd',
60+
Jc::THAI_DISTRIBUTE => '\qt',
61+
Jc::HIGH_KASHIDA => '\qk20',
62+
Jc::MEDIUM_KASHIDA => '\qk10',
63+
Jc::LOW_KASHIDA => '\qk0',
6364
];
6465
$bidiAlignments = [
6566
Jc::START => '\qr',
6667
Jc::END => '\ql',
6768
Jc::CENTER => '\qc',
6869
Jc::BOTH => '\qj',
69-
self::LEFT => '\ql',
70-
self::RIGHT => '\qr',
71-
self::JUSTIFY => '\qj',
70+
Jc::LEFT => '\ql',
71+
Jc::RIGHT => '\qr',
72+
Jc::JUSTIFY => '\qj',
73+
Jc::DISTRIBUTE => '\qd',
74+
Jc::THAI_DISTRIBUTE => '\qt',
75+
Jc::HIGH_KASHIDA => '\qk20',
76+
Jc::MEDIUM_KASHIDA => '\qk10',
77+
Jc::LOW_KASHIDA => '\qk0',
7278
];
7379

74-
$spaceAfter = $style->getSpaceAfter();
75-
$spaceBefore = $style->getSpaceBefore();
76-
7780
$content = '';
7881
if ($this->nestedLevel == 0) {
79-
$content .= '\pard\nowidctlpar ';
82+
$content .= '\pard';
8083
}
84+
85+
// Alignment
8186
$alignment = $style->getAlignment();
8287
$bidi = $style->isBidi();
8388
if ($alignment === '' && $bidi !== null) {
8489
$alignment = Jc::START;
8590
}
91+
92+
// Right to left
8693
if (isset($alignments[$alignment])) {
8794
$content .= $bidi ? $bidiAlignments[$alignment] : $alignments[$alignment];
8895
}
89-
$content .= $this->writeIndentation($style->getIndentation());
90-
$content .= $this->getValueIf($spaceBefore !== null, '\sb' . round($spaceBefore ?? 0));
91-
$content .= $this->getValueIf($spaceAfter !== null, '\sa' . round($spaceAfter ?? 0));
92-
$lineHeight = $style->getLineHeight();
93-
if ($lineHeight) {
94-
$lineHeightAdjusted = (int) ($lineHeight * 240);
95-
$content .= "\\sl$lineHeightAdjusted\\slmult1";
96-
}
97-
if ($style->hasPageBreakBefore()) {
98-
$content .= '\\page';
99-
}
96+
$content .= $this->getValueIf($style->isBidi(), '\rtlpar');
97+
98+
// Indentation
99+
$content .= $this->writeChildStyle('Indentation', $style->getIndentation());
100+
$content .= $this->writeChildStyle('Spacing', $style->getSpace());
101+
// Future: Add Shading
100102

101103
$styles = $style->getStyleValues();
102104
$content .= $this->writeTabs($styles['tabs']);
103105

104-
return $content;
105-
}
106+
// Contextual Spacing
107+
$content .= $this->getValueIf($style->hasContextualSpacing(), '\contextualspace');
108+
// Future: Add Text Alignment
106109

107-
/**
108-
* Writes an \PhpOffice\PhpWord\Style\Indentation.
109-
*
110-
* @param null|\PhpOffice\PhpWord\Style\Indentation $indent
111-
*
112-
* @return string
113-
*/
114-
private function writeIndentation($indent = null)
115-
{
116-
if (isset($indent) && $indent instanceof \PhpOffice\PhpWord\Style\Indentation) {
117-
$writer = new Indentation($indent);
110+
// Pagination
111+
$content .= $this->getValueIf($style->hasWidowControl(), '\widctlpar');
112+
$content .= $this->getValueIf(!$style->hasWidowControl(), '\nowidctlpar');
113+
$content .= $this->getValueIf($style->isKeepNext(), '\keepn');
114+
$content .= $this->getValueIf($style->isKeepLines(), '\keep');
115+
$content .= $this->getValueIf($style->hasPageBreakBefore(), '\pagebb');
118116

119-
return $writer->write();
120-
}
117+
// Hyphenation
118+
$content .= $this->getValueIf($style->hasSuppressAutoHyphens(), '\hyphpar0');
119+
120+
// Tabs
121+
$styles = $style->getStyleValues();
122+
$content .= $this->writeTabs($styles['tabs']);
121123

122-
return '';
124+
return $content . ' ';
123125
}
124126

125127
/**

0 commit comments

Comments
 (0)