Skip to content

Commit

Permalink
Feature: Ruby (phonetic guide) text - Word2007 Read/Write, HTML Read/…
Browse files Browse the repository at this point in the history
…Write, RTF write, ODT basic write (PHPOffice#2727)

* Allow reading ruby text in from Word2007 docs

* Rename ruby language to language Id

* Add basic ruby Word2007 writer

* Update changelog

* Add ruby element docs

* Run PHP CS Fixer

* Fix PHPStan errors

* Run PHP CS Fixer again

* Fix ruby in titles when reading

* Add/tweak tests for reading/writing Title with ruby

* Use elementExists to try and fix PHP7.1

* Review: add missing type hints; refine types

* Add setters to Ruby class

* Add basic sample for ruby text

* Add base constructor to RubyProperties

* Add basic ruby tests

* Run PHP CS Fixer

* Update sample to handle properties better

* Add Ruby HTML output including tests

* Update changelog

* Update PhpStand baseline

* Forgot to run php-cs-fixer again

* Update 1.4.0.md

* Fix TextRun::getText not working with Ruby element

* Add test for TextRun ruby element

* Add RTF ruby output

* Add ruby HTML reading

* Tweak param calls in testRubyWriting

* Update changelog

* Update sample 46 to be less confusing with ruby output

* Move ODText/Element/Text::replaceTabs method

Moved to parent AbstractElement so other classes could make use of it

* Add very basic ODT Ruby output

* Run PHP CS Fixer

* Update 1.4.0.md

* Try to fix unexpected EOF in HtmlTest

* Add missing tests for RubyProperties
  • Loading branch information
Deadpikle authored Jan 28, 2025
1 parent a4468f2 commit 2d27595
Show file tree
Hide file tree
Showing 28 changed files with 1,725 additions and 43 deletions.
1 change: 1 addition & 0 deletions docs/changes/1.x/1.4.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- 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

Expand Down
57 changes: 57 additions & 0 deletions docs/usage/elements/ruby.md
Original file line number Diff line number Diff line change
@@ -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
<?php
$phpWord = new PhpWord();

$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('わたし');

$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);
```
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
20 changes: 15 additions & 5 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,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
Expand All @@ -442,7 +447,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

-
Expand Down Expand Up @@ -1051,14 +1056,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\\.$#"
Expand Down Expand Up @@ -1689,6 +1694,11 @@ parameters:
message: "#^Cannot access property \\$length on DOMNodeList\\<DOMNode\\>\\|false\\.$#"
count: 9
path: tests/PhpWordTests/Writer/HTML/ElementTest.php

-
message: "#^Cannot access property \\$length on DOMNodeList\\<DOMNode\\>\\|false\\.$#"
count: 2
path: tests/PhpWordTests/Writer/HTML/Element/RubyTest.php

-
message: "#^Cannot call method item\\(\\) on DOMNodeList\\<DOMNode\\>\\|false\\.$#"
Expand Down
70 changes: 70 additions & 0 deletions samples/Sample_46_RubyPhoneticGuide.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

use PhpOffice\PhpWord\ComplexType\RubyProperties;
use PhpOffice\PhpWord\Element\TextRun;

include_once 'Sample_Header.php';

// New Word Document
echo date('H:i:s'), ' Create sample for Ruby (Phonetic Guide) use', EOL;
$phpWord = new PhpOffice\PhpWord\PhpWord();

// Section for demonstrating ruby (phonetic guide) features
$section = $phpWord->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';
}
188 changes: 188 additions & 0 deletions src/PhpWord/ComplexType/RubyProperties.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
<?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\ComplexType;

use InvalidArgumentException;

/**
* Ruby properties.
*
* @see https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.wordprocessing.rubyproperties?view=openxml-3.0.1
*/
class RubyProperties
{
const ALIGNMENT_CENTER = 'center';
const ALIGNMENT_DISTRIBUTE_LETTER = 'distributeLetter';
const ALIGNMENT_DISTRIBUTE_SPACE = 'distributeSpace';
const ALIGNMENT_LEFT = 'left';
const ALIGNMENT_RIGHT = 'right';
const ALIGNMENT_RIGHT_VERTICAL = 'rightVertical';

/**
* Ruby alignment (w:rubyAlign).
*
* @var string
*/
private $alignment;

/**
* Ruby font face size (w:hps).
*
* @var float
*/
private $fontFaceSize;

/**
* Ruby font points above base text (w:hpsRaise).
*
* @var float
*/
private $fontPointsAboveText;

/**
* Ruby font size for base text (w:hpsBaseText).
*
* @var float
*/
private $baseTextFontSize;

/**
* Ruby type/language id (w:lid).
*
* @var string
*/
private $languageId;

/**
* Create a new RubyProperties object.
*/
public function __construct()
{
// these defaults came from opening a new Word doc, adding some ruby text to some
// Japanese text, and copying out the defaults.
$this->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;
}
}
Loading

0 comments on commit 2d27595

Please sign in to comment.