mod editor;
mod writer;
use crate::ebook::element::Href;
use crate::ebook::errors::EbookResult;
use crate::ebook::resource::Resource;
use crate::epub::Epub;
use crate::epub::archive::EpubArchive;
use crate::epub::consts::opf;
use crate::epub::manifest::{EpubManifestData, EpubManifestMut};
use crate::epub::metadata::{
DetachedEpubMetaEntry, EpubMetadataData, EpubMetadataMut, EpubVersion,
};
use crate::epub::package::{EpubPackageData, EpubPackageMut};
use crate::epub::spine::{EpubSpineData, EpubSpineMut};
use crate::epub::toc::{EpubTocData, EpubTocMut};
use crate::epub::write::writer::{EpubWriteConfig, EpubWriter};
use crate::input::Many;
use crate::util::borrow::MaybeOwned;
use crate::util::sync::SendAndSync;
use crate::util::uri;
use std::fmt::Debug;
use std::io::{Cursor, Write};
use std::path::Path;
pub use editor::{EpubChapter, EpubEditor};
impl Epub {
pub fn new() -> Self {
const RBOOK: &str = concat!("rbook v", env!("CARGO_PKG_VERSION"));
let mut epub = Self {
archive: EpubArchive::empty(),
package: EpubPackageData::new("/OEBPS/package.opf".to_owned(), EpubVersion::EPUB3),
metadata: EpubMetadataData::empty(),
manifest: EpubManifestData::empty(),
spine: EpubSpineData::empty(),
toc: EpubTocData::empty(),
};
epub.metadata_mut()
.push(DetachedEpubMetaEntry::meta_name(opf::GENERATOR).value(RBOOK));
epub
}
pub fn builder() -> EpubEditor<'static> {
EpubEditor::new()
}
pub fn edit(&mut self) -> EpubEditor<'_> {
EpubEditor {
epub: MaybeOwned::Borrowed(self),
}
}
pub fn package_mut(&mut self) -> EpubPackageMut<'_> {
EpubPackageMut::new(&mut self.archive, &mut self.package)
}
pub fn metadata_mut(&mut self) -> EpubMetadataMut<'_> {
EpubMetadataMut::new(&mut self.package, &mut self.metadata)
}
pub fn manifest_mut(&mut self) -> EpubManifestMut<'_> {
EpubManifestMut::new(self)
}
pub fn spine_mut(&mut self) -> EpubSpineMut<'_> {
EpubSpineMut::new(self)
}
pub fn toc_mut(&mut self) -> EpubTocMut<'_> {
EpubTocMut::new(self)
}
pub fn cleanup(&mut self) {
self.manifest.remove_non_existent_references();
let manifest_entries = &self.manifest.entries;
self.spine
.entries
.retain(|entry| manifest_entries.contains_key(&entry.idref));
let hrefs = manifest_entries
.iter()
.map(|(_, entry)| entry.href.as_str())
.collect::<std::collections::HashSet<_>>();
self.toc.recursive_retain(|entry| {
match &entry.href {
Some(href) if !uri::has_scheme(href) => hrefs.contains(uri::path(href)),
_ => true,
}
});
}
#[must_use]
pub fn write(&self) -> EpubWriteOptions<&Self> {
EpubWriteOptions::<&Self>::new(self)
}
}
impl Default for Epub {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct EpubWriteOptions<T = ()> {
container: T,
config: EpubWriteConfig,
}
impl<T> EpubWriteOptions<T> {
fn save_epub(epub: &Epub, config: &EpubWriteConfig, path: &Path) -> EbookResult<()> {
const TEMP: &str = "rbook.tmp";
let temp = path.with_extension(TEMP);
let write_result = (|| {
let file = std::fs::File::create(&temp)?;
let mut buf = std::io::BufWriter::new(file);
Self::write_epub(epub, config, &mut buf)?;
buf.flush()?;
std::fs::rename(&temp, path)?;
Ok(())
})();
if let Err(error) = write_result {
let _ = std::fs::remove_file(&temp);
return Err(error);
}
Ok(())
}
fn write_epub<W: Write>(epub: &Epub, config: &EpubWriteConfig, write_to: W) -> EbookResult<W> {
EpubWriter::new(config, epub, write_to).write()
}
fn vec_epub(epub: &Epub, config: &EpubWriteConfig) -> EbookResult<Vec<u8>> {
let cursor = Cursor::new(Vec::new());
Self::write_epub(epub, config, cursor).map(Cursor::into_inner)
}
pub fn target(&mut self, target: impl Many<EpubVersion>) -> &mut Self {
self.config.targets.clear();
for version in target.iter_many() {
self.config.targets.add(version);
}
self
}
pub fn generate_toc(&mut self, generate: bool) -> &mut Self {
self.config.generate_toc = generate;
self
}
pub fn toc_stylesheet<'a>(&mut self, location: impl Many<Resource<'a>>) -> &mut Self {
self.config.generated_toc_stylesheets = Some(
location
.iter_many()
.filter_map(|resource| resource.key().value().map(str::to_owned))
.collect(),
);
self
}
pub fn compression(&mut self, level: u8) -> &mut Self {
self.config.compression = level.min(9);
self
}
pub fn keep_orphans(&mut self, keep: impl OrphanFilter + 'static) -> &mut Self {
self.config.keep_orphans = Some(std::sync::Arc::new(keep));
self
}
}
impl<'ebook> EpubWriteOptions<&'ebook Epub> {
fn new(epub: &'ebook Epub) -> Self {
Self {
container: epub,
config: EpubWriteConfig::default(),
}
}
pub fn save(&self, path: impl AsRef<Path>) -> EbookResult<()> {
Self::save_epub(self.container, &self.config, path.as_ref())
}
pub fn write<W: Write>(&self, writer: W) -> EbookResult<W> {
Self::write_epub(self.container, &self.config, writer)
}
pub fn to_vec(&self) -> EbookResult<Vec<u8>> {
Self::vec_epub(self.container, &self.config)
}
}
impl EpubWriteOptions {
pub fn save(&self, epub: &Epub, path: impl AsRef<Path>) -> EbookResult<()> {
Self::save_epub(epub, &self.config, path.as_ref())
}
pub fn write<W: Write>(&self, epub: &Epub, write_to: W) -> EbookResult<W> {
Self::write_epub(epub, &self.config, write_to)
}
pub fn to_vec(&self, epub: &Epub) -> EbookResult<Vec<u8>> {
Self::vec_epub(epub, &self.config)
}
}
impl Default for EpubWriteOptions {
fn default() -> Self {
Self {
container: (),
config: EpubWriteConfig::default(),
}
}
}
pub trait OrphanFilter: SendAndSync {
fn filter(&self, path: Href<'_>) -> bool;
}
impl OrphanFilter for bool {
fn filter(&self, _href: Href<'_>) -> bool {
*self
}
}
impl<F: Fn(Href<'_>) -> bool + SendAndSync + 'static> OrphanFilter for F {
fn filter(&self, href: Href<'_>) -> bool {
self(href)
}
}