Skip to content

Feature: Ruby (phonetic guide) text - Word2007 Read/Write, HTML Read/Write, RTF write, ODT basic write #2727

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 38 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
7c2b5cd
Allow reading ruby text in from Word2007 docs
Deadpikle Jan 23, 2025
e513c94
Rename ruby language to language Id
Deadpikle Jan 23, 2025
5bede93
Add basic ruby Word2007 writer
Deadpikle Jan 23, 2025
68ecb1a
Update changelog
Deadpikle Jan 23, 2025
1650bce
Add ruby element docs
Deadpikle Jan 23, 2025
4d60f38
Run PHP CS Fixer
Deadpikle Jan 23, 2025
8d4af09
Fix PHPStan errors
Deadpikle Jan 23, 2025
6552d71
Run PHP CS Fixer again
Deadpikle Jan 23, 2025
588d808
Fix ruby in titles when reading
Deadpikle Jan 23, 2025
8bc7ceb
Add/tweak tests for reading/writing Title with ruby
Deadpikle Jan 23, 2025
4cecbec
Use elementExists to try and fix PHP7.1
Deadpikle Jan 23, 2025
cc6b6ec
Review: add missing type hints; refine types
Deadpikle Jan 23, 2025
91a49bf
Add setters to Ruby class
Deadpikle Jan 23, 2025
89a00be
Add basic sample for ruby text
Deadpikle Jan 24, 2025
74f8941
Add base constructor to RubyProperties
Deadpikle Jan 24, 2025
106c8d6
Add basic ruby tests
Deadpikle Jan 24, 2025
b563e79
Run PHP CS Fixer
Deadpikle Jan 24, 2025
3c363ce
Update sample to handle properties better
Deadpikle Jan 24, 2025
4366ec0
Add Ruby HTML output including tests
Deadpikle Jan 24, 2025
7decf92
Update changelog
Deadpikle Jan 24, 2025
05ff3f5
Update PhpStand baseline
Deadpikle Jan 24, 2025
ef28187
Forgot to run php-cs-fixer again
Deadpikle Jan 24, 2025
09a38f6
Update 1.4.0.md
Deadpikle Jan 24, 2025
376754e
Fix TextRun::getText not working with Ruby element
Deadpikle Jan 26, 2025
46143dd
Add test for TextRun ruby element
Deadpikle Jan 26, 2025
0423961
Add RTF ruby output
Deadpikle Jan 26, 2025
66232da
Add ruby HTML reading
Deadpikle Jan 26, 2025
94ee043
Tweak param calls in testRubyWriting
Deadpikle Jan 26, 2025
1e55972
Update changelog
Deadpikle Jan 26, 2025
cf51a65
Update sample 46 to be less confusing with ruby output
Deadpikle Jan 26, 2025
c115fa8
Move ODText/Element/Text::replaceTabs method
Deadpikle Jan 26, 2025
fb98e25
Add very basic ODT Ruby output
Deadpikle Jan 26, 2025
3281b3b
Run PHP CS Fixer
Deadpikle Jan 26, 2025
a4368df
Update 1.4.0.md
Deadpikle Jan 26, 2025
00c4b2d
Try to fix unexpected EOF in HtmlTest
Deadpikle Jan 26, 2025
68e1dbb
Merge branch 'master' of https://github.com/PHPOffice/PHPWord into fe…
Deadpikle Jan 28, 2025
04aefba
Add missing tests for RubyProperties
Deadpikle Jan 28, 2025
ec18c81
Merge branch 'master' of https://github.com/PHPOffice/PHPWord into fe…
Deadpikle Jan 28, 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
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
Loading