rbook


A fast, format-agnostic, ergonomic ebook library with a focus on EPUB and upcoming support for MOBI/AZW3.
The primary goal of rbook is to provide an easy-to-use high-level API for reading, creating, and modifying ebooks.
Most importantly, this library is designed with future formats in mind
(CBZ, FB2, MOBI, etc.) via core traits defined within the ebook
and reader module, allowing all formats to share the same "base" API.
Documentation
Features
Here is a non-exhaustive list of the features rbook provides:
| Feature |
Overview |
Documentation |
| EPUB 2 and 3 |
EPUB builder and parser, with simple and advanced read/write views for EPUB 2 and 3. |
epub module |
| Reader |
Random‐access or sequential iteration over readable content. |
reader module |
| Detailed Types |
Abstractions built on expressive traits and types. |
|
| Metadata |
Typed access to dates, titles, creators, publishers, languages, tags, roles, attributes, and more. |
metadata module |
| Manifest |
Lookup and traverse contained resources such as readable content (XHTML) and images. |
manifest module |
| Spine |
Chronological reading order and preferred page direction. |
spine module |
| Table of Contents (ToC) |
Navigation points, including the EPUB 2 guide and EPUB 3 landmarks. |
toc module |
| Resources |
Lazy retrieval of bytes or strings for any manifest resource; data is not loaded up-front until requested. |
resource module |
Default Crate Features
These are toggleable features for rbook that are
enabled by default in a project's Cargo.toml file:
| Feature |
Description |
| write |
Creation and modification for EPUB 2 and 3. |
| prelude |
Convenience prelude only including common traits. |
| threadsafe |
Enables Send + Sync constraint for Epub. |
Usage
rbook can be used by adding it as a dependency in a project's Cargo.toml file:
[dependencies]
rbook = "0.7.6"
WebAssembly
The wasm32-unknown-unknown target is supported by default.
Examples
Opening and reading an EPUB file
use rbook::Epub;
fn main() {
let epub = Epub::open("example.epub").unwrap();
for data_result in epub.reader() {
let data = data_result.unwrap();
let kind = data.manifest_entry().kind();
assert_eq!("application/xhtml+xml", kind.as_str());
assert_eq!("xhtml", kind.subtype());
println!("{}", data.content());
}
}
Accessing metadata: Retrieving the main title
use rbook::Epub;
use rbook::ebook::metadata::{LanguageKind, TitleKind};
fn main() {
let epub = Epub::options()
.strict(true) .skip_toc(true) .open("example.epub")
.unwrap();
let title = epub.metadata().title().unwrap();
assert_eq!("Example EPUB", title.value());
assert_eq!(TitleKind::Main, title.kind());
let alternate_script = title.alternate_scripts().next().unwrap();
assert_eq!("サンプルEPUB", alternate_script.value());
assert_eq!("ja", alternate_script.language().scheme().code());
assert_eq!(LanguageKind::Bcp47, alternate_script.language().kind());
}
Accessing metadata: Retrieving the year and first creator
use rbook::Epub;
use rbook::ebook::metadata::LanguageKind;
fn main() {
let epub = Epub::options()
.skip_toc(true)
.skip_manifest(true)
.skip_spine(true)
.open("example.epub")
.unwrap();
let published = epub.metadata().published().unwrap();
assert_eq!(2023, published.date().year());
assert!(published.time().is_local());
let creator = epub.metadata().creators().next().unwrap();
assert_eq!("John Doe", creator.value());
assert_eq!(Some("Doe, John"), creator.file_as());
assert_eq!(0, creator.order());
let role = creator.main_role().unwrap();
assert_eq!("aut", role.code());
assert_eq!(Some("marc:relators"), role.source());
let alternate_script = creator.alternate_scripts().next().unwrap();
assert_eq!("山田太郎", alternate_script.value());
assert_eq!("ja", alternate_script.language().scheme().code());
assert_eq!(LanguageKind::Bcp47, alternate_script.language().kind());
}
Extracting images from the manifest
use rbook::Epub;
use std::fs::{self, File};
use std::path::Path;
fn main() {
let epub = Epub::open("example.epub").unwrap();
let out = Path::new("extracted_images");
fs::create_dir_all(&out).unwrap();
for image in epub.manifest().images() {
let filename = image.href().name().decode();
let file = File::create(out.join(&*filename)).unwrap();
image.copy_bytes(file).unwrap();
}
}
Modifying an existing EPUB
use rbook::epub::{Epub, EpubChapter};
use rbook::ebook::errors::EbookResult;
fn main() -> EbookResult<()> {
Epub::open("old.epub")?
.edit()
.author("Jane Doe")
.chapter(EpubChapter::new("Chapter 1337").xhtml_body("1337"))
.modified_now()
.write()
.compression(9)
.save("new.epub")
}
Creating a backwards-compatible EPUB 3 file
This example uses the high-level builder API.
See the epub module
for lower-level control over the package, manifest, metadata, spine, etc.
use rbook::epub::Epub;
use rbook::epub::manifest::DetachedEpubManifestEntry;
use rbook::epub::metadata::{DetachedEpubMetaEntry, EpubVersion};
use rbook::epub::toc::DetachedEpubTocEntry;
use rbook::ebook::errors::EbookResult;
use rbook::ebook::toc::TocEntryKind;
const XHTML: &[u8] = b"<xhtml>...</xhtml>";
fn main() -> EbookResult<()> {
let mut epub = Epub::new();
epub.edit()
.rights("Apache License 2.0")
.generator("Example App");
let mut metadata = epub.metadata_mut();
metadata.push(DetachedEpubMetaEntry::identifier("urn:example"));
metadata.push(
DetachedEpubMetaEntry::title("The Doe Story")
.file_as("Doe Story, The")
);
metadata.push([
DetachedEpubMetaEntry::creator("Jane Doe")
.role("aut"),
DetachedEpubMetaEntry::creator("Hanako Yamada")
.role("ill")
.role("trl") .file_as("Yamada, Hanako")
.alternate_script("ja", "山田花子"),
]);
metadata.push(("dc:language", "en"));
let mut manifest = epub.manifest_mut();
manifest.push(
DetachedEpubManifestEntry::new("chapter_1")
.href("c1.xhtml")
.content(XHTML)
);
let mut spine = epub.spine_mut();
spine.push("chapter_1");
let mut toc = epub.toc_mut();
let contents = DetachedEpubTocEntry::new("Table of Contents")
.children([
DetachedEpubTocEntry::new("Chapter 1")
.kind(TocEntryKind::Chapter)
.href("c1.xhtml"),
DetachedEpubTocEntry::new("Chapter 1.a")
.href("c1.xhtml#section-a"),
]);
toc.insert_root(TocEntryKind::Toc, EpubVersion::EPUB3, contents);
epub.write()
.compression(9)
.save("doe_story.epub")
}
use rbook::epub::{Epub, EpubChapter};
use rbook::ebook::errors::EbookResult;
use rbook::ebook::toc::TocEntryKind;
use std::path::Path;
const XHTML: &[u8] = b"<xhtml>...</xhtml>";
fn main() -> EbookResult<()> {
Epub::builder()
.identifier("urn:example")
.title("The Doe Story")
.author(["John Doe", "Jane Doe"])
.language("en")
.cover_image(("cover.png", Path::new("local/file/cover.png")))
.chapter([
EpubChapter::new("Volume I").xhtml(XHTML).children(
EpubChapter::new("I: Intro")
.kind(TocEntryKind::Introduction)
.href("v1c1.xhtml")
.xhtml_body("<p>Basic text</p>"),
),
EpubChapter::new("Volume II").children([
EpubChapter::new("I").href("v2/c1.xhtml").xhtml(Path::new("path/to/c1.xhtml")),
EpubChapter::new("Section 1").href("v2/c1.xhtml#section-1"),
EpubChapter::unlisted("v3extras.xhtml").xhtml(XHTML),
]),
])
.write()
.compression(0)
.save("doe_story.epub")
}
[!TIP]
More examples are available in the documentation: https://docs.rs/rbook
MSRV
The current Minimum Supported Rust Version is 1.88.0.
License
Licensed under Apache License, Version 2.0.
Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license,
shall be licensed as above, without any additional terms or conditions.