Nenhum produto encontrado nessa seleção.

Provavelmente você já deve ter lido algum livro digital no formato EPUB (Electronic Publication) um formato de arquivo digital padrão específico para ebooks mantido pela idpf. Este é um formato muito adotado nos leitores de livros digitais atuais, como Calibre e até mesmo a Google Play Books, do Android (além de outros milhares).

A parte interessante dos arquivos no formato EPUB é que suas páginas são inteiramente criadas com XHTML, o que facilita a vida dos desenvolvedores front-end. Eles incluem até mesmo um arquivo CSS, para estilizar as páginas conforme preferir.

Neste artigo vou explicar como extrair ou criar livros EPUB manualmente e em seguida vou apresentar uma classe que criei para gerar livros EPUB com PHP.

Então vamos lá.

Como extrair livros EPUB manualmente

Quando você lê um livro com a extensão EPUB, na verdade está lendo um pacote compactado (zipado) com a extensão modificada de .zip para .epub. Isso significa que os arquivos que estão dentro desse pacote podem ser extraídos e manipulados.

Para extrair tais arquivos basta modificar a extensão do arquivo de .epub para .zip e, em seguida, descompactar a pasta do mesmo.

Criei um vídeo tutorial para um blog de tecnologia há algum tempo detalhando como fazer todo o processo. Assista abaixo.

Agora vamos ver como criar livros EPUB manualmente.

Como criar livros EPUB manualmente

Não será necessário utilizar nenhum programa adicional para criar seu livro, com exceção de um editor de textos HTML. Minha recomendação é o Notepad++.

Você vai precisar de um livro digital EPUB para servir como base de arquivos para seu projeto. Você pode extrair seu próprio livro e tentar seguir o tutorial utilizando ele, ou pode baixar o exemplo que vou utilizar para este tutorial no endereço abaixo.

Guarde bem este livro, já que ele servirá como base para todos os livros EPUB que desejar criar.

Estrutura dos arquivos EPUB

Antes de qualquer coisa, temos que saber algumas partes importantes para a criação do seu livro digital, tais como arquivos necessários e a estrutura dos mesmos.

Se você baixou nosso arquivo de exemplo (epub.zip), extraia seu conteúdo e você terá uma pasta chamada “Epub“.

Dentro dessa pasta temos a seguinte estrutura de arquivos:

| mimetype
|
---META-INF
	| container.xml
	|
---OEBPS
	| content.opf
	| css.css
	| pagina1.xhtml
	| pagina2.xhtml
	| toc.ncx
	|
	---images
		imagem1.jpg

Calma que eu já vou explicar.

A pasta EPUB

Nela existem mais duas pastas: META-INF e OEBPS.

O arquivo mimetype

Também existe um arquivo mimetype sem extensão nenhuma. Este arquivo contém apenas uma linha indicando exatamente o mimetype do arquivo EPUB:

application/epub+zip

A pasta META-INF

Dentro da pasta META-INF existe apenas um arquivo chamado container.xml. Este arquivo XML indica onde está o arquivo OPF, o qual lista todos os arquivos presentes no seu EPUB.

Não é necessário alterá-lo, mas veja seu conteúdo:

<?xml version="1.0" encoding="UTF-8" ?>
<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
	<rootfiles>
		<rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml"/>
	</rootfiles>
</container>

Observação: A pasta META-INF e o arquivo mimetype não precisam ser editados caso você mantenha a estrutura de arquivos da pasta que disponibilizei para download.

A pasta OEBPS

A outra pasta que ainda não falamos (OEBPS) é onde o baile irá ocorrer!

Nela teremos todas as páginas do seu livro, o arquivo CSS, a pasta de imagens, o arquivo OPF e o arquivo NCX, vamos falar deles posteriormente.

A pasta images

Essa pasta é simplesmente o local onde você salva as imagens que vai utilizar em seu livro.

Conteúdo do livro EPUB

Como você já deve ter percebido, deixei os arquivos da pasta OEBPS para serem explicados agora. Isso porque eles são os arquivos que vão criar seu livro. Alguns para a parte de estilização (como o CSS), outros serão as páginas do livro (os arquivos XHTML), outros a configuração do seu livro (content.opf e toc.ncx).

Vamos começar visualizando o arquivo que vai configurar seu livro, a tabela de conteúdo (toc.ncx). Vou postar abaixo seu conteúdo, em seguida vou explicar as partes importantes que devem ser alteradas.

O arquivo toc.ncx

<?xml version="1.0" encoding="UTF-8"?>
<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1">
	<head>
		<meta name="dtb:uid" content="teoteste"/>
		<meta name="dtb:depth" content="1"/>
		<meta name="dtb:totalPageCount" content="0"/>
		<meta name="dtb:maxPageNumber" content="0"/>
	</head>
	<docTitle>
		<text>TEO test</text>
	</docTitle>
	<navMap>
		<navPoint id="pagina1" playOrder="1">
			<navLabel>
				<text>Página 1</text>
			</navLabel>
			<content src="pagina1.xhtml"/>
		</navPoint>
		<navPoint id="pagina2" playOrder="2">
			<navLabel>
				<text>Página 2</text>
			</navLabel>
			<content src="pagina2.xhtml"/>
		</navPoint>
	</navMap>
</ncx>

Muita coisa, não é? Mas vamos quebrar tudo em partes e ver o que realmente importa.

UID

Na parte da <head>, a única linha que deve ser alterada é a linha que identifica seu livro:

<meta name="dtb:uid" content="IDDOLIVRO"/>

Você deve alterar IDDOLIVRO para o ID único do seu livro. E quando digo único, é único mesmo, algo como 2eb60380-b2d7-11e3-a5e2-0800200c9a66 estaria de bom tamanho.

Caso queira gerar um ID único para seu livro, você pode acessar esta ferramenta ou fazer como preferir.

docTitle
<docTitle>
	<text><strong>Tutsup test</strong></text>
</docTitle>

Este é o título do seu livro, basta substituir Tutsup test para o título que deseja.

navMap

Este é o local onde você adiciona as páginas do seu livro. Cada página deverá ter uma sessão parecida com o trecho abaixo:

<navPoint id="pagina1" playOrder="1">
	<navLabel>
		<text>Página 1</text>
	</navLabel>
	<content src="pagina1.xhtml"/>
</navPoint>

O id, playorder e o content deverão ser diferentes para cada página. Por exemplo, para um livro com três páginas, o código ficaria assim:

<navPoint id="pagina1" playOrder="1">
	<navLabel>
		<text>Título da Página 1</text>
	</navLabel>
	<content src="pagina1.xhtml"/>
</navPoint>

<navPoint id="pagina2" playOrder="2">
	<navLabel>
		<text>Título da Página 2</text>
	</navLabel>
	<content src="pagina2.xhtml"/>
</navPoint>

<navPoint id="pagina3" playOrder="3">
	<navLabel>
		<text>Título da Página 3</text>
	</navLabel>
	<content src="pagina3.xhtml"/>
</navPoint>

Perceba os números que utilizei para identificar cada página em cada uma das sessões. Você não precisa utilizar números se cada identificador for diferente para cada página, contudo, creio que é a melhor maneira de organização.

Outra observação importante: A tag content indica o local do arquivo XHTML que está sua página, este será explicado posteriormente.

O arquivo content.opf

O arquivo content.opf  “diz” ao seu EPUB quais são os arquivos que você vai utilizar no seu livro. Todos eles deverão ser listados nele, todas as páginas, todas as imagens, o arquivo CSS, o arquivo NCX, tudo deve ser listado nele. Além disso, ainda temos mais algumas tags que deverão ser preenchidas.

Vamos ver o arquivo como um todo, em seguida pegamos pequenas partes do mesmo para explicação.

Content.opf
<?xml version="1.0" encoding="UTF-8"??>
<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="BookID" version="2.0" >
	<metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">
		<dc:title>Nome do livro</dc:title>
		<dc:creator opf:role="aut">Criador</dc:creator>
		<dc:language>pt</dc:language>
		<dc:rights>Public Domain</dc:rights>
		<dc:publisher>todoespacoonline.com/w</dc:publisher>
		<dc:identifier id="BookID" opf:scheme="UUID">IDDOLIVRO</dc:identifier>
	</metadata>
	<manifest>
		<item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml" />
		<item id="style" href="css.css" media-type="text/css" />
		<item id="pagina1" href="pagina1.xhtml" media-type="application/xhtml+xml" />
		<item id="pagina2" href="pagina2.xhtml" media-type="application/xhtml+xml" />
		<item id="imgl" href="images/sample.png" media-type="image/png" />
	</manifest>
	<spine toc="ncx">
		<itemref idref="pagina1" />
		<itemref idref="pagina2" />
	</spine>
</package>

As tags da parte superior do documento são autoexplicativas, nome do livro, criador, idioma e demais. Porém, a última tag (identifier) é o mesmo UID que você gerou para seu livro.

<dc:identifier id="BookID" opf:scheme="UUID">IDDOLIVRO</dc:identifier>

Portanto, substitua IDDOLIVRO para o seu UUID.

manifest

A parte das tags <manifest></manifest> deverão ser alteradas, nela você indica todos os arquivos que estão presentes no seu EPUB seguindo o padrão abaixo:

<manifest>
	<item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml" />
	<item id="style" href="css.css" media-type="text/css" />
	<item id="pagina1" href="pagina1.xhtml" media-type="application/xhtml+xml" />
	<item id="pagina2" href="pagina2.xhtml" media-type="application/xhtml+xml" />
	<item id="imgl" href="images/sample.png" media-type="image/png" />
</manifest>

Se você quiser continuar seguindo nosso exemplo, você só vai precisar adicionar as páginas e as imagens, o que diminui suas alterações para:

<item id="pagina1" href="pagina1.xhtml" media-type="application/xhtml+xml" />
<item id="pagina2" href="pagina2.xhtml" media-type="application/xhtml+xml" />
<item id="imgl" href="images/sample.png" media-type="image/png" />

Cada página e imagem deverá ser listada como descrito acima.

spine

A parte entre as tags <spine></spine> você só precisa listar o ID das páginas, como no exemplo:

<spine toc="ncx">
	<itemref idref="pagina1" />
	<itemref idref="pagina2" />
</spine>

Para cada página deverá ser criado um idref, como no código acima.

Os arquivos XHTML

Os arquivos XHTML são bastante parecidos com arquivos HTML comum, no entanto, não devem existir tags sem fechamento. Isso quer dizer que só serão permitidas tags como <p></p>, <b></b>, <img />, e demais.

Na verdade, existe uma lista gigante que mostra todas as tags que podem ser utilizadas.

Veja o exemplo da nossa página:

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
	<meta content="application/xhtml+xml; charset=utf-8" http-equiv="Content-Type"/>
	<link href="css.css" type="text/css" rel="stylesheet"/>
	<title>Página 2</title>
</head>
<body>
	<p>Oi, sou a página 2. <a href="pagina1.xhtml">Clique aqui para ir para a página 1</a>.</p>
</body>
</html>

A parte inicial deverá ser mantida:

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
	<meta content="application/xhtml+xml; charset=utf-8" http-equiv="Content-Type"/>
	<link href="css.css" type="text/css" rel="stylesheet"/>
	<title>Página 2</title>
</head>

No entanto, o título “Página 2“, poderá ser alterado para o título correto da sua página.

A segunda parte:

<body>
	<p>Oi, sou a página 2. <a href="pagina1.xhtml">Clique aqui para ir para a página 1</a>.</p>
</body>
</html>

Poderá ser alterada dentro da tag <body></body>. Por exemplo, nosso texto visível para o leitor será apenas:

<p>Oi, sou a página 2. <a href="pagina1.xhtml">Clique aqui para ir para a página 1</a>.</p>

O arquivo css.css

O CSS é simplesmente um CSS comum, veja abaixo o nosso arquivo css.css

body {
	margin-left: .5em;
	margin-right: .5em;
	text-align: left;
	direction: ltr;
	font-family: arial;
	direction: ltr;
	font-size:12pt;
	font-weight:400;
}

Básico do básico.

Compactando e transformando em EPUB

Agora vem a parte boa, você não precisa de programa nenhum para transformar todos estes arquivos que criamos em um livro EPUB, veja como é simples.

  1. Clique com o botão direito do mouse sobre a pasta que tem os arquivos do livro;
  2. Passe o mouse sobre “Enviar para” e clique em “Pasta compactada”. Isso deverá criar um arquivo com o nome da sua pasta, mais a extensão .zip;
  3. Clique com o botão direito do mouse sobre a pasta e selecione a opção “Renomear”;
  4. Troque a extensão .zip para .epub e pronto

Classe PHP para gerar livros EPUB

Para facilitar sua vida, criei uma classe PHP chamada TPEpubCreator que gera os livros EPUB para você, então não será necessário passar por todo o processo que descrevi acima para criar seus e-books EPUB.

Antes de mais nada, vamos ver onde você pode conseguir a classe e como contribuir com o projeto.

Download da classe TPEpubCreator

Caso queira fazer o download da classe TPEpubCreator.php, acesse o link abaixo:

php-epub-creator.zip

Ou você pode acessar pelo Github e contribuir com o projeto:

php-epub-creator.zip

Você escolhe.

Conteúdo da classe TPEpubCreator

A classe faz tudo por você, ou seja, cria todas as pastas e arquivos. Ela também gera os arquivos XHTML, você só precisa enviar o conteúdo dos mesmos.

Veja a classe completa:

<?php
/**
 * TPEpubCreator - PHP EPUB Creator
 *
 * This PHP class creates e-books using the EPUB standard format. An example can
 * be found at ../index.php.
 *
 * @package  TPEpubCreator
 * @author   Luiz Otávio Miranda <[email protected]/w>
 * @version  $Revision: 1.0.0 $
 * @access   public
 * @see      http://www.todoespacoonline.com/w/ 
 */
class TPEpubCreator
{
    /**
     * This is the abspath for this file
     *
     * @access private
     * @var string
     * @since 1.0.0
     */
    private $abspath;
    
    /**
     * This is the cover img path
     *
     * @access private
     * @var string
     * @since 1.0.0
     */
    private $cover_img;
    
    /**
     * This is the content.opf file
     *
     * @access private
     * @var array
     * @since 1.0.0
     */
    private $opf = array();
    
    /**
     * This is the toc.ncx file
     *
     * @access private
     * @var array
     * @since 1.0.0
     */
    private $ncx = array();

    /**
     * This is the pages array
     *
     * @access private
     * @var array
     * @since 1.0.0
     */
    private $pages = array();
    
    /**
     * This is the images array
     *
     * @access private
     * @var array
     * @since 1.0.0
     */
    private $images = array();
    
    /**
     * This is the new images array
     *
     * @access private
     * @var array
     * @since 1.0.0
     */
    private $new_images = array();
    
    /**
     * This is to check if a cover has been added
     *
     * @access private
     * @var bool
     * @since 1.0.0
     */
    private $cover;
    
    /**
     * This is our errors output
     *
     * @access public
     * @var string
     * @since 1.0.0
     */
    public $error;
    
    /**
     * This is the book's uuid
     *
     * @access public
     * @var string
     * @since 1.0.0
     */
    public $uuid;
    
    /**
     * This is the book's title
     *
     * @access public
     * @var string
     * @since 1.0.0
     */
    public $title = 'Untitled';
    
    /**
     * This is the book's creator
     *
     * @access public
     * @var string
     * @since 1.0.0
     */
    public $creator = 'Tutsup.com';
    
    /**
     * This is the book's language
     *
     * @access public
     * @var string
     * @since 1.0.0
     */
    public $language = 'pt';
    
    /**
     * This is the book's rights
     *
     * @access public
     * @var string
     * @since 1.0.0
     */
    public $rights = 'Public Domain';
    
    /**
     * This is the book's publisher
     *
     * @access public
     * @var string
     * @since 1.0.0
     */
    public $publisher = 'http://www.todoespacoonline.com/w/';
    
    /**
     * This is the book's css
     *
     * @access public
     * @var string
     * @since 1.0.0
     */
    public $css;
    
    /**
     * This is the temp folder used to store the book's files before zip it
     *
     * @access public
     * @var string
     * @since 1.0.0
     */
    public $temp_folder;
    
    /**
     * This is the path to output the epub file
     *
     * @access public
     * @var string
     * @since 1.0.0
     */
    public $epub_file;
    
    /**
     * This is the container.xml file
     *
     * @access public
     * @var string
     * @since 1.0.0
     */
    public $container;
    
    /**
     * This is the key for the pages array
     *
     * @access public
     * @var int
     * @since 1.0.0
     */
    public $key = 0;
    
    /**
     * This is the key for the images array
     *
     * @access public
     * @var int
     * @since 1.0.0
     */
    public $image_key = 0;
    
    /**
     * Constructor.
     *
     * This only sets the abspath and uuid
     *
     * @since 1.0.0
     * @access public
     *
     */   
    public function __construct () {
        $this->abspath = dirname( __FILE__ );
        $this->uuid = md5( microtime() );
    }
    
    /**
     * Add Image
     *
     * This stores the images in the images array and set the cover.
     *
     * @since 1.0.0
     * @access public
     *
     * @param string $path Image's Path
     * @param string $type Image's Mime-type
     * @param bool $cover Whether it will or will not be a cover
     *
     * @return bool false if the image does not exists
     */    
    public function AddImage( $path = false, $type = false, $cover = 0 ) {
        $this->image_key++;
        
        // Checks if the image exists first
        /*if ( ! file_exists( $path ) ) {
            $this->error = 'Cannot find image ' . $path . '.';
            return;
        }*/
        
        $this->images[$this->image_key]['path'] = $path;
        $this->images[$this->image_key]['type'] = $type;
        $this->images[$this->image_key]['cover'] = $cover;
    }
    
    /**
     * Add Page
     *
     * This stores the pages in the pages array. It'll store the XHTML content
     * for the page, but won't check or parse it.
     *
     * @since 1.0.0
     * @access public
     *
     * @param string $content Page content (XHTML)
     * @param string $file A file that has the page content (XHTML)
     * @param string $title Page's title
     * @param bool $download_images Whether to download images from the HTML or not
     *
     * @return bool false if the image does not exists
     */    
    public function AddPage( 
        $content = null, 
        $file = null, 
        $title = 'Untitled',
        $download_images = false
    ) {
        // Set the key for the page
        $this->key++;
        
        // If nothing to add, nothing to do
        if ( ! $content && ! $file ) {
            $this->error = 'No content or file added.';
            return;
        }
        
        // If it's XHTML
        if ( $content ) {
            $this->pages[$this->key]['content'] = $content;
        }
        
        // If it's a file
        if ( $file ) {
            // If the file does not exists, won't do anything
            if ( ! file_exists( $file ) ) {
                $this->error = "File {$file} does not exists.";
            }
            
            $file = file_get_contents( $file );
            $this->pages[$this->key]['content'] = $file;
        }
        
        // If the $download_images param is set to true, we'll try to download
        // images found and add it to you e-book
        if ( $download_images ) {
            $found_images = preg_match_all(
                '/(<img.*?src=['|"])(.*?)(['|"].*?>)/mis', 
                $this->pages[$this->key]['content'], 
                $image_matches
            );
            
            // Just need the URLs
            if ( $found_images ) {
                if ( ! empty( $image_matches[2] ) ) {
                    foreach ( $image_matches[2] as $img ) {
                        $this->AddImage( $img );
                    }
                }
            }
        }
        
        // Set the page title
        $this->pages[$this->key]['title'] = $title;        
    }
    
    /**
     * Create EPUB
     *
     * This creates the epub file. It uses lots of other methods to accomplish
     * its task.
     *
     * @since 1.0.0
     * @access public
     */        
    public function CreateEPUB() {
        // Creates all the folders needed
        $this->CreateFolders();
        
        // If there's no error we're good to go
        if ( $this->error ) {
            return;
        }
        
        // Open the content.opf file
        $this->OpenOPF();
        
        // Open the toc.ncx file
        $this->OpenNCX();
        
        // Open the css.css file
        $this->OpenCSS();
        
        // Variables needed to put everything in the right place
        $ncx = null;
        $opf = null;
        $fill_opf_spine = null;
        
        // Loop the pages array and fill the content.opf and toc.ncx content
        foreach( $this->pages as $key => $value ) {
            // The page
            $page = 'page' . $key;
            
            // OPF
            $opf .= '<item id="' . $page . '" href="' . $page . '.xhtml" media-type="application/xhtml+xml" />' . "rn";
            
            // NCX
            $ncx  .= '<navPoint id="' . $page . '" playOrder="' . $key . '">' . "rn";
            $ncx .= '<navLabel>' . "rn";
            $ncx .= '<text>' . $value['title'] . '</text>' . "rn";
            $ncx .= '</navLabel>' . "rn";
            $ncx .= '<content src="' . $page . '.xhtml"/>' . "rn";
            $ncx .= '</navPoint>' . "rn";
            
            // Fill the spine
            $fill_opf_spine .= '<itemref idref="' . $page . '" />' . "rn";
        }
        
        // If there are images, loop the values
        if ( ! empty( $this->images ) ) {
            foreach( $this->images as $image_key => $image_value ) {
                
                // New image have the same name as the old one
                $new_image  = $this->temp_folder . '/OEBPS/images/';
                $new_image .= mt_rand(0,9999) . '_';
                $new_image .= basename( $image_value['path'] );
                
                // Mime-type
                $image_type = $image_value['type'];
                
                // If we don't have a mimetype for the image
                // We'll try to get it
                if ( ! $image_type ) {
                    $image_type = getimagesize( $image_value['path'] );
                    $image_type = $image_type['mime'];
                }
                
                // Try to copy the image
                if ( ! @copy( $image_value['path'], $new_image ) ) {
                    $this->error = 'Cannot copy ' . $image_value . '.';
                    return;
                }
                
                // Set the new images name
                $this->new_images[$image_key]['path'] = $new_image;
                
                // If there is a cover, create another ID and XHTML page later
                if ( ! empty( $image_value['cover'] ) ) {
                    $opf .= '<item id="cover" href="cover.xhtml" media-type="application/xhtml+xml" />' . "rn";
                    $opf .= '<item id="cover-image';
                    $this->cover_img = basename( $new_image );
                } else {
                    $opf .= '<item id="img' . $image_key;
                }
                
                // End the image <item> tag
                $opf .= '" href="images/' . basename( $new_image );
                $opf .= '" media-type="' . $image_type . '" />' . "rn";
            }           
        }
        
        // Fill the NCX and OPF
        $this->ncx[] = $ncx;
        
        $this->opf[] = $opf;
        $this->opf[] = '</manifest><spine toc="ncx">' . "rn";
        
        // If there's a cover, we'll need an <itemref idref="cover" />
        if ( $this->cover_img ) {
            $this->opf[] = "<itemref idref="cover" />rn";
        }
        
        // Fill the spine
        $this->opf[] = $fill_opf_spine;
        
        // Closes the OPF and NCX
        $this->CloseOPF();
        $this->CloseNCX();
        
        // Create the OPF and NCX files
        $this->CreateOPF();
        $this->CreateNCX();
        
        // XHTML default page header
        $page_content  = "<?xml version='1.0' encoding='utf-8'?>" . "rn";
        $page_content .= '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">' . "rn";
        $page_content .= '<html xmlns="http://www.w3.org/1999/xhtml">' . "rn";
        $page_content .= '<head>' . "rn";
        $page_content .= '<meta content="application/xhtml+xml; charset=utf-8" http-equiv="Content-Type"/>' . "rn";
        $page_content .= '<link href="css.css" type="text/css" rel="stylesheet"/>' . "rn";
        $page_content .= '</head>' . "rn";
        $page_content .= '<body>' . "rn";
        
        // Loop the pages
        foreach( $this->pages as $key => $value ) {
            
            // Page file
            $page = 'page' . $key . '.xhtml';
            
            // Replace unwanted tags (for now scripts and iframes)
            $value['content'] = preg_replace(
                '/<(script|iframe)[^>]*>.*?</(script|iframe)>/mis', 
                '', 
                $value['content']
            );
            
            // Fill the page content and ends the XHTML
            $value['content']  = $page_content . $value['content'];
            $value['content'] .= '</body></html>';
            
            // Replace the HTML images to the new images
            foreach( $this->images as $check_image_key => $check_images ) {
                $value['content'] = str_replace ( 
                    $check_images['path'],
                    'images/' . basename( $this->new_images[$check_image_key]['path'] ),
                    $value['content']
                );
            }
            
            // Create the file
            $this->CreateFile( $this->temp_folder . '/OEBPS/' . $page, $value['content'] );
        }
        
        // If there's a cover, create its page
        if ( ! empty( $this->cover_img )  ) {
            $cover_page  = $page_content;
            $cover_page .= '<img class="cover-image" width="600" height="800" src="images/' . $this->cover_img . '" />' . "rn";
            $cover_page .= '</body></html>';
            $this->CreateFile( $this->temp_folder . '/OEBPS/cover.xhtml', $cover_page );
        }
        
        // Create the zip file
        $this->CreateZip();
    }
    
    /**
     * Open CSS
     *
     * It will simply fill the $css property
     */    
    public function OpenCSS() {
        if ( ! $this->css ) {
            $this->css  = 'body {';
            $this->css .= 'margin-left: .5em;';
            $this->css .= 'margin-right: .5em;';
            $this->css .= 'text-align: left;';
            $this->css .= 'direction: ltr;';
            $this->css .= 'font-family: arial;';
            $this->css .= 'direction: ltr;';
            $this->css .= 'font-size:12pt;';
            $this->css .= 'font-weight:400;';
            $this->css .= '};';
        }
    }
    
    /**
     * Open OPF
     *
     * Fill the content.opf file ($opf property)
     */    
    private function OpenOPF() {
        $this->opf[] = '<?xml version="1.0" encoding="UTF-8"?>' . "rn";
        $this->opf[] = '<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="BookID" version="2.0" >' . "rn";
        $this->opf[] = '<metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">' . "rn";
        $this->opf[] = '<dc:title>' . $this->title . '</dc:title>' . "rn";
        $this->opf[] = '<dc:creator opf:file-as="' . $this->creator . '" opf:role="aut">' . $this->creator . '</dc:creator>' . "rn";
        $this->opf[] = '<dc:language>' . $this->language . '</dc:language>' . "rn";
        $this->opf[] = '<dc:rights>' . $this->rights . '</dc:rights>' . "rn";
        $this->opf[] = '<dc:publisher>' . $this->publisher . '</dc:publisher>';
        $this->opf[] = '<dc:identifier id="BookID" opf:scheme="UUID">' . $this->uuid . '</dc:identifier>' . "rn";
        $this->opf[] = '<meta name="cover" content="cover" />' . "rn";
        $this->opf[] = '</metadata><manifest>' . "rn";
        $this->opf[] = '<item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml" />' . "rn";
        $this->opf[] = '<item id="style" href="css.css" media-type="text/css" />' . "rn";
    }
    
    /**
     * Close OPF
     *
     * End of the content.opf file
     */    
    private function CloseOPF() {
        $this->opf[] = '</spine></package>' . "rn";
    }
    
    /**
     * Create OPF
     *
     * Creates the content.opf file
     */    
    private function CreateOPF() {
        $opf = null;
        
        foreach( $this->opf as $lines ) { 
            $opf .= "$linesrn";
        }
        
        $this->CreateFile( $this->temp_folder . '/OEBPS/content.opf', $opf );
    }
    
    /**
     * Open NCX
     *
     * Fill the toc.ncx content ($ncx property)
     */    
    private function OpenNCX() {
        $this->ncx[] = '<?xml version="1.0" encoding="UTF-8"?>' . "rn";
        $this->ncx[] = '<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1">' . "rn";
        $this->ncx[] = '<meta name="dtb:uid" content="' . $this->uuid . '"/>' . "rn";
        $this->ncx[] = '<head>' . "rn";
        $this->ncx[] = '<meta name="dtb:depth" content="1"/>' . "rn";
        $this->ncx[] = '<meta name="dtb:totalPageCount" content="0"/>' . "rn";
        $this->ncx[] = '<meta name="dtb:maxPageNumber" content="0"/>' . "rn";
        $this->ncx[] = '</head>' . "rn";
        $this->ncx[] = '<docTitle><text>' . $this->title . '</text></docTitle>' . "rn";
        $this->ncx[] = '<navMap>' . "rn";
    }
    
    /**
     * Close NCX
     *
     * Closes the toc.ncx file content
     */    
    private function CloseNCX() {
        $this->ncx[] = '</navMap>' . "rn";
        $this->ncx[] = '</ncx>' . "rn";
    }
    
    /**
     * Create NCX
     *
     * Creates toc.ncx file
     */    
    private function CreateNCX() {
        $ncx = null;
        
        foreach( $this->ncx as $lines ) { 
            $ncx .= "$linesrn";
        }
        
        $this->CreateFile( $this->temp_folder . '/OEBPS/toc.ncx', $ncx );
    }
    
    /**
     * Create folders
     *
     * Create all the temp folders needed
     */    
    private function CreateFolders() {
        
        // If the user do not specify a temp folder, we'll assume it.
        if ( ! $this->temp_folder ) {
            $this->temp_folder = preg_replace( '/[^A-Za-z0-9]/is', '', $this->title );
            $this->temp_folder = strtolower( $this->temp_folder );
        }
        
        // Temp folder is the book's uuid
        $this->temp_folder .= $this->uuid . '/';
        
        // Check to see if there's no folder with the same name
        if( is_dir( $this->temp_folder ) ) {
            $this->error = 'Folder already exists.';
            return;
        }
        
        // Creates the main temp folder
        mkdir( $this->temp_folder, 0777 );
        
        // Check the folder
        if ( ! is_dir( $this->temp_folder ) ) {
            $this->error = "Cannot create EPUB folder "{$this->temp_folder}".";
            return;
        }
        
        // Creates the other needed folders
        mkdir( $this->temp_folder . '/META-INF', 0777 );
        mkdir( $this->temp_folder . '/OEBPS', 0777 );
        mkdir( $this->temp_folder . '/OEBPS/images', 0777 );
        
        // Open the CSS
        $this->OpenCSS();
        
        // Creates the container.xml
        $this->CreateContainer();
        
        // Creates the needed epub files
        $this->CreateFile( $this->temp_folder . '/mimetype', 'application/epub+zip');
        $this->CreateFile( $this->temp_folder . '/OEBPS/css.css', $this->css);
        $this->CreateFile( $this->temp_folder . '/META-INF/container.xml', $this->container);
    }
    
    /**
     * Create container
     *
     * Creates the container.xml file
     */    
    private function CreateContainer() {
        $this->container  = '<?xml version="1.0" encoding="UTF-8" ?>';
        $this->container .= '<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">';
        $this->container .= '<rootfiles>';
        $this->container .= '<rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml"/>';
        $this->container .= '</rootfiles>';
        $this->container .= '</container>';
    }
    
    /**
     * Create Files
     */    
    private function CreateFile( $file, $content = null ) {
        $handle = fopen( $file, 'w+' );
        $ler = fwrite( $handle, $content );
        fclose($handle);
    }
    
    /**
     * Create Zip
     *
     * This creates the zip file as epub.
     *
     * @since 1.0.0
     * @access private
     */        
    private function CreateZip () {
        // Checks the zip extension
        if ( ! extension_loaded('zip') ) {
            $this->error('zip extension is not loaded');
            return false;
        }
        
        // If the user do not specify the epub file, we'll assume it.
        if ( ! $this->epub_file ) {
            $this->epub_file  = preg_replace( '/[^A-Za-z0-9]/is', '', $this->title );
            $this->epub_file .= '.epub';
        }
        
        $zip = new ZipArchive();
        
        if ( ! $zip->open( $this->epub_file, ZIPARCHIVE::CREATE ) ) {
            $this->error('Failed to create zip file.');
            return false;
        }
        
        // Folders array
        $folders = array(
            $this->temp_folder . '/META-INF', 
            $this->temp_folder . '/OEBPS',
            $this->temp_folder . '/OEBPS/images',
            $this->temp_folder, 
        );
        
        // Files we'll delete later
        $files_to_delete = array();
        
        // Loop the folders
        foreach ( $folders as $folder ) {
            // The files inside the folders
            $scan = scandir( $folder );
            
            // Loop the files
            foreach ( $scan as $subfolder ) {
                // Prevent . and .. paths
                if ( '.' === $subfolder || '..' === $subfolder ) continue;
                
                $full_path = $folder . '/' . $subfolder;
                
                // We just want files, not directories
                if ( is_dir( $full_path ) ) continue;
                
                // Add the file
                $zip->addFile( $full_path, str_replace( $this->temp_folder . '/', '', $full_path )  );
                
                // Fill the array, so we'll know what to delete later
                $files_to_delete[] = $full_path;
            }
        }
        
        $zip->close();
        
        // Delete the files
        foreach ( $files_to_delete as $delete ) unlink( $delete );
        
        // Delete folders
        rmdir( $this->temp_folder . '/META-INF' );
        rmdir( $this->temp_folder . '/OEBPS/images' );
        rmdir( $this->temp_folder . '/OEBPS' );
        rmdir( $this->temp_folder );
    }
}

A classe é tão extensa justamente para facilitar a sua vida. Vamos ver como utilizá-la a seguir.

Utilizando a classe TPEpubCreator

Veja como ficaria para criar um livro com

<?php
// Só pra ter certeza que estamos utilizando UTF-8
// Você pode remover essa linha.
header('Content-Type: text/html; charset=utf-8');

// A classe está pasta classes
require 'classes/TPEpubCreator.php';

// Objeto da classe
$epub = new TPEpubCreator();

// A pasta temporária e o caminho do arquivo final
$epub->temp_folder = 'temp_folder/';
$epub->epub_file = 'epubs/epub_name.epub';

// Configurações do ebook
$epub->title = 'Epub title';
$epub->creator = 'Luiz Otávio Miranda';
$epub->language = 'pt';
$epub->rights = 'Public Domain';
$epub->publisher = 'http://www.todoespacoonline.com/w/';

// Você pode adicionar seu próprio CSS
$epub->css = file_get_contents('base.css');

// $epub->uuid = '';  // Você pode espeificar seu UUID se quiser

// Adiciona a página de um arquivo (só o conteúdo do <body>)
// Você deve remover o doctype, head, body e html tags
// Sintax: $epub->AddImage( XHTML, arquivo, título, baixar imagens );
$epub->AddPage( false, 'file.txt', 'Título (check accent)' );

// Adiciona a página diretamente (só o conteúdo do <body>)
// Não utilize tags como doctype, html, head ou body
$epub->AddPage( '<b>Test</b>', false, 'Title 2' );
$epub->AddPage( '<img src="images/2.jpg" />', false, 'Title 3' );

// Aqui é apenas um exemplo com o último parâmetro, onde 
// mandamos a classe baixar a imagem.
// A classe pode baixar qualquer tipo de imagem de qualquer
// página disponível online (incluindo http://...)
$epub->AddPage( '<img src="images/3.jpg" />', false, 'Title 4', true );

// Aqui uma inclusão de página normal (sem download de imagens)
$epub->AddPage( '<img src="images/4.jpg" />', false, 'Title 5' );

// Adiciona uma imagem de cover
// Lembre-se, apenas uma imagem pode ser cover ( último argumento = true).
// Se mais de uma imagem for cover, alguns leitores não vão carregar o livro
// Sintax: $epub->AddImage( caminho da image, mimetype, cover );
$epub->AddImage( 'images/1.jpg', false, true );

// Adiciona outra imagem (não é cover)
$epub->AddImage( 'images/2.jpg', 'image/jpeg', false );

// Se você não enviar o mimetype, a classe fará isso por você
$epub->AddImage( 'images/4.jpg', false, false );

// Cria o livro
// Se tiver erro, testamos primeiro aqui
if ( ! $epub->error ) {

    // Isso também pode gerar erros
    // Então vamos verificar novamente
    $epub->CreateEPUB();
    
    // Se não tiver erro, seu ebook foi criado com sucesso
    if ( ! $epub->error ) {
        echo 'Success: Download your book <a href="' . $epub->epub_file . '">here</a>.';
    }
    
} else {
    // Se tiver erro, você poderá ver o que acontece aqui
    echo $epub->error;
}

Veja nos comentários acima, tudo o que você precisa fazer para criar seus arquivos EPUB é seguir o modelo.

Concluindo

Falamos muito sobre arquivos EPUB neste artigo, na verdade eu descrevi tudo o que eu sei sobre tais formatos de arquivos. Tire um tempo para estudar todo o conteúdo e tire suas dúvidas nos comentários.

A classe está documentada em inglês, pois este é o formato que utilizamos para documentar nossos arquivos globalmente. Caso você entenda sobre os EPUBs e PHP, me ajude com o projeto no github.

E, novamente, caso tenha dúvidas, comente-as!