diff --git a/docs/changes/1.x/1.4.0.md b/docs/changes/1.x/1.4.0.md index 32f5ba47c9..c8a019115d 100644 --- a/docs/changes/1.x/1.4.0.md +++ b/docs/changes/1.x/1.4.0.md @@ -11,6 +11,9 @@ - Writer Word2007: Support for padding in Table Cell by [@Azamat8405](https://github.com/Azamat8405) in [#2697](https://github.com/PHPOffice/PHPWord/pull/2697) - Added support for PHP 8.4 by [@Progi1984](https://github.com/Progi1984) in [#2660](https://github.com/PHPOffice/PHPWord/pull/2660) - Autoload : Allow to use PHPWord without Composer fixing [#2543](https://github.com/PHPOffice/PHPWord/issues/2543), [#2552](https://github.com/PHPOffice/PHPWord/issues/2552), [#2716](https://github.com/PHPOffice/PHPWord/issues/2716), [#2717](https://github.com/PHPOffice/PHPWord/issues/2717) in [#2722](https://github.com/PHPOffice/PHPWord/pull/2722) +- Add Default font color for Word by [@Collie-IT](https://github.com/Collie-IT) in [#2700](https://github.com/PHPOffice/PHPWord/pull/2700) +- Writer HTML: Support Default font color by [@MichaelPFrey](https://github.com/MichaelPFrey) +- 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) ### Bug fixes diff --git a/docs/usage/elements/ruby.md b/docs/usage/elements/ruby.md new file mode 100644 index 0000000000..508b97cd84 --- /dev/null +++ b/docs/usage/elements/ruby.md @@ -0,0 +1,57 @@ +# Ruby + +Ruby (phonetic guide) text can be added by using the ``addRuby`` method. Ruby elements require a ``RubyProperties`` object, a ``TextRun`` for the base text, and a ``TextRun`` for the actual ruby (phonetic guide) text. + +Here is one example for a complete ruby element setup: + +``` php +addSection(); +$properties = new RubyProperties(); +$properties->setAlignment(RubyProperties::ALIGNMENT_RIGHT_VERTICAL); +$properties->setFontFaceSize(10); +$properties->setFontPointsAboveBaseText(4); +$properties->setFontSizeForBaseText(18); +$properties->setLanguageId('ja-JP'); + +$baseTextRun = new TextRun(null); +$baseTextRun->addText('私'); +$rubyTextRun = new TextRun(null); +$rubyTextRun->addText('わたし'); + +$section->addRuby($baseTextRun, $rubyTextRun, $properties); +``` + +- ``$baseTextRun``. ``TextRun`` to be used for the base text. +- ``$rubyTextRun``. ``TextRun`` to be used for the ruby text. +- ``$properties``. ``RubyProperties`` properties object for the ruby text. + +A title with a phonetic guide is a little more complex, but still possible. Make sure you add the appropraite title style to your document. + +```php +$phpWord = new PhpWord(); +$fontStyle = new Font(); +$fontStyle->setAllCaps(true); +$fontStyle->setBold(true); +$fontStyle->setSize(24); +$phpWord->addTitleStyle(1, ['name' => 'Arial', 'size' => 24, 'bold' => true, 'color' => '990000']); + +$section = $phpWord->addSection(); +$properties = new RubyProperties(); +$properties->setAlignment(RubyProperties::ALIGNMENT_RIGHT_VERTICAL); +$properties->setFontFaceSize(10); +$properties->setFontPointsAboveBaseText(4); +$properties->setFontSizeForBaseText(18); +$properties->setLanguageId('ja-JP'); + +$baseTextRun = new TextRun(null); +$baseTextRun->addText('私'); +$rubyTextRun = new TextRun(null); +$rubyTextRun->addText('わたし'); + +$textRun = new TextRun(); +$textRun->addRuby($baseTextRun, $rubyTextRun, $properties); +$section->addTitle($textRun, 1); +``` \ No newline at end of file diff --git a/docs/usage/introduction.md b/docs/usage/introduction.md index 6aa42aaae0..19d6aff51f 100644 --- a/docs/usage/introduction.md +++ b/docs/usage/introduction.md @@ -127,13 +127,14 @@ You can alter the default paper by using the following function: ### Default font -By default, every text appears in Arial 10 point. You can alter the -default font by using the following two functions: +By default, every text appears in Arial 10 point in the color black (000000). +You can alter the default font by using the following functions: ``` php setDefaultFontName('Times New Roman'); +$phpWord->setDefaultFontColor('FF0000'); $phpWord->setDefaultFontSize(12); ``` diff --git a/mkdocs.yml b/mkdocs.yml index 6b2c1b7fa6..0462c9c8a4 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -63,6 +63,7 @@ nav: - OLE Object: 'usage/elements/oleobject.md' - Page Break: 'usage/elements/pagebreak.md' - Preserve Text: 'usage/elements/preservetext.md' + - Ruby: 'usage/elements/ruby.md' - Text: 'usage/elements/text.md' - TextBox: 'usage/elements/textbox.md' - Text Break: 'usage/elements/textbreak.md' diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 35898e0e55..5e1f9cd2c2 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -425,6 +425,11 @@ parameters: count: 1 path: src/PhpWord/Shared/Html.php + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:parseRuby\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:parseStyleDeclarations\\(\\) has no return type specified\\.$#" count: 1 @@ -437,7 +442,7 @@ parameters: - message: "#^Parameter \\#1 \\$attribute of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:parseStyle\\(\\) expects DOMAttr, DOMNode given\\.$#" - count: 1 + count: 3 path: src/PhpWord/Shared/Html.php - @@ -1046,14 +1051,14 @@ parameters: path: src/PhpWord/Writer/ODText/Element/Table.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Element\\\\Text\\:\\:replacetabs\\(\\) has parameter \\$text with no type specified\\.$#" + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Element\\\\AbstractElement\\:\\:replaceTabs\\(\\) has parameter \\$text with no type specified\\.$#" count: 1 - path: src/PhpWord/Writer/ODText/Element/Text.php + path: src/PhpWord/Writer/ODText/Element/AbstractElement.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Element\\\\Text\\:\\:replacetabs\\(\\) has parameter \\$xmlWriter with no type specified\\.$#" + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Element\\\\AbstractElement\\:\\:replaceTabs\\(\\) has parameter \\$xmlWriter with no type specified\\.$#" count: 1 - path: src/PhpWord/Writer/ODText/Element/Text.php + path: src/PhpWord/Writer/ODText/Element/AbstractElement.php - message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Element\\\\Text\\:\\:writeChangeInsertion\\(\\) has parameter \\$start with no type specified\\.$#" @@ -1684,6 +1689,11 @@ parameters: message: "#^Cannot access property \\$length on DOMNodeList\\\\|false\\.$#" count: 9 path: tests/PhpWordTests/Writer/HTML/ElementTest.php + + - + message: "#^Cannot access property \\$length on DOMNodeList\\\\|false\\.$#" + count: 2 + path: tests/PhpWordTests/Writer/HTML/Element/RubyTest.php - message: "#^Cannot call method item\\(\\) on DOMNodeList\\\\|false\\.$#" diff --git a/phpword.ini.dist b/phpword.ini.dist index f3f66dbe2e..21d3b50609 100644 --- a/phpword.ini.dist +++ b/phpword.ini.dist @@ -14,6 +14,7 @@ outputEscapingEnabled = false defaultFontName = Arial defaultFontSize = 10 +defaultFontColor = 000000 [Paper] diff --git a/samples/Sample_46_RubyPhoneticGuide.php b/samples/Sample_46_RubyPhoneticGuide.php new file mode 100644 index 0000000000..0d991de756 --- /dev/null +++ b/samples/Sample_46_RubyPhoneticGuide.php @@ -0,0 +1,70 @@ +addSection(); + +$section->addText('Here is some normal text with no ruby, also known as "phonetic guide", text.'); + +$properties = new RubyProperties(); +$properties->setAlignment(RubyProperties::ALIGNMENT_CENTER); +$properties->setFontFaceSize(10); +$properties->setFontPointsAboveBaseText(20); +$properties->setFontSizeForBaseText(18); +$properties->setLanguageId('en-US'); + +$textRun = $section->addTextRun(); +$textRun->addText('Here is a demonstration of ruby text for '); +$baseTextRun = new TextRun(null); +$baseTextRun->addText('this'); +$rubyTextRun = new TextRun(null); +$rubyTextRun->addText('ruby-text'); +$textRun->addRuby($baseTextRun, $rubyTextRun, $properties); +$textRun->addText(' word.'); + +$textRun = $section->addTextRun(); +$properties = new RubyProperties(); +$properties->setAlignment(RubyProperties::ALIGNMENT_CENTER); +$properties->setFontFaceSize(10); +$properties->setFontPointsAboveBaseText(20); +$properties->setFontSizeForBaseText(18); +$properties->setLanguageId('ja-JP'); +$textRun->addText('Here is a demonstration of ruby text for Japanese text: '); +$baseTextRun = new TextRun(null); +$baseTextRun->addText('私'); +$rubyTextRun = new TextRun(null); +$rubyTextRun->addText('わたし'); +$textRun->addRuby($baseTextRun, $rubyTextRun, $properties); + +$section->addText('You can also have ruby text for titles:'); + +$phpWord->addTitleStyle(1, ['name' => 'Arial', 'size' => 24, 'bold' => true, 'color' => '000099']); + +$properties = new RubyProperties(); +$properties->setAlignment(RubyProperties::ALIGNMENT_CENTER); +$properties->setFontFaceSize(10); +$properties->setFontPointsAboveBaseText(50); +$properties->setFontSizeForBaseText(18); +$properties->setLanguageId('ja-JP'); + +$baseTextRun = new TextRun(null); +$baseTextRun->addText('私'); +$rubyTextRun = new TextRun(null); +$rubyTextRun->addText('わたし'); +$textRun = new TextRun(); +$textRun->addRuby($baseTextRun, $rubyTextRun, $properties); +$section->addTitle($textRun, 1); + +// Save file +echo write($phpWord, basename(__FILE__, '.php'), $writers); +if (!CLI) { + include_once 'Sample_Footer.php'; +} diff --git a/src/PhpWord/ComplexType/RubyProperties.php b/src/PhpWord/ComplexType/RubyProperties.php new file mode 100644 index 0000000000..2409151644 --- /dev/null +++ b/src/PhpWord/ComplexType/RubyProperties.php @@ -0,0 +1,188 @@ +alignment = self::ALIGNMENT_DISTRIBUTE_SPACE; + $this->fontFaceSize = 12; + $this->fontPointsAboveText = 22; + $this->languageId = 'ja-JP'; + $this->baseTextFontSize = 24; + } + + /** + * Get the ruby alignment. + */ + public function getAlignment(): string + { + return $this->alignment; + } + + /** + * Set the Ruby Alignment (center, distributeLetter, distributeSpace, left, right, rightVertical). + */ + public function setAlignment(string $alignment): self + { + $alignmentTypes = [ + self::ALIGNMENT_CENTER, + self::ALIGNMENT_DISTRIBUTE_LETTER, + self::ALIGNMENT_DISTRIBUTE_SPACE, + self::ALIGNMENT_LEFT, + self::ALIGNMENT_RIGHT, + self::ALIGNMENT_RIGHT_VERTICAL, + ]; + + if (in_array($alignment, $alignmentTypes)) { + $this->alignment = $alignment; + } else { + throw new InvalidArgumentException('Invalid value, alignments of ' . implode(', ', $alignmentTypes) . ' possible'); + } + + return $this; + } + + /** + * Get the ruby font face size. + */ + public function getFontFaceSize(): float + { + return $this->fontFaceSize; + } + + /** + * Set the ruby font face size. + */ + public function setFontFaceSize(float $size): self + { + $this->fontFaceSize = $size; + + return $this; + } + + /** + * Get the ruby font points above base text. + */ + public function getFontPointsAboveBaseText(): float + { + return $this->fontPointsAboveText; + } + + /** + * Set the ruby font points above base text. + */ + public function setFontPointsAboveBaseText(float $size): self + { + $this->fontPointsAboveText = $size; + + return $this; + } + + /** + * Get the ruby font size for base text. + */ + public function getFontSizeForBaseText(): float + { + return $this->baseTextFontSize; + } + + /** + * Set the ruby font size for base text. + */ + public function setFontSizeForBaseText(float $size): self + { + $this->baseTextFontSize = $size; + + return $this; + } + + /** + * Get the ruby language id. + */ + public function getLanguageId(): string + { + return $this->languageId; + } + + /** + * Set the ruby language id. + */ + public function setLanguageId(string $langId): self + { + $this->languageId = $langId; + + return $this; + } +} diff --git a/src/PhpWord/Element/AbstractContainer.php b/src/PhpWord/Element/AbstractContainer.php index f311d68206..37140b4582 100644 --- a/src/PhpWord/Element/AbstractContainer.php +++ b/src/PhpWord/Element/AbstractContainer.php @@ -50,6 +50,7 @@ * @method FormField addFormField(string $type, mixed $fStyle = null, mixed $pStyle = null) * @method SDT addSDT(string $type) * @method Formula addFormula(Math $math) + * @method Ruby addRuby(TextRun $baseText, TextRun $rubyText, \PhpOffice\PhpWord\ComplexType\RubyProperties $properties) * @method \PhpOffice\PhpWord\Element\OLEObject addObject(string $source, mixed $style = null) deprecated, use addOLEObject instead * * @since 0.10.0 @@ -91,7 +92,7 @@ public function __call($function, $args) 'Footnote', 'Endnote', 'CheckBox', 'TextBox', 'Field', 'Line', 'Shape', 'Title', 'TOC', 'PageBreak', 'Chart', 'FormField', 'SDT', 'Comment', - 'Formula', + 'Formula', 'Ruby', ]; $functions = []; foreach ($elements as $element) { diff --git a/src/PhpWord/Element/Ruby.php b/src/PhpWord/Element/Ruby.php new file mode 100644 index 0000000000..4b032a220d --- /dev/null +++ b/src/PhpWord/Element/Ruby.php @@ -0,0 +1,114 @@ +baseTextRun = $baseTextRun; + $this->rubyTextRun = $rubyTextRun; + $this->properties = $properties; + } + + /** + * Get base text run. + */ + public function getBaseTextRun(): TextRun + { + return $this->baseTextRun; + } + + /** + * Set the base text run. + */ + public function setBaseTextRun(TextRun $textRun): self + { + $this->baseTextRun = $textRun; + + return $this; + } + + /** + * Get ruby text run. + */ + public function getRubyTextRun(): TextRun + { + return $this->rubyTextRun; + } + + /** + * Set the ruby text run. + */ + public function setRubyTextRun(TextRun $textRun): self + { + $this->rubyTextRun = $textRun; + + return $this; + } + + /** + * Get ruby properties. + */ + public function getProperties(): RubyProperties + { + return $this->properties; + } + + /** + * Set the ruby properties. + */ + public function setProperties(RubyProperties $properties): self + { + $this->properties = $properties; + + return $this; + } +} diff --git a/src/PhpWord/Element/TextRun.php b/src/PhpWord/Element/TextRun.php index 5dc8ef53c7..0c9a2322a7 100644 --- a/src/PhpWord/Element/TextRun.php +++ b/src/PhpWord/Element/TextRun.php @@ -86,6 +86,9 @@ public function getText(): string foreach ($this->getElements() as $element) { if ($element instanceof Text) { $outstr .= $element->getText(); + } elseif ($element instanceof Ruby) { + $outstr .= $element->getBaseTextRun()->getText() . + ' (' . $element->getRubyTextRun()->getText() . ')'; } } diff --git a/src/PhpWord/PhpWord.php b/src/PhpWord/PhpWord.php index 593fbd2089..11e678a1ff 100644 --- a/src/PhpWord/PhpWord.php +++ b/src/PhpWord/PhpWord.php @@ -267,7 +267,7 @@ public function getDefaultAsianFontName(): string } /** - * Set default font name. + * Set default asian font name. * * @param string $fontName */ @@ -276,6 +276,22 @@ public function setDefaultAsianFontName($fontName): void Settings::setDefaultAsianFontName($fontName); } + /** + * Set default font color. + */ + public function setDefaultFontColor(string $fontColor): void + { + Settings::setDefaultFontColor($fontColor); + } + + /** + * Get default font color. + */ + public function getDefaultFontColor(): string + { + return Settings::getDefaultFontColor(); + } + /** * Get default font size. * diff --git a/src/PhpWord/Reader/Word2007/AbstractPart.php b/src/PhpWord/Reader/Word2007/AbstractPart.php index f342a50ec5..697478c7e8 100644 --- a/src/PhpWord/Reader/Word2007/AbstractPart.php +++ b/src/PhpWord/Reader/Word2007/AbstractPart.php @@ -22,10 +22,13 @@ use DOMElement; use InvalidArgumentException; use PhpOffice\Math\Reader\OfficeMathML; +use PhpOffice\PhpWord\ComplexType\RubyProperties; use PhpOffice\PhpWord\ComplexType\TblWidth as TblWidthComplexType; use PhpOffice\PhpWord\Element\AbstractContainer; use PhpOffice\PhpWord\Element\AbstractElement; use PhpOffice\PhpWord\Element\FormField; +use PhpOffice\PhpWord\Element\Ruby; +use PhpOffice\PhpWord\Element\Text; use PhpOffice\PhpWord\Element\TextRun; use PhpOffice\PhpWord\Element\TrackChange; use PhpOffice\PhpWord\PhpWord; @@ -297,7 +300,8 @@ protected function readParagraph(XMLReader $xmlReader, DOMElement $domNode, $par if ($headingDepth !== null) { $textContent = null; $nodes = $xmlReader->getElements('w:r|w:hyperlink', $domNode); - if ($nodes->length === 1) { + $hasRubyElement = $xmlReader->elementExists('w:r/w:ruby', $domNode); + if ($nodes->length === 1 && !$hasRubyElement) { $textContent = htmlspecialchars($xmlReader->getValue('w:t', $nodes->item(0)), ENT_QUOTES, 'UTF-8'); } else { $textContent = new TextRun($paragraphStyle); @@ -585,9 +589,47 @@ protected function readRunChild(XMLReader $xmlReader, DOMElement $node, Abstract } } elseif ($node->nodeName == 'w:softHyphen') { $element = $parent->addText("\u{200c}", $fontStyle, $paragraphStyle); + } elseif ($node->nodeName == 'w:ruby') { + $rubyPropertiesNode = $xmlReader->getElement('w:rubyPr', $node); + $properties = $this->readRubyProperties($xmlReader, $rubyPropertiesNode); + // read base text node + $baseText = new TextRun($paragraphStyle); + $baseTextNode = $xmlReader->getElement('w:rubyBase/w:r', $node); + $this->readRun($xmlReader, $baseTextNode, $baseText, $docPart, $paragraphStyle); + // read the actual ruby text (e.g. furigana in Japanese) + $rubyText = new TextRun($paragraphStyle); + $rubyTextNode = $xmlReader->getElement('w:rt/w:r', $node); + $this->readRun($xmlReader, $rubyTextNode, $rubyText, $docPart, $paragraphStyle); + // add element to parent + $parent->addRuby($baseText, $rubyText, $properties); } } + /** + * Read w:rubyPr element. + * + * @param XMLReader $xmlReader reader for XML + * @param DOMElement $domNode w:RubyPr element + * + * @return RubyProperties ruby properties from element + */ + protected function readRubyProperties(XMLReader $xmlReader, DOMElement $domNode): RubyProperties + { + $rubyAlignment = $xmlReader->getElement('w:rubyAlign', $domNode)->getAttribute('w:val'); + $rubyHps = $xmlReader->getElement('w:hps', $domNode)->getAttribute('w:val'); // font face + $rubyHpsRaise = $xmlReader->getElement('w:hpsRaise', $domNode)->getAttribute('w:val'); // pts above base text + $rubyHpsBaseText = $xmlReader->getElement('w:hpsBaseText', $domNode)->getAttribute('w:val'); // base text size + $rubyLid = $xmlReader->getElement('w:lid', $domNode)->getAttribute('w:val'); // type of ruby + $properties = new RubyProperties(); + $properties->setAlignment($rubyAlignment); + $properties->setFontFaceSize((float) $rubyHps); + $properties->setFontPointsAboveBaseText((float) $rubyHpsRaise); + $properties->setFontSizeForBaseText((float) $rubyHpsBaseText); + $properties->setLanguageId($rubyLid); + + return $properties; + } + /** * Read w:tbl. * diff --git a/src/PhpWord/Reader/Word2007/Styles.php b/src/PhpWord/Reader/Word2007/Styles.php index 2e81501d04..81aaf203c1 100644 --- a/src/PhpWord/Reader/Word2007/Styles.php +++ b/src/PhpWord/Reader/Word2007/Styles.php @@ -47,6 +47,9 @@ public function read(PhpWord $phpWord): void if (array_key_exists('size', $fontDefaultStyle)) { $phpWord->setDefaultFontSize($fontDefaultStyle['size']); } + if (array_key_exists('color', $fontDefaultStyle)) { + $phpWord->setDefaultFontColor($fontDefaultStyle['color']); + } if (array_key_exists('lang', $fontDefaultStyle)) { $phpWord->getSettings()->setThemeFontLang(new Language($fontDefaultStyle['lang'])); } diff --git a/src/PhpWord/Settings.php b/src/PhpWord/Settings.php index 545920419c..c4b7c38dfc 100644 --- a/src/PhpWord/Settings.php +++ b/src/PhpWord/Settings.php @@ -121,12 +121,19 @@ class Settings private static $defaultFontName = self::DEFAULT_FONT_NAME; /** - * Default font name. + * Default asian font name. * * @var string */ private static $defaultAsianFontName = self::DEFAULT_FONT_NAME; + /** + * Default font color. + * + * @var string + */ + private static $defaultFontColor = self::DEFAULT_FONT_COLOR; + /** * Default font size. * @@ -397,6 +404,28 @@ public static function setDefaultAsianFontName(string $value): bool return false; } + /** + * Get default font color. + */ + public static function getDefaultFontColor(): string + { + return self::$defaultFontColor; + } + + /** + * Set default font color. + */ + public static function setDefaultFontColor(string $value): bool + { + if (trim($value) !== '') { + self::$defaultFontColor = $value; + + return true; + } + + return false; + } + /** * Get default font size. * diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index a809495ad6..d2e679a518 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -23,6 +23,7 @@ use DOMNode; use DOMXPath; use Exception; +use PhpOffice\PhpWord\ComplexType\RubyProperties; use PhpOffice\PhpWord\Element\AbstractContainer; use PhpOffice\PhpWord\Element\Row; use PhpOffice\PhpWord\Element\Table; @@ -354,6 +355,7 @@ protected static function parseNode($node, $element, $styles = [], $data = []): 'a' => ['Link', $node, $element, $styles, null, null, null], 'input' => ['Input', $node, $element, $styles, null, null, null], 'hr' => ['HorizRule', $node, $element, $styles, null, null, null], + 'ruby' => ['Ruby', $node, $element, $styles, null, null, null], ]; $newElement = null; @@ -831,6 +833,10 @@ protected static function parseStyleDeclarations(array $selectors, array $styles case 'text-align': $styles['alignment'] = self::mapAlign($value, $bidi); + break; + case 'ruby-align': + $styles['rubyAlignment'] = self::mapRubyAlign($value); + break; case 'display': $styles['hidden'] = $value === 'none' || $value === 'hidden'; @@ -1249,6 +1255,23 @@ protected static function mapAlign($cssAlignment, $bidi) } } + /** + * Transforms a HTML/CSS ruby alignment into a \PhpOffice\PhpWord\SimpleType\Jc. + */ + protected static function mapRubyAlign(string $cssRubyAlignment): string + { + switch ($cssRubyAlignment) { + case 'center': + return RubyProperties::ALIGNMENT_CENTER; + case 'start': + return RubyProperties::ALIGNMENT_LEFT; + case 'space-between': + return RubyProperties::ALIGNMENT_DISTRIBUTE_SPACE; + default: + return ''; + } + } + /** * Transforms a HTML/CSS vertical alignment. * @@ -1378,6 +1401,59 @@ protected static function parseHorizRule($node, $element): void // - repeated text, e.g. underline "_", because of unpredictable line wrapping } + /** + * Parse ruby node. + * + * @param DOMNode $node + * @param AbstractContainer $element + * @param array $styles + */ + protected static function parseRuby($node, $element, &$styles) + { + $rubyProperties = new RubyProperties(); + $baseTextRun = new TextRun($styles['paragraph']); + $rubyTextRun = new TextRun(null); + if ($node->hasAttributes()) { + $langAttr = $node->attributes->getNamedItem('lang'); + if ($langAttr !== null) { + $rubyProperties->setLanguageId($langAttr->textContent); + } + $styleAttr = $node->attributes->getNamedItem('style'); + if ($styleAttr !== null) { + $styles = self::parseStyle($styleAttr, $styles['paragraph']); + if (isset($styles['rubyAlignment']) && $styles['rubyAlignment'] !== '') { + $rubyProperties->setAlignment($styles['rubyAlignment']); + } + if (isset($styles['size']) && $styles['size'] !== '') { + $rubyProperties->setFontSizeForBaseText($styles['size']); + } + $baseTextRun->setParagraphStyle($styles); + } + } + foreach ($node->childNodes as $child) { + if ($child->nodeName === '#text') { + $content = trim($child->textContent); + if ($content !== '') { + $baseTextRun->addText($content); + } + } elseif ($child->nodeName === 'rt') { + $rubyTextRun->addText(trim($child->textContent)); + if ($child->hasAttributes()) { + $styleAttr = $child->attributes->getNamedItem('style'); + if ($styleAttr !== null) { + $styles = self::parseStyle($styleAttr, []); + if (isset($styles['size']) && $styles['size'] !== '') { + $rubyProperties->setFontFaceSize($styles['size']); + } + $rubyTextRun->setParagraphStyle($styles); + } + } + } + } + + return $element->addRuby($baseTextRun, $rubyTextRun, $rubyProperties); + } + private static function convertRgb(string $rgb): string { if (preg_match(self::RGB_REGEXP, $rgb, $matches) === 1) { diff --git a/src/PhpWord/Writer/HTML/Element/Ruby.php b/src/PhpWord/Writer/HTML/Element/Ruby.php new file mode 100644 index 0000000000..b3ab13c35e --- /dev/null +++ b/src/PhpWord/Writer/HTML/Element/Ruby.php @@ -0,0 +1,130 @@ +processFontStyle(); + + /** @var \PhpOffice\PhpWord\Element\Ruby $element Type hint */ + $element = $this->element; + + $baseText = $this->parentWriter->escapeHTML($element->getBaseTextRun()->getText()); + $rubyText = $this->parentWriter->escapeHTML($element->getRubyTextRun()->getText()); + + $rubyTagPropertyCSS = $this->getPropertyCssForRubyTag($element->getProperties()); + $lang = $element->getProperties()->getLanguageId(); + $content = "getParagraphStyleForTextRun($element->getBaseTextRun(), $rubyTagPropertyCSS)} lang=\"{$lang}\">"; + $content .= $baseText; + $content .= ' ('; + $rtTagPropertyCSS = $this->getPropertyCssForRtTag($element->getProperties()); + $content .= "getParagraphStyleForTextRun($element->getRubyTextRun(), $rtTagPropertyCSS)}>"; + $content .= $rubyText; + $content .= ''; + $content .= ')'; + $content .= ''; + + return $content; + } + + /** + * Get property CSS for the tag. + */ + private function getPropertyCssForRubyTag(RubyProperties $properties): string + { + // alignment CSS: https://developer.mozilla.org/en-US/docs/Web/CSS/ruby-align + $alignment = 'space-between'; + switch ($properties->getAlignment()) { + case RubyProperties::ALIGNMENT_CENTER: + $alignment = 'center'; + + break; + case RubyProperties::ALIGNMENT_LEFT: + $alignment = 'start'; + + break; + default: + $alignment = 'space-between'; + + break; + } + + return + 'font-size:' . $properties->getFontSizeForBaseText() . 'pt' . ';' . + 'ruby-align:' . $alignment . ';'; + } + + /** + * Get property CSS for the tag. + */ + private function getPropertyCssForRtTag(RubyProperties $properties): string + { + // alignment CSS: https://developer.mozilla.org/en-US/docs/Web/CSS/ruby-align + return 'font-size:' . $properties->getFontFaceSize() . 'pt' . ';'; + } + + /** + * Write paragraph style for a given TextRun. + */ + private function getParagraphStyleForTextRun(TextRun $textRun, string $extraCSS): string + { + $style = ''; + if (!method_exists($textRun, 'getParagraphStyle')) { + return $style; + } + + $paragraphStyle = $textRun->getParagraphStyle(); + $pStyleIsObject = ($paragraphStyle instanceof Paragraph); + if ($pStyleIsObject) { + $styleWriter = new ParagraphStyleWriter($paragraphStyle); + $styleWriter->setParentWriter($this->parentWriter); + $style = $styleWriter->write(); + } elseif (is_string($paragraphStyle)) { + $style = $paragraphStyle; + } + if ($style !== null && $style !== '') { + if ($pStyleIsObject) { + // CSS pairs (style="...") + $style = " style=\"{$style}{$extraCSS}\""; + } else { + // class name; need to append extra styles manually + $style = " class=\"{$style}\" style=\"{$extraCSS}\""; + } + } elseif ($extraCSS !== '') { + $style = " style=\"{$extraCSS}\""; + } + + return $style; + } +} diff --git a/src/PhpWord/Writer/HTML/Part/Body.php b/src/PhpWord/Writer/HTML/Part/Body.php index 4a391b53ef..bad1415c21 100644 --- a/src/PhpWord/Writer/HTML/Part/Body.php +++ b/src/PhpWord/Writer/HTML/Part/Body.php @@ -23,7 +23,7 @@ use PhpOffice\PhpWord\Writer\PDF\TCPDF; /** - * RTF body part writer. + * HTML body part writer. * * @since 0.11.0 */ diff --git a/src/PhpWord/Writer/HTML/Part/Head.php b/src/PhpWord/Writer/HTML/Part/Head.php index 0a34e5095f..0f295e58c3 100644 --- a/src/PhpWord/Writer/HTML/Part/Head.php +++ b/src/PhpWord/Writer/HTML/Part/Head.php @@ -30,7 +30,7 @@ use PhpOffice\PhpWord\Writer\HTML\Style\Table as TableStyleWriter; /** - * RTF head part writer. + * HTML head part writer. * * @since 0.11.0 */ @@ -85,11 +85,12 @@ public function write() private function writeStyles(): string { $css = '