diff --git a/docs/changes/1.x/1.4.0.md b/docs/changes/1.x/1.4.0.md index 6fdef4e74f..711acf37e6 100644 --- a/docs/changes/1.x/1.4.0.md +++ b/docs/changes/1.x/1.4.0.md @@ -17,7 +17,8 @@ - Add basic ruby text (phonetic guide) support for Word2007 and HTML Reader/Writer, RTF Writer, basic support for ODT writing by [@Deadpikle](https://github.com/Deadpikle) in [#2727](https://github.com/PHPOffice/PHPWord/pull/2727) - Reader HTML: Support font styles for h1/h6 by [@Progi1984](https://github.com/Progi1984) fixing [#2619](https://github.com/PHPOffice/PHPWord/issues/2619) in [#2737](https://github.com/PHPOffice/PHPWord/pull/2737) - Writer EPub3: Basic support by [@Sambit003](https://github.com/Sambit003) fixing [#55](https://github.com/PHPOffice/PHPWord/issues/55) in [#2724](https://github.com/PHPOffice/PHPWord/pull/2724) -- Writer2007: Added support for background and border color transparency in Text Box element [@chudy20007](https://github.com/Chudy20007) in [#2555](https://github.com/PHPOffice/PHPWord/pull/2555) +- Writer Word2007: Added support for background and border color transparency in Text Box element by [@chudy20007](https://github.com/Chudy20007) in [#2555](https://github.com/PHPOffice/PHPWord/pull/2555) +- Writer/Reader Word2007: Added support for the firstLineChars for Indentation by [@liuzhilinux](https://github.com/liuzhilinux) & [@Progi1984](https://github.com/Progi1984) in [#2753](https://github.com/PHPOffice/PHPWord/pull/2753) ### Bug fixes diff --git a/docs/usage/styles/paragraph.md b/docs/usage/styles/paragraph.md index e9ca155005..f51d5ba030 100644 --- a/docs/usage/styles/paragraph.md +++ b/docs/usage/styles/paragraph.md @@ -7,7 +7,7 @@ Available Paragraph style options: - ``basedOn``. Parent style. - ``hanging``. Hanging indentation in *half inches*. - ``indent``. Indent (left indentation) in *half inches*. -- ``indentation``. An array of indentation key => value pairs in *twip*. Supports *left*, *right*, *firstLine* and *hanging* indentation. +- ``indentation``. An array of indentation key => value pairs in *twip*. Supports *left*, *right*, *firstLine*, *firstLineChars* and *hanging* indentation. See ``\PhpOffice\PhpWord\Style\Indentation`` for possible identation types. - ``keepLines``. Keep all lines on one page, *true* or *false*. - ``keepNext``. Keep paragraph with next paragraph, *true* or *false*. diff --git a/src/PhpWord/Reader/Word2007/AbstractPart.php b/src/PhpWord/Reader/Word2007/AbstractPart.php index 96d4a46f8a..9d49573d69 100644 --- a/src/PhpWord/Reader/Word2007/AbstractPart.php +++ b/src/PhpWord/Reader/Word2007/AbstractPart.php @@ -708,6 +708,7 @@ protected function readParagraphStyle(XMLReader $xmlReader, DOMElement $domNode) 'indentRight' => [self::READ_VALUE, 'w:ind', 'w:right'], 'indentHanging' => [self::READ_VALUE, 'w:ind', 'w:hanging'], 'indentFirstLine' => [self::READ_VALUE, 'w:ind', 'w:firstLine'], + 'indentFirstLineChars' => [self::READ_VALUE, 'w:ind', 'w:firstLineChars'], 'spaceAfter' => [self::READ_VALUE, 'w:spacing', 'w:after'], 'spaceBefore' => [self::READ_VALUE, 'w:spacing', 'w:before'], 'widowControl' => [self::READ_FALSE, 'w:widowControl'], diff --git a/src/PhpWord/Style/Indentation.php b/src/PhpWord/Style/Indentation.php index c2032dd22f..f2b646f7b6 100644 --- a/src/PhpWord/Style/Indentation.php +++ b/src/PhpWord/Style/Indentation.php @@ -47,6 +47,13 @@ class Indentation extends AbstractStyle */ private $firstLine = 0; + /** + * Additional first line chars indentation (twip). + * + * @var int + */ + private $firstLineChars = 0; + /** * Indentation removed from first line (twip). * @@ -118,6 +125,24 @@ public function setFirstLine(?float $value): self return $this; } + /** + * Get first line chars. + */ + public function getFirstLineChars(): int + { + return $this->firstLineChars; + } + + /** + * Set first line chars. + */ + public function setFirstLineChars(int $value): self + { + $this->firstLineChars = $this->setIntVal($value, $this->firstLineChars); + + return $this; + } + /** * Get hanging. */ diff --git a/src/PhpWord/Style/Paragraph.php b/src/PhpWord/Style/Paragraph.php index 5dda985fe5..5ab7ade673 100644 --- a/src/PhpWord/Style/Paragraph.php +++ b/src/PhpWord/Style/Paragraph.php @@ -432,6 +432,14 @@ public function setIndentFirstLine(?float $value = null): self return $this->setIndentation(['firstLine' => $value]); } + /** + * Set firstlineChars indentation. + */ + public function setIndentFirstLineChars(int $value = 0): self + { + return $this->setIndentation(['firstLineChars' => $value]); + } + /** * Set left indentation. */ diff --git a/src/PhpWord/Writer/Word2007/Style/Indentation.php b/src/PhpWord/Writer/Word2007/Style/Indentation.php index 85880d4c22..3588683cd9 100644 --- a/src/PhpWord/Writer/Word2007/Style/Indentation.php +++ b/src/PhpWord/Writer/Word2007/Style/Indentation.php @@ -44,6 +44,9 @@ public function write(): void $firstLine = $style->getFirstLine(); $xmlWriter->writeAttributeIf(null !== $firstLine, 'w:firstLine', $this->convertTwip($firstLine)); + $firstLineChars = $style->getFirstLineChars(); + $xmlWriter->writeAttributeIf(0 !== $firstLineChars, 'w:firstLineChars', $this->convertTwip($firstLineChars)); + $hanging = $style->getHanging(); $xmlWriter->writeAttributeIf(null !== $hanging, 'w:hanging', $this->convertTwip($hanging)); diff --git a/tests/PhpWordTests/Element/AbstractElementTest.php b/tests/PhpWordTests/Element/AbstractElementTest.php index 8219871f73..307225b8f4 100644 --- a/tests/PhpWordTests/Element/AbstractElementTest.php +++ b/tests/PhpWordTests/Element/AbstractElementTest.php @@ -30,7 +30,13 @@ class AbstractElementTest extends \PHPUnit\Framework\TestCase */ public function testElementIndex(): void { - $stub = $this->getMockForAbstractClass(AbstractElement::class); + if (method_exists($this, 'getMockForAbstractClass')) { + $stub = $this->getMockForAbstractClass(AbstractElement::class); + } else { + /** @var AbstractElement $stub */ + $stub = new class() extends AbstractElement { + }; + } $ival = mt_rand(0, 100); $stub->setElementIndex($ival); self::assertEquals($ival, $stub->getElementIndex()); @@ -41,7 +47,13 @@ public function testElementIndex(): void */ public function testElementId(): void { - $stub = $this->getMockForAbstractClass(AbstractElement::class); + if (method_exists($this, 'getMockForAbstractClass')) { + $stub = $this->getMockForAbstractClass(AbstractElement::class); + } else { + /** @var AbstractElement $stub */ + $stub = new class() extends AbstractElement { + }; + } $stub->setElementId(); self::assertEquals(6, strlen($stub->getElementId())); } diff --git a/tests/PhpWordTests/Reader/Word2007/StyleTest.php b/tests/PhpWordTests/Reader/Word2007/StyleTest.php index 347c0350f9..7f4afde476 100644 --- a/tests/PhpWordTests/Reader/Word2007/StyleTest.php +++ b/tests/PhpWordTests/Reader/Word2007/StyleTest.php @@ -334,8 +334,14 @@ public function testPageVerticalAlign(): void /** * @dataProvider providerIndentation */ - public function testIndentation(string $indent, float $left, float $right, ?float $hanging, float $firstLine): void - { + public function testIndentation( + string $indent, + float $left, + float $right, + ?float $hanging, + float $firstLine, + int $firstLineChars + ): void { $documentXml = " $indent @@ -359,16 +365,53 @@ public function testIndentation(string $indent, float $left, float $right, ?floa self::assertSame($right, $indentation->getRight()); self::assertSame($hanging, $indentation->getHanging()); self::assertSame($firstLine, $indentation->getFirstLine()); + self::assertSame($firstLineChars, $indentation->getFirstLineChars()); } /** - * @return Generator + * @return Generator */ public static function providerIndentation() { - yield ['', 709.00, 488.00, 10.0, 490.00]; - yield ['', 0, 0, 10.0, 490.00]; - yield ['', 709.00, 0, 0, 0]; - yield ['', 0, 488.00, 0, 0]; + yield [ + '', + 709.00, + 488.00, + 10.0, + 490.00, + 140, + ]; + yield [ + '', + 709.00, + 488.00, + 10.0, + 490.00, + 0, + ]; + yield [ + '', + 0, + 0, + 10.0, + 490.00, + 0, + ]; + yield [ + '', + 709.00, + 0, + 0, + 0, + 0, + ]; + yield [ + '', + 0, + 488.00, + 0, + 0, + 0, + ]; } } diff --git a/tests/PhpWordTests/Style/AbstractStyleTest.php b/tests/PhpWordTests/Style/AbstractStyleTest.php index 2f1585fd5a..2b8d3d8d17 100644 --- a/tests/PhpWordTests/Style/AbstractStyleTest.php +++ b/tests/PhpWordTests/Style/AbstractStyleTest.php @@ -20,6 +20,7 @@ use InvalidArgumentException; use PhpOffice\PhpWord\SimpleType\Jc; +use PhpOffice\PhpWord\Style\AbstractStyle; use PhpOffice\PhpWord\Style\Paragraph; use ReflectionClass; @@ -35,7 +36,13 @@ class AbstractStyleTest extends \PHPUnit\Framework\TestCase */ public function testSetStyleByArray(): void { - $stub = $this->getMockForAbstractClass('\PhpOffice\PhpWord\Style\AbstractStyle'); + if (method_exists($this, 'getMockForAbstractClass')) { + $stub = $this->getMockForAbstractClass(AbstractStyle::class); + } else { + /** @var AbstractStyle $stub */ + $stub = new class() extends AbstractStyle { + }; + } $stub->setStyleByArray(['index' => 1]); self::assertEquals(1, $stub->getIndex()); @@ -62,7 +69,13 @@ public function testSetStyleByArrayWithAlignment(): void */ public function testSetValNormal(): void { - $stub = $this->getMockForAbstractClass('\PhpOffice\PhpWord\Style\AbstractStyle'); + if (method_exists($this, 'getMockForAbstractClass')) { + $stub = $this->getMockForAbstractClass(AbstractStyle::class); + } else { + /** @var AbstractStyle $stub */ + $stub = new class() extends AbstractStyle { + }; + } self::assertTrue(self::callProtectedMethod($stub, 'setBoolVal', [true, false])); self::assertEquals(12, self::callProtectedMethod($stub, 'setIntVal', [12, 200])); @@ -76,7 +89,13 @@ public function testSetValNormal(): void */ public function testSetValDefault(): void { - $stub = $this->getMockForAbstractClass('\PhpOffice\PhpWord\Style\AbstractStyle'); + if (method_exists($this, 'getMockForAbstractClass')) { + $stub = $this->getMockForAbstractClass(AbstractStyle::class); + } else { + /** @var AbstractStyle $stub */ + $stub = new class() extends AbstractStyle { + }; + } self::assertNotTrue(self::callProtectedMethod($stub, 'setBoolVal', ['a', false])); self::assertEquals(200, self::callProtectedMethod($stub, 'setIntVal', ['foo', 200])); @@ -90,7 +109,13 @@ public function testSetValDefault(): void public function testSetValEnumException(): void { $this->expectException(InvalidArgumentException::class); - $stub = $this->getMockForAbstractClass('\PhpOffice\PhpWord\Style\AbstractStyle'); + if (method_exists($this, 'getMockForAbstractClass')) { + $stub = $this->getMockForAbstractClass(AbstractStyle::class); + } else { + /** @var AbstractStyle $stub */ + $stub = new class() extends AbstractStyle { + }; + } self::assertEquals('b', self::callProtectedMethod($stub, 'setEnumVal', ['z', ['a', 'b'], 'b'])); } diff --git a/tests/PhpWordTests/Style/IndentationTest.php b/tests/PhpWordTests/Style/IndentationTest.php index db13407ff3..76e3c74cb7 100644 --- a/tests/PhpWordTests/Style/IndentationTest.php +++ b/tests/PhpWordTests/Style/IndentationTest.php @@ -37,6 +37,7 @@ public function testGetSetProperties(): void 'left' => [0, 10], 'right' => [0, 10], 'firstLine' => [null, 20], + 'firstLineChars' => [0, 20], 'hanging' => [null, 20], ]; foreach ($properties as $property => $value) { diff --git a/tests/PhpWordTests/Writer/EPub3/Part/AbstractPartTest.php b/tests/PhpWordTests/Writer/EPub3/Part/AbstractPartTest.php index 5151eea24c..c12980c7e2 100644 --- a/tests/PhpWordTests/Writer/EPub3/Part/AbstractPartTest.php +++ b/tests/PhpWordTests/Writer/EPub3/Part/AbstractPartTest.php @@ -15,7 +15,16 @@ class AbstractPartTest extends TestCase protected function setUp(): void { - $this->part = $this->getMockForAbstractClass(AbstractPart::class); + if (method_exists($this, 'getMockForAbstractClass')) { + $this->part = $this->getMockForAbstractClass(AbstractPart::class); + } else { + $this->part = new class() extends AbstractPart { + public function write(): string + { + return ''; + } + }; + } } public function testParentWriter(): void diff --git a/tests/PhpWordTests/Writer/EPub3/Style/AbstractStyleTest.php b/tests/PhpWordTests/Writer/EPub3/Style/AbstractStyleTest.php index fb9135f0cd..12be0f2e4f 100644 --- a/tests/PhpWordTests/Writer/EPub3/Style/AbstractStyleTest.php +++ b/tests/PhpWordTests/Writer/EPub3/Style/AbstractStyleTest.php @@ -14,7 +14,17 @@ class AbstractStyleTest extends TestCase public function testParentWriter(): void { $parentWriter = new EPub3(); - $style = $this->getMockForAbstractClass(AbstractStyle::class); + if (method_exists($this, 'getMockForAbstractClass')) { + $style = $this->getMockForAbstractClass(AbstractStyle::class); + } else { + /** @var AbstractStyle $style */ + $style = new class() extends AbstractStyle { + public function write(): string + { + return ''; + } + }; + } $result = $style->setParentWriter($parentWriter); diff --git a/tests/PhpWordTests/Writer/ODText/Part/AbstractPartTest.php b/tests/PhpWordTests/Writer/ODText/Part/AbstractPartTest.php index 6742b20ad5..27008dae82 100644 --- a/tests/PhpWordTests/Writer/ODText/Part/AbstractPartTest.php +++ b/tests/PhpWordTests/Writer/ODText/Part/AbstractPartTest.php @@ -34,7 +34,17 @@ class AbstractPartTest extends \PHPUnit\Framework\TestCase */ public function testSetGetParentWriter(): void { - $object = $this->getMockForAbstractClass(ODText\Part\AbstractPart::class); + if (method_exists($this, 'getMockForAbstractClass')) { + $object = $this->getMockForAbstractClass(ODText\Part\AbstractPart::class); + } else { + /** @var ODText\Part\AbstractPart $object */ + $object = new class() extends ODText\Part\AbstractPart { + public function write(): string + { + return ''; + } + }; + } $object->setParentWriter(new ODText()); self::assertEquals(new ODText(), $object->getParentWriter()); } @@ -46,7 +56,17 @@ public function testSetGetParentWriterNull(): void { $this->expectException(Exception::class); $this->expectExceptionMessage('No parent WriterInterface assigned.'); - $object = $this->getMockForAbstractClass(ODText\Part\AbstractPart::class); + if (method_exists($this, 'getMockForAbstractClass')) { + $object = $this->getMockForAbstractClass(ODText\Part\AbstractPart::class); + } else { + /** @var ODText\Part\AbstractPart $object */ + $object = new class() extends ODText\Part\AbstractPart { + public function write(): string + { + return ''; + } + }; + } $object->getParentWriter(); } } diff --git a/tests/PhpWordTests/Writer/Word2007/Part/AbstractPartTest.php b/tests/PhpWordTests/Writer/Word2007/Part/AbstractPartTest.php index 04cdb5ff52..ca38d5d3fc 100644 --- a/tests/PhpWordTests/Writer/Word2007/Part/AbstractPartTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/AbstractPartTest.php @@ -32,9 +32,19 @@ class AbstractPartTest extends \PHPUnit\Framework\TestCase */ public function testSetGetParentWriter(): void { - $object = $this->getMockForAbstractClass(Word2007\Part\AbstractPart::class); - $object->setParentWriter(new Word2007()); - self::assertEquals(new Word2007(), $object->getParentWriter()); + if (method_exists($this, 'getMockForAbstractClass')) { + $stub = $this->getMockForAbstractClass(Word2007\Part\AbstractPart::class); + } else { + /** @var Word2007\Part\AbstractPart $stub */ + $stub = new class() extends Word2007\Part\AbstractPart { + public function write(): string + { + return ''; + } + }; + } + $stub->setParentWriter(new Word2007()); + self::assertEquals(new Word2007(), $stub->getParentWriter()); } /** @@ -44,7 +54,17 @@ public function testSetGetParentWriterNull(): void { $this->expectException(Exception::class); $this->expectExceptionMessage('No parent WriterInterface assigned.'); - $object = $this->getMockForAbstractClass(Word2007\Part\AbstractPart::class); - $object->getParentWriter(); + if (method_exists($this, 'getMockForAbstractClass')) { + $stub = $this->getMockForAbstractClass(Word2007\Part\AbstractPart::class); + } else { + /** @var Word2007\Part\AbstractPart $stub */ + $stub = new class() extends Word2007\Part\AbstractPart { + public function write(): string + { + return ''; + } + }; + } + $stub->getParentWriter(); } } diff --git a/tests/PhpWordTests/Writer/Word2007/Style/IndentationTest.php b/tests/PhpWordTests/Writer/Word2007/Style/IndentationTest.php new file mode 100644 index 0000000000..44d071415a --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Style/IndentationTest.php @@ -0,0 +1,71 @@ +addSection(); + $text = $section->addText('AA'); + $paragraphStyle = $text->getParagraphStyle(); + self::assertInstanceOf(Paragraph::class, $paragraphStyle); + $paragraphStyle->setIndentation([]); + $doc = TestHelperDOCX::getDocument($word, 'Word2007'); + + $path = '/w:document/w:body/w:p[1]/w:pPr/w:ind'; + self::assertTrue($doc->elementExists($path)); + self::assertFalse($doc->hasElementAttribute($path, 'w:firstLineChars')); + } + + public function testFirstLineChars(): void + { + $word = new PhpWord(); + Settings::setDefaultRtl(true); + $section = $word->addSection(); + $text = $section->addText('AA'); + $paragraphStyle = $text->getParagraphStyle(); + self::assertInstanceOf(Paragraph::class, $paragraphStyle); + $paragraphStyle->setIndentation([ + 'firstLineChars' => 1440, + ]); + $doc = TestHelperDOCX::getDocument($word, 'Word2007'); + + $path = '/w:document/w:body/w:p[1]/w:pPr/w:ind'; + self::assertTrue($doc->elementExists($path)); + self::assertTrue($doc->hasElementAttribute($path, 'w:firstLineChars')); + self::assertSame('1440', $doc->getElementAttribute($path, 'w:firstLineChars')); + } +}