diff --git a/.gitattributes b/.gitattributes
index adf6e01..1fe9b0d 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -5,4 +5,3 @@ bin/ export-ignore
.gitignore export-ignore
composer.lock export-ignore
phpunit.xml export-ignore
-hamepub export-ignore
diff --git a/README.md b/README.md
index 3ec2d0f..21d709d 100755
--- a/README.md
+++ b/README.md
@@ -58,7 +58,7 @@ foreach( $contents as $key => $html ){
$factory->opf->setLang( 'en_US' );
$factory->opf->setTitle( 'My First eBook', 'main-title' );
$factory->opf->putXML();
-$factory->container->pubXML();
+$factory->container->putXML();
// Save it!
$factory->compile('path/to/epub');
```
@@ -71,6 +71,10 @@ $factory->compile('path/to/epub');
This library is under alpha and highly experimental. Do not trust this until Beta!
+## Acknowledgement
+
+The sample picture is credited by [Public Domain Pictures](https://www.pexels.com/ja-jp/photo/87742/) and [Nadi Lindsay](https://www.pexels.com/ja-jp/photo/3078831/).
+
## License
As wrote in LICENSE file, this library is released under [MIT](https://opensource.org/licenses/MIT).
diff --git a/bin/hamepub b/bin/hamepub
index d4ec737..c848b36 100755
--- a/bin/hamepub
+++ b/bin/hamepub
@@ -29,6 +29,15 @@ $app->registerCommand('generate', function( CommandCall $input ) use ($app, $roo
// This is not file.
$app->getPrinter()->error('Setting file not found.', false);
}
+ // Generate temp directory.
+ $tmp_dir = $input->hasParam( 'tmp' ) ? $input->getParam( 'tmp' ) : tempnam( sys_get_temp_dir(), 'hamepub-' );
+ try {
+ $packager = \Hametuha\HamePub\Packager::get();
+ $path = $packager->parse( $file, $tmp_dir );
+ $app->getPrinter()->success("Generated ePub: {$path}", false);
+ } catch ( \Exception $e ) {
+ $app->getPrinter()->error($e->getMessage(), false);
+ }
});
diff --git a/setting.json b/setting.json
new file mode 100644
index 0000000..07235aa
--- /dev/null
+++ b/setting.json
@@ -0,0 +1,28 @@
+{
+ "root": "./tests/dist/",
+ "id": "my-sample-ebook",
+ "isbn": "1234567890123",
+ "title": "My Sample Book",
+ "author": [
+ {
+ "name": "Kazuo Ishiguro",
+ "id": "author-1",
+ "role": "aut"
+ },
+ {
+ "name": "Haruki Murakami",
+ "id": "author-2",
+ "role": "aut"
+ },
+ {
+ "name": "Mike Jacob",
+ "id": "translator-1",
+ "role": "trl",
+ "type": "contributor"
+ }
+ ],
+ "target": "./tests/tmp",
+ "published": "2023-01-01T23:00:00Z",
+ "direction": "ltr",
+ "cover": "./tests/dist/img/cover.jpg"
+}
diff --git a/src/Hametuha/HamePub/Oebps/Content.php b/src/Hametuha/HamePub/Oebps/Content.php
index a996c61..8b61fe3 100755
--- a/src/Hametuha/HamePub/Oebps/Content.php
+++ b/src/Hametuha/HamePub/Oebps/Content.php
@@ -70,14 +70,39 @@ public function setTitle($string, $id, $type = 'main', $sequence = 1)
$meta['property'] = 'display-seq';
}
+ /**
+ * Add author to as meta value.
+ *
+ * @param string $value Name of author.
+ * @param string $id ID of author.
+ * @param string $tag Default is 'creator' or 'contributor'
+ * @param string $role Role tag. See {https://www.loc.gov/marc/relators/relaterm.html}
+ *
+ * @return void
+ */
+ public function addAuthor( $value, $id, $tag = 'creator', $role = '' ) {
+ $creator = $this->dom->metadata->addChild( $tag, $this->h( $value ), Schemas::DC );
+ $creator['id'] = $id;
+ if ( $role ) {
+ $meta = $this->dom->metadata->addChild( 'meta', $role );
+ $meta['refines'] = '#' . $id;
+ $meta['property'] = 'role';
+ $meta['scheme'] = 'marc:relators';
+ $meta['id'] = 'role-of-' . $id;
+ }
+ }
+
/**
* Add modified date
*
- * @param int $timestamp UTC timestamp
+ * @param int|string $timestamp If int, treated as UTC timestamp.
*/
public function setModifiedDate($timestamp)
{
- $this->addMeta('meta', date('Y-m-d\TH:i:s\Z', $timestamp), [
+ if ( is_int( $timestamp ) ) {
+ $timestamp = date('Y-m-d\TH:i:s\Z', $timestamp);
+ }
+ $this->addMeta('meta', $timestamp, [
'property' => 'dcterms:modified',
]);
}
@@ -104,7 +129,7 @@ public function addMeta($tag, $value, array $attributes = [])
}
/**
- * Add item to
+ * Add item to Content OPF.
*
* @param string $relative_path
* @param string $id If empty, path will convert to id
diff --git a/src/Hametuha/HamePub/Oebps/Toc.php b/src/Hametuha/HamePub/Oebps/Toc.php
index 762d0aa..754120b 100644
--- a/src/Hametuha/HamePub/Oebps/Toc.php
+++ b/src/Hametuha/HamePub/Oebps/Toc.php
@@ -85,7 +85,7 @@ public function getHTML($header = '', $footer = '')
HTML;
- $html .= $this->getNavHTML();
+ $html .= $this->getNavHTML( $title );
$html .= <<
diff --git a/src/Hametuha/HamePub/Packager.php b/src/Hametuha/HamePub/Packager.php
index fd54a7e..6e8f73f 100644
--- a/src/Hametuha/HamePub/Packager.php
+++ b/src/Hametuha/HamePub/Packager.php
@@ -2,11 +2,154 @@
namespace Hametuha\HamePub;
+use Hametuha\HamePub\Parser\SettingParser;
use Hametuha\HamePub\Pattern\Singleton;
+use PHPUnit\Framework\Error\Error;
/**
* Package ePub from directory.
*/
-class Packager extends Singleton
-{
+class Packager extends Singleton {
+
+ use SettingParser;
+
+ /**
+ * @var array Setting.
+ */
+ protected $setting = [];
+
+ /**
+ * @var string[] HTML strings.
+ */
+ protected $htmls = [];
+
+ /**
+ * @var string Newest modified HTML.
+ */
+ protected $newest = 0;
+
+ /**
+ * Parse and save setting.
+ *
+ * @param string $file Path to setting file.
+ * @param string $tmp_dir Temporary directory.
+ *
+ * @return string ePub file path.
+ * @throws \Exception
+ */
+ public function parse( $file = 'setting.json', $tmp_dir = '' ) {
+ $this->setting = $this->getSettingFromFile( $file );
+ // Set temporary directory.
+ if ( empty( $tmp_dir ) ) {
+ $tmp_dir = tempnam( sys_get_temp_dir(), 'hamepub-' );
+ }
+ // Load HTMLs.
+ $this->loadHtml();
+ if ( empty( $this->htmls ) ) {
+ throw new \Exception( 'No HTML files found.' );
+ }
+ // Start parsing.
+ $factory = Factory::init( $this->setting[ 'id' ], $tmp_dir );
+ // Set metadata.
+ $factory->opf->setLang( $this->setting[ 'lang' ] );
+ $factory->opf->setTitle( $this->setting[ 'title' ], 'main-title' );
+ $factory->opf->setModifiedDate( $this->setting['published'] );
+ $factory->opf->direction = $this->setting['direction'];
+ // Set authors.
+ if ( is_array( $this->setting['author'] ) ) {
+ foreach ( $this->setting['author'] as $index => $author ) {
+ $factory->opf->addAuthor(
+ $author['name'],
+ ( $author['id'] ?? sprintf( 'creator-%d', $index + 1 ) ),
+ ( ( isset( $author['type'] ) && $author['type'] === 'contributor' ) ? 'contributor' : 'creator'),
+ ( $author['role'] ?? '' )
+ );
+ }
+ } else {
+ $factory->opf->addAuthor( $this->setting['author'], 'creator' );
+ }
+ foreach ( $this->htmls as $key => $html ) {
+ // Register toc
+ $toc = $factory->toc->addChild( $key, $key . '.xhtml' );
+ // Grab all headers and add them to toc.
+ $dom = $factory->parser->html5->loadHTML( $html );
+ // Grab header and add ID attributes.
+ $factory->parser->grabHeaders( $toc, $dom, true, $this->setting[ 'header' ][ 'max_level' ], $this->setting[ 'header' ][ 'max_level' ] );
+ // Convert from dom object to string.
+ $html = $factory->parser->convertToString( $dom );
+ // Recreate DOM.
+ $dom = $factory->registerHTML( $key, $html, $this->getLinear( $key ) );
+ // Grab all images
+ foreach ( $factory->parser->extractAssets( $dom, 'img', 'src', $this->setting[ 'url_base' ], $this->setting[ 'root' ] ) as $path ) {
+ $factory->opf->addItem( $path, '' );
+ }
+ // Grab all CSS
+ foreach ( $factory->parser->extractAssets( $dom, 'link', 'href', $this->setting[ 'url_base' ], $this->setting[ 'root' ] ) as $path ) {
+ $factory->opf->addItem( $path, '' );
+ }
+ // Register to OPF
+ $factory->opf->addItem( "Text/{$key}.xhtml", "{$key}.xhtml" );
+ // Save HTML
+ $factory->parser->saveDom( $dom, "{$key}.xhtml" );
+ }
+ // If TOC is set, save it.
+ if ( !empty($this->setting['toc']) ) {
+ $factory->toc->label = $this->setting['toc'];
+ $toc_html = $factory->toc->getHTML();
+ $factory->opf->addItem( 'Text/toc.xhtml', 'toc.xhtml', ['nav'] );
+ $factory->parser->saveDom( $factory->registerHTML( 'toc', $toc_html, 'no' ), 'toc.xhtml' );
+ }
+ // Set OPF.
+ if ( ! empty( $this->setting[ 'isbn' ] ) ) {
+ $factory->opf->setIdentifier( $this->setting[ 'isbn' ] );
+ }
+ // If cover is set, add it.
+ if ( $this->setting['cover'] ) {
+ $factory->addCover( $this->setting['cover'] );
+ }
+ $factory->opf->putXML();
+ $factory->container->putXML();
+ // Save it!
+ $target = tempnam( $this->setting['target'], $this->setting['id'] . '-' ) . '.epub';
+ if ( ! is_writable( dirname( $target ) ) ) {
+ throw new \Exception( 'Target directory is not writable: ' . $target );
+ }
+ $factory->compile( $target );
+ return $target;
+ }
+
+ /**
+ * Get linear property.
+ *
+ * @param string $key HTML name.
+ * @return string
+ */
+ protected function getLinear( $key ) {
+ return in_array( $key, $this->setting['hidden'], true ) ? 'no' : 'yes';
+ }
+
+ /**
+ * Load HTML and save it in $htmls.
+ * @return void
+ */
+ public function loadHtml() {
+ $files = glob( $this->setting[ 'root' ] . '/*.html' );
+ foreach ( $files as $file ) {
+ $this->htmls[ basename( $file, '.html' ) ] = file_get_contents( $file );
+ // Save modified time.
+ $time = filemtime( $file );
+ if ( $time > $this->newest ) {
+ $this->newest = $time;
+ }
+ }
+ }
+
+ /**
+ * Dump setting for debugging.
+ *
+ * @return void
+ */
+ public function dumpSetting() {
+ var_dump( $this->setting, $this->htmls );
+ }
}
diff --git a/src/Hametuha/HamePub/Parser/SettingParser.php b/src/Hametuha/HamePub/Parser/SettingParser.php
new file mode 100644
index 0000000..daa6ccf
--- /dev/null
+++ b/src/Hametuha/HamePub/Parser/SettingParser.php
@@ -0,0 +1,67 @@
+defaultSetting(), $setting );
+ // Validate if isbn is not set.
+ if ( empty( $setting['id']) ) {
+ throw new \Exception( 'id fields must not be empty: ' . $setting['id'] );
+ }
+ // Validate if published is not set.
+ if ( ! preg_match( '/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/u', $setting['published' ] ) ) {
+ throw new \Exception( 'published field is malformed. Should be GMT in ISO8601 e.g. "2000-01-01T00:00:00Z": ' . $setting['published'] );
+ }
+ // Validate if author is set.
+ if ( empty( $setting['author'] ) ) {
+ throw new \Exception( 'At least 1 author should be set.' );
+ }
+ return $setting;
+ }
+
+ /**
+ * Default setting.
+ *
+ * @return array
+ */
+ public function defaultSetting() {
+ return [
+ 'lang' => 'en',
+ 'id' => '',
+ 'isbn' => '',
+ 'title' => '',
+ 'author' => '',
+ 'published' => '',
+ 'root' => './dist/',
+ 'header' => [
+ 'max_level' => 3,
+ 'depth' => 2,
+ ],
+ 'toc' => 'Table of Contents',
+ 'url_base' => '#\./#u',
+ 'target' => './tmp',
+ 'direction' => 'default',
+ 'hidden' => [ 'toc' ],
+ 'cover' => '',
+ ];
+ }
+}
diff --git a/src/Hametuha/HamePub/Pattern/Singleton.php b/src/Hametuha/HamePub/Pattern/Singleton.php
index 9d450bd..d2c16e5 100755
--- a/src/Hametuha/HamePub/Pattern/Singleton.php
+++ b/src/Hametuha/HamePub/Pattern/Singleton.php
@@ -7,7 +7,7 @@
*
* @package Hametuha\HamePub\Pattern
*/
-class Singleton
+abstract class Singleton
{
/**
* @var static[]
@@ -17,25 +17,32 @@ class Singleton
/**
* Constructor
*
- * @param array $settings
*/
- protected function __construct(array $settings = [])
+ protected function __construct()
{
- // Do nothing
+ $this->init();
}
/**
- * Get instance
+ * Executed in constructor.
*
- * @param array $settings
+ * @return void
+ */
+ protected function init()
+ {
+ // Do something.
+ }
+
+ /**
+ * Get instance
*
* @return static
*/
- public static function get(array $settings = [])
+ public static function get()
{
$class_name = get_called_class();
if (!isset(self::$instances[$class_name])) {
- self::$instances[$class_name] = new $class_name($settings);
+ self::$instances[$class_name] = new $class_name();
}
return self::$instances[$class_name];
}
diff --git a/tests/dist/chapter-1.html b/tests/dist/chapter-1.html
new file mode 100644
index 0000000..af1ed1a
--- /dev/null
+++ b/tests/dist/chapter-1.html
@@ -0,0 +1,129 @@
+
+
+
+
+ Chapter 1 | Lorem Ipsum
+
+
+
+
+
+
+
How Lipsum Starts
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed accumsan finibus diam et molestie. Mauris gravida
+ mi sed risus fermentum suscipit. Duis sodales turpis id neque finibus, non consectetur augue euismod. Praesent
+ ante metus, mattis eget commodo vel, sollicitudin sed urna. Morbi fringilla posuere velit non imperdiet. Donec
+ ac ex fringilla, congue arcu vehicula, egestas arcu. Quisque vitae lacus mauris. Nunc auctor nisi quis nulla
+ mollis condimentum. Fusce scelerisque suscipit suscipit. Duis commodo fringilla justo a varius. Aliquam bibendum
+ interdum mi, id mattis augue rutrum ac. Mauris volutpat semper nulla non scelerisque. Nunc purus eros, mollis ut
+ velit et, vehicula egestas mi. Fusce vestibulum interdum efficitur.
+
+
+ Duis non ante in libero facilisis semper. Cras turpis nibh, consectetur ullamcorper semper ut, ultrices gravida
+ odio. Proin non pulvinar elit. Nunc mattis, nunc eget lobortis pulvinar, neque nisl eleifend ex, in imperdiet
+ velit quam et dui. Nam molestie ex ac lectus gravida tristique. Mauris in dolor bibendum, posuere velit non,
+ egestas quam. Aliquam at metus sagittis, pharetra neque ut, vulputate tellus. Maecenas vitae mauris sed nunc
+ efficitur ullamcorper efficitur et lectus. Maecenas sed tempor dolor. Nulla dignissim eros ipsum, quis efficitur
+ nisi maximus et.
+
+
+ Integer nec mi consectetur, maximus mauris sed, mattis mauris. Vestibulum ipsum arcu, blandit vel consectetur
+ nec, tincidunt a quam. Duis tincidunt aliquet efficitur. Proin maximus, justo vel semper malesuada, sem nibh
+ ultrices purus, eget aliquet leo lacus a tortor. Etiam id nunc mollis, blandit est quis, faucibus odio.
+ Pellentesque ipsum lectus, euismod eget accumsan vel, blandit id dolor. Curabitur vel augue suscipit lacus
+ ultrices varius ullamcorper at odio. Duis in vehicula odio, vitae posuere quam. Pellentesque scelerisque justo
+ tristique est vestibulum venenatis. Maecenas ornare ut nulla nec malesuada.
+
+
Heading 2
+
+ Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nullam nunc tellus,
+ feugiat at risus vel, eleifend lacinia sem. Ut lacinia mattis dui sed vehicula. Mauris sed dui bibendum, mattis
+ augue vitae, egestas nunc. Suspendisse potenti. Nulla nec mattis eros, at bibendum turpis. Donec volutpat dolor
+ nec commodo imperdiet. Aenean leo odio, tincidunt id rutrum nec, pretium id dolor.
+
+
+ Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc mollis tellus purus,
+ vitae porta ex lacinia vitae. Nunc facilisis, eros ac rhoncus volutpat, tortor ante feugiat leo, non ullamcorper
+ dolor neque sit amet justo. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus
+ mus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Maecenas
+ ullamcorper consequat enim ac vulputate. Cras dapibus est eu nibh ultrices, vitae condimentum augue lacinia. Sed
+ venenatis est augue, in finibus velit vestibulum nec. Phasellus ornare mi id orci pharetra, ac tempus nibh
+ pellentesque. Morbi iaculis eu mauris quis accumsan. Phasellus sollicitudin molestie consequat. Phasellus
+ sodales varius hendrerit. Phasellus sit amet mi in urna consequat mattis non non massa.
+
+
Heading 3
+
+ Nam dictum quis dolor vitae aliquet. Fusce porttitor velit ex, in ultrices lectus vehicula id. Morbi eu sem
+ odio. Duis commodo ullamcorper molestie. Cras bibendum semper nibh, dictum tempus dui varius non. Donec in neque
+ neque. Integer at quam non dolor commodo bibendum id vel enim. Donec tristique est a leo porta, eu fermentum
+ lorem tempus. Fusce id lacinia justo.
+
+
+ Cras tempor vulputate efficitur. Vestibulum tempus odio nisl, vitae viverra felis placerat vitae. Nam luctus
+ feugiat mauris ut tristique. Nam et posuere lectus. Ut nisl orci, euismod sit amet elementum vel, venenatis a
+ dui. Aliquam iaculis eleifend viverra. Mauris in elementum nibh.
+
+
+ Donec eu nisl sed ante finibus tristique. Maecenas ac leo ante. Sed pretium mi sit amet dui viverra scelerisque
+ ac ut lorem. Quisque imperdiet tellus ex, vel pellentesque leo semper eget. Ut lectus nisl, feugiat eu suscipit
+ vitae, semper vitae enim. Praesent lobortis consequat sem, a faucibus nibh lacinia sed. Proin auctor eros in
+ nisl vestibulum eleifend. Etiam ultricies, mauris quis varius placerat, nisl lacus tincidunt lorem, quis gravida
+ justo leo sit amet sem. Aliquam in feugiat justo, eget euismod ligula. Nulla et maximus nulla, in scelerisque
+ lacus. Sed eu ipsum vel arcu pretium consequat. Ut maximus faucibus ipsum et semper. Cras porta efficitur ex ut
+ hendrerit. Sed condimentum et risus in tempor. Fusce vehicula viverra magna.
+
+
+ In a mauris viverra, malesuada orci id, pulvinar arcu. Nullam nibh ex, pulvinar eu commodo eget, consectetur
+ quis elit. Etiam pulvinar, quam et hendrerit molestie, sem diam tempus elit, in rutrum ante nunc et ante. Nunc
+ tincidunt volutpat pellentesque. Cras sapien ipsum, bibendum at pharetra vitae, scelerisque nec nibh. Fusce
+ sagittis dignissim nisl non malesuada. Cras vehicula nibh libero, at varius sem fermentum a.
+
+
Heading 3
+
+ Integer libero sem, rhoncus a posuere eget, mattis a tellus. Duis scelerisque ornare lectus, eu sodales odio
+ laoreet sed. Nunc egestas feugiat urna iaculis tempor. Cras vehicula libero et lacus consequat facilisis. Sed
+ non tempor sapien, ac rutrum neque. Etiam tristique quam eu felis rhoncus, at faucibus diam rhoncus. Etiam
+ rhoncus, purus eget mollis imperdiet, tortor neque interdum ex, quis fringilla nibh nunc in lectus. Curabitur
+ dignissim convallis ipsum sed pellentesque. Duis facilisis leo vestibulum neque blandit, id tincidunt nisl
+ venenatis. Etiam vitae pellentesque nunc, id efficitur nunc. Nam eget libero sit amet ante accumsan cursus vel
+ scelerisque libero. Morbi pharetra metus ut iaculis vestibulum.
+
+
Heading 3-1
+
+ Ut quis erat sed eros vehicula posuere at sed sem. Nulla at neque nibh. Vivamus et mauris id massa euismod
+ congue. Nulla mollis sem non velit ultrices, quis tempus ligula dictum. Aenean pharetra eget leo eget congue.
+ Etiam tempor condimentum nisl id rhoncus. Praesent auctor a odio ut posuere. Etiam eget venenatis felis. In hac
+ habitasse platea dictumst. Sed mattis varius ex eget bibendum. Interdum et malesuada fames ac ante ipsum primis
+ in faucibus. Phasellus libero ipsum, scelerisque ut lobortis at, tincidunt vel erat. Fusce ultrices accumsan
+ lacinia.
+
+
Heading 3-2
+
+ Quisque eu venenatis neque. Nullam viverra dui nulla, sed euismod risus volutpat ac. Duis tincidunt luctus urna,
+ sit amet pulvinar velit elementum ut. Duis condimentum mi a odio feugiat vestibulum. Proin vehicula ut magna id
+ congue. Suspendisse eu pellentesque nisi. Suspendisse ac nisi nec turpis sodales mollis. Nam gravida nunc id
+ erat molestie, eget volutpat justo condimentum.
+
+
Heading 3-3
+
+ Maecenas mattis ac justo id interdum. Mauris vulputate nisl quis mi commodo convallis. Aliquam sed laoreet
+ metus, sit amet ultrices eros. Morbi molestie ligula suscipit eros faucibus, at sodales sem sagittis. In id
+ turpis semper, finibus quam ut, cursus odio. Etiam diam nunc, volutpat non arcu ac, pretium convallis arcu. Sed
+ condimentum, urna vitae interdum interdum, sem quam varius nisl, nec molestie massa turpis a nisi. Vestibulum
+ scelerisque vestibulum leo vel tempus. Phasellus finibus auctor mauris eget ornare. Sed scelerisque iaculis erat
+ vitae vulputate.
+
+
Heading 3-4
+
+ Nullam leo dui, rhoncus at dui a, facilisis ultricies nisi. Duis dapibus nibh sed nibh fringilla aliquam eu nec
+ est. Sed bibendum ante in risus viverra, eu pretium sapien fermentum. Cras lacus mauris, congue a sodales vel,
+ pharetra consectetur lacus. Vivamus convallis accumsan dolor, vitae dictum lectus lobortis ac. Nulla lacus
+ tortor, molestie nec dapibus et, vulputate in ipsum. Maecenas semper orci aliquam ipsum iaculis, id faucibus
+ urna feugiat. Praesent ac sem et lectus pharetra mattis a vel massa. Nullam rhoncus dui non sapien vestibulum,
+ sed tincidunt eros auctor. Proin volutpat gravida dapibus.
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed accumsan finibus diam et molestie. Mauris gravida
+ mi sed risus fermentum suscipit. Duis sodales turpis id neque finibus, non consectetur augue euismod. Praesent
+ ante metus, mattis eget commodo vel, sollicitudin sed urna. Morbi fringilla posuere velit non imperdiet. Donec
+ ac ex fringilla, congue arcu vehicula, egestas arcu. Quisque vitae lacus mauris. Nunc auctor nisi quis nulla
+ mollis condimentum. Fusce scelerisque suscipit suscipit. Duis commodo fringilla justo a varius. Aliquam bibendum
+ interdum mi, id mattis augue rutrum ac. Mauris volutpat semper nulla non scelerisque. Nunc purus eros, mollis ut
+ velit et, vehicula egestas mi. Fusce vestibulum interdum efficitur.
+
+
+
+ This is a picture of the castle.
+
+
+ Duis non ante in libero facilisis semper. Cras turpis nibh, consectetur ullamcorper semper ut, ultrices gravida
+ odio. Proin non pulvinar elit. Nunc mattis, nunc eget lobortis pulvinar, neque nisl eleifend ex, in imperdiet
+ velit quam et dui. Nam molestie ex ac lectus gravida tristique. Mauris in dolor bibendum, posuere velit non,
+ egestas quam. Aliquam at metus sagittis, pharetra neque ut, vulputate tellus. Maecenas vitae mauris sed nunc
+ efficitur ullamcorper efficitur et lectus. Maecenas sed tempor dolor. Nulla dignissim eros ipsum, quis efficitur
+ nisi maximus et.
+
+
+
+ Integer nec mi consectetur, maximus mauris sed, mattis mauris. Vestibulum ipsum arcu, blandit vel
+ consectetur
+ nec, tincidunt a quam. Duis tincidunt aliquet efficitur. Proin maximus, justo vel semper malesuada, sem nibh
+ ultrices purus, eget aliquet leo lacus a tortor. Etiam id nunc mollis, blandit est quis, faucibus odio.
+ Pellentesque ipsum lectus, euismod eget accumsan vel, blandit id dolor. Curabitur vel augue suscipit lacus
+ ultrices varius ullamcorper at odio. Duis in vehicula odio, vitae posuere quam. Pellentesque scelerisque
+ justo
+ tristique est vestibulum venenatis. Maecenas ornare ut nulla nec malesuada.
+
+
+
The End of Lipsum
+
+ Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nullam nunc tellus,
+ feugiat at risus vel, eleifend lacinia sem. Ut lacinia mattis dui sed vehicula. Mauris sed dui bibendum, mattis
+ augue vitae, egestas nunc. Suspendisse potenti. Nulla nec mattis eros, at bibendum turpis. Donec volutpat dolor
+ nec commodo imperdiet. Aenean leo odio, tincidunt id rutrum nec, pretium id dolor.
+
+
+
+
+
diff --git a/tests/dist/css/style.css b/tests/dist/css/style.css
new file mode 100644
index 0000000..0f92b20
--- /dev/null
+++ b/tests/dist/css/style.css
@@ -0,0 +1,13 @@
+body {
+ color: #444;
+ font-weight: 400;
+}
+
+img {
+ max-width: 100%;
+ height: auto;
+}
+
+figcaption {
+ text-align: center;
+}
diff --git a/tests/dist/img/cover.jpg b/tests/dist/img/cover.jpg
new file mode 100644
index 0000000..bb3b055
Binary files /dev/null and b/tests/dist/img/cover.jpg differ
diff --git a/tests/dist/img/sample.jpg b/tests/dist/img/sample.jpg
new file mode 100644
index 0000000..b03037b
Binary files /dev/null and b/tests/dist/img/sample.jpg differ