cadmus_core/document/epub/
mod.rs

1use super::html::css::CssParser;
2use super::html::dom::{NodeRef, XmlTree};
3use super::html::engine::{Engine, Page, ResourceFetcher};
4use super::html::layout::TextAlign;
5use super::html::layout::{DrawCommand, DrawState, ImageCommand, RootData, TextCommand};
6use super::html::layout::{LoopContext, StyleData};
7use super::html::style::StyleSheet;
8use super::html::xml::XmlParser;
9use super::pdf::PdfOpener;
10use crate::document::{chapter_from_uri, BoundedText, Document, Location, TextLocation, TocEntry};
11use crate::framebuffer::Pixmap;
12use crate::geom::{Boundary, CycleDir};
13use crate::helpers::{decode_entities, Normalize};
14use crate::unit::pt_to_px;
15use anyhow::{format_err, Error};
16use fxhash::FxHashMap;
17use percent_encoding::percent_decode_str;
18use std::collections::BTreeSet;
19use std::fs::{self, File};
20use std::io::{Cursor, Read, Seek};
21use std::path::{Path, PathBuf};
22use zip::ZipArchive;
23
24const VIEWER_STYLESHEET: &str = "css/epub.css";
25const USER_STYLESHEET: &str = "css/epub-user.css";
26
27type UriCache = FxHashMap<String, usize>;
28
29impl<R: Read + Seek> ResourceFetcher for ZipArchive<R> {
30    fn fetch(&mut self, name: &str) -> Result<Vec<u8>, Error> {
31        let mut file = self.by_name(name)?;
32        let mut buf = Vec::new();
33        file.read_to_end(&mut buf)?;
34        Ok(buf)
35    }
36}
37
38/// Generic EPUB document that works with any Read + Seek source.
39pub struct EpubDocument<R: Read + Seek> {
40    archive: ZipArchive<R>,
41    info: XmlTree,
42    parent: PathBuf,
43    engine: Engine,
44    spine: Vec<Chunk>,
45    cache: FxHashMap<usize, Vec<Page>>,
46    ignore_document_css: bool,
47}
48
49/// Type alias for file-based EPUB documents (backward compatibility).
50pub type EpubDocumentFile = EpubDocument<File>;
51
52/// Type alias for static EPUB documents (zero-copy for embedded assets).
53pub type EpubDocumentStatic = EpubDocument<Cursor<&'static [u8]>>;
54
55#[derive(Debug)]
56struct Chunk {
57    path: String,
58    size: usize,
59}
60
61unsafe impl<R: Read + Seek> Send for EpubDocument<R> {}
62unsafe impl<R: Read + Seek> Sync for EpubDocument<R> {}
63
64impl<R: Read + Seek> EpubDocument<R> {
65    fn from_archive(mut archive: ZipArchive<R>) -> Result<Self, Error> {
66        let opf_path = {
67            let mut zf = archive.by_name("META-INF/container.xml")?;
68            let mut text = String::new();
69            zf.read_to_string(&mut text)?;
70            let root = XmlParser::new(&text).parse();
71            root.root()
72                .find("rootfile")
73                .and_then(|e| e.attribute("full-path"))
74                .map(String::from)
75        }
76        .ok_or_else(|| format_err!("can't get the OPF path"))?;
77
78        let parent = Path::new(&opf_path)
79            .parent()
80            .unwrap_or_else(|| Path::new(""));
81
82        let text = {
83            let mut zf = archive.by_name(&opf_path)?;
84            let mut text = String::new();
85            zf.read_to_string(&mut text)?;
86            text
87        };
88
89        let info = XmlParser::new(&text).parse();
90        let mut spine = Vec::new();
91
92        {
93            let manifest = info
94                .root()
95                .find("manifest")
96                .ok_or_else(|| format_err!("the manifest is missing"))?;
97
98            let spn = info
99                .root()
100                .find("spine")
101                .ok_or_else(|| format_err!("the spine is missing"))?;
102
103            for child in spn.children() {
104                let vertebra_opt = child
105                    .attribute("idref")
106                    .and_then(|idref| manifest.find_by_id(idref))
107                    .and_then(|entry| entry.attribute("href"))
108                    .and_then(|href| {
109                        let href = decode_entities(href);
110                        let href = percent_decode_str(&href).decode_utf8_lossy();
111                        let href_path = parent.join::<&str>(href.as_ref());
112                        href_path.to_str().and_then(|path| {
113                            archive
114                                .by_name(path)
115                                .map_err(|e| {
116                                    tracing::error!(
117                                        "Can't retrieve '{}' from the archive: {:#}.",
118                                        path,
119                                        e
120                                    )
121                                    // We're assuming that the size of the spine is less than 4 GiB.
122                                })
123                                .map(|zf| (zf.size() as usize, path.to_string()))
124                                .ok()
125                        })
126                    });
127
128                if let Some((size, path)) = vertebra_opt {
129                    spine.push(Chunk { path, size });
130                }
131            }
132        }
133
134        if spine.is_empty() {
135            return Err(format_err!("the spine is empty"));
136        }
137
138        Ok(EpubDocument {
139            archive,
140            info,
141            parent: parent.to_path_buf(),
142            engine: Engine::new(),
143            spine,
144            cache: FxHashMap::default(),
145            ignore_document_css: false,
146        })
147    }
148
149    fn offset(&self, index: usize) -> usize {
150        self.spine.iter().take(index).map(|c| c.size).sum()
151    }
152
153    fn size(&self) -> usize {
154        self.offset(self.spine.len())
155    }
156
157    fn vertebra_coordinates_with<F>(&self, test: F) -> Option<(usize, usize)>
158    where
159        F: Fn(usize, usize) -> bool,
160    {
161        let mut start_offset = 0;
162        let mut end_offset = start_offset;
163        let mut index = 0;
164
165        while index < self.spine.len() {
166            end_offset += self.spine[index].size;
167            if test(index, end_offset) {
168                return Some((index, start_offset));
169            }
170            start_offset = end_offset;
171            index += 1;
172        }
173
174        None
175    }
176
177    fn vertebra_coordinates(&self, offset: usize) -> Option<(usize, usize)> {
178        self.vertebra_coordinates_with(|_, end_offset| offset < end_offset)
179    }
180
181    fn vertebra_coordinates_from_name(&self, name: &str) -> Option<(usize, usize)> {
182        self.vertebra_coordinates_with(|index, _| self.spine[index].path == name)
183    }
184
185    fn walk_toc_ncx(
186        &mut self,
187        node: NodeRef,
188        toc_dir: &Path,
189        index: &mut usize,
190        cache: &mut UriCache,
191    ) -> Vec<TocEntry> {
192        let mut entries = Vec::new();
193        // TODO: Take `playOrder` into account?
194
195        for child in node.children() {
196            if child.tag_name() == Some("navPoint") {
197                let title = child
198                    .find("navLabel")
199                    .and_then(|label| label.find("text"))
200                    .map(|text| decode_entities(&text.text()).into_owned())
201                    .unwrap_or_default();
202
203                // Example URI: pr03.html#codecomma_and_what_to_do_with_it
204                let rel_uri = child
205                    .find("content")
206                    .and_then(|content| {
207                        content.attribute("src").map(|src| {
208                            percent_decode_str(&decode_entities(src))
209                                .decode_utf8_lossy()
210                                .into_owned()
211                        })
212                    })
213                    .unwrap_or_default();
214
215                let loc = toc_dir
216                    .join(&rel_uri)
217                    .normalize()
218                    .to_str()
219                    .map(|uri| Location::Uri(uri.to_string()));
220
221                let current_index = *index;
222                *index += 1;
223
224                let sub_entries = if child.children().count() > 2 {
225                    self.walk_toc_ncx(child, toc_dir, index, cache)
226                } else {
227                    Vec::new()
228                };
229
230                if let Some(location) = loc {
231                    entries.push(TocEntry {
232                        title,
233                        location,
234                        index: current_index,
235                        children: sub_entries,
236                    });
237                }
238            }
239        }
240
241        entries
242    }
243
244    fn walk_toc_nav(
245        &mut self,
246        node: NodeRef,
247        toc_dir: &Path,
248        index: &mut usize,
249        cache: &mut UriCache,
250    ) -> Vec<TocEntry> {
251        let mut entries = Vec::new();
252
253        for child in node.children() {
254            if child.tag_name() == Some("li") {
255                let link = child.children().find(|child| child.tag_name() == Some("a"));
256                let title = link
257                    .map(|link| decode_entities(&link.text()).into_owned())
258                    .unwrap_or_default();
259                let rel_uri = link
260                    .and_then(|link| {
261                        link.attribute("href").map(|href| {
262                            percent_decode_str(&decode_entities(href))
263                                .decode_utf8_lossy()
264                                .into_owned()
265                        })
266                    })
267                    .unwrap_or_default();
268
269                let loc = toc_dir
270                    .join(&rel_uri)
271                    .normalize()
272                    .to_str()
273                    .map(|uri| Location::Uri(uri.to_string()));
274
275                let current_index = *index;
276                *index += 1;
277
278                let sub_entries = if let Some(sub_list) = child.find("ol") {
279                    self.walk_toc_nav(sub_list, toc_dir, index, cache)
280                } else {
281                    Vec::new()
282                };
283
284                if let Some(location) = loc {
285                    entries.push(TocEntry {
286                        title,
287                        location,
288                        index: current_index,
289                        children: sub_entries,
290                    });
291                }
292            }
293        }
294
295        entries
296    }
297
298    #[inline]
299    fn page_index(&mut self, offset: usize, index: usize, start_offset: usize) -> Option<usize> {
300        if !self.cache.contains_key(&index) {
301            let display_list = self.build_display_list(index, start_offset);
302            self.cache.insert(index, display_list);
303        }
304        self.cache.get(&index).map(|display_list| {
305            if display_list.len() < 2
306                || display_list[1].first().map(|dc| offset < dc.offset()) == Some(true)
307            {
308                return 0;
309            } else if display_list[display_list.len() - 1]
310                .first()
311                .map(|dc| offset >= dc.offset())
312                == Some(true)
313            {
314                return display_list.len() - 1;
315            } else {
316                for i in 1..display_list.len() - 1 {
317                    if display_list[i].first().map(|dc| offset >= dc.offset()) == Some(true)
318                        && display_list[i + 1].first().map(|dc| offset < dc.offset()) == Some(true)
319                    {
320                        return i;
321                    }
322                }
323            }
324            0
325        })
326    }
327
328    fn resolve_link(&mut self, uri: &str, cache: &mut UriCache) -> Option<usize> {
329        let frag_index_opt = uri.find('#');
330        let name = &uri[..frag_index_opt.unwrap_or_else(|| uri.len())];
331
332        let (index, start_offset) = self.vertebra_coordinates_from_name(name)?;
333
334        if frag_index_opt.is_some() {
335            let mut text = String::new();
336            {
337                let mut zf = self.archive.by_name(name).ok()?;
338                zf.read_to_string(&mut text).ok()?;
339            }
340            let root = XmlParser::new(&text).parse();
341            self.cache_uris(root.root(), name, start_offset, cache);
342            cache.get(uri).cloned()
343        } else {
344            let page_index = self.page_index(start_offset, index, start_offset)?;
345            let offset = self
346                .cache
347                .get(&index)
348                .and_then(|display_list| display_list[page_index].first())
349                .map(DrawCommand::offset)?;
350            cache.insert(uri.to_string(), offset);
351            Some(offset)
352        }
353    }
354
355    fn cache_uris(&mut self, node: NodeRef, name: &str, start_offset: usize, cache: &mut UriCache) {
356        if let Some(id) = node.attribute("id") {
357            let location = start_offset + node.offset();
358            cache.insert(format!("{}#{}", name, id), location);
359        }
360        for child in node.children() {
361            self.cache_uris(child, name, start_offset, cache);
362        }
363    }
364
365    fn build_display_list(&mut self, index: usize, start_offset: usize) -> Vec<Page> {
366        let mut text = String::new();
367        let mut spine_dir = PathBuf::default();
368
369        {
370            let path = &self.spine[index].path;
371            if let Some(parent) = Path::new(path).parent() {
372                spine_dir = parent.to_path_buf();
373            }
374
375            if let Ok(mut zf) = self.archive.by_name(path) {
376                zf.read_to_string(&mut text).ok();
377            }
378        }
379
380        let mut root = XmlParser::new(&text).parse();
381        root.wrap_lost_inlines();
382
383        let mut stylesheet = StyleSheet::new();
384
385        if let Ok(text) = fs::read_to_string(VIEWER_STYLESHEET) {
386            let mut css = CssParser::new(&text).parse();
387            stylesheet.append(&mut css, true);
388        }
389
390        if let Ok(text) = fs::read_to_string(USER_STYLESHEET) {
391            let mut css = CssParser::new(&text).parse();
392            stylesheet.append(&mut css, true);
393        }
394
395        if !self.ignore_document_css {
396            let mut inner_css = StyleSheet::new();
397            if let Some(head) = root.root().find("head") {
398                for child in head.children() {
399                    if child.tag_name() == Some("link")
400                        && child.attribute("rel") == Some("stylesheet")
401                    {
402                        if let Some(href) = child.attribute("href") {
403                            if let Some(name) = spine_dir.join(href).normalize().to_str() {
404                                let mut text = String::new();
405                                if let Ok(mut zf) = self.archive.by_name(name) {
406                                    zf.read_to_string(&mut text).ok();
407                                    let mut css = CssParser::new(&text).parse();
408                                    inner_css.append(&mut css, false);
409                                }
410                            }
411                        }
412                    } else if child.tag_name() == Some("style")
413                        && child.attribute("type") == Some("text/css")
414                    {
415                        let mut css = CssParser::new(&child.text()).parse();
416                        inner_css.append(&mut css, false);
417                    }
418                }
419            }
420
421            stylesheet.append(&mut inner_css, true);
422        }
423
424        let mut display_list = Vec::new();
425
426        if let Some(body) = root.root().find("body") {
427            let mut rect = self.engine.rect();
428            rect.shrink(&self.engine.margin);
429
430            let language = self.language().or_else(|| {
431                root.root()
432                    .find("html")
433                    .and_then(|html| html.attribute("xml:lang"))
434                    .map(String::from)
435            });
436
437            let style = StyleData {
438                language,
439                font_size: self.engine.font_size,
440                line_height: pt_to_px(
441                    self.engine.line_height * self.engine.font_size,
442                    self.engine.dpi,
443                )
444                .round() as i32,
445                text_align: self.engine.text_align,
446                start_x: rect.min.x,
447                end_x: rect.max.x,
448                width: rect.max.x - rect.min.x,
449                ..Default::default()
450            };
451
452            let loop_context = LoopContext::default();
453            let mut draw_state = DrawState {
454                position: rect.min,
455                ..Default::default()
456            };
457
458            let root_data = RootData {
459                start_offset,
460                spine_dir,
461                rect,
462            };
463
464            display_list.push(Vec::new());
465
466            self.engine.build_display_list(
467                body,
468                &style,
469                &loop_context,
470                &stylesheet,
471                &root_data,
472                &mut self.archive,
473                &mut draw_state,
474                &mut display_list,
475            );
476
477            display_list.retain(|page| !page.is_empty());
478
479            if display_list.is_empty() {
480                display_list.push(vec![DrawCommand::Marker(start_offset + body.offset())]);
481            }
482        } else {
483            display_list.push(vec![DrawCommand::Marker(start_offset)]);
484        }
485
486        display_list
487    }
488
489    pub fn categories(&self) -> BTreeSet<String> {
490        let mut result = BTreeSet::new();
491
492        if let Some(md) = self.info.root().find("metadata") {
493            for child in md.children() {
494                if child.tag_qualified_name() == Some("dc:subject") {
495                    let text = child.text();
496                    let subject = decode_entities(&text);
497                    // Pipe separated list of BISAC categories
498                    if subject.contains(" / ") {
499                        for categ in subject.split('|') {
500                            let start_index = if let Some(index) = categ.find(" - ") {
501                                index + 3
502                            } else {
503                                0
504                            };
505                            result.insert(categ[start_index..].trim().replace(" / ", "."));
506                        }
507                    } else {
508                        result.insert(subject.into_owned());
509                    }
510                }
511            }
512        }
513
514        result
515    }
516
517    fn chapter_aux<'a>(
518        &mut self,
519        toc: &'a [TocEntry],
520        offset: usize,
521        next_offset: usize,
522        path: &str,
523        end_offset: &mut usize,
524        chap_before: &mut Option<&'a TocEntry>,
525        offset_before: &mut usize,
526        chap_after: &mut Option<&'a TocEntry>,
527        offset_after: &mut usize,
528    ) {
529        for entry in toc {
530            if let Location::Uri(ref uri) = entry.location {
531                if uri.starts_with(path) {
532                    if let Some(entry_offset) = self.resolve_location(entry.location.clone()) {
533                        if entry_offset < offset
534                            && (chap_before.is_none() || entry_offset > *offset_before)
535                        {
536                            *chap_before = Some(entry);
537                            *offset_before = entry_offset;
538                        }
539                        if entry_offset >= offset
540                            && entry_offset < next_offset
541                            && (chap_after.is_none() || entry_offset < *offset_after)
542                        {
543                            *chap_after = Some(entry);
544                            *offset_after = entry_offset;
545                        }
546                        if entry_offset >= next_offset && entry_offset < *end_offset {
547                            *end_offset = entry_offset;
548                        }
549                    }
550                }
551            }
552            self.chapter_aux(
553                &entry.children,
554                offset,
555                next_offset,
556                path,
557                end_offset,
558                chap_before,
559                offset_before,
560                chap_after,
561                offset_after,
562            );
563        }
564    }
565
566    fn previous_chapter<'a>(
567        &mut self,
568        chap: Option<&TocEntry>,
569        start_offset: usize,
570        end_offset: usize,
571        toc: &'a [TocEntry],
572    ) -> Option<&'a TocEntry> {
573        for entry in toc.iter().rev() {
574            let result = self.previous_chapter(chap, start_offset, end_offset, &entry.children);
575            if result.is_some() {
576                return result;
577            }
578
579            if let Some(chap) = chap {
580                if entry.index < chap.index {
581                    let entry_offset = self.resolve_location(entry.location.clone())?;
582                    if entry_offset < start_offset || entry_offset >= end_offset {
583                        return Some(entry);
584                    }
585                }
586            } else {
587                let entry_offset = self.resolve_location(entry.location.clone())?;
588                if entry_offset < start_offset {
589                    return Some(entry);
590                }
591            }
592        }
593        None
594    }
595
596    fn next_chapter<'a>(
597        &mut self,
598        chap: Option<&TocEntry>,
599        start_offset: usize,
600        end_offset: usize,
601        toc: &'a [TocEntry],
602    ) -> Option<&'a TocEntry> {
603        for entry in toc {
604            if let Some(chap) = chap {
605                if entry.index > chap.index {
606                    let entry_offset = self.resolve_location(entry.location.clone())?;
607                    if entry_offset < start_offset || entry_offset >= end_offset {
608                        return Some(entry);
609                    }
610                }
611            } else {
612                let entry_offset = self.resolve_location(entry.location.clone())?;
613                if entry_offset >= end_offset {
614                    return Some(entry);
615                }
616            }
617
618            let result = self.next_chapter(chap, start_offset, end_offset, &entry.children);
619            if result.is_some() {
620                return result;
621            }
622        }
623        None
624    }
625
626    pub fn series(&self) -> Option<(String, String)> {
627        self.info.root().find("metadata").and_then(|md| {
628            let mut title = None;
629            let mut index = None;
630
631            for child in md.children() {
632                if child.tag_name() == Some("meta") {
633                    if child.attribute("name") == Some("calibre:series") {
634                        title = child
635                            .attribute("content")
636                            .map(|s| decode_entities(s).into_owned());
637                    } else if child.attribute("name") == Some("calibre:series_index") {
638                        index = child
639                            .attribute("content")
640                            .map(|s| decode_entities(s).into_owned());
641                    } else if child.attribute("property") == Some("belongs-to-collection") {
642                        title = Some(decode_entities(&child.text()).into_owned());
643                    } else if child.attribute("property") == Some("group-position") {
644                        index = Some(decode_entities(&child.text()).into_owned());
645                    }
646                }
647
648                if title.is_some() && index.is_some() {
649                    break;
650                }
651            }
652
653            title.into_iter().zip(index).next()
654        })
655    }
656
657    pub fn cover_image(&self) -> Option<&str> {
658        self.info
659            .root()
660            .find("metadata")
661            .and_then(|md| {
662                md.children().find(|child| {
663                    child.tag_name() == Some("meta") && child.attribute("name") == Some("cover")
664                })
665            })
666            .and_then(|entry| entry.attribute("content"))
667            .and_then(|cover_id| {
668                self.info
669                    .root()
670                    .find("manifest")
671                    .and_then(|entry| entry.find_by_id(cover_id))
672                    .and_then(|entry| entry.attribute("href"))
673            })
674            .or_else(|| {
675                self.info
676                    .root()
677                    .find("manifest")
678                    .and_then(|mf| {
679                        mf.children().find(|child| {
680                            (child
681                                .attribute("href")
682                                .map_or(false, |hr| hr.contains("cover") || hr.contains("Cover"))
683                                || child.id().map_or(false, |id| id.contains("cover")))
684                                && child
685                                    .attribute("media-type")
686                                    .map_or(false, |mt| mt.starts_with("image/"))
687                        })
688                    })
689                    .and_then(|entry| entry.attribute("href"))
690            })
691    }
692
693    pub fn description(&self) -> Option<String> {
694        self.metadata("dc:description")
695    }
696
697    pub fn publisher(&self) -> Option<String> {
698        self.metadata("dc:publisher")
699    }
700
701    pub fn language(&self) -> Option<String> {
702        self.metadata("dc:language")
703    }
704
705    pub fn year(&self) -> Option<String> {
706        self.metadata("dc:date")
707            .map(|s| s.chars().take(4).collect())
708    }
709}
710
711impl EpubDocumentFile {
712    pub fn new<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
713        let file = File::open(path)?;
714        let archive = ZipArchive::new(file)?;
715        Self::from_archive(archive)
716    }
717}
718
719impl EpubDocumentStatic {
720    pub fn new_from_static(bytes: &'static [u8]) -> Result<Self, Error> {
721        let cursor = Cursor::new(bytes);
722        let archive = ZipArchive::new(cursor)?;
723        Self::from_archive(archive)
724    }
725}
726
727impl<R: Read + Seek> Document for EpubDocument<R> {
728    fn preview_pixmap(&mut self, width: f32, height: f32, samples: usize) -> Option<Pixmap> {
729        let opener = PdfOpener::new()?;
730        self.cover_image()
731            .map(|path| self.parent.join(path).to_string_lossy().into_owned())
732            .and_then(|path| {
733                self.archive
734                    .fetch(&path)
735                    .ok()
736                    .and_then(|buf| opener.open_memory(&path, &buf))
737                    .and_then(|mut doc| {
738                        doc.dims(0).and_then(|dims| {
739                            let scale = (width / dims.0).min(height / dims.1);
740                            doc.pixmap(Location::Exact(0), scale, samples)
741                        })
742                    })
743            })
744            .or_else(|| {
745                self.dims(0).and_then(|dims| {
746                    let scale = (width / dims.0).min(height / dims.1);
747                    self.pixmap(Location::Exact(0), scale, samples)
748                })
749            })
750            .map(|(pixmap, _)| pixmap)
751    }
752
753    #[inline]
754    fn dims(&self, _index: usize) -> Option<(f32, f32)> {
755        Some((self.engine.dims.0 as f32, self.engine.dims.1 as f32))
756    }
757
758    fn pages_count(&self) -> usize {
759        self.spine.iter().map(|c| c.size).sum()
760    }
761
762    fn toc(&mut self) -> Option<Vec<TocEntry>> {
763        let name = self
764            .info
765            .root()
766            .find("spine")
767            .and_then(|spine| spine.attribute("toc"))
768            .and_then(|toc_id| {
769                self.info
770                    .root()
771                    .find("manifest")
772                    .and_then(|manifest| manifest.find_by_id(toc_id))
773                    .and_then(|entry| entry.attribute("href"))
774            })
775            .or_else(|| {
776                self.info
777                    .root()
778                    .find("manifest")
779                    .and_then(|manifest| {
780                        manifest.children().find(|child| {
781                            child
782                                .attribute("properties")
783                                .iter()
784                                .any(|props| props.split_whitespace().any(|prop| prop == "nav"))
785                        })
786                    })
787                    .and_then(|entry| entry.attribute("href"))
788            })
789            .map(|href| {
790                self.parent
791                    .join(href)
792                    .normalize()
793                    .to_string_lossy()
794                    .into_owned()
795            })?;
796
797        let toc_dir = Path::new(&name).parent().unwrap_or_else(|| Path::new(""));
798
799        let mut text = String::new();
800        if let Ok(mut zf) = self.archive.by_name(&name) {
801            zf.read_to_string(&mut text).ok()?;
802        } else {
803            return None;
804        }
805
806        let root = XmlParser::new(&text).parse();
807
808        if name.ends_with(".ncx") {
809            root.root()
810                .find("navMap")
811                .map(|map| self.walk_toc_ncx(map, toc_dir, &mut 0, &mut FxHashMap::default()))
812        } else {
813            root.root()
814                .descendants()
815                .find(|desc| {
816                    desc.tag_name() == Some("nav") && desc.attribute("epub:type") == Some("toc")
817                })
818                .and_then(|map| map.find("ol"))
819                .map(|map| self.walk_toc_nav(map, toc_dir, &mut 0, &mut FxHashMap::default()))
820        }
821    }
822
823    fn chapter<'a>(&mut self, offset: usize, toc: &'a [TocEntry]) -> Option<(&'a TocEntry, f32)> {
824        let next_offset = self
825            .resolve_location(Location::Next(offset))
826            .unwrap_or(usize::MAX);
827        let (index, start_offset) = self.vertebra_coordinates(offset)?;
828        let path = self.spine[index].path.clone();
829        let mut end_offset = start_offset + self.spine[index].size;
830        let mut chap_before = None;
831        let mut chap_after = None;
832        let mut offset_before = 0;
833        let mut offset_after = usize::MAX;
834
835        self.chapter_aux(
836            toc,
837            offset,
838            next_offset,
839            &path,
840            &mut end_offset,
841            &mut chap_before,
842            &mut offset_before,
843            &mut chap_after,
844            &mut offset_after,
845        );
846
847        if chap_after.is_none() && chap_before.is_none() {
848            for i in (0..index).rev() {
849                let chap = chapter_from_uri(&self.spine[i].path, toc);
850                if chap.is_some() {
851                    end_offset = if let Some(j) = (index + 1..self.spine.len())
852                        .find(|&j| chapter_from_uri(&self.spine[j].path, toc).is_some())
853                    {
854                        self.offset(j)
855                    } else {
856                        self.size()
857                    };
858                    let chap_offset = self.offset(i);
859                    let progress =
860                        (offset - chap_offset) as f32 / (end_offset - chap_offset) as f32;
861                    return chap.zip(Some(progress));
862                }
863            }
864            None
865        } else {
866            match (chap_after, chap_before) {
867                (Some(..), _) => chap_after.zip(Some(0.0)),
868                (None, Some(..)) => chap_before.zip(Some(
869                    (offset - offset_before) as f32 / (end_offset - offset_before) as f32,
870                )),
871                _ => None,
872            }
873        }
874    }
875
876    fn chapter_relative<'a>(
877        &mut self,
878        offset: usize,
879        dir: CycleDir,
880        toc: &'a [TocEntry],
881    ) -> Option<&'a TocEntry> {
882        let next_offset = self
883            .resolve_location(Location::Next(offset))
884            .unwrap_or(usize::MAX);
885        let chap = self.chapter(offset, toc).map(|(c, _)| c);
886
887        match dir {
888            CycleDir::Previous => self.previous_chapter(chap, offset, next_offset, toc),
889            CycleDir::Next => self.next_chapter(chap, offset, next_offset, toc),
890        }
891    }
892
893    fn resolve_location(&mut self, loc: Location) -> Option<usize> {
894        self.engine.load_fonts();
895
896        match loc {
897            Location::Exact(offset) => {
898                let (index, start_offset) = self.vertebra_coordinates(offset)?;
899                let page_index = self.page_index(offset, index, start_offset)?;
900                self.cache
901                    .get(&index)
902                    .and_then(|display_list| display_list[page_index].first())
903                    .map(DrawCommand::offset)
904            }
905            Location::Previous(offset) => {
906                let (index, start_offset) = self.vertebra_coordinates(offset)?;
907                let page_index = self.page_index(offset, index, start_offset)?;
908                if page_index > 0 {
909                    self.cache.get(&index).and_then(|display_list| {
910                        display_list[page_index - 1]
911                            .first()
912                            .map(DrawCommand::offset)
913                    })
914                } else {
915                    if index == 0 {
916                        return None;
917                    }
918                    let (index, start_offset) =
919                        (index - 1, start_offset - self.spine[index - 1].size);
920                    if !self.cache.contains_key(&index) {
921                        let display_list = self.build_display_list(index, start_offset);
922                        self.cache.insert(index, display_list);
923                    }
924                    self.cache.get(&index).and_then(|display_list| {
925                        display_list
926                            .last()
927                            .and_then(|page| page.first())
928                            .map(DrawCommand::offset)
929                    })
930                }
931            }
932            Location::Next(offset) => {
933                let (index, start_offset) = self.vertebra_coordinates(offset)?;
934                let page_index = self.page_index(offset, index, start_offset)?;
935                if page_index < self.cache.get(&index).map(Vec::len)? - 1 {
936                    self.cache.get(&index).and_then(|display_list| {
937                        display_list[page_index + 1]
938                            .first()
939                            .map(DrawCommand::offset)
940                    })
941                } else {
942                    if index == self.spine.len() - 1 {
943                        return None;
944                    }
945                    let (index, start_offset) = (index + 1, start_offset + self.spine[index].size);
946                    if !self.cache.contains_key(&index) {
947                        let display_list = self.build_display_list(index, start_offset);
948                        self.cache.insert(index, display_list);
949                    }
950                    self.cache.get(&index).and_then(|display_list| {
951                        display_list
952                            .first()
953                            .and_then(|page| page.first())
954                            .map(|dc| dc.offset())
955                    })
956                }
957            }
958            Location::LocalUri(offset, ref uri) => {
959                let mut cache = FxHashMap::default();
960                let normalized_uri: String = {
961                    let (index, _) = self.vertebra_coordinates(offset)?;
962                    let path = &self.spine[index].path;
963                    if uri.starts_with('#') {
964                        format!("{}{}", path, uri)
965                    } else {
966                        let parent = Path::new(path).parent().unwrap_or_else(|| Path::new(""));
967                        parent.join(uri).normalize().to_string_lossy().into_owned()
968                    }
969                };
970                self.resolve_link(&normalized_uri, &mut cache)
971            }
972            Location::Uri(ref uri) => {
973                let mut cache = FxHashMap::default();
974                self.resolve_link(uri, &mut cache)
975            }
976        }
977    }
978
979    fn words(&mut self, loc: Location) -> Option<(Vec<BoundedText>, usize)> {
980        if self.spine.is_empty() {
981            return None;
982        }
983
984        let offset = self.resolve_location(loc)?;
985        let (index, start_offset) = self.vertebra_coordinates(offset)?;
986        let page_index = self.page_index(offset, index, start_offset)?;
987
988        self.cache.get(&index).map(|display_list| {
989            (
990                display_list[page_index]
991                    .iter()
992                    .filter_map(|dc| match dc {
993                        DrawCommand::Text(TextCommand {
994                            text, rect, offset, ..
995                        }) => Some(BoundedText {
996                            text: text.clone(),
997                            rect: (*rect).into(),
998                            location: TextLocation::Dynamic(*offset),
999                        }),
1000                        _ => None,
1001                    })
1002                    .collect(),
1003                offset,
1004            )
1005        })
1006    }
1007
1008    fn lines(&mut self, _loc: Location) -> Option<(Vec<BoundedText>, usize)> {
1009        None
1010    }
1011
1012    fn links(&mut self, loc: Location) -> Option<(Vec<BoundedText>, usize)> {
1013        if self.spine.is_empty() {
1014            return None;
1015        }
1016
1017        let offset = self.resolve_location(loc)?;
1018        let (index, start_offset) = self.vertebra_coordinates(offset)?;
1019        let page_index = self.page_index(offset, index, start_offset)?;
1020
1021        self.cache.get(&index).map(|display_list| {
1022            (
1023                display_list[page_index]
1024                    .iter()
1025                    .filter_map(|dc| match dc {
1026                        DrawCommand::Text(TextCommand {
1027                            uri, rect, offset, ..
1028                        })
1029                        | DrawCommand::Image(ImageCommand {
1030                            uri, rect, offset, ..
1031                        }) if uri.is_some() => Some(BoundedText {
1032                            text: uri.clone().unwrap(),
1033                            rect: (*rect).into(),
1034                            location: TextLocation::Dynamic(*offset),
1035                        }),
1036                        _ => None,
1037                    })
1038                    .collect(),
1039                offset,
1040            )
1041        })
1042    }
1043
1044    fn images(&mut self, loc: Location) -> Option<(Vec<Boundary>, usize)> {
1045        if self.spine.is_empty() {
1046            return None;
1047        }
1048
1049        let offset = self.resolve_location(loc)?;
1050        let (index, start_offset) = self.vertebra_coordinates(offset)?;
1051        let page_index = self.page_index(offset, index, start_offset)?;
1052
1053        self.cache.get(&index).map(|display_list| {
1054            (
1055                display_list[page_index]
1056                    .iter()
1057                    .filter_map(|dc| match dc {
1058                        DrawCommand::Image(ImageCommand { rect, .. }) => Some((*rect).into()),
1059                        _ => None,
1060                    })
1061                    .collect(),
1062                offset,
1063            )
1064        })
1065    }
1066
1067    fn pixmap(&mut self, loc: Location, scale: f32, samples: usize) -> Option<(Pixmap, usize)> {
1068        if self.spine.is_empty() {
1069            return None;
1070        }
1071
1072        let offset = self.resolve_location(loc)?;
1073        let (index, start_offset) = self.vertebra_coordinates(offset)?;
1074
1075        let page_index = self.page_index(offset, index, start_offset)?;
1076        let page = self.cache.get(&index)?.get(page_index)?.clone();
1077
1078        let pixmap = self
1079            .engine
1080            .render_page(&page, scale, samples, &mut self.archive)?;
1081
1082        Some((pixmap, offset))
1083    }
1084
1085    fn layout(&mut self, width: u32, height: u32, font_size: f32, dpi: u16) {
1086        self.engine.layout(width, height, font_size, dpi);
1087        self.cache.clear();
1088    }
1089
1090    fn set_text_align(&mut self, text_align: TextAlign) {
1091        self.engine.set_text_align(text_align);
1092        self.cache.clear();
1093    }
1094
1095    fn set_font_family(&mut self, family_name: &str, search_path: &str) {
1096        self.engine.set_font_family(family_name, search_path);
1097        self.cache.clear();
1098    }
1099
1100    fn set_margin_width(&mut self, width: i32) {
1101        self.engine.set_margin_width(width);
1102        self.cache.clear();
1103    }
1104
1105    fn set_line_height(&mut self, line_height: f32) {
1106        self.engine.set_line_height(line_height);
1107        self.cache.clear();
1108    }
1109
1110    fn set_hyphen_penalty(&mut self, hyphen_penalty: i32) {
1111        self.engine.set_hyphen_penalty(hyphen_penalty);
1112        self.cache.clear();
1113    }
1114
1115    fn set_stretch_tolerance(&mut self, stretch_tolerance: f32) {
1116        self.engine.set_stretch_tolerance(stretch_tolerance);
1117        self.cache.clear();
1118    }
1119
1120    fn set_ignore_document_css(&mut self, ignore: bool) {
1121        self.ignore_document_css = ignore;
1122        self.cache.clear();
1123    }
1124
1125    fn title(&self) -> Option<String> {
1126        self.metadata("dc:title")
1127    }
1128
1129    fn author(&self) -> Option<String> {
1130        // TODO: Consider the opf:file-as attribute?
1131        self.metadata("dc:creator")
1132    }
1133
1134    fn metadata(&self, key: &str) -> Option<String> {
1135        self.info
1136            .root()
1137            .find("metadata")
1138            .and_then(|md| {
1139                md.children()
1140                    .find(|child| child.tag_qualified_name() == Some(key))
1141            })
1142            .map(|child| decode_entities(&child.text()).into_owned())
1143    }
1144
1145    fn is_reflowable(&self) -> bool {
1146        true
1147    }
1148
1149    fn has_synthetic_page_numbers(&self) -> bool {
1150        true
1151    }
1152}