epub_builder/
lib.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with
3// this file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! A library to generate EPUB files.
6//!
7//! The purpose for this library is to make it easier to generate EPUB files:
8//! it should take care of most of the boilerplate for you, leaving you only
9//! with the task of filling the actual content.
10//!
11//! # Usage
12//!
13//! Add this in your `Cargo.toml` file:
14//!
15//! ```toml
16//! [dependencies]
17//! epub-builder = "0.8"
18//! ```
19//!
20//! # Example
21//! ```rust
22//! use epub_builder::EpubBuilder;
23//! use epub_builder::EpubContent;
24//! use epub_builder::ReferenceType;
25//! use epub_builder::Result;
26//! use epub_builder::TocElement;
27//! use epub_builder::ZipLibrary;
28//! 
29//! use std::io;
30//! use std::io::Write;
31//! 
32//! // Try to print Zip file to stdout
33//! fn run() -> Result<()> {
34//!     env_logger::init();
35//!     // Some dummy content to fill our book
36//!     let dummy_content = r#"<?xml version="1.0" encoding="UTF-8"?>
37//! //! <html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops">
38//! <body>
39//! <p>Text of the page</p>
40//! </body>
41//! </html>"#;
42//!     let dummy_image = "Not really a PNG image";
43//!     let dummy_css = "body { background-color: pink }";
44//! 
45//!     // Create a new EpubBuilder using the zip library
46//!     let mut builder = EpubBuilder::new(ZipLibrary::new()?)?;
47//!     // Set some metadata
48//!     builder
49//!         .metadata("author", "Joan Doe")?
50//!         .metadata("title", "Dummy Book <T>")?
51//!         // Set the stylesheet (create a "stylesheet.css" file in EPUB that is used by some generated files)
52//!         .stylesheet(dummy_css.as_bytes())?
53//!         // Add a image cover file
54//!         .add_cover_image("cover.png", dummy_image.as_bytes(), "image/png")?
55//!         // Add a resource that is not part of the linear document structure
56//!         .add_resource("some_image.png", dummy_image.as_bytes(), "image/png")?
57//!         // Add a cover page
58//!         .add_content(
59//!             EpubContent::new("cover.xhtml", dummy_content.as_bytes())
60//!                 .title("Cover")
61//!                 .reftype(ReferenceType::Cover),
62//!         )?
63//!         // Add a title page
64//!         .add_content(
65//!             EpubContent::new("title.xhtml", dummy_content.as_bytes())
66//!                 .title("Title <T>")
67//!                 .reftype(ReferenceType::TitlePage),
68//!         )?
69//!         // Add a chapter, mark it as beginning of the "real content"
70//!         .add_content(
71//!             EpubContent::new("chapter_1.xhtml", dummy_content.as_bytes())
72//!                 .title("Chapter 1 <T>")
73//!                 .reftype(ReferenceType::Text),
74//!         )?
75//!         // Add a second chapter; this one has more toc information about its internal structure
76//!         .add_content(
77//!             EpubContent::new("chapter_2.xhtml", dummy_content.as_bytes())
78//!                 .title("Chapter 2 <T>")
79//!                 .child(TocElement::new("chapter_2.xhtml#1", "Chapter 2, section 1")),
80//!         )?
81//!         // Add a section. Since its level is set to 2, it will be attached to the previous chapter.
82//!         .add_content(
83//!             EpubContent::new("section.xhtml", dummy_content.as_bytes())
84//!                 .title("Chapter 2 <T>, section 2")
85//!                 .level(2),
86//!         )?
87//!         // Add a chapter without a title, which will thus not appear in the TOC.
88//!         .add_content(EpubContent::new("notes.xhtml", dummy_content.as_bytes()))?
89//!         // Generate a toc inside of the document, that will be part of the linear structure.
90//!         .inline_toc();
91//!     // Finally, write the EPUB file to stdout
92//!     builder.generate(&mut io::stdout())?; // generate into stout
93//! 
94//!     log::debug!("dummy book generation is done");
95//!     Ok(())
96//! }
97//! 
98//! fn main() {
99//!     match run() {
100//!         Ok(_) => writeln!(
101//!             &mut io::stderr(),
102//!             "Successfully wrote epub document to stdout!"
103//!         )
104//!         .unwrap(),
105//!         Err(err) => writeln!(&mut io::stderr(), "Error: {}", err).unwrap(),
106//!     };
107//! }
108//! ```
109//!
110//! # Features
111//!
112//! `epub-builder`'s aim is to make EPUB generation simpler. It takes care of zipping
113//! the files and generate the following ones:
114//!
115//! * `mimetype`
116//! * `toc.ncx`
117//! * `nav.xhtml`
118//! * `manifest.xml`
119//! * `content.opf`
120//! * `com.apple.ibooks.display-options.xml`.
121//!
122//! It also tries to make it easier to have a correct table of contents, and optionally
123//! generate an inline one in the document.
124//!
125//! Supported EPUB versions:
126//!
127//! * 2.0.1 (default)
128//! * 3.0.1
129//!
130//! ## Missing features
131//!
132//! There are various EPUB features that `epub-builder` doesn't handle. Particularly,
133//! there are some metadata that could be better
134//! handled (e.g. support multiple authors, multiple languages in the document and so on).
135//!
136//! There are also various things that aren't in the scope of this library: it doesn't
137//! provide a default CSS, templates for your XHTML content and so on. This is left to
138//! libraries or applications using it.
139//!
140//! # Conditional compilation
141//!
142//! EPUB files are Zip files, so we need to zip. By default, this library provides
143//! wrappers around both the [Rust zip library](https://crates.io/crates/zip) and calls
144//! to the `zip` command that may (or may not) be installed on your system.
145//!
146//! It is possible to disable the compilation (and the dependencies) of either of these
147//! wrappers, using `no-default-features`. (If you don't enable at least one of them this
148//! library will be pretty useless).
149//!
150//! # License
151//!
152//! This is free software, published under the [Mozilla Public License,
153//! version 2.0](https://www.mozilla.org/en-US/MPL/2.0/).
154#![deny(missing_docs)]
155
156mod common;
157mod epub;
158mod epub_content;
159mod templates;
160mod toc;
161mod zip;
162#[cfg(feature = "zip-command")]
163mod zip_command;
164#[cfg(feature = "zip-command")]
165#[cfg(feature = "libzip")]
166mod zip_command_or_library;
167#[cfg(feature = "libzip")]
168mod zip_library;
169
170pub use epub::EpubBuilder;
171pub use epub::EpubVersion;
172pub use epub::MetadataOpf;
173pub use epub::MetadataOpfV3;
174pub use epub::PageDirection;
175pub use epub_content::EpubContent;
176pub use epub_content::ReferenceType;
177use libzip::result::ZipError;
178pub use toc::Toc;
179pub use toc::TocElement;
180#[cfg(feature = "zip-command")]
181pub use zip_command::ZipCommand;
182#[cfg(feature = "zip-command")]
183#[cfg(feature = "libzip")]
184pub use zip_command_or_library::ZipCommandOrLibrary;
185#[cfg(feature = "libzip")]
186pub use zip_library::ZipLibrary;
187
188/// Error type of this crate. Each variant represent a type of event that may happen during this crate's operations.
189#[derive(thiserror::Error, Debug)]
190pub enum Error {
191    /// An error caused while processing a template or its rendering.
192    #[error("{msg}: {cause:?}")]
193    TemplateError {
194        /// A message explaining what was happening when we recieved this error.
195        msg: String,
196        /// The root cause of the error.
197        // Box the error, since it is quite large (at least 136 bytes, thanks clippy!)
198        cause: Box<upon::Error>,
199    },
200    /// An error returned when encountering an unknown [`PageDirection`].
201    #[error("Invalid page direction specification: {0}")]
202    PageDirectionError(String),
203    /// An error returned when an unknown metadata key has been encountered.
204    #[error("Invalid metadata key: {0}")]
205    InvalidMetadataError(String),
206    /// An error returned when attempting to access the filesystem
207    #[error("{msg}: {cause:?}")]
208    IoError {
209        /// A message explaining what was happening when we recieved this error.
210        msg: String,
211        /// The root cause of the error.
212        cause: std::io::Error,
213    },
214    /// An error returned when something happened while invoking a zip program. See [`ZipCommand`].
215    #[error("Error while executing zip command: {0}")]
216    ZipCommandError(String),
217    /// An error returned when the zip library itself returned an error. See [`ZipLibrary`].
218    #[error(transparent)]
219    ZipError(#[from] ZipError),
220    /// An error returned when the zip library itself returned an error, but with an additional message. See [`ZipLibrary`].
221    #[error("{msg}: {cause:?}")]
222    ZipErrorWithMessage {
223        /// A message explaining what was happening when we recieved this error.
224        msg: String,
225        /// The root cause of the error.
226        cause: ZipError,
227    },
228    /// An error returned when an invalid [`Path`] has been encountered during epub processing.
229    #[error("Invalid path: {0}")]
230    InvalidPath(String),
231}
232
233impl From<std::io::Error> for Error {
234    fn from(value: std::io::Error) -> Self {
235        Error::IoError {
236            msg: format!("{value:?}"),
237            cause: value,
238        }
239    }
240}
241
242/// A more convenient shorthand for functions returning an error in this crate.
243pub type Result<T> = std::result::Result<T, Error>;