diff --git a/.gitignore b/.gitignore
index 0b9d0608d0..6918df72e6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@ _build
/build
phpunit.xml
composer.phar
+composer.lock
vendor
/report
/build
diff --git a/composer.json b/composer.json
index efebe941e7..48fc16caed 100644
--- a/composer.json
+++ b/composer.json
@@ -108,13 +108,13 @@
"require": {
"php": "^7.1|^8.0",
"ext-dom": "*",
- "ext-gd": "*",
"ext-json": "*",
"ext-xml": "*",
- "ext-zip": "*",
"phpoffice/math": "^0.2"
},
"require-dev": {
+ "ext-zip": "*",
+ "ext-gd": "*",
"ext-libxml": "*",
"dompdf/dompdf": "^2.0 || ^3.0",
"friendsofphp/php-cs-fixer": "^3.3",
diff --git a/src/PhpWord/IOFactory.php b/src/PhpWord/IOFactory.php
index 55c374a8b9..50c419cae2 100644
--- a/src/PhpWord/IOFactory.php
+++ b/src/PhpWord/IOFactory.php
@@ -36,7 +36,7 @@ abstract class IOFactory
*/
public static function createWriter(PhpWord $phpWord, $name = 'Word2007')
{
- if ($name !== 'WriterInterface' && !in_array($name, ['ODText', 'RTF', 'Word2007', 'HTML', 'PDF'], true)) {
+ if ($name !== 'WriterInterface' && !in_array($name, ['ODText', 'RTF', 'Word2007', 'HTML', 'PDF', 'EPub3'], true)) {
throw new Exception("\"{$name}\" is not a valid writer.");
}
diff --git a/src/PhpWord/Writer/EPub3.php b/src/PhpWord/Writer/EPub3.php
new file mode 100644
index 0000000000..b9eb5a00f2
--- /dev/null
+++ b/src/PhpWord/Writer/EPub3.php
@@ -0,0 +1,91 @@
+setPhpWord($phpWord);
+
+ // Create parts
+ $this->parts = [
+ 'Mimetype' => 'mimetype',
+ 'Content' => 'content.opf',
+ 'Toc' => 'toc.ncx',
+ 'Styles' => 'styles.css',
+ 'Manifest' => 'META-INF/container.xml',
+ ];
+ foreach (array_keys($this->parts) as $partName) {
+ $partClass = static::class . '\\Part\\' . $partName;
+ if (class_exists($partClass)) {
+ /** @var AbstractPart $partObject Type hint */
+ $partObject = new $partClass();
+ $partObject->setParentWriter($this);
+ $this->writerParts[strtolower($partName)] = $partObject;
+ }
+ }
+
+ // Set package paths
+ $this->mediaPaths = ['image' => 'Images/', 'object' => 'Objects/'];
+ }
+
+ /**
+ * Save PhpWord to file.
+ */
+ public function save(string $filename): void
+ {
+ $filename = $this->getTempFile($filename);
+ $zip = $this->getZipArchive($filename);
+
+ // Add section media files
+ $sectionMedia = Media::getElements('section');
+ if (!empty($sectionMedia)) {
+ $this->addFilesToPackage($zip, $sectionMedia);
+ }
+
+ // Write parts
+ foreach ($this->parts as $partName => $fileName) {
+ if ($fileName === '') {
+ continue;
+ }
+ $part = $this->getWriterPart($partName);
+ if (!$part instanceof AbstractPart) {
+ continue;
+ }
+
+ $zip->addFromString($fileName, $part->write());
+ }
+
+ // Close zip archive and cleanup temp file
+ $zip->close();
+ $this->cleanupTempFile();
+ }
+}
diff --git a/src/PhpWord/Writer/EPub3/Part/AbstractPart.php b/src/PhpWord/Writer/EPub3/Part/AbstractPart.php
new file mode 100644
index 0000000000..c3b79b2d26
--- /dev/null
+++ b/src/PhpWord/Writer/EPub3/Part/AbstractPart.php
@@ -0,0 +1,63 @@
+parentWriter = $writer;
+
+ return $this;
+ }
+
+ /**
+ * Get parent writer.
+ *
+ * @return AbstractWriter
+ */
+ public function getParentWriter()
+ {
+ return $this->parentWriter;
+ }
+
+ /**
+ * Write part content.
+ *
+ * @return string
+ */
+ abstract public function write();
+}
diff --git a/src/PhpWord/Writer/EPub3/Part/Content.php b/src/PhpWord/Writer/EPub3/Part/Content.php
new file mode 100644
index 0000000000..add42b35c5
--- /dev/null
+++ b/src/PhpWord/Writer/EPub3/Part/Content.php
@@ -0,0 +1,49 @@
+';
+ $content .= '';
+ $content .= '';
+ $content .= 'Sample EPub3 Document';
+ $content .= 'en';
+ $content .= '';
+ $content .= '';
+ $content .= ' ';
+ $content .= '';
+ $content .= '';
+ $content .= '';
+ $content .= '';
+ $content .= '';
+
+ return $content;
+ }
+}
diff --git a/src/PhpWord/Writer/EPub3/Part/Manifest.php b/src/PhpWord/Writer/EPub3/Part/Manifest.php
new file mode 100644
index 0000000000..9c5143f434
--- /dev/null
+++ b/src/PhpWord/Writer/EPub3/Part/Manifest.php
@@ -0,0 +1,42 @@
+';
+ $content .= '';
+ $content .= '';
+ $content .= '';
+ $content .= '';
+ $content .= '';
+
+ return $content;
+ }
+}
diff --git a/src/PhpWord/Writer/EPub3/Part/Meta.php b/src/PhpWord/Writer/EPub3/Part/Meta.php
new file mode 100644
index 0000000000..04531f45d2
--- /dev/null
+++ b/src/PhpWord/Writer/EPub3/Part/Meta.php
@@ -0,0 +1,43 @@
+';
+ $content .= '';
+ $content .= 'Sample EPub3 Document';
+ $content .= 'en';
+ $content .= 'urn:uuid:12345';
+ $content .= '2023-01-01T00:00:00Z';
+ $content .= '';
+
+ return $content;
+ }
+}
diff --git a/src/PhpWord/Writer/EPub3/Part/Mimetype.php b/src/PhpWord/Writer/EPub3/Part/Mimetype.php
new file mode 100644
index 0000000000..36dc9168b1
--- /dev/null
+++ b/src/PhpWord/Writer/EPub3/Part/Mimetype.php
@@ -0,0 +1,35 @@
+parentWriter = $writer;
+
+ return $this;
+ }
+
+ /**
+ * Get parent writer.
+ *
+ * @return AbstractWriter
+ */
+ public function getParentWriter()
+ {
+ return $this->parentWriter;
+ }
+
+ /**
+ * Write style content.
+ *
+ * @return string
+ */
+ abstract public function write();
+}
diff --git a/src/PhpWord/Writer/EPub3/Style/Font.php b/src/PhpWord/Writer/EPub3/Style/Font.php
new file mode 100644
index 0000000000..3f3ffd1544
--- /dev/null
+++ b/src/PhpWord/Writer/EPub3/Style/Font.php
@@ -0,0 +1,41 @@
+getPhpWord());
+ self::assertEquals('./', $object->getDiskCachingDirectory());
+ foreach (['Content', 'Manifest', 'Mimetype'] as $part) {
+ self::assertInstanceOf(
+ "PhpOffice\\PhpWord\\Writer\\Epub3\\Part\\{$part}",
+ $object->getWriterPart($part)
+ );
+ self::assertInstanceOf(
+ 'PhpOffice\\PhpWord\\Writer\\Epub3',
+ $object->getWriterPart($part)->getParentWriter()
+ );
+ }
+ }
+
+ /**
+ * Test construction with null.
+ */
+ public function testConstructWithNull(): void
+ {
+ $this->expectException(\PhpOffice\PhpWord\Exception\Exception::class);
+ $this->expectExceptionMessage('No PhpWord assigned.');
+ $object = new EPub3();
+ $object->getPhpWord();
+ }
+
+ /**
+ * Test saving document.
+ */
+ public function testSave(): void
+ {
+ $imageSrc = __DIR__ . '/../_files/images/PhpWord.png';
+ $file = __DIR__ . '/../_files/temp.epub';
+
+ $phpWord = new PhpWord();
+ $section = $phpWord->addSection();
+ $section->addText('Test 1');
+ $section->addTextBreak();
+ $section->addText('Test 2', null, ['alignment' => Jc::CENTER]);
+ $section->addLink('https://github.com/PHPOffice/PHPWord');
+ $section->addTitle('Test', 1);
+ $section->addPageBreak();
+ $section->addImage($imageSrc);
+ $writer = new EPub3($phpWord);
+ $writer->save($file);
+ self::assertFileExists($file);
+ unlink($file);
+ }
+
+ /**
+ * Test PHP output.
+ */
+ public function testSavePhpOutput(): void
+ {
+ $phpWord = new PhpWord();
+ $section = $phpWord->addSection();
+ $section->addText('Test');
+ $writer = new EPub3($phpWord);
+ ob_start();
+ $writer->save('php://output');
+ $contents = ob_get_contents();
+ self::assertTrue(ob_end_clean());
+ self::assertNotEmpty($contents);
+ }
+
+ /**
+ * Test disk caching.
+ */
+ public function testSetGetUseDiskCaching(): void
+ {
+ $object = new EPub3();
+ $object->setUseDiskCaching(true, PHPWORD_TESTS_BASE_DIR);
+ self::assertTrue($object->isUseDiskCaching());
+ self::assertEquals(PHPWORD_TESTS_BASE_DIR, $object->getDiskCachingDirectory());
+ }
+
+ /**
+ * Test disk caching exception.
+ */
+ public function testSetUseDiskCachingException(): void
+ {
+ $this->expectException(\PhpOffice\PhpWord\Exception\Exception::class);
+ $dir = implode(DIRECTORY_SEPARATOR, [PHPWORD_TESTS_BASE_DIR, 'foo']);
+
+ $object = new EPub3();
+ $object->setUseDiskCaching(true, $dir);
+ }
+}