rbook/
lib.rs

1//! - Repository: <https://github.com/DevinSterling/rbook>
2//! - Documentation: <https://docs.rs/rbook>
3//!
4//! A fast, format-agnostic, ergonomic ebook library with a current focus on EPUB.
5//!
6//! The primary goal of `rbook` is to provide an easy-to-use high-level API for handling ebooks.
7//! Most importantly, this library is designed with future formats in mind
8//! (`CBZ`, `FB2`, `MOBI`, etc.) via core traits defined within the [`ebook`] and [`reader`]
9//! module, allowing all formats to share the same "base" API.
10//!
11//! Traits such as [`Ebook`] allow formats to be handled generically.
12//! For example, retrieving the data of a cover image agnostic to the
13//! concrete format (e.g., [`Epub`]):
14//! ```
15//! # use rbook::Ebook;
16//! # use rbook::ebook::manifest::{Manifest, ManifestEntry};
17//! // Here `ebook` may be of any supported format.
18//! fn cover_image_bytes<E: Ebook>(ebook: &E) -> Option<Vec<u8>> {
19//!     // 1 - An ebook may not have a `cover_image` entry, hence the try operator (`?`).
20//!     // 2 - `read_bytes` returns a `Result`; `ok()` converts the result into `Option`.
21//!     ebook.manifest().cover_image()?.read_bytes().ok()
22//! }
23//! ```
24//!
25//! # Features
26//! Here is a non-exhaustive list of the features `rbook` provides:
27//!
28//! | Feature                                   | Overview                                                                                                        |
29//! |-------------------------------------------|-----------------------------------------------------------------------------------------------------------------|
30//! | [**EPUB 2 and 3**](epub)                  | Read-only (for now) view of EPUB `2` and `3` formats.                                                           |
31//! | [**Streaming Reader**](reader)            | Random‐access or sequential iteration over readable content.                                                    |
32//! | **Detailed Types**                        | Abstractions built on expressive traits and types.                                                              |
33//! | [**Metadata**](ebook::metadata)           | Typed access to titles, creators, publishers, languages, tags, roles, attributes, and more.                     |
34//! | [**Manifest**](ebook::manifest)           | Lookup and traverse contained resources such as readable content (XHTML) and images.                            |
35//! | [**Spine**](ebook::spine)                 | Chronological reading order and preferred page direction.                                                       |
36//! | [**Table of Contents (ToC)**](ebook::toc) | Navigation points, including the EPUB 2 guide and EPUB 3 landmarks.                                             |
37//! | [**Resources**](ebook::resource)          | On-demand retrieval of bytes or strings for any manifest resource; data is not loaded up-front until requested. |                                  |
38//!
39//! ## Default crate features
40//! These are toggleable features for `rbook` that are
41//! enabled by default in a project's `Cargo.toml` file:
42//!
43//! | Feature                | Description                                             |
44//! |------------------------|---------------------------------------------------------|
45//! | [**prelude**](prelude) | Convenience prelude ***only*** including common traits. |
46//! | **threadsafe**         | Enables `Send` + `Sync` constraint for `Epub`.          |
47//!
48//! Default features can be disabled and toggled selectively.
49//! For example, omitting the `prelude` while retaining the `threadsafe` feature:
50//! ```toml
51//! [dependencies]
52//! rbook = { version = "0.6.10", default-features = false, features = ["threadsafe"] }
53//! ```
54//!
55//! # Opening an [`Ebook`]
56//! `rbook` supports several methods to open an ebook ([`Epub`]):
57//! - A directory containing the contents of an unzipped ebook:
58//!   ```no_run
59//!   # use rbook::Epub;
60//!   let epub = Epub::open("/ebooks/unzipped_epub_dir");
61//!   ```
62//! - A file path:
63//!   ```no_run
64//!   # use rbook::Epub;
65//!   let epub = Epub::open("/ebooks/zipped.epub");
66//!   ```
67//! - Or any implementation of [`Read`](std::io::Read) + [`Seek`](std::io::Seek)
68//!   (and [`Send`] + [`Sync`] if the `threadsafe` feature is enabled):
69//!   ```no_run
70//!   # use rbook::Epub;
71//!   # let bytes = Vec::new();
72//!   let cursor = std::io::Cursor::new(bytes);
73//!   let epub = Epub::options().read(cursor);
74//!   ```
75//!
76//! Aside from how the contents of an ebook are stored, options can also be given
77//! to control parser behavior, such as [strictness](epub::EpubOpenOptions::strict):
78//! ```
79//! # use rbook::Epub;
80//! let epub = Epub::options()
81//!     .strict(false) // Disable strict checks (`true` by default)
82//!     // If only metadata is needed, skipping helps quicken parsing time and reduce space.
83//!     .skip_toc(true) // Skips ToC-related parsing, such as toc.ncx (`false` by default)
84//!     .skip_manifest(true) // Skips manifest-related parsing (`false` by default)
85//!     .skip_spine(true) // Skips spine-related parsing (`false` by default)
86//!     .open("tests/ebooks/example_epub")
87//!     .unwrap();
88//! ```
89//! # Reading an [`Ebook`]
90//! Reading the contents of an ebook is handled by a [`Reader`](reader::Reader),
91//! which traverses end-user-readable resources in canonical order:
92//! ```
93//! # use rbook::{Ebook, Epub};
94//! // Import traits (Alternatively, rbook::prelude::*)
95//! use rbook::ebook::manifest::ManifestEntry;
96//! use rbook::reader::{Reader, ReaderContent};
97//! # let epub = Epub::open("tests/ebooks/example_epub").unwrap();
98//!
99//! // Create a reader instance
100//! let mut reader = epub.reader();
101//!
102//! // Print the readable content
103//! while let Some(Ok(data)) = reader.read_next() {
104//!     let resource_kind = data.manifest_entry().resource_kind();
105//!
106//!     assert_eq!("application/xhtml+xml", resource_kind.as_str());
107//!     assert_eq!("xhtml", resource_kind.subtype());
108//!     println!("{}", data.content());
109//! }
110//! ```
111//! Prior to creation, a reader can receive options to control its behavior,
112//! such as [linearity](epub::reader::EpubReaderOptions::linear_behavior):
113//! ```
114//! # use rbook::{Ebook, Epub};
115//! use rbook::epub::reader::LinearBehavior;
116//!
117//! # let epub = Epub::open("tests/ebooks/example_epub").unwrap();
118//! let mut reader = epub.reader_builder()
119//!                      // Make a reader omit non-linear content
120//!                      .linear_behavior(LinearBehavior::LinearOnly)
121//!                      .create();
122//! ```
123//!
124//! # Resource retrieval from an [`Ebook`]
125//! All files such as text, images, and video are accessible within an ebook programmatically.
126//!
127//! The simplest way to access and retrieve resources from an ebook is through the
128//! [`Manifest`](ebook::manifest::Manifest), specifically through its entries via
129//! [`ManifestEntry::read_str`](ebook::manifest::ManifestEntry::read_str) and
130//! [`ManifestEntry::read_bytes`](ebook::manifest::ManifestEntry::read_bytes):
131//! ```
132//! # use rbook::ebook::errors::EbookResult;
133//! # use rbook::ebook::manifest::{Manifest, ManifestEntry};
134//! # use rbook::{Ebook, Epub};
135//! # fn main() -> EbookResult<()> {
136//! # let epub = Epub::open("tests/ebooks/example_epub")?;
137//! let manifest_entry = epub.manifest().cover_image().unwrap();
138//! let cover_image_bytes = manifest_entry.read_bytes()?;
139//!
140//! // process bytes //
141//! # Ok(())
142//! # }
143//! ```
144//!
145//! For finer grain control, the [`Ebook`] trait provides two methods
146//! that accept a [`Resource`](ebook::resource::Resource) as an argument:
147//! - [`Ebook::read_resource_str`] to retrieve the content as a UTF-8 string.
148//! - [`Ebook::read_resource_bytes`] to retrieve the content in the form of raw bytes.
149//! ```
150//! # use rbook::ebook::errors::EbookResult;
151//! # use rbook::ebook::manifest::{Manifest, ManifestEntry};
152//! # use rbook::{Ebook, Epub};
153//! # fn main() -> EbookResult<()> {
154//! # let epub = Epub::open("tests/ebooks/example_epub")?;
155//! let manifest_entry = epub.manifest().cover_image().unwrap();
156//!
157//! let bytes_a = epub.read_resource_bytes(manifest_entry)?;
158//! let bytes_b = epub.read_resource_bytes("/EPUB/img/cover.webm")?;
159//!
160//! assert_eq!(bytes_a, bytes_b);
161//! # Ok(())
162//! # }
163//! ```
164//!
165//! All resource retrieval methods are fallible, and attempts to access malformed or missing
166//! resources will return an [`EbookError::Archive`](ebook::errors::EbookError::Archive) error.
167//!
168//! ## See Also
169//! - [`Epub`] documentation of `read_resource_*` methods for normalization details.
170//!
171//! # The [`prelude`]
172//! `rbook` provides a prelude consisting **only** of traits for convenience.
173//! It circumvents manually importing each trait and helps keep imports lean:
174//! ```no_run
175//! // Without the prelude (Verbose; manually importing each trait):
176//! /*1*/ use rbook::Ebook;
177//! /*2*/ use rbook::ebook::manifest::ManifestEntry;
178//! /*3*/ use rbook::ebook::spine::{Spine, SpineEntry};
179//! # use rbook::ebook::errors::EbookResult;
180//!
181//! // With the prelude, lines 1, 2, and 3 can be consolidated into `use rbook::prelude::*;`
182//!
183//! # fn main() -> EbookResult<()> {
184//! # use rbook::ebook::errors::EbookResult;
185//! // Retrieve the manifest entry associated with a spine entry:
186//! let epub = rbook::Epub::open("tests/ebooks/example_epub")?;
187//! let spine_entry = epub.spine().by_order(2).unwrap();
188//! let manifest_entry_a = spine_entry.manifest_entry().unwrap();
189//!
190//! assert_eq!("c1", spine_entry.idref());
191//! assert_eq!("c1", manifest_entry_a.id());
192//! # Ok(())
193//! # }
194//! ```
195//!
196//! The idea of libraries providing a prelude is subjective and may not be desirable.
197//! As such, it is set as a default crate feature that can be disabled inside a
198//! project's `Cargo.toml` file.
199//! For example, omitting the `prelude` while retaining the `threadsafe` feature:
200//! ```toml
201//! [dependencies]
202//! rbook = { version = "0.6.10", default-features = false, features = ["threadsafe"] }
203//! ```
204//!
205//! # Examples
206//! ## Accessing [`Metadata`](ebook::metadata::Metadata): Retrieving the main title
207//! ```
208//! # use rbook::{Ebook, Epub};
209//! # use rbook::ebook::metadata::{Metadata, MetaEntry, Title, TitleKind, LanguageKind};
210//! # let epub = Epub::open("tests/ebooks/example_epub").unwrap();
211//! // Retrieve the main title (all titles retrievable via `titles()`)
212//! let title = epub.metadata().title().unwrap();
213//! assert_eq!("Example EPUB", title.value());
214//! assert_eq!(TitleKind::Main, title.kind());
215//!
216//! // Retrieve the first alternate script of a title
217//! let alternate_script = title.alternate_scripts().next().unwrap();
218//! assert_eq!("サンプルEPUB", alternate_script.value());
219//! assert_eq!("ja", alternate_script.language().scheme().code());
220//! assert_eq!(LanguageKind::Bcp47, alternate_script.language().kind());
221//! ```
222//! ## Accessing [`Metadata`](ebook::metadata::Metadata): Retrieving the first creator
223//! ```
224//! # use rbook::{Ebook, Epub};
225//! # use rbook::ebook::metadata::{Metadata, MetaEntry, Contributor, LanguageKind};
226//! # let epub = Epub::open("tests/ebooks/example_epub").unwrap();
227//! // Retrieve the first creator
228//! let creator = epub.metadata().creators().next().unwrap();
229//! assert_eq!("John Doe", creator.value());
230//! assert_eq!(Some("Doe, John"), creator.file_as());
231//! assert_eq!(0, creator.order());
232//!
233//! // Retrieve the main role of a creator (all roles retrievable via `roles()`)
234//! let role = creator.main_role().unwrap();
235//! assert_eq!("aut", role.code());
236//! assert_eq!(Some("marc:relators"), role.source());
237//!
238//! // Retrieve the first alternate script of a creator
239//! let alternate_script = creator.alternate_scripts().next().unwrap();
240//! assert_eq!("山田太郎", alternate_script.value());
241//! assert_eq!("ja", alternate_script.language().scheme().code());
242//! assert_eq!(LanguageKind::Bcp47, alternate_script.language().kind());
243//! ```
244//! ## Extracting images from the [`Manifest`](ebook::manifest::Manifest)
245//! ```no_run
246//! use std::{fs, path::Path};
247//! # use rbook::{Ebook, Epub};
248//! # use rbook::ebook::manifest::{Manifest, ManifestEntry};
249//! # let epub = Epub::open("tests/ebooks/example_epub").unwrap();
250//!
251//! // Create an output directory for the extracted images
252//! let out = Path::new("extracted_images");
253//! fs::create_dir_all(&out).unwrap();
254//!
255//! for image in epub.manifest().images() {
256//!     // Retrieve the raw image bytes
257//!     let bytes = image.read_bytes().unwrap();
258//!
259//!     // Extract the filename from the href and write to disk
260//!     let filename = image.href().name().decode(); // Decode as EPUB hrefs may be URL-encoded
261//!     fs::write(out.join(&*filename), bytes).unwrap();
262//! }
263//! ```
264//! ## Accessing [`EpubManifest`](epub::manifest::EpubManifest) fallbacks
265//! ```
266//! # use rbook::{Ebook, Epub};
267//! # use rbook::ebook::errors::EbookResult;
268//! # use rbook::ebook::manifest::{Manifest, ManifestEntry};
269//! # use rbook::ebook::metadata::MetaEntry;
270//! # let epub = Epub::open("tests/ebooks/example_epub").unwrap();
271//! // Fallbacks
272//! let webm_cover = epub.manifest().cover_image().unwrap();
273//! let kind = webm_cover.resource_kind();
274//! assert_eq!(("image", "webm"), (kind.maintype(), kind.subtype()));
275//!
276//! // If the app does not support `webm`; fallback
277//! let avif_cover = webm_cover.fallback().unwrap();
278//! assert_eq!("image/avif", avif_cover.media_type());
279//!
280//! // If the app does not support `avif`; fallback
281//! let png_cover = avif_cover.fallback().unwrap();
282//! assert_eq!("image/png", png_cover.media_type());
283//!
284//! // No more fallbacks
285//! assert_eq!(None, png_cover.fallback());
286//! ```
287
288#![warn(missing_docs)]
289#![cfg_attr(docsrs, feature(doc_cfg))]
290
291mod parser;
292mod util;
293
294pub mod ebook;
295pub mod reader;
296
297pub use self::{ebook::Ebook, epub::Epub};
298pub use crate::ebook::epub;
299
300/// The rbook prelude for convenient imports of the core
301/// [`ebook`] and [`reader`] **traits**.
302///
303/// This is a crate feature, `prelude`, that is enabled by default.
304#[cfg(feature = "prelude")]
305pub mod prelude {
306    pub use crate::ebook::{
307        Ebook,
308        manifest::{Manifest, ManifestEntry},
309        metadata::{Contributor, Identifier, Language, MetaEntry, Metadata, Tag, Title},
310        spine::{Spine, SpineEntry},
311        toc::{Toc, TocChildren, TocEntry},
312    };
313    pub use crate::reader::{Reader, ReaderContent};
314}