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
38pub 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
49pub type EpubDocumentFile = EpubDocument<File>;
51
52pub 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 })
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 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 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 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 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}