You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
snekdown/src/format/epub/epub_writer.rs

130 lines
3.5 KiB
Rust

use epub_builder::{EpubBuilder, EpubContent, ReferenceType, Result, ZipLibrary};
use htmlescape::{encode_attribute, encode_minimal};
use std::cmp::max;
use std::collections::HashMap;
use std::io;
use std::io::{Read, Write};
use std::mem;
#[derive(Clone, Debug, PartialOrd, PartialEq)]
struct Buffer {
pub inner: Vec<u8>,
}
impl Default for Buffer {
fn default() -> Self {
Self { inner: Vec::new() }
}
}
impl Read for Buffer {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let written = buf.write(&self.inner[self.position..])?;
self.inner.reverse();
self.inner.truncate(self.inner.len() - written);
self.inner.reverse();
Ok(written)
}
}
pub struct EpubWriter {
inner: Box<dyn Write>,
builder: EpubBuilder<ZipLibrary>,
content_begin: bool,
sections: Vec<Box<EpubContent<Buffer>>>,
content_buffer: Buffer,
section_level: u8,
section_count: HashMap<u8, usize>,
}
impl EpubWriter {
pub fn new(writer: Box<dyn Write>) -> Result<Self> {
Ok(Self {
inner: writer,
builder: EpubBuilder::new(ZipLibrary::new()?)?,
content_begin: false,
sections: Vec::new(),
content_buffer: Buffer::default(),
section_count: HashMap::new(),
section_level: 0,
})
}
/// Sets the metadata of the Epub File
pub fn metadata(&mut self, key: &str, value: &str) -> Result<()> {
self.builder.metadata(key, value)?;
Ok(())
}
/// Sets the stylesheet of the epub
pub fn stylesheet(&mut self, style: &String) -> Result<()> {
self.builder.stylesheet(style.as_bytes())?;
Ok(())
}
/// Adds a resource
pub fn resource(&mut self, path: &str, resource: Vec<u8>, mimetype: &str) -> Result<()> {
self.builder
.add_resource(path, resource.as_slice(), mimetype)?;
Ok(())
}
/// adds a section
pub fn section(&mut self, level: u8, title: String) -> Result<()> {
if self.section_level >= level {
if let Some(mut section) = self.sections.pop() {
section.content = mem::replace(&mut self.content_buffer, Buffer::default());
self.builder.add_content(*section)?;
}
}
let mut section = EpubContent::new(
format!("{}.xhtml", self.next_section_name(level),),
Buffer::default(),
)
.title(title)
.level(level as i32);
if !self.content_begin {
section = section.reftype(ReferenceType::Text);
}
self.sections.push(Box::new(section));
self.section_level = level;
Ok(())
}
/// Adds (string) content to the epub file
pub fn content(&mut self, content: String) {
self.content_buffer
.inner
.append(&mut content.as_bytes().to_vec());
}
pub fn escaped_content(&mut self, content: String) {
self.content(encode_minimal(content.as_str()));
}
pub fn escaped_attribute_content(&mut self, content: String) {
self.content(encode_attribute(content.as_str()));
}
/// Finishes writing the epub
pub fn finish(&mut self) -> Result<()> {
self.builder.generate(&mut self.inner)?;
Ok(())
}
/// Returns the next section name for a section level
fn next_section_name(&mut self, level: u8) -> String {
let count = *self.section_count.get(&level).unwrap_or(&1);
self.section_count.insert(level, count);
format!("s{}_{}", level, count)
}
}