Skip to content

Commit 5914e8e

Browse files
authored
Writer EPub3 : Added support (#2724)
* Feat: Added Epub3 writer support * Add: Tests * Fix: Code convention * Update: composer.lock file with latest requirement * Update: composer.lock file with latest requirement * Update: composer.lock file with latest requirement * Remove: composer.lock * Update: Code conventions for PHP 8.x & Update: .gitignore to include composer.lock (in support of PR #2722 : Autoload) * Add: Text & Image Element * Improvement of Epub3 elements code * Add: Unit tests for full EPub3 codebase * Fix: Code convention errors for EPub3 Unit tests * Fix: Added the suggestions * Revert: composer.json changes -> Now again included the gd & zip extension * Update: composer.json * Add: Generating Epub samples with adherence to the Epub 3 checking procedures * Fix: Null type error in php8.x runtime * Update: Changelog
1 parent fd06f96 commit 5914e8e

39 files changed

+1695
-4
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ _build
1212
/build
1313
phpunit.xml
1414
composer.phar
15+
composer.lock
1516
vendor
1617
/report
1718
/build

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,10 @@
108108
"require": {
109109
"php": "^7.1|^8.0",
110110
"ext-dom": "*",
111-
"ext-gd": "*",
111+
"ext-gd": "*",
112+
"ext-zip": "*",
112113
"ext-json": "*",
113114
"ext-xml": "*",
114-
"ext-zip": "*",
115115
"phpoffice/math": "^0.2"
116116
},
117117
"require-dev": {

docs/changes/1.x/1.4.0.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
- Writer ODText: Support Default font color by [@MichaelPFrey](https://github.com/MichaelPFrey) in [#2735](https://github.com/PHPOffice/PHPWord/pull/2735)
1717
- 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)
1818

19+
- Added Support for Writer Epub3 by [@Sambit003](https://github.com/Sambit003) in [#2724](https://github.com/PHPOffice/PHPWord/pull/2724)
20+
1921
### Bug fixes
2022

2123
- Writer ODText: Support for images inside a textRun by [@Progi1984](https://github.com/Progi1984) fixing [#2240](https://github.com/PHPOffice/PHPWord/issues/2240) in [#2668](https://github.com/PHPOffice/PHPWord/pull/2668)

samples/Sample_Header.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
}
3232

3333
// Set writers
34-
$writers = ['Word2007' => 'docx', 'ODText' => 'odt', 'RTF' => 'rtf', 'HTML' => 'html', 'PDF' => 'pdf'];
34+
$writers = ['Word2007' => 'docx', 'ODText' => 'odt', 'RTF' => 'rtf', 'HTML' => 'html', 'PDF' => 'pdf', 'EPub3' => 'epub'];
3535

3636
// Set PDF renderer
3737
if (null === Settings::getPdfRendererPath()) {

src/PhpWord/IOFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ abstract class IOFactory
3636
*/
3737
public static function createWriter(PhpWord $phpWord, $name = 'Word2007')
3838
{
39-
if ($name !== 'WriterInterface' && !in_array($name, ['ODText', 'RTF', 'Word2007', 'HTML', 'PDF'], true)) {
39+
if ($name !== 'WriterInterface' && !in_array($name, ['ODText', 'RTF', 'Word2007', 'HTML', 'PDF', 'EPub3'], true)) {
4040
throw new Exception("\"{$name}\" is not a valid writer.");
4141
}
4242

src/PhpWord/Shared/ZipArchive.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,4 +423,15 @@ public function pclzipLocateName($filename)
423423

424424
return ($listIndex > -1) ? $listIndex : false;
425425
}
426+
427+
/**
428+
* Add an empty directory to the zip archive (emulate \ZipArchive).
429+
*
430+
* @param string $dirname Directory name to add to the zip archive
431+
*/
432+
public function addEmptyDir(string $dirname): bool
433+
{
434+
// Create a directory entry by adding an empty file with trailing slash
435+
return $this->addFromString(rtrim($dirname, '/') . '/', '');
436+
}
426437
}

src/PhpWord/Writer/EPub3.php

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?php
2+
3+
/**
4+
* This file is part of PHPWord - A pure PHP library for reading and writing
5+
* word processing documents.
6+
*
7+
* PHPWord is free software distributed under the terms of the GNU Lesser
8+
* General Public License version 3 as published by the Free Software Foundation.
9+
*
10+
* For the full copyright and license information, please read the LICENSE
11+
* file that was distributed with this source code. For the full list of
12+
* contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
13+
*
14+
* @see https://github.com/PHPOffice/PHPWord
15+
*
16+
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
17+
*/
18+
19+
namespace PhpOffice\PhpWord\Writer;
20+
21+
use PhpOffice\PhpWord\PhpWord;
22+
use PhpOffice\PhpWord\Writer\EPub3\Part\AbstractPart;
23+
24+
/**
25+
* EPub3 writer.
26+
*/
27+
class EPub3 extends AbstractWriter implements WriterInterface
28+
{
29+
/**
30+
* Create new EPub3 writer.
31+
*/
32+
public function __construct(?PhpWord $phpWord = null)
33+
{
34+
// Assign PhpWord
35+
$this->setPhpWord($phpWord);
36+
37+
// Create parts
38+
$this->parts = [
39+
'Mimetype' => 'mimetype',
40+
'Content' => 'content.opf',
41+
'Toc' => 'toc.ncx',
42+
'Styles' => 'styles.css',
43+
'Manifest' => 'META-INF/container.xml',
44+
'Nav' => 'nav.xhtml',
45+
'ContentXhtml' => 'content.xhtml',
46+
];
47+
foreach (array_keys($this->parts) as $partName) {
48+
$partClass = static::class . '\\Part\\' . $partName;
49+
if (class_exists($partClass)) {
50+
/** @var WriterPartInterface $part */
51+
$part = new $partClass($partName === 'Content' || $partName === 'ContentXhtml' ? $phpWord : null);
52+
$part->setParentWriter($this);
53+
$this->writerParts[strtolower($partName)] = $part;
54+
}
55+
}
56+
57+
// Set package paths
58+
$this->mediaPaths = ['image' => 'Images/', 'object' => 'Objects/'];
59+
}
60+
61+
/**
62+
* Save PhpWord to file.
63+
*/
64+
public function save(string $filename): void
65+
{
66+
$filename = $this->getTempFile($filename);
67+
$zip = $this->getZipArchive($filename);
68+
69+
// Add mimetype first without compression
70+
$zip->addFromString('mimetype', 'application/epub+zip');
71+
$zip->addEmptyDir('META-INF');
72+
73+
// Add other files
74+
foreach ($this->parts as $partName => $fileName) {
75+
if ($fileName === '') {
76+
continue;
77+
}
78+
$part = $this->getWriterPart($partName);
79+
if (!$part instanceof AbstractPart) {
80+
continue;
81+
}
82+
$zip->addFromString($fileName, $part->write());
83+
}
84+
85+
// Close zip archive
86+
$zip->close();
87+
88+
// Cleanup temp file
89+
$this->cleanupTempFile();
90+
}
91+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
/**
4+
* This file is part of PHPWord - A pure PHP library for reading and writing
5+
* word processing documents.
6+
*
7+
* PHPWord is free software distributed under the terms of the GNU Lesser
8+
* General Public License version 3 as published by the Free Software Foundation.
9+
*
10+
* For the full copyright and license information, please read the LICENSE
11+
* file that was distributed with this source code. For the full list of
12+
* contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
13+
*
14+
* @see https://github.com/PHPOffice/PHPWord
15+
*
16+
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
17+
*/
18+
19+
namespace PhpOffice\PhpWord\Writer\EPub3\Element;
20+
21+
use PhpOffice\PhpWord\Writer\Word2007\Element\AbstractElement as Word2007AbstractElement;
22+
23+
/**
24+
* Abstract element writer.
25+
*
26+
* @since 0.11.0
27+
*/
28+
abstract class AbstractElement extends Word2007AbstractElement
29+
{
30+
/**
31+
* Get class name of writer element based on read element.
32+
*
33+
* @param \PhpOffice\PhpWord\Element\AbstractElement $element
34+
*/
35+
public static function getElementClass($element): string
36+
{
37+
$elementClass = str_replace('PhpOffice\\PhpWord\\Element\\', '', get_class($element));
38+
$writerClass = 'PhpOffice\\PhpWord\\Writer\\EPub3\\Element\\' . $elementClass;
39+
if (!class_exists($writerClass)) {
40+
throw new \PhpOffice\PhpWord\Exception\Exception("Writer element class {$writerClass} not found.");
41+
}
42+
43+
return $writerClass;
44+
}
45+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
namespace PhpOffice\PhpWord\Writer\EPub3\Element;
4+
5+
use PhpOffice\PhpWord\Element\Image as ImageElement;
6+
7+
/**
8+
* Image element writer for EPub3.
9+
*/
10+
class Image extends AbstractElement
11+
{
12+
/**
13+
* Write element.
14+
*/
15+
public function write(): void
16+
{
17+
$xmlWriter = $this->getXmlWriter();
18+
$xmlWriter->setIndent(false);
19+
$element = $this->getElement();
20+
if (!$element instanceof ImageElement) {
21+
return;
22+
}
23+
$mediaIndex = $element->getMediaIndex();
24+
$target = 'media/image' . $mediaIndex . '.' . $element->getImageExtension();
25+
if (!$this->withoutP) {
26+
$xmlWriter->startElement('p');
27+
}
28+
$xmlWriter->startElement('img');
29+
$xmlWriter->writeAttribute('src', $target);
30+
$style = '';
31+
if ($element->getStyle()->getWidth() !== null) {
32+
$style .= 'width:' . $element->getStyle()->getWidth() . 'px;';
33+
}
34+
if ($element->getStyle()->getHeight() !== null) {
35+
$style .= 'height:' . $element->getStyle()->getHeight() . 'px;';
36+
}
37+
if ($style !== '') {
38+
$xmlWriter->writeAttribute('style', $style);
39+
}
40+
$xmlWriter->endElement(); // img
41+
if (!$this->withoutP) {
42+
$xmlWriter->endElement(); // p
43+
}
44+
}
45+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
namespace PhpOffice\PhpWord\Writer\EPub3\Element;
4+
5+
/**
6+
* Text element writer for EPub3.
7+
*/
8+
class Text extends AbstractElement
9+
{
10+
/**
11+
* Write element.
12+
*/
13+
public function write(): void
14+
{
15+
$xmlWriter = $this->getXmlWriter();
16+
$xmlWriter->setIndent(true);
17+
$xmlWriter->setIndentString(' ');
18+
$element = $this->getElement();
19+
if (!$element instanceof \PhpOffice\PhpWord\Element\Text) {
20+
return;
21+
}
22+
23+
$fontStyle = $element->getFontStyle();
24+
$paragraphStyle = $element->getParagraphStyle();
25+
26+
if (!$this->withoutP) {
27+
$xmlWriter->startElement('p');
28+
if (is_string($paragraphStyle) && $paragraphStyle !== '') {
29+
$xmlWriter->writeAttribute('class', $paragraphStyle);
30+
}
31+
}
32+
33+
if (!empty($fontStyle)) {
34+
$xmlWriter->startElement('span');
35+
if (is_string($fontStyle)) {
36+
$xmlWriter->writeAttribute('class', $fontStyle);
37+
}
38+
}
39+
40+
$xmlWriter->text($element->getText());
41+
42+
if (!empty($fontStyle)) {
43+
$xmlWriter->endElement(); // span
44+
}
45+
46+
if (!$this->withoutP) {
47+
$xmlWriter->endElement(); // p
48+
}
49+
}
50+
}

src/PhpWord/Writer/EPub3/Part.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
/**
4+
* This file is part of PHPWord - A pure PHP library for reading and writing
5+
* word processing documents.
6+
*
7+
* PHPWord is free software distributed under the terms of the GNU Lesser
8+
* General Public License version 3 as published by the Free Software Foundation.
9+
*
10+
* For the full copyright and license information, please read the LICENSE
11+
* file that was distributed with this source code. For the full list of
12+
* contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
13+
*
14+
* @see https://github.com/PHPOffice/PHPWord
15+
*
16+
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
17+
*/
18+
19+
namespace PhpOffice\PhpWord\Writer\EPub3;
20+
21+
use PhpOffice\PhpWord\Exception\Exception;
22+
23+
/**
24+
* Factory class for EPub3 parts.
25+
*/
26+
class Part
27+
{
28+
/**
29+
* Get the fully qualified class name for a specific part type.
30+
*
31+
* @param string $type The type of part (Content, Manifest, Meta, Mimetype)
32+
*
33+
* @return string The fully qualified class name
34+
*/
35+
public static function getPartClass(string $type): string
36+
{
37+
$class = 'PhpOffice\\PhpWord\\Writer\\EPub3\\Part\\' . $type;
38+
39+
if (!class_exists($class)) {
40+
throw new Exception("Invalid part type: {$type}");
41+
}
42+
43+
return $class;
44+
}
45+
}

0 commit comments

Comments
 (0)