use crate::epub::Epub;
use crate::epub::consts::{dc, opf, xml};
use crate::epub::metadata::EpubMetaEntryData;
use crate::epub::package::Prefixes;
use crate::epub::write::writer::package::spine::SpineIdGenerator;
use crate::epub::write::writer::{EpubWriter, EpubWriterContext};
use crate::util::uri::{self, UriResolver};
use crate::writer::WriterResult;
use crate::writer::xml::{XmlWriter, write_element};
use std::io::Write;
mod guide;
mod manifest;
mod metadata;
mod spine;
pub(super) struct PackageIdGenerator<'ebook> {
prefix: &'ebook str,
epub: &'ebook Epub,
count: Option<usize>,
}
impl<'ebook> PackageIdGenerator<'ebook> {
pub(super) fn new(prefix: &'ebook str, epub: &'ebook Epub) -> Self {
Self {
count: None,
prefix,
epub,
}
}
fn check_meta(prefix: &str, max: &mut usize, entry: &EpubMetaEntryData) {
if let Some(id) = entry.id.as_deref() {
Self::check_id(prefix, max, id);
}
for refinements in entry.refinements.iter() {
Self::check_meta(prefix, max, refinements);
}
}
fn check_id(prefix: &str, max: &mut usize, id: &str) {
if let Some(Ok(num)) = id.strip_prefix(prefix).map(str::parse) {
*max = (*max).max(num);
}
}
fn generate_id(&mut self) -> String {
let prefix = self.prefix;
let count = self.count.get_or_insert_with(|| {
let epub = self.epub;
let mut max = 0;
for entry in epub.metadata.entries.values().flatten() {
Self::check_meta(prefix, &mut max, entry);
}
for (id, entry) in &epub.manifest.entries {
Self::check_id(prefix, &mut max, id);
for refinement in entry.refinements.iter() {
Self::check_meta(prefix, &mut max, refinement);
}
}
for entry in &epub.spine.entries {
if let Some(id) = entry.id.as_deref() {
Self::check_id(prefix, &mut max, id);
}
for refinement in entry.refinements.iter() {
Self::check_meta(prefix, &mut max, refinement);
}
}
max
});
*count += 1;
format!("{prefix}{count}")
}
}
pub(super) struct PackageWriter<'ebook, W> {
ctx: &'ebook EpubWriterContext<'ebook>,
resolver: UriResolver<'ebook>,
writer: XmlWriter<'ebook, W>,
generated_spine_ids: SpineIdGenerator<'ebook>,
}
impl<'ebook, W: Write> PackageWriter<'ebook, W> {
fn new(ctx: &'ebook EpubWriterContext<'ebook>, writer: W) -> Self {
Self {
resolver: UriResolver::parent_of(&ctx.epub.package.location),
writer: XmlWriter::new(writer),
generated_spine_ids: SpineIdGenerator::new(ctx.epub),
ctx,
}
}
fn write_opf(mut self) -> WriterResult<()> {
let supported = self.ctx.supports_epub3();
let package = &self.ctx.epub.package;
self.writer.write_utf8_declaration()?;
write_element! {
writer: self.writer,
tag: opf::PACKAGE,
attributes: {
xml::XMLNS => opf::OPF_NS,
dc::XMLNS_DC => None,
opf::XMLNS_OPF => None,
opf::VERSION => package.version.raw.as_str(),
opf::UNIQUE_ID => package.unique_identifier.as_str(),
opf::TEXT_DIR where supported && !package.text_direction.is_auto() => package.text_direction.as_str(),
xml::LANG where supported => package.language.as_deref(),
opf::PREFIX where supported => Self::prefixes_to_string(&package.prefixes).as_deref(),
..package.attributes.iter_key_value(),
}
inner_content: {
self.write_metadata()?;
self.write_manifest()?;
self.write_spine()?;
self.write_guide()?;
}
}
}
fn prefixes_to_string(prefixes: &Prefixes) -> Option<String> {
let mut buffer = String::new();
for prefix in prefixes {
buffer.push_str(prefix.name());
buffer.push_str(": ");
buffer.push_str(prefix.uri());
buffer.push(' ');
}
buffer.pop();
(!buffer.is_empty()).then_some(buffer)
}
}
impl<W: Write> EpubWriter<'_, W> {
pub(super) fn write_package(&mut self) -> WriterResult<()> {
let package_location = uri::decode(&self.ctx.epub.package.location);
self.zip.start_file(&package_location)?;
PackageWriter::new(&self.ctx, &mut self.zip).write_opf()
}
}