Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
50ef633
Update Paragraph.php - Add Pagination
rasamassen Sep 9, 2025
5e9976e
Update Paragraph.php - Fix widow/orphan control
rasamassen Sep 9, 2025
0c66a65
Update 1.5.0.md - Changelog for Pull 2824
rasamassen Sep 9, 2025
e61dfd6
Update Paragraph.php - Just making the spacing look nice.
rasamassen Sep 9, 2025
33ba3bc
Update RtfEscaper2Test.php - Replace nowidctlpar with widctlpar - Wid…
rasamassen Sep 9, 2025
a547f04
Update RtfEscaper3Test.php - Replace nowidctlpar with widctlpar - Wid…
rasamassen Sep 9, 2025
6a4ba01
Update Element2Test.php - Replace nowidctlpar with widctlpar - Widow/…
rasamassen Sep 9, 2025
97ddc80
Update ElementTest.php - Replace nowidctlpar with widctlpar - Widow/O…
rasamassen Sep 9, 2025
bf7f10b
Update StyleTest.php - Replace nowidctlpar with widctlpar - Widow/Orp…
rasamassen Sep 9, 2025
097c050
Update RtfEscaper3Test.php - Fix Test
rasamassen Sep 9, 2025
fcf45f2
Update TableTest.php - Fix test
rasamassen Sep 9, 2025
7e30fd6
Update Element2Test.php - Fix Test
rasamassen Sep 9, 2025
63559b5
Update ElementTest.php - Fix Test
rasamassen Sep 9, 2025
1e333b6
Update StyleTest.php - Fix test
rasamassen Sep 9, 2025
b8aac48
Update TableTest.php - Fix test
rasamassen Sep 9, 2025
5c22beb
Update TableTest.php - Hopefully final Fix test
rasamassen Sep 9, 2025
d7045a2
Update TableTest.php - Fix test
rasamassen Sep 9, 2025
3d63e96
Update ElementTest.php - Fix tests
rasamassen Sep 9, 2025
06c7c85
Update Paragraph.php - Fix deprecated features
rasamassen Oct 3, 2025
66de874
Update Paragraph.php - Fix justification, add missing options.
rasamassen Oct 3, 2025
0c7f3b2
Create ParagraphTest.php - Initial build
rasamassen Oct 3, 2025
1a8897f
Update ParagraphTest.php
rasamassen Oct 3, 2025
953cc31
Update ParagraphTest.php
rasamassen Oct 3, 2025
6eb482a
Update ParagraphTest.php
rasamassen Oct 3, 2025
5aa7396
Update ParagraphTest.php - Add tests
rasamassen Oct 4, 2025
eecc70d
Update Paragraph.php - add missing features
rasamassen Oct 4, 2025
f43e923
Update ParagraphTest.php
rasamassen Oct 4, 2025
d238a7e
Update Paragraph.php - add spacing and spacingLineRule
rasamassen Oct 4, 2025
995692f
Update ParagraphTest.php
rasamassen Oct 4, 2025
0b08710
Update Paragraph.php
rasamassen Oct 4, 2025
7d4ce32
Update Paragraph.php
rasamassen Oct 4, 2025
ff94ce8
Update Paragraph.php
rasamassen Oct 4, 2025
9143d56
Update Paragraph.php
rasamassen Oct 4, 2025
fcba983
Update ParagraphTest.php
rasamassen Oct 4, 2025
cbb2cbd
Update StyleTest.php
rasamassen Oct 4, 2025
2c4c25d
Update RtfEscaper3Test.php
rasamassen Oct 4, 2025
0516d30
Update ParagraphTest.php
rasamassen Oct 4, 2025
b394c75
Update 1.5.0.md
rasamassen Oct 4, 2025
1c356ca
Update Paragraph.php
rasamassen Oct 10, 2025
6bf16a3
Update AbstractStyle.php - create writeChildStyle function
rasamassen Oct 10, 2025
07b4001
Update AbstractStyle.php
rasamassen Oct 14, 2025
61ec5ea
Update AbstractStyle.php
rasamassen Oct 14, 2025
6f91bc4
Update AbstractStyle.php
rasamassen Oct 14, 2025
0e180e2
Create Spacing.php
rasamassen Oct 14, 2025
38630c7
Update Paragraph.php
rasamassen Oct 14, 2025
7385414
Update AbstractStyle.php
rasamassen Oct 14, 2025
e1701e8
Update AbstractStyle.php
rasamassen Oct 14, 2025
acda641
Update Spacing.php
rasamassen Oct 14, 2025
02da372
Update Spacing.php
rasamassen Oct 14, 2025
9ac06cc
Update ParagraphTest.php
rasamassen Oct 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/changes/1.x/1.5.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
### Bug fixes

- Set writeAttribute return type by [@radarhere](https://github.com/radarhere) fixing [#2204](https://github.com/PHPOffice/PHPWord/issues/2204) in [#2776](https://github.com/PHPOffice/PHPWord/pull/2776)
- Writer RTF: Add multiple missing controls and fix existing controls in Paragraph by [@rasamassen](https://github.com/rasamassen) in [#2824](https://github.com/PHPOffice/PHPWord/pull/2824)

### Miscellaneous

Expand All @@ -16,4 +17,4 @@

### BC Breaks

### Notes
### Notes
23 changes: 23 additions & 0 deletions src/PhpWord/Writer/RTF/Style/AbstractStyle.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,27 @@ protected function getValueIf($condition, $value)
{
return $condition == true ? $value : '';
}

/**
* Write child style.
*
* @param string $name
* @param mixed $style
*
* @return string
*/
protected function writeChildStyle($name, $style = null)
{
$stylePath = 'PhpOffice\\PhpWord\\Style\\' . ucfirst($name);
if (isset($style) && $style instanceof $stylePath) {
$writerPath = 'PhpOffice\\PhpWord\\Writer\\RTF\\Style\\' . ucfirst($name);
$writer = new $writerPath($style);

if (method_exists($writer, 'write')) {
return $writer->write();
}
}

return '';
}
}
85 changes: 42 additions & 43 deletions src/PhpWord/Writer/RTF/Style/Paragraph.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,6 @@ class Paragraph extends AbstractStyle
*/
private $nestedLevel = 0;

private const LEFT = Jc::LEFT;
private const RIGHT = Jc::RIGHT;
private const JUSTIFY = Jc::JUSTIFY;

/**
* Write style.
*
Expand All @@ -57,69 +53,72 @@ public function write()
Jc::END => '\qr',
Jc::CENTER => '\qc',
Jc::BOTH => '\qj',
self::LEFT => '\ql',
self::RIGHT => '\qr',
self::JUSTIFY => '\qj',
Jc::LEFT => '\ql',
Jc::RIGHT => '\qr',
Jc::JUSTIFY => '\qj',
Jc::DISTRIBUTE => '\qd',
Jc::THAI_DISTRIBUTE => '\qt',
Jc::HIGH_KASHIDA => '\qk20',
Jc::MEDIUM_KASHIDA => '\qk10',
Jc::LOW_KASHIDA => '\qk0',
];
$bidiAlignments = [
Jc::START => '\qr',
Jc::END => '\ql',
Jc::CENTER => '\qc',
Jc::BOTH => '\qj',
self::LEFT => '\ql',
self::RIGHT => '\qr',
self::JUSTIFY => '\qj',
Jc::LEFT => '\ql',
Jc::RIGHT => '\qr',
Jc::JUSTIFY => '\qj',
Jc::DISTRIBUTE => '\qd',
Jc::THAI_DISTRIBUTE => '\qt',
Jc::HIGH_KASHIDA => '\qk20',
Jc::MEDIUM_KASHIDA => '\qk10',
Jc::LOW_KASHIDA => '\qk0',
];

$spaceAfter = $style->getSpaceAfter();
$spaceBefore = $style->getSpaceBefore();

$content = '';
if ($this->nestedLevel == 0) {
$content .= '\pard\nowidctlpar ';
$content .= '\pard';
}

// Alignment
$alignment = $style->getAlignment();
$bidi = $style->isBidi();
if ($alignment === '' && $bidi !== null) {
$alignment = Jc::START;
}

// Right to left
if (isset($alignments[$alignment])) {
$content .= $bidi ? $bidiAlignments[$alignment] : $alignments[$alignment];
}
$content .= $this->writeIndentation($style->getIndentation());
$content .= $this->getValueIf($spaceBefore !== null, '\sb' . round($spaceBefore ?? 0));
$content .= $this->getValueIf($spaceAfter !== null, '\sa' . round($spaceAfter ?? 0));
$lineHeight = $style->getLineHeight();
if ($lineHeight) {
$lineHeightAdjusted = (int) ($lineHeight * 240);
$content .= "\\sl$lineHeightAdjusted\\slmult1";
}
if ($style->hasPageBreakBefore()) {
$content .= '\\page';
}
$content .= $this->getValueIf($style->isBidi(), '\rtlpar');

$styles = $style->getStyleValues();
$content .= $this->writeTabs($styles['tabs']);
// Indentation - Future: Create writeChildStyle in AbstractStyle
$content .= $this->writeChildStyle('Indentation', $style->getIndentation());
$content .= $this->writeChildStyle('Spacing', $style->getSpace());
// Future: Add Shading

return $content;
}
// Contextual Spacing
$content .= $this->getValueIf($style->hasContextualSpacing(), '\contextualspace');
// Future: Add Text Alignment

/**
* Writes an \PhpOffice\PhpWord\Style\Indentation.
*
* @param null|\PhpOffice\PhpWord\Style\Indentation $indent
*
* @return string
*/
private function writeIndentation($indent = null)
{
if (isset($indent) && $indent instanceof \PhpOffice\PhpWord\Style\Indentation) {
$writer = new Indentation($indent);
// Pagination
$content .= $this->getValueIf($style->hasWidowControl(), '\widctlpar');
$content .= $this->getValueIf(!$style->hasWidowControl(), '\nowidctlpar');
$content .= $this->getValueIf($style->isKeepNext(), '\keepn');
$content .= $this->getValueIf($style->isKeepLines(), '\keep');
$content .= $this->getValueIf($style->hasPageBreakBefore(), '\pagebb');

return $writer->write();
}
// Hyphenation
$content .= $this->getValueIf($style->hasSuppressAutoHyphens(), '\hyphpar0');

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

return '';
return $content . ' ';
}

/**
Expand Down
72 changes: 72 additions & 0 deletions src/PhpWord/Writer/RTF/Style/Spacing.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?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
*
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
*/

namespace PhpOffice\PhpWord\Writer\RTF\Style;

use PhpOffice\PhpWord\SimpleType\LineSpacingRule;

/**
* RTF Spacing style writer.
*
* @since 0.11.0
*/
class Spacing extends AbstractStyle
{
/**
* Write style.
*
* @return string
*/
public function write()
{
$style = $this->getStyle();
if (!$style instanceof \PhpOffice\PhpWord\Style\Spacing) {
return '';
}

$spacingRules = [
LineSpacingRule::AUTO => '\slmult1',
LineSpacingRule::EXACT => '\slmult0',
LineSpacingRule::AT_LEAST => '\slmult0',
];

$content = '';

// Space Before and After
$content .= $this->getValueIf($style->getBefore() !== null, '\sb' . round($style->getBefore() ?? 0));
$content .= $this->getValueIf($style->getAfter() !== null, '\sa' . round($style->getAfter() ?? 0));

// Space Between Lines
$line = $style->getLine();
if (null !== $line && $style->getLineRule() === LineSpacingRule::AUTO) {
$line += \PhpOffice\PhpWord\Style\Paragraph::LINE_HEIGHT;
}
// Exact is specified by using negative numbers
if ($style->getLineRule() === LineSpacingRule::EXACT && $line > 0) {
$line = $line * -1;
}
$content .= $this->getValueIf($line !== null, '\sl' . round($line ?? 0));

// Spacing Multiple
if ($line !== null) {
$content .= $this->getValueIf(isset($spacingRules[$style->getLineRule()]), $spacingRules[$style->getLineRule()]);
}

return $content;
}
}
2 changes: 1 addition & 1 deletion tests/PhpWordTests/Escaper/RtfEscaper2Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
*/
class RtfEscaper2Test extends \PHPUnit\Framework\TestCase
{
const HEADER = '\\pard\\nowidctlpar {\\cf0\\f0 ';
const HEADER = '\\pard\\widctlpar {\\cf0\\f0 ';
const TRAILER = '}\\par';

public function escapestring($str)
Expand Down
4 changes: 2 additions & 2 deletions tests/PhpWordTests/Escaper/RtfEscaper3Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
*/
class RtfEscaper3Test extends \PHPUnit\Framework\TestCase
{
const HEADER = '\\pard\\nowidctlpar \ql{\\cf0\\f0 ';
const HEADER_RTL = '\\pard\\nowidctlpar \qr{\\rtlch\\cf0\\f0 ';
const HEADER = '\\pard\\ql\\widctlpar {\\cf0\\f0 ';
const HEADER_RTL = '\\pard\\qr\\rtlpar\\widctlpar {\\rtlch\\cf0\\f0 ';
const TRAILER = '}\\par';

protected function tearDown(): void
Expand Down
10 changes: 5 additions & 5 deletions tests/PhpWordTests/Writer/RTF/Element/TableTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,15 @@ public function testTable(): void
'\\pard',
"\\trowd \\cellx$width \\cellx$width2 ",
'\\intbl',
'\\ql{\\cf0\\f0 1}\\par',
'\\ql\\widctlpar {\\cf0\\f0 1}\\par',
'\\cell',
'\\intbl',
'{\\cf0\\f0 2}\\par',
'\\cell',
'\\row',
"\\trowd \\cellx$width \\cellx$width2 ",
'\\intbl',
'\\ql{\\cf0\\f0 3}\\par',
'\\ql\\widctlpar {\\cf0\\f0 3}\\par',
'\\cell',
'\\intbl',
'{\\cf0\\f0 4}\par',
Expand Down Expand Up @@ -102,7 +102,7 @@ public function testTableStyle(): void
'\\clbrdrr\\brdrs\\brdrw2\\brdrcf0',
"\\cellx$width ",
'\\intbl',
'\\ql{\\cf0\\f0 1}\\par',
'\\ql\\widctlpar {\\cf0\\f0 1}\\par',
'\\cell',
'\\row',
'\\pard',
Expand All @@ -128,7 +128,7 @@ public function testTableStyleNotExisting(): void
'\\pard',
"\\trowd \\cellx$width ",
'\\intbl',
'\\ql{\\cf0\\f0 1}\\par',
'\\ql\\widctlpar {\\cf0\\f0 1}\\par',
'\\cell',
'\\row',
'\\pard',
Expand Down Expand Up @@ -158,7 +158,7 @@ public function testTableCellStyle(): void
'\\clbrdrr\\brdrdot\\brdrw2\\brdrcf0',
"\\cellx$width ",
'\\intbl',
'\\ql{\\cf0\\f0 1}\\par',
'\\ql\\widctlpar {\\cf0\\f0 1}\\par',
'\\cell',
'\\row',
'\\pard',
Expand Down
6 changes: 3 additions & 3 deletions tests/PhpWordTests/Writer/RTF/Element2Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public function testTextRun(): void
$element->addText('Hello ');
$element->addText('there.');
$textrun = new WriterTextRun($parentWriter, $element);
$expect = "\\pard\\nowidctlpar \\ql{{\\cf0\\f0 Hello }{\\cf0\\f0 there.}}\\par\n";
$expect = "\\pard\\ql\\widctlpar {{\\cf0\\f0 Hello }{\\cf0\\f0 there.}}\\par\n";
self::assertEquals($expect, $this->removeCr($textrun));
}

Expand All @@ -59,7 +59,7 @@ public function testTextRunParagraphStyle(): void
$element->addText('Hello ');
$element->addText('there.');
$textrun = new WriterTextRun($parentWriter, $element);
$expect = "\\pard\\nowidctlpar \\ql\\sb0\\sa0{{\\cf0\\f0 Hello }{\\cf0\\f0 there.}}\\par\n";
$expect = "\\pard\\ql\\sb0\\sa0\\widctlpar {{\\cf0\\f0 Hello }{\\cf0\\f0 there.}}\\par\n";
self::assertEquals($expect, $this->removeCr($textrun));
}

Expand All @@ -72,7 +72,7 @@ public function testTitle(): void
$section = $phpWord->addSection();
$element = $section->addTitle('First Heading', 1);
$elwrite = new WriterTitle($parentWriter, $element);
$expect = "\\pard\\nowidctlpar \\ql\\sb0\\sa0{\\outlinelevel0{\\cf0\\f0 First Heading}\\par\n}";
$expect = "\\pard\\ql\\sb0\\sa0\\widctlpar {\\outlinelevel0{\\cf0\\f0 First Heading}\\par\n}";
self::assertEquals($expect, $this->removeCr($elwrite));
Settings::setDefaultRtl(null);
}
Expand Down
14 changes: 7 additions & 7 deletions tests/PhpWordTests/Writer/RTF/ElementTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,15 +121,15 @@ public function testTable(): void
'\\pard',
"\\trowd \\cellx$width \\cellx$width2 ",
'\\intbl',
'{\\cf0\\f0 1}\\par',
'\\widctlpar {\\cf0\\f0 1}\\par',
'\\cell',
'\\intbl',
'{\\cf0\\f0 2}\\par',
'\\cell',
'\\row',
"\\trowd \\cellx$width \\cellx$width2 ",
'\\intbl',
'{\\cf0\\f0 3}\\par',
'\\widctlpar {\\cf0\\f0 3}\\par',
'\\cell',
'\\intbl',
'{\\cf0\\f0 4}\par',
Expand All @@ -149,7 +149,7 @@ public function testTextRun(): void
$element->addText('Hello ');
$element->addText('there.');
$textrun = new RTF\Element\TextRun($parentWriter, $element);
$expect = "\\pard\\nowidctlpar {{\\cf0\\f0 Hello }{\\cf0\\f0 there.}}\\par\n";
$expect = "\\pard\\widctlpar {{\\cf0\\f0 Hello }{\\cf0\\f0 there.}}\\par\n";
self::assertEquals($expect, $this->removeCr($textrun));
}

Expand All @@ -160,7 +160,7 @@ public function testTextRunParagraphStyle(): void
$element->addText('Hello ');
$element->addText('there.');
$textrun = new RTF\Element\TextRun($parentWriter, $element);
$expect = "\\pard\\nowidctlpar \\sb0\\sa0{{\\cf0\\f0 Hello }{\\cf0\\f0 there.}}\\par\n";
$expect = "\\pard\\sb0\\sa0\\widctlpar {{\\cf0\\f0 Hello }{\\cf0\\f0 there.}}\\par\n";
self::assertEquals($expect, $this->removeCr($textrun));
}

Expand All @@ -172,7 +172,7 @@ public function testTitle(): void
$section = $phpWord->addSection();
$element = $section->addTitle('First Heading', 1);
$elwrite = new RTF\Element\Title($parentWriter, $element);
$expect = "\\pard\\nowidctlpar \\sb0\\sa0{\\outlinelevel0{\\cf0\\f0 First Heading}\\par\n}";
$expect = "\\pard\\sb0\\sa0\\widctlpar {\\outlinelevel0{\\cf0\\f0 First Heading}\\par\n}";
self::assertEquals($expect, $this->removeCr($elwrite));
}

Expand All @@ -188,7 +188,7 @@ public function testRuby(): void
$element->addRuby($baseTextRun, $rubyTextRun, $properties);

$textrun = new RTF\Element\TextRun($parentWriter, $element);
$expect = "\\pard\\nowidctlpar {{base text (ruby)}}\\par\n";
$expect = "\\pard\\widctlpar {{base text (ruby)}}\\par\n";
self::assertEquals($expect, $this->removeCr($textrun));
}

Expand All @@ -213,7 +213,7 @@ public function testRubyTitle(): void
$element = $section->addTitle($textRun, 1);
$elwrite = new RTF\Element\Title($parentWriter, $element);

$expect = "\\pard\\nowidctlpar \\sb0\\sa2{\\outlinelevel0{\\cf0\\f0\\fs48\\b base text (ruby)}\\par\n}";
$expect = "\\pard\\sb0\\sa2\\widctlpar {\\outlinelevel0{\\cf0\\f0\\fs48\\b base text (ruby)}\\par\n}";
self::assertEquals($expect, $this->removeCr($elwrite));
}
}
Loading
Loading