diff --git a/docs/changes/1.x/1.5.0.md b/docs/changes/1.x/1.5.0.md index b96865bada..c5ede40ead 100644 --- a/docs/changes/1.x/1.5.0.md +++ b/docs/changes/1.x/1.5.0.md @@ -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 @@ -16,4 +17,4 @@ ### BC Breaks -### Notes \ No newline at end of file +### Notes diff --git a/src/PhpWord/Writer/RTF/Style/Paragraph.php b/src/PhpWord/Writer/RTF/Style/Paragraph.php index 040c60b5aa..92711097b8 100644 --- a/src/PhpWord/Writer/RTF/Style/Paragraph.php +++ b/src/PhpWord/Writer/RTF/Style/Paragraph.php @@ -19,6 +19,7 @@ namespace PhpOffice\PhpWord\Writer\RTF\Style; use PhpOffice\PhpWord\SimpleType\Jc; +use PhpOffice\PhpWord\SimpleType\LineSpacingRule; /** * RTF paragraph style writer. @@ -36,10 +37,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. * @@ -57,18 +54,33 @@ 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', + ]; + $spacingRules = [ + LineSpacingRule::AUTO => '\slmult1', + LineSpacingRule::EXACT => '\slmult0', + LineSpacingRule::AT_LEAST => '\slmult0', ]; $spaceAfter = $style->getSpaceAfter(); @@ -76,7 +88,7 @@ public function write() $content = ''; if ($this->nestedLevel == 0) { - $content .= '\pard\nowidctlpar '; + $content .= '\pard'; } $alignment = $style->getAlignment(); $bidi = $style->isBidi(); @@ -86,22 +98,36 @@ public function write() if (isset($alignments[$alignment])) { $content .= $bidi ? $bidiAlignments[$alignment] : $alignments[$alignment]; } + $content .= $this->getValueIf($style->isBidi(), '\rtlpar'); $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"; + $content .= "\\sl$lineHeightAdjusted"; + } else { + $content .= $this->getValueIf($style->getSpacing() !== null, '\sl' . round($style->getSpacing() ?? 0)); } - if ($style->hasPageBreakBefore()) { - $content .= '\\page'; + $spacingRule = $style->getSpacingLineRule(); + if (isset($spacingRules[$spacingRule])) { + $content .= $this->getValueIf($style->getSpacing() !== null, $spacingRules[$spacingRule]); } + $content .= $this->getValueIf($style->hasContextualSpacing(), '\contextualspace'); + + // 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'); + $content .= $this->getValueIf($style->hasSuppressAutoHyphens(), '\hyphpar0'); + $styles = $style->getStyleValues(); $content .= $this->writeTabs($styles['tabs']); - return $content; + return $content . ' '; } /** diff --git a/tests/PhpWordTests/Escaper/RtfEscaper2Test.php b/tests/PhpWordTests/Escaper/RtfEscaper2Test.php index d65b543f21..79e564ecc8 100644 --- a/tests/PhpWordTests/Escaper/RtfEscaper2Test.php +++ b/tests/PhpWordTests/Escaper/RtfEscaper2Test.php @@ -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) diff --git a/tests/PhpWordTests/Escaper/RtfEscaper3Test.php b/tests/PhpWordTests/Escaper/RtfEscaper3Test.php index 1aebea52f0..1460db0e0f 100644 --- a/tests/PhpWordTests/Escaper/RtfEscaper3Test.php +++ b/tests/PhpWordTests/Escaper/RtfEscaper3Test.php @@ -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 diff --git a/tests/PhpWordTests/Writer/RTF/Element/TableTest.php b/tests/PhpWordTests/Writer/RTF/Element/TableTest.php index 7c1ceac68e..df8c0e6258 100644 --- a/tests/PhpWordTests/Writer/RTF/Element/TableTest.php +++ b/tests/PhpWordTests/Writer/RTF/Element/TableTest.php @@ -59,7 +59,7 @@ 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', @@ -67,7 +67,7 @@ public function testTable(): void '\\row', "\\trowd \\cellx$width \\cellx$width2 ", '\\intbl', - '\\ql{\\cf0\\f0 3}\\par', + '\\ql\\widctlpar {\\cf0\\f0 3}\\par', '\\cell', '\\intbl', '{\\cf0\\f0 4}\par', @@ -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', @@ -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', @@ -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', diff --git a/tests/PhpWordTests/Writer/RTF/Element2Test.php b/tests/PhpWordTests/Writer/RTF/Element2Test.php index 2220d93b68..ed33b92e21 100644 --- a/tests/PhpWordTests/Writer/RTF/Element2Test.php +++ b/tests/PhpWordTests/Writer/RTF/Element2Test.php @@ -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)); } @@ -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)); } @@ -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); } diff --git a/tests/PhpWordTests/Writer/RTF/ElementTest.php b/tests/PhpWordTests/Writer/RTF/ElementTest.php index 36504a18f8..1c2de17ca7 100644 --- a/tests/PhpWordTests/Writer/RTF/ElementTest.php +++ b/tests/PhpWordTests/Writer/RTF/ElementTest.php @@ -121,7 +121,7 @@ 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', @@ -129,7 +129,7 @@ public function testTable(): void '\\row', "\\trowd \\cellx$width \\cellx$width2 ", '\\intbl', - '{\\cf0\\f0 3}\\par', + '\\widctlpar {\\cf0\\f0 3}\\par', '\\cell', '\\intbl', '{\\cf0\\f0 4}\par', @@ -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)); } @@ -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)); } @@ -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)); } @@ -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)); } @@ -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)); } } diff --git a/tests/PhpWordTests/Writer/RTF/Style/ParagraphTest.php b/tests/PhpWordTests/Writer/RTF/Style/ParagraphTest.php new file mode 100644 index 0000000000..007a6d8373 --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Style/ParagraphTest.php @@ -0,0 +1,175 @@ +write()); + } + + /** + * Test alignment. + * See page 79 of RTF Specification 1.9.1 for Alignment. + * See page 81 of RTF Specification 1.9.1 for Bidirectional Controls. + */ + public function testParagraphAlign(): void + { + $parentWriter = new RTF(); + $style = new ParagraphStyle(); + $writer = new ParagraphWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setAlignment(Jc::START); + $expect = '\pard\ql\widctlpar '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setAlignment(Jc::CENTER); + $expect = '\pard\qc\widctlpar '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setAlignment(Jc::END); + $expect = '\pard\qr\widctlpar '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setAlignment(Jc::BOTH); + $expect = '\pard\qj\widctlpar '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setAlignment(Jc::DISTRIBUTE); + $expect = '\pard\qd\widctlpar '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setAlignment(Jc::THAI_DISTRIBUTE); + $expect = '\pard\qt\widctlpar '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setAlignment(Jc::HIGH_KASHIDA); + $expect = '\pard\qk20\widctlpar '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setAlignment(Jc::MEDIUM_KASHIDA); + $expect = '\pard\qk10\widctlpar '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setAlignment(Jc::LOW_KASHIDA); + $expect = '\pard\qk0\widctlpar '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setAlignment(Jc::START); + $style->setBidi(true); + $expect = '\pard\qr\rtlpar\widctlpar '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setAlignment(Jc::END); + $style->setBidi(true); + $expect = '\pard\ql\rtlpar\widctlpar '; + self::assertEquals($expect, $this->removeCr($writer)); + } + + /** + * Test indentation. + * See PHPOFfice\Tests\Writer\RTF\Style\IndentationTest. + */ + + /** + * Test formatting. + * See page 78 of RTF Specification 1.9.1 for Formatting. + */ + public function testParagraphFormatting(): void + { + $parentWriter = new RTF(); + $style = new ParagraphStyle(); + $writer = new ParagraphWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setSuppressAutoHyphens(true); + $style->setKeepLines(true); + $style->setKeepNext(true); + $style->setWidowControl(true); + $style->setPageBreakBefore(true); + $expect = '\pard\widctlpar\keepn\keep\pagebb\hyphpar0 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setSuppressAutoHyphens(false); + $style->setKeepLines(false); + $style->setKeepNext(false); + $style->setWidowControl(false); + $style->setPageBreakBefore(false); + $expect = '\pard\nowidctlpar '; + self::assertEquals($expect, $this->removeCr($writer)); + } + + /** + * Test spacing. + * See page 80 of RTF Specification 1.9.1 for Spacing. + */ + public function testParagraphSpacing(): void + { + $parentWriter = new RTF(); + $style = new ParagraphStyle(); + $writer = new ParagraphWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setSpaceBefore(240); + $style->setSpaceAfter(120); + $style->setLineHeight(1.5); + $style->setContextualSpacing(false); + $expect = '\pard\sb240\sa120\sl360\slmult1\widctlpar '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style = new ParagraphStyle(); + $writer = new ParagraphWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setSpaceBefore(480); + $style->setSpaceAfter(360); + $style->setSpacing(30); + $style->setSpacingLineRule(LineSpacingRule::EXACT); + $style->setContextualSpacing(true); + $expect = '\pard\sb480\sa360\sl30\slmult0\contextualspace\widctlpar '; + self::assertEquals($expect, $this->removeCr($writer)); + } + + /** + * Test tabs. + * See PHPOFfice\Tests\Writer\RTF\Style\TabTest. + */ + + /** + * Not Done: basedOn, next, numLevel, numStyle, shading, textAlignment. + */ +} diff --git a/tests/PhpWordTests/Writer/RTF/StyleTest.php b/tests/PhpWordTests/Writer/RTF/StyleTest.php index 8ba2bcb9c9..bd0e452476 100644 --- a/tests/PhpWordTests/Writer/RTF/StyleTest.php +++ b/tests/PhpWordTests/Writer/RTF/StyleTest.php @@ -126,7 +126,7 @@ public function testRTL(): void $parentWriter = new RTF(); $element = new \PhpOffice\PhpWord\Element\Text('אב גד', ['RTL' => true]); $text = new RTF\Element\Text($parentWriter, $element); - $expect = "\\pard\\nowidctlpar {\\rtlch\\cf0\\f0 \\uc0{\\u1488}\\uc0{\\u1489} \\uc0{\\u1490}\\uc0{\\u1491}}\\par\n"; + $expect = "\\pard\\widctlpar {\\rtlch\\cf0\\f0 \\uc0{\\u1488}\\uc0{\\u1489} \\uc0{\\u1490}\\uc0{\\u1491}}\\par\n"; self::assertEquals($expect, $this->removeCr($text)); } @@ -136,7 +136,7 @@ public function testRTL2(): void $parentWriter = new RTF(); $element = new \PhpOffice\PhpWord\Element\Text('אב גד'); $text = new RTF\Element\Text($parentWriter, $element); - $expect = "\\pard\\nowidctlpar \\qr{\\rtlch\\cf0\\f0 \\uc0{\\u1488}\\uc0{\\u1489} \\uc0{\\u1490}\\uc0{\\u1491}}\\par\n"; + $expect = "\\pard\\qr\\rtlpar\\widctlpar {\\rtlch\\cf0\\f0 \\uc0{\\u1488}\\uc0{\\u1489} \\uc0{\\u1490}\\uc0{\\u1491}}\\par\n"; self::assertEquals($expect, $this->removeCr($text)); } @@ -145,7 +145,7 @@ public function testPageBreakLineHeight(): void $parentWriter = new RTF(); $element = new \PhpOffice\PhpWord\Element\Text('New page', null, ['lineHeight' => 1.08, 'pageBreakBefore' => true]); $text = new RTF\Element\Text($parentWriter, $element); - $expect = "\\pard\\nowidctlpar \\sl259\\slmult1\\page{\\cf0\\f0 New page}\\par\n"; + $expect = "\\pard\\sl259\\slmult1\\widctlpar\\pagebb {\\cf0\\f0 New page}\\par\n"; self::assertEquals($expect, $this->removeCr($text)); } @@ -155,7 +155,7 @@ public function testPageBreakLineHeight2(): void $parentWriter = new RTF(); $element = new \PhpOffice\PhpWord\Element\Text('New page', null, ['lineHeight' => 1.08, 'pageBreakBefore' => true]); $text = new RTF\Element\Text($parentWriter, $element); - $expect = "\\pard\\nowidctlpar \\ql\\sl259\\slmult1\\page{\\cf0\\f0 New page}\\par\n"; + $expect = "\\pard\\ql\\sl259\\slmult1\\widctlpar\\pagebb {\\cf0\\f0 New page}\\par\n"; self::assertEquals($expect, $this->removeCr($text)); }