use crate::epub::Epub;
use crate::epub::errors::EpubError;
use crate::epub::manifest::EpubManifestEntry;
use crate::epub::spine::EpubSpineEntry;
use crate::reader::errors::{ReaderError, ReaderResult};
use crate::reader::{Reader, ReaderContent, ReaderKey};
use crate::util::iter::IndexCursor;
use crate::util::{Sealed, doc};
use std::cmp::PartialEq;
#[derive(Clone, Debug, PartialEq)]
pub struct EpubReader<'ebook> {
entries: Vec<EpubSpineEntry<'ebook>>,
cursor: IndexCursor,
}
impl<'ebook> EpubReader<'ebook> {
pub(super) fn new(epub: &'ebook Epub, config: &EpubReaderConfig) -> Self {
let entries = Self::get_entries(epub, config.linear_behavior);
EpubReader {
cursor: IndexCursor::new(entries.len()),
entries,
}
}
fn get_entries(epub: &'ebook Epub, behavior: LinearBehavior) -> Vec<EpubSpineEntry<'ebook>> {
let iterator = epub.spine().iter();
match behavior {
LinearBehavior::Original => iterator.collect(),
LinearBehavior::LinearOnly | LinearBehavior::NonLinearOnly => {
let predicate = behavior == LinearBehavior::LinearOnly;
iterator
.filter(|entry| entry.is_linear() == predicate)
.collect()
}
LinearBehavior::PrependNonLinear | LinearBehavior::AppendNonLinear => {
let (mut linear, mut non_linear) =
iterator.partition::<Vec<_>, _>(EpubSpineEntry::is_linear);
if matches!(&behavior, LinearBehavior::AppendNonLinear) {
linear.extend(non_linear);
linear
} else {
non_linear.extend(linear);
non_linear
}
}
}
}
fn get_manifest_entry(
spine_entry: EpubSpineEntry<'ebook>,
) -> ReaderResult<EpubManifestEntry<'ebook>> {
spine_entry.manifest_entry().ok_or_else(|| {
ReaderError::Format(EpubError::InvalidIdref(spine_entry.idref().to_owned()).into())
})
}
fn find_entry_by_idref(&self, idref: &str) -> ReaderResult<usize> {
self.entries
.iter()
.position(|entry| entry.idref() == idref)
.ok_or_else(|| ReaderError::NoMapping(idref.to_string()))
}
fn find_entry_by_position(&self, position: usize) -> ReaderResult<EpubReaderContent<'ebook>> {
let spine_entry = self.entries[position];
let manifest_entry = Self::get_manifest_entry(spine_entry)?;
Self::create_reader_content(position, spine_entry, manifest_entry)
}
fn find_entry_by_str(&self, idref: &str) -> ReaderResult<(usize, EpubReaderContent<'ebook>)> {
let position = self.find_entry_by_idref(idref)?;
let spine_entry = self.entries[position];
let manifest_entry = Self::get_manifest_entry(spine_entry)?;
Ok((
position,
Self::create_reader_content(position, spine_entry, manifest_entry)?,
))
}
fn create_reader_content(
position: usize,
spine_entry: EpubSpineEntry<'ebook>,
manifest_entry: EpubManifestEntry<'ebook>,
) -> ReaderResult<EpubReaderContent<'ebook>> {
Ok(EpubReaderContent {
content: manifest_entry.read_str()?,
position,
spine_entry,
manifest_entry,
})
}
#[doc = doc::inherent!(Reader, reset)]
pub fn reset(&mut self) {
self.cursor.reset();
}
#[doc = doc::inherent!(Reader, read_next)]
pub fn read_next(&mut self) -> Option<ReaderResult<EpubReaderContent<'ebook>>> {
self.cursor
.increment()
.map(|index| self.find_entry_by_position(index))
}
#[doc = doc::inherent!(Reader, read_prev)]
pub fn read_prev(&mut self) -> Option<ReaderResult<EpubReaderContent<'ebook>>> {
self.cursor
.decrement()
.map(|index| self.find_entry_by_position(index))
}
#[doc = doc::inherent!(Reader, read_current)]
pub fn read_current(&self) -> Option<ReaderResult<EpubReaderContent<'ebook>>> {
self.current_position()
.map(|position| self.find_entry_by_position(position))
}
#[doc = doc::inherent!(Reader, read)]
pub fn read<'a>(
&mut self,
key: impl Into<ReaderKey<'a>>,
) -> ReaderResult<EpubReaderContent<'ebook>> {
match key.into() {
ReaderKey::Value(idref) => {
let (index, content) = self.find_entry_by_str(idref)?;
self.cursor.set(index);
Ok(content)
}
ReaderKey::Position(index) if index < self.entries.len() => {
let content = self.find_entry_by_position(index);
self.cursor.set(index);
content
}
ReaderKey::Position(index) => Err(ReaderError::OutOfBounds {
position: index,
len: self.entries.len(),
}),
}
}
#[doc = doc::inherent!(Reader, seek)]
pub fn seek<'a>(&mut self, key: impl Into<ReaderKey<'a>>) -> ReaderResult<usize> {
match key.into() {
ReaderKey::Value(idref) => {
let index = self.find_entry_by_idref(idref)?;
self.cursor.set(index);
Ok(index)
}
ReaderKey::Position(index) if index < self.entries.len() => {
self.cursor.set(index);
Ok(index)
}
ReaderKey::Position(index) => Err(ReaderError::OutOfBounds {
position: index,
len: self.entries.len(),
}),
}
}
#[doc = doc::inherent!(Reader, get)]
pub fn get<'a>(
&self,
key: impl Into<ReaderKey<'a>>,
) -> ReaderResult<EpubReaderContent<'ebook>> {
match key.into() {
ReaderKey::Value(manifest_id) => self
.find_entry_by_str(manifest_id)
.map(|(_, content)| content),
ReaderKey::Position(index) => self.find_entry_by_position(index),
}
}
#[doc = doc::inherent!(Reader, len)]
pub fn len(&self) -> usize {
self.entries.len()
}
#[doc = doc::inherent!(Reader, current_position)]
pub fn current_position(&self) -> Option<usize> {
self.cursor.index()
}
#[doc = doc::inherent!(Reader, remaining)]
pub fn remaining(&self) -> usize {
Reader::remaining(self)
}
#[doc = doc::inherent!(Reader, is_empty)]
pub fn is_empty(&self) -> bool {
Reader::is_empty(self)
}
}
impl Sealed for EpubReader<'_> {}
#[allow(refining_impl_trait)]
impl<'ebook> Reader<'ebook> for EpubReader<'ebook> {
fn reset(&mut self) {
self.reset();
}
fn read_next(&mut self) -> Option<ReaderResult<EpubReaderContent<'ebook>>> {
self.read_next()
}
fn read_prev(&mut self) -> Option<ReaderResult<EpubReaderContent<'ebook>>> {
self.read_prev()
}
fn read_current(&self) -> Option<ReaderResult<EpubReaderContent<'ebook>>> {
self.read_current()
}
fn read<'a>(
&mut self,
key: impl Into<ReaderKey<'a>>,
) -> ReaderResult<EpubReaderContent<'ebook>> {
self.read(key)
}
fn seek<'a>(&mut self, key: impl Into<ReaderKey<'a>>) -> ReaderResult<usize> {
self.seek(key)
}
fn get<'a>(&self, key: impl Into<ReaderKey<'a>>) -> ReaderResult<EpubReaderContent<'ebook>> {
self.get(key)
}
fn len(&self) -> usize {
self.len()
}
fn current_position(&self) -> Option<usize> {
self.current_position()
}
}
impl<'ebook> Iterator for EpubReader<'ebook> {
type Item = ReaderResult<EpubReaderContent<'ebook>>;
fn next(&mut self) -> Option<Self::Item> {
self.read_next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.remaining();
(remaining, Some(remaining))
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct EpubReaderContent<'ebook> {
content: String,
position: usize,
spine_entry: EpubSpineEntry<'ebook>,
manifest_entry: EpubManifestEntry<'ebook>,
}
impl<'ebook> EpubReaderContent<'ebook> {
#[doc = doc::inherent!(ReaderContent, position)]
pub fn position(&self) -> usize {
self.position
}
#[doc = doc::inherent!(ReaderContent, content)]
pub fn content(&self) -> &str {
self.content.as_str()
}
#[doc = doc::inherent!(ReaderContent, spine_entry)]
pub fn spine_entry(&self) -> EpubSpineEntry<'ebook> {
self.spine_entry
}
#[doc = doc::inherent!(ReaderContent, manifest_entry)]
pub fn manifest_entry(&self) -> EpubManifestEntry<'ebook> {
self.manifest_entry
}
#[doc = doc::inherent!(ReaderContent, into_string)]
pub fn into_string(self) -> String {
ReaderContent::into_string(self)
}
#[doc = doc::inherent!(ReaderContent, into_bytes)]
pub fn into_bytes(self) -> Vec<u8> {
ReaderContent::into_bytes(self)
}
}
impl Sealed for EpubReaderContent<'_> {}
#[allow(refining_impl_trait)]
impl<'ebook> ReaderContent<'ebook> for EpubReaderContent<'ebook> {
fn position(&self) -> usize {
self.position()
}
fn content(&self) -> &str {
self.content()
}
fn spine_entry(&self) -> EpubSpineEntry<'ebook> {
self.spine_entry()
}
fn manifest_entry(&self) -> EpubManifestEntry<'ebook> {
self.manifest_entry()
}
}
impl<'ebook> From<EpubReaderContent<'ebook>> for String {
fn from(value: EpubReaderContent<'ebook>) -> Self {
value.content
}
}
impl<'ebook> From<EpubReaderContent<'ebook>> for Vec<u8> {
fn from(value: EpubReaderContent<'ebook>) -> Self {
value.content.into_bytes()
}
}
#[derive(Copy, Clone, Debug, Default, Hash, PartialEq, Eq)]
pub enum LinearBehavior {
#[default]
Original,
LinearOnly,
NonLinearOnly,
PrependNonLinear,
AppendNonLinear,
}
#[derive(Clone, Debug)]
pub(super) struct EpubReaderConfig {
linear_behavior: LinearBehavior,
}
impl Default for EpubReaderConfig {
fn default() -> Self {
Self {
linear_behavior: LinearBehavior::Original,
}
}
}
#[non_exhaustive]
#[derive(Clone, Debug, Default)]
pub struct EpubReaderOptions<T = ()> {
container: T,
config: EpubReaderConfig,
}
impl<T> EpubReaderOptions<T> {
pub fn linear_behavior(mut self, linear_behavior: LinearBehavior) -> Self {
self.config.linear_behavior = linear_behavior;
self
}
}
impl<'ebook> EpubReaderOptions<&'ebook Epub> {
pub(super) fn new(epub: &'ebook Epub) -> Self {
Self {
container: epub,
config: EpubReaderConfig::default(),
}
}
pub fn create(self) -> EpubReader<'ebook> {
EpubReader::new(self.container, &self.config)
}
}
impl EpubReaderOptions {
pub fn new() -> Self {
Self::default()
}
pub fn create<'ebook>(&self, epub: &'ebook Epub) -> EpubReader<'ebook> {
EpubReader::new(epub, &self.config)
}
}