Skip to main content

cadmus_core/view/reader/
mod.rs

1mod bottom_bar;
2mod chapter_label;
3mod margin_cropper;
4mod results_bar;
5mod results_label;
6mod tool_bar;
7
8use self::bottom_bar::BottomBar;
9use self::margin_cropper::{MarginCropper, BUTTON_DIAMETER};
10use self::results_bar::ResultsBar;
11use self::tool_bar::ToolBar;
12use super::top_bar::{TopBar, TopBarVariant};
13use crate::color::{BLACK, WHITE};
14use crate::context::Context;
15use crate::device::CURRENT_DEVICE;
16use crate::document::epub::EpubDocumentStatic;
17use crate::document::html::HtmlDocument;
18use crate::document::{
19    annotations_as_html, bookmarks_as_html, toc_as_html, SimpleTocEntry, TocEntry, TocLocation,
20};
21use crate::document::{
22    open, BoundedText, Document, Location, Neighbors, TextLocation, BYTES_PER_PAGE,
23};
24use crate::font::family_names;
25use crate::font::Fonts;
26use crate::framebuffer::{Framebuffer, Pixmap, UpdateMode};
27use crate::frontlight::LightLevels;
28use crate::geom::{halves, Axis, CycleDir, DiagDir, Dir, LinearDir, Region};
29use crate::geom::{BorderSpec, Boundary, CornerSpec, Point, Rectangle, Vec2};
30use crate::gesture::GestureEvent;
31use crate::helpers::AsciiExtension;
32use crate::input::{ButtonCode, ButtonStatus, DeviceEvent, FingerStatus};
33use crate::metadata::{make_query, CroppingMargins, Margin};
34use crate::metadata::{
35    Annotation, FileInfo, Info, PageScheme, ReaderInfo, ScrollMode, TextAlign, ZoomMode,
36};
37use crate::metadata::{DEFAULT_CONTRAST_EXPONENT, DEFAULT_CONTRAST_GRAY};
38use crate::settings::{
39    guess_frontlight, BottomRightGestureAction, EastStripAction, FinishedAction,
40    SouthEastCornerAction, SouthStripAction, WestStripAction,
41};
42use crate::settings::{
43    DEFAULT_FONT_FAMILY, DEFAULT_LINE_HEIGHT, DEFAULT_MARGIN_WIDTH, DEFAULT_TEXT_ALIGN,
44};
45use crate::settings::{HYPHEN_PENALTY, STRETCH_TOLERANCE};
46use crate::unit::{mm_to_px, scale_by_dpi};
47use crate::view::common::{locate, locate_by_id, rlocate};
48use crate::view::common::{toggle_battery_menu, toggle_clock_menu, toggle_main_menu};
49use crate::view::filler::Filler;
50use crate::view::keyboard::Keyboard;
51use crate::view::menu::{Menu, MenuKind};
52use crate::view::menu_entry::MenuEntry;
53use crate::view::named_input::NamedInput;
54use crate::view::notification::Notification;
55use crate::view::search_bar::SearchBar;
56use crate::view::{AppCmd, Bus, Event, Hub, RenderData, RenderQueue, ToggleEvent, View};
57use crate::view::{EntryId, EntryKind, Id, SliderId, ViewId, ID_FEEDER};
58use crate::view::{BIG_BAR_HEIGHT, SMALL_BAR_HEIGHT, THICKNESS_MEDIUM};
59use chrono::Local;
60use fxhash::{FxHashMap, FxHashSet};
61use rand_core::Rng;
62use regex::Regex;
63use septem::prelude::*;
64use septem::{Digit, Roman};
65use std::collections::{BTreeMap, VecDeque};
66use std::fs::OpenOptions;
67use std::io::prelude::*;
68use std::path::PathBuf;
69use std::sync::atomic::AtomicBool;
70use std::sync::atomic::Ordering as AtomicOrdering;
71use std::sync::{Arc, Mutex};
72use std::thread;
73use tracing::{debug, error, info, warn};
74
75const HISTORY_SIZE: usize = 32;
76const RECT_DIST_JITTER: f32 = 24.0;
77const ANNOTATION_DRIFT: u8 = 0x44;
78const HIGHLIGHT_DRIFT: u8 = 0x22;
79const MEM_SCHEME: &str = "mem:";
80
81pub struct Reader {
82    id: Id,
83    rect: Rectangle,
84    children: Vec<Box<dyn View>>,
85    doc: Arc<Mutex<Box<dyn Document>>>,
86    cache: BTreeMap<usize, Resource>,         // Cached page pixmaps.
87    chunks: Vec<RenderChunk>,                 // Chunks of pages being rendered.
88    text: FxHashMap<usize, Vec<BoundedText>>, // Text of the current chunks.
89    annotations: FxHashMap<usize, Vec<Annotation>>, // Annotations for the current chunks.
90    noninverted_regions: FxHashMap<usize, Vec<Boundary>>,
91    focus: Option<ViewId>,
92    search: Option<Search>,
93    search_direction: LinearDir,
94    held_buttons: FxHashSet<ButtonCode>,
95    selection: Option<Selection>,
96    target_annotation: Option<[TextLocation; 2]>,
97    history: VecDeque<usize>,
98    state: State,
99    info: Info,
100    current_page: usize,
101    pages_count: usize,
102    view_port: ViewPort,
103    contrast: Contrast,
104    synthetic: bool,
105    page_turns: usize,
106    reflowable: bool,
107    ephemeral: bool,
108    finished: bool,
109}
110
111struct ViewPort {
112    zoom_mode: ZoomMode,
113    scroll_mode: ScrollMode,
114    page_offset: Point, // Offset relative to the top left corner of a resource's frame.
115    margin_width: i32,
116}
117
118impl Default for ViewPort {
119    fn default() -> Self {
120        ViewPort {
121            zoom_mode: ZoomMode::FitToPage,
122            scroll_mode: ScrollMode::Screen,
123            page_offset: pt!(0, 0),
124            margin_width: 0,
125        }
126    }
127}
128
129#[derive(Debug, Copy, Clone, Eq, PartialEq)]
130enum State {
131    Idle,
132    Selection(i32),
133    AdjustSelection,
134}
135
136struct Selection {
137    start: TextLocation,
138    end: TextLocation,
139    anchor: TextLocation,
140}
141
142struct Resource {
143    pixmap: Pixmap,
144    frame: Rectangle, // The pixmap's rectangle minus the cropping margins.
145    scale: f32,
146}
147
148#[derive(Debug, Clone)]
149struct RenderChunk {
150    location: usize,
151    frame: Rectangle, // A subrectangle of the corresponding resource's frame.
152    position: Point,
153    scale: f32,
154}
155
156struct Search {
157    query: String,
158    highlights: BTreeMap<usize, Vec<Vec<Boundary>>>,
159    running: Arc<AtomicBool>,
160    current_page: usize,
161    results_count: usize,
162}
163
164impl Default for Search {
165    fn default() -> Self {
166        Search {
167            query: String::new(),
168            highlights: BTreeMap::new(),
169            running: Arc::new(AtomicBool::new(true)),
170            current_page: 0,
171            results_count: 0,
172        }
173    }
174}
175
176struct Contrast {
177    exponent: f32,
178    gray: f32,
179}
180
181impl Default for Contrast {
182    fn default() -> Contrast {
183        Contrast {
184            exponent: DEFAULT_CONTRAST_EXPONENT,
185            gray: DEFAULT_CONTRAST_GRAY,
186        }
187    }
188}
189
190fn scaling_factor(
191    rect: &Rectangle,
192    cropping_margin: &Margin,
193    screen_margin_width: i32,
194    dims: (f32, f32),
195    zoom_mode: ZoomMode,
196) -> f32 {
197    if let ZoomMode::Custom(sf) = zoom_mode {
198        return sf;
199    }
200
201    let (page_width, page_height) = dims;
202    let surface_width = (rect.width() as i32 - 2 * screen_margin_width) as f32;
203    let frame_width = (1.0 - (cropping_margin.left + cropping_margin.right)) * page_width;
204    let width_ratio = surface_width / frame_width;
205    match zoom_mode {
206        ZoomMode::FitToPage => {
207            let surface_height = (rect.height() as i32 - 2 * screen_margin_width) as f32;
208            let frame_height = (1.0 - (cropping_margin.top + cropping_margin.bottom)) * page_height;
209            let height_ratio = surface_height / frame_height;
210            width_ratio.min(height_ratio)
211        }
212        ZoomMode::FitToWidth => width_ratio,
213        ZoomMode::Custom(_) => unreachable!(),
214    }
215}
216
217fn build_pixmap(rect: &Rectangle, doc: &mut dyn Document, location: usize) -> (Pixmap, usize) {
218    let scale = scaling_factor(
219        rect,
220        &Margin::default(),
221        0,
222        doc.dims(location).unwrap(),
223        ZoomMode::FitToPage,
224    );
225    doc.pixmap(
226        Location::Exact(location),
227        scale,
228        CURRENT_DEVICE.color_samples(),
229    )
230    .unwrap()
231}
232
233fn find_cut(
234    frame: &Rectangle,
235    y_pos: i32,
236    scale: f32,
237    dir: LinearDir,
238    lines: &[BoundedText],
239) -> Option<i32> {
240    let y_pos_u = y_pos as f32 / scale;
241    let frame_u = frame.to_boundary() / scale;
242    let mut rect_a: Option<Boundary> = None;
243    let max_line_height = frame_u.height() / 10.0;
244
245    for line in lines {
246        if frame_u.contains(&line.rect)
247            && line.rect.height() <= max_line_height
248            && y_pos_u >= line.rect.min.y
249            && y_pos_u < line.rect.max.y
250        {
251            rect_a = Some(line.rect);
252            break;
253        }
254    }
255
256    rect_a.map(|ra| {
257        if dir == LinearDir::Backward {
258            (scale * ra.min.y).floor() as i32
259        } else {
260            (scale * ra.max.y).ceil() as i32
261        }
262    })
263}
264
265fn word_separator(lang: &str) -> &'static str {
266    let l = lang.to_ascii_lowercase();
267    match l.as_str() {
268        // https://en.wikipedia.org/wiki/Scriptio_continua
269        // Japanese, Chinese, Burmese, Lao, Khmer, Thai, Bengali, Javanese, Sundanese.
270        "ja" | "zh" | "my" | "lo" | "km" | "th" | "bn" | "jv" | "su" => "",
271        _ => " ",
272    }
273}
274
275impl Reader {
276    pub fn new(
277        rect: Rectangle,
278        mut info: Info,
279        hub: &Hub,
280        context: &mut Context,
281    ) -> Option<Reader> {
282        let id = ID_FEEDER.next();
283        let settings = &context.settings;
284        let path = if !info.file.absolute_path.as_os_str().is_empty() {
285            info.file.absolute_path.clone()
286        } else {
287            context.library.home.join(&info.file.path)
288        };
289
290        debug!(
291            resolved_path = %path.display(),
292            file_exists = path.exists(),
293            "Opening document"
294        );
295
296        let doc = open(&path);
297        if doc.is_none() {
298            warn!(
299                resolved_path = %path.display(),
300                file_exists = path.exists(),
301                "Failed to open document: open() returned None"
302            );
303        }
304        doc.and_then(|mut doc| {
305            let (width, height) = context.display.dims;
306            let font_size = info
307                .reader
308                .as_ref()
309                .and_then(|r| r.font_size)
310                .unwrap_or(settings.reader.font_size);
311
312            doc.layout(width, height, font_size, CURRENT_DEVICE.dpi);
313
314            let margin_width = info
315                .reader
316                .as_ref()
317                .and_then(|r| r.margin_width)
318                .unwrap_or(settings.reader.margin_width);
319
320            if margin_width != DEFAULT_MARGIN_WIDTH {
321                doc.set_margin_width(margin_width);
322            }
323
324            let font_family = info
325                .reader
326                .as_ref()
327                .and_then(|r| r.font_family.as_ref())
328                .unwrap_or(&settings.reader.font_family);
329
330            if font_family != DEFAULT_FONT_FAMILY {
331                doc.set_font_family(font_family, &settings.reader.font_path);
332            }
333
334            let line_height = info
335                .reader
336                .as_ref()
337                .and_then(|r| r.line_height)
338                .unwrap_or(settings.reader.line_height);
339
340            if (line_height - DEFAULT_LINE_HEIGHT).abs() > f32::EPSILON {
341                doc.set_line_height(line_height);
342            }
343
344            let text_align = info
345                .reader
346                .as_ref()
347                .and_then(|r| r.text_align)
348                .unwrap_or(settings.reader.text_align);
349
350            if text_align != DEFAULT_TEXT_ALIGN {
351                doc.set_text_align(text_align);
352            }
353
354            let hyphen_penalty = settings.reader.paragraph_breaker.hyphen_penalty;
355
356            if hyphen_penalty != HYPHEN_PENALTY {
357                doc.set_hyphen_penalty(hyphen_penalty);
358            }
359
360            let stretch_tolerance = settings.reader.paragraph_breaker.stretch_tolerance;
361
362            if stretch_tolerance != STRETCH_TOLERANCE {
363                doc.set_stretch_tolerance(stretch_tolerance);
364            }
365
366            if settings.reader.ignore_document_css {
367                doc.set_ignore_document_css(true);
368            }
369
370            let first_location = doc.resolve_location(Location::Exact(0));
371            if first_location.is_none() {
372                warn!(
373                    resolved_path = %path.display(),
374                    "Document opened but resolve_location(Exact(0)) returned None"
375                );
376            }
377            let first_location = first_location?;
378
379            let mut view_port = ViewPort::default();
380            let mut contrast = Contrast::default();
381            let pages_count = doc.pages_count();
382            let current_page;
383
384            // TODO: use get_or_insert_with?
385            if let Some(ref mut r) = info.reader {
386                r.opened = Local::now().naive_local();
387
388                if r.finished {
389                    r.finished = false;
390                    r.current_page = first_location;
391                    r.page_offset = None;
392                }
393
394                current_page = doc
395                    .resolve_location(Location::Exact(r.current_page))
396                    .unwrap_or(first_location);
397
398                if let Some(zoom_mode) = r.zoom_mode {
399                    view_port.zoom_mode = zoom_mode;
400                }
401
402                if let Some(scroll_mode) = r.scroll_mode {
403                    view_port.scroll_mode = scroll_mode;
404                } else {
405                    view_port.scroll_mode = if settings.reader.continuous_fit_to_width {
406                        ScrollMode::Screen
407                    } else {
408                        ScrollMode::Page
409                    };
410                }
411
412                if let Some(page_offset) = r.page_offset {
413                    view_port.page_offset = page_offset;
414                }
415
416                if !doc.is_reflowable() {
417                    view_port.margin_width = mm_to_px(
418                        r.screen_margin_width.unwrap_or(0) as f32,
419                        CURRENT_DEVICE.dpi,
420                    ) as i32;
421                }
422
423                if let Some(exponent) = r.contrast_exponent {
424                    contrast.exponent = exponent;
425                }
426
427                if let Some(gray) = r.contrast_gray {
428                    contrast.gray = gray;
429                }
430            } else {
431                current_page = first_location;
432
433                info.reader = Some(ReaderInfo {
434                    current_page,
435                    pages_count,
436                    ..Default::default()
437                });
438            }
439
440            let synthetic = doc.has_synthetic_page_numbers();
441            let reflowable = doc.is_reflowable();
442
443            if info.toc.is_none() {
444                if let Some(toc) = doc.toc() {
445                    let simple_toc: Vec<SimpleTocEntry> =
446                        toc.iter().map(SimpleTocEntry::from).collect();
447                    context
448                        .library
449                        .sync_toc(&info.file.path, simple_toc.clone());
450                    info.toc = Some(simple_toc);
451                }
452            }
453
454            info!("{}", info.file.path.display());
455
456            hub.send(Event::Update(UpdateMode::Partial)).ok();
457
458            Some(Reader {
459                id,
460                rect,
461                children: Vec::new(),
462                doc: Arc::new(Mutex::new(doc)),
463                cache: BTreeMap::new(),
464                chunks: Vec::new(),
465                text: FxHashMap::default(),
466                annotations: FxHashMap::default(),
467                noninverted_regions: FxHashMap::default(),
468                focus: None,
469                search: None,
470                search_direction: LinearDir::Forward,
471                held_buttons: FxHashSet::default(),
472                selection: None,
473                target_annotation: None,
474                history: VecDeque::new(),
475                state: State::Idle,
476                info,
477                current_page,
478                pages_count,
479                view_port,
480                synthetic,
481                page_turns: 0,
482                contrast,
483                ephemeral: false,
484                reflowable,
485                finished: false,
486            })
487        })
488    }
489
490    pub fn from_html(
491        rect: Rectangle,
492        html: &str,
493        link_uri: Option<&str>,
494        hub: &Hub,
495        context: &mut Context,
496    ) -> Reader {
497        let id = ID_FEEDER.next();
498
499        let mut info = Info {
500            file: FileInfo {
501                path: PathBuf::from(MEM_SCHEME),
502                kind: "html".to_string(),
503                size: html.len() as u64,
504                ..Default::default()
505            },
506            ..Default::default()
507        };
508
509        let mut doc = HtmlDocument::new_from_memory(html);
510        let (width, height) = context.display.dims;
511        let font_size = context.settings.reader.font_size;
512        doc.layout(width, height, font_size, CURRENT_DEVICE.dpi);
513        let pages_count = doc.pages_count();
514        info.title = doc.title().unwrap_or_default();
515
516        let mut current_page = 0;
517        if let Some(link_uri) = link_uri {
518            let mut loc = Location::Exact(0);
519            while let Some((links, offset)) = doc.links(loc) {
520                if links.iter().any(|link| link.text == link_uri) {
521                    current_page = offset;
522                    break;
523                }
524                loc = Location::Next(offset);
525            }
526        }
527
528        hub.send(Event::Update(UpdateMode::Partial)).ok();
529
530        Reader {
531            id,
532            rect,
533            children: Vec::new(),
534            doc: Arc::new(Mutex::new(Box::new(doc))),
535            cache: BTreeMap::new(),
536            chunks: Vec::new(),
537            text: FxHashMap::default(),
538            annotations: FxHashMap::default(),
539            noninverted_regions: FxHashMap::default(),
540            focus: None,
541            search: None,
542            search_direction: LinearDir::Forward,
543            held_buttons: FxHashSet::default(),
544            selection: None,
545            target_annotation: None,
546            history: VecDeque::new(),
547            state: State::Idle,
548            info,
549            current_page,
550            pages_count,
551            view_port: ViewPort::default(),
552            synthetic: true,
553            page_turns: 0,
554            contrast: Contrast::default(),
555            ephemeral: true,
556            reflowable: true,
557            finished: false,
558        }
559    }
560
561    #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
562    pub fn from_embedded_epub(
563        rect: Rectangle,
564        epub_bytes: &'static [u8],
565        hub: &Hub,
566        context: &mut Context,
567    ) -> Option<Reader> {
568        let id = ID_FEEDER.next();
569
570        let mut doc = EpubDocumentStatic::new_from_static(epub_bytes).ok()?;
571
572        let info = Info {
573            file: FileInfo {
574                path: PathBuf::from("mem:documentation.epub"),
575                kind: "epub".to_string(),
576                size: epub_bytes.len() as u64,
577                ..Default::default()
578            },
579            title: doc.title().unwrap_or_default(),
580            ..Default::default()
581        };
582
583        let (width, height) = context.display.dims;
584        doc.layout(width, height, 7.0, CURRENT_DEVICE.dpi);
585        doc.set_margin_width(mm_to_px(0.0, CURRENT_DEVICE.dpi) as i32);
586        let pages_count = doc.pages_count();
587
588        hub.send(Event::Update(UpdateMode::Partial)).ok();
589
590        Some(Reader {
591            id,
592            rect,
593            children: Vec::new(),
594            doc: Arc::new(Mutex::new(Box::new(doc))),
595            cache: BTreeMap::new(),
596            chunks: Vec::new(),
597            text: FxHashMap::default(),
598            annotations: FxHashMap::default(),
599            noninverted_regions: FxHashMap::default(),
600            focus: None,
601            search: None,
602            search_direction: LinearDir::Forward,
603            held_buttons: FxHashSet::default(),
604            selection: None,
605            target_annotation: None,
606            history: VecDeque::new(),
607            state: State::Idle,
608            info,
609            current_page: 0,
610            pages_count,
611            view_port: ViewPort::default(),
612            synthetic: true,
613            page_turns: 0,
614            contrast: Contrast::default(),
615            ephemeral: true,
616            reflowable: true,
617            finished: false,
618        })
619    }
620
621    fn load_pixmap(&mut self, location: usize) {
622        if self.cache.contains_key(&location) {
623            return;
624        }
625
626        let mut doc = self.doc.lock().unwrap();
627        let cropping_margin = self
628            .info
629            .reader
630            .as_ref()
631            .and_then(|r| r.cropping_margins.as_ref().map(|c| c.margin(location)))
632            .cloned()
633            .unwrap_or_default();
634        let dims = doc.dims(location).unwrap_or((3.0, 4.0));
635        let screen_margin_width = self.view_port.margin_width;
636        let scale = scaling_factor(
637            &self.rect,
638            &cropping_margin,
639            screen_margin_width,
640            dims,
641            self.view_port.zoom_mode,
642        );
643        if let Some((pixmap, _)) = doc.pixmap(
644            Location::Exact(location),
645            scale,
646            CURRENT_DEVICE.color_samples(),
647        ) {
648            let frame = rect![
649                (cropping_margin.left * pixmap.width as f32).ceil() as i32,
650                (cropping_margin.top * pixmap.height as f32).ceil() as i32,
651                ((1.0 - cropping_margin.right) * pixmap.width as f32).floor() as i32,
652                ((1.0 - cropping_margin.bottom) * pixmap.height as f32).floor() as i32
653            ];
654            self.cache.insert(
655                location,
656                Resource {
657                    pixmap,
658                    frame,
659                    scale,
660                },
661            );
662        } else {
663            let width = (dims.0 as f32 * scale).max(1.0) as u32;
664            let height = (dims.1 as f32 * scale).max(1.0) as u32;
665            let pixmap = Pixmap::empty(width, height, CURRENT_DEVICE.color_samples());
666            let frame = pixmap.rect();
667            self.cache.insert(
668                location,
669                Resource {
670                    pixmap,
671                    frame,
672                    scale,
673                },
674            );
675        }
676    }
677
678    fn load_text(&mut self, location: usize) {
679        if self.text.contains_key(&location) {
680            return;
681        }
682
683        let mut doc = self.doc.lock().unwrap();
684        let loc = Location::Exact(location);
685        let words = doc.words(loc).map(|(words, _)| words).unwrap_or_default();
686        self.text.insert(location, words);
687    }
688
689    fn go_to_page(
690        &mut self,
691        location: usize,
692        record: bool,
693        hub: &Hub,
694        rq: &mut RenderQueue,
695        context: &Context,
696    ) {
697        let loc = {
698            let mut doc = self.doc.lock().unwrap();
699            doc.resolve_location(Location::Exact(location))
700        };
701
702        if let Some(location) = loc {
703            if record {
704                self.history.push_back(self.current_page);
705                if self.history.len() > HISTORY_SIZE {
706                    self.history.pop_front();
707                }
708            }
709
710            if let Some(ref mut s) = self.search {
711                s.current_page = s.highlights.range(..=location).count().saturating_sub(1);
712            }
713
714            self.current_page = location;
715            self.view_port.page_offset = pt!(0);
716            self.selection = None;
717            self.state = State::Idle;
718            self.update(None, hub, rq, context);
719            self.update_bottom_bar(rq);
720
721            if self.search.is_some() {
722                self.update_results_bar(rq);
723            }
724        }
725    }
726
727    fn go_to_chapter(&mut self, dir: CycleDir, hub: &Hub, rq: &mut RenderQueue, context: &Context) {
728        let current_page = self.current_page;
729        let loc = {
730            let mut doc = self.doc.lock().unwrap();
731            if let Some(toc) = self.toc().or_else(|| doc.toc()) {
732                let chap_offset = if dir == CycleDir::Previous {
733                    doc.chapter(current_page, &toc)
734                        .and_then(|(chap, _)| doc.resolve_location(chap.location.clone()))
735                        .and_then(|chap_offset| {
736                            if chap_offset < current_page {
737                                Some(chap_offset)
738                            } else {
739                                None
740                            }
741                        })
742                } else {
743                    None
744                };
745                chap_offset.or_else(|| {
746                    doc.chapter_relative(current_page, dir, &toc)
747                        .and_then(|rel_chap| doc.resolve_location(rel_chap.location.clone()))
748                })
749            } else {
750                None
751            }
752        };
753        if let Some(location) = loc {
754            self.go_to_page(location, true, hub, rq, context);
755        }
756    }
757
758    fn text_location_range(&self) -> Option<[TextLocation; 2]> {
759        let mut min_loc = None;
760        let mut max_loc = None;
761        for chunk in &self.chunks {
762            for word in &self.text[&chunk.location] {
763                let rect = (word.rect * chunk.scale).to_rect();
764                if rect.overlaps(&chunk.frame) {
765                    if let Some(ref mut min) = min_loc {
766                        if word.location < *min {
767                            *min = word.location;
768                        }
769                    } else {
770                        min_loc = Some(word.location);
771                    }
772                    if let Some(ref mut max) = max_loc {
773                        if word.location > *max {
774                            *max = word.location;
775                        }
776                    } else {
777                        max_loc = Some(word.location);
778                    }
779                }
780            }
781        }
782
783        min_loc.and_then(|min| max_loc.map(|max| [min, max]))
784    }
785
786    fn go_to_bookmark(
787        &mut self,
788        dir: CycleDir,
789        hub: &Hub,
790        rq: &mut RenderQueue,
791        context: &Context,
792    ) {
793        let loc_bkm = self.info.reader.as_ref().and_then(|r| match dir {
794            CycleDir::Next => r.bookmarks.range(self.current_page + 1..).next().cloned(),
795            CycleDir::Previous => r.bookmarks.range(..self.current_page).next_back().cloned(),
796        });
797
798        if let Some(location) = loc_bkm {
799            self.go_to_page(location, true, hub, rq, context);
800        }
801    }
802
803    fn go_to_annotation(
804        &mut self,
805        dir: CycleDir,
806        hub: &Hub,
807        rq: &mut RenderQueue,
808        context: &Context,
809    ) {
810        let loc_annot = self.info.reader.as_ref().and_then(|r| match dir {
811            CycleDir::Next => self.text_location_range().and_then(|[_, max]| {
812                r.annotations
813                    .iter()
814                    .filter(|annot| annot.selection[0] > max)
815                    .map(|annot| annot.selection[0])
816                    .min()
817                    .map(|tl| tl.location())
818            }),
819            CycleDir::Previous => self.text_location_range().and_then(|[min, _]| {
820                r.annotations
821                    .iter()
822                    .filter(|annot| annot.selection[1] < min)
823                    .map(|annot| annot.selection[1])
824                    .max()
825                    .map(|tl| tl.location())
826            }),
827        });
828
829        if let Some(location) = loc_annot {
830            self.go_to_page(location, true, hub, rq, context);
831        }
832    }
833
834    fn go_to_last_page(&mut self, hub: &Hub, rq: &mut RenderQueue, context: &Context) {
835        if let Some(location) = self.history.pop_back() {
836            self.go_to_page(location, false, hub, rq, context);
837        }
838    }
839
840    fn vertical_scroll(
841        &mut self,
842        delta_y: i32,
843        hub: &Hub,
844        rq: &mut RenderQueue,
845        context: &mut Context,
846    ) {
847        if delta_y == 0 || self.view_port.zoom_mode == ZoomMode::FitToPage || self.cache.is_empty()
848        {
849            return;
850        }
851
852        let mut next_top_offset = self.view_port.page_offset.y + delta_y;
853        let mut location = self.current_page;
854
855        match self.view_port.scroll_mode {
856            ScrollMode::Screen => {
857                let max_top_offset = self.cache[&location].frame.height().saturating_sub(1) as i32;
858
859                if next_top_offset < 0 {
860                    let mut doc = self.doc.lock().unwrap();
861                    if let Some(previous_location) =
862                        doc.resolve_location(Location::Previous(location))
863                    {
864                        if !self.cache.contains_key(&previous_location) {
865                            return;
866                        }
867                        location = previous_location;
868                        let frame = self.cache[&location].frame;
869                        next_top_offset = (frame.height() as i32 + next_top_offset).max(0);
870                    } else {
871                        next_top_offset = 0;
872                    }
873                } else if next_top_offset > max_top_offset {
874                    let mut doc = self.doc.lock().unwrap();
875                    if let Some(next_location) = doc.resolve_location(Location::Next(location)) {
876                        if !self.cache.contains_key(&next_location) {
877                            return;
878                        }
879                        location = next_location;
880                        let frame = self.cache[&location].frame;
881                        let mto = frame.height().saturating_sub(1) as i32;
882                        next_top_offset = (next_top_offset - max_top_offset - 1).min(mto);
883                    } else {
884                        next_top_offset = max_top_offset;
885                    }
886                }
887
888                {
889                    let Resource { frame, scale, .. } = *self.cache.get(&location).unwrap();
890                    let mut doc = self.doc.lock().unwrap();
891                    if let Some((lines, _)) = doc.lines(Location::Exact(location)) {
892                        if let Some(mut y_pos) = find_cut(
893                            &frame,
894                            frame.min.y + next_top_offset,
895                            scale,
896                            LinearDir::Forward,
897                            &lines,
898                        ) {
899                            y_pos = y_pos.clamp(frame.min.y, frame.max.y - 1);
900                            next_top_offset = y_pos - frame.min.y;
901                        }
902                    }
903                }
904            }
905            ScrollMode::Page => {
906                let frame_height = self.cache[&location].frame.height() as i32;
907                let available_height = self.rect.height() as i32 - 2 * self.view_port.margin_width;
908                if frame_height > available_height {
909                    next_top_offset = next_top_offset.max(0).min(frame_height - available_height);
910                } else {
911                    next_top_offset = self.view_port.page_offset.y;
912                }
913            }
914        }
915
916        let location_changed = location != self.current_page;
917        if !location_changed && next_top_offset == self.view_port.page_offset.y {
918            return;
919        }
920
921        self.view_port.page_offset.y = next_top_offset;
922        self.current_page = location;
923        self.update(None, hub, rq, context);
924
925        if location_changed {
926            if let Some(ref mut s) = self.search {
927                s.current_page = s.highlights.range(..=location).count().saturating_sub(1);
928            }
929            self.update_bottom_bar(rq);
930            if self.search.is_some() {
931                self.update_results_bar(rq);
932            }
933        }
934    }
935
936    fn directional_scroll(
937        &mut self,
938        delta: Point,
939        hub: &Hub,
940        rq: &mut RenderQueue,
941        context: &mut Context,
942    ) {
943        if delta == pt!(0) || self.cache.is_empty() {
944            return;
945        }
946
947        let Resource { frame, .. } = self.cache[&self.current_page];
948        let next_page_offset = self.view_port.page_offset + delta;
949        let vpw = self.rect.width() as i32 - 2 * self.view_port.margin_width;
950        let vph = self.rect.height() as i32 - 2 * self.view_port.margin_width;
951        let vprect = rect![pt!(0), pt!(vpw, vph)] + next_page_offset + frame.min;
952
953        if vprect.overlaps(&frame) {
954            self.view_port.page_offset = next_page_offset;
955            self.update(None, hub, rq, context);
956        }
957    }
958
959    fn go_to_neighbor(
960        &mut self,
961        dir: CycleDir,
962        hub: &Hub,
963        rq: &mut RenderQueue,
964        context: &mut Context,
965    ) {
966        if self.chunks.is_empty() {
967            return;
968        }
969
970        let current_page = self.current_page;
971        let page_offset = self.view_port.page_offset;
972
973        let loc = {
974            let neighloc = match dir {
975                CycleDir::Previous => match self.view_port.zoom_mode {
976                    ZoomMode::FitToPage => Location::Previous(current_page),
977                    ZoomMode::FitToWidth => match self.view_port.scroll_mode {
978                        ScrollMode::Screen => {
979                            let first_chunk = self.chunks.first().cloned().unwrap();
980                            let mut location = first_chunk.location;
981                            let available_height =
982                                self.rect.height() as i32 - 2 * self.view_port.margin_width;
983                            let mut height = 0;
984
985                            loop {
986                                self.load_pixmap(location);
987                                self.load_text(location);
988                                let Resource { mut frame, .. } = self.cache[&location];
989                                if location == first_chunk.location {
990                                    frame.max.y = first_chunk.frame.min.y;
991                                }
992                                height += frame.height() as i32;
993                                if height >= available_height {
994                                    break;
995                                }
996                                let mut doc = self.doc.lock().unwrap();
997                                if let Some(previous_location) =
998                                    doc.resolve_location(Location::Previous(location))
999                                {
1000                                    location = previous_location;
1001                                } else {
1002                                    break;
1003                                }
1004                            }
1005
1006                            let mut next_top_offset = (height - available_height).max(0);
1007                            if height > available_height {
1008                                let Resource { frame, scale, .. } = self.cache[&location];
1009                                let mut doc = self.doc.lock().unwrap();
1010                                if let Some((lines, _)) = doc.lines(Location::Exact(location)) {
1011                                    if let Some(mut y_pos) = find_cut(
1012                                        &frame,
1013                                        frame.min.y + next_top_offset,
1014                                        scale,
1015                                        LinearDir::Forward,
1016                                        &lines,
1017                                    ) {
1018                                        y_pos = y_pos.clamp(frame.min.y, frame.max.y - 1);
1019                                        next_top_offset = y_pos - frame.min.y;
1020                                    }
1021                                }
1022                            }
1023
1024                            self.view_port.page_offset.y = next_top_offset;
1025                            Location::Exact(location)
1026                        }
1027                        ScrollMode::Page => {
1028                            let available_height =
1029                                self.rect.height() as i32 - 2 * self.view_port.margin_width;
1030                            if self.view_port.page_offset.y > 0 {
1031                                self.view_port.page_offset.y =
1032                                    (self.view_port.page_offset.y - available_height).max(0);
1033                                Location::Exact(current_page)
1034                            } else {
1035                                let previous_location = self
1036                                    .doc
1037                                    .lock()
1038                                    .unwrap()
1039                                    .resolve_location(Location::Previous(current_page));
1040                                if let Some(location) = previous_location {
1041                                    self.load_pixmap(location);
1042                                    let frame = self.cache[&location].frame;
1043                                    self.view_port.page_offset.y =
1044                                        (frame.height() as i32 - available_height).max(0);
1045                                }
1046                                Location::Previous(current_page)
1047                            }
1048                        }
1049                    },
1050                    ZoomMode::Custom(_) => {
1051                        self.view_port.page_offset = pt!(0);
1052                        Location::Previous(current_page)
1053                    }
1054                },
1055                CycleDir::Next => match self.view_port.zoom_mode {
1056                    ZoomMode::FitToPage => Location::Next(current_page),
1057                    ZoomMode::FitToWidth => match self.view_port.scroll_mode {
1058                        ScrollMode::Screen => {
1059                            let &RenderChunk {
1060                                location, frame, ..
1061                            } = self.chunks.last().unwrap();
1062                            self.load_pixmap(location);
1063                            self.load_text(location);
1064                            let pixmap_frame = self.cache[&location].frame;
1065                            let next_top_offset = frame.max.y - pixmap_frame.min.y;
1066                            if next_top_offset == pixmap_frame.height() as i32 {
1067                                self.view_port.page_offset.y = 0;
1068                                Location::Next(location)
1069                            } else {
1070                                self.view_port.page_offset.y = next_top_offset;
1071                                Location::Exact(location)
1072                            }
1073                        }
1074                        ScrollMode::Page => {
1075                            let available_height =
1076                                self.rect.height() as i32 - 2 * self.view_port.margin_width;
1077                            let frame_height = self.cache[&current_page].frame.height() as i32;
1078                            let next_top_offset = self.view_port.page_offset.y + available_height;
1079                            if frame_height < available_height || next_top_offset == frame_height {
1080                                self.view_port.page_offset.y = 0;
1081                                Location::Next(current_page)
1082                            } else {
1083                                self.view_port.page_offset.y =
1084                                    next_top_offset.min(frame_height - available_height);
1085                                Location::Exact(current_page)
1086                            }
1087                        }
1088                    },
1089                    ZoomMode::Custom(_) => {
1090                        self.view_port.page_offset = pt!(0);
1091                        Location::Next(current_page)
1092                    }
1093                },
1094            };
1095            let mut doc = self.doc.lock().unwrap();
1096            doc.resolve_location(neighloc)
1097        };
1098        match loc {
1099            Some(location)
1100                if location != current_page || self.view_port.page_offset != page_offset =>
1101            {
1102                if let Some(ref mut s) = self.search {
1103                    s.current_page = s.highlights.range(..=location).count().saturating_sub(1);
1104                }
1105
1106                self.current_page = location;
1107                self.selection = None;
1108                self.state = State::Idle;
1109                self.update(None, hub, rq, context);
1110                self.update_bottom_bar(rq);
1111
1112                if self.search.is_some() {
1113                    self.update_results_bar(rq);
1114                }
1115            }
1116            _ => match dir {
1117                CycleDir::Next => {
1118                    self.finished = true;
1119                    let action = if self.ephemeral {
1120                        FinishedAction::Notify
1121                    } else {
1122                        context
1123                            .settings
1124                            .libraries
1125                            .get(context.settings.selected_library)
1126                            .and_then(|lib| lib.finished)
1127                            .unwrap_or(context.settings.reader.finished)
1128                    };
1129                    match action {
1130                        FinishedAction::Notify => {
1131                            let notif = Notification::new(
1132                                None,
1133                                "No next page.".to_string(),
1134                                false,
1135                                hub,
1136                                rq,
1137                                context,
1138                            );
1139                            self.children.push(Box::new(notif) as Box<dyn View>);
1140                        }
1141                        FinishedAction::Close => {
1142                            self.quit(context);
1143                            hub.send(Event::Back).ok();
1144                        }
1145                        FinishedAction::GoToNext => {
1146                            let next = self
1147                                .info
1148                                .fp
1149                                .and_then(|fp| context.library.next_book_after(fp));
1150
1151                            self.quit(context);
1152
1153                            match next {
1154                                Some(next_info) => {
1155                                    hub.send(Event::Open(Box::new(next_info))).ok();
1156                                }
1157                                None => {
1158                                    hub.send(Event::Back).ok();
1159                                }
1160                            }
1161                        }
1162                    }
1163                }
1164                CycleDir::Previous => {
1165                    let notif = Notification::new(
1166                        None,
1167                        "No previous page.".to_string(),
1168                        false,
1169                        hub,
1170                        rq,
1171                        context,
1172                    );
1173                    self.children.push(Box::new(notif) as Box<dyn View>);
1174                }
1175            },
1176        }
1177    }
1178
1179    fn go_to_results_page(
1180        &mut self,
1181        index: usize,
1182        hub: &Hub,
1183        rq: &mut RenderQueue,
1184        context: &Context,
1185    ) {
1186        let mut loc = None;
1187        if let Some(ref mut s) = self.search {
1188            if index < s.highlights.len() {
1189                s.current_page = index;
1190                loc = Some(*s.highlights.keys().nth(index).unwrap());
1191            }
1192        }
1193        if let Some(location) = loc {
1194            self.current_page = location;
1195            self.view_port.page_offset = pt!(0, 0);
1196            self.selection = None;
1197            self.state = State::Idle;
1198            self.update_results_bar(rq);
1199            self.update_bottom_bar(rq);
1200            self.update(None, hub, rq, context);
1201        }
1202    }
1203
1204    fn go_to_results_neighbor(
1205        &mut self,
1206        dir: CycleDir,
1207        hub: &Hub,
1208        rq: &mut RenderQueue,
1209        context: &Context,
1210    ) {
1211        let loc = self.search.as_ref().and_then(|s| match dir {
1212            CycleDir::Next => s
1213                .highlights
1214                .range(self.current_page + 1..)
1215                .next()
1216                .map(|e| *e.0),
1217            CycleDir::Previous => s
1218                .highlights
1219                .range(..self.current_page)
1220                .next_back()
1221                .map(|e| *e.0),
1222        });
1223        if let Some(location) = loc {
1224            if let Some(ref mut s) = self.search {
1225                s.current_page = s.highlights.range(..=location).count().saturating_sub(1);
1226            }
1227            self.view_port.page_offset = pt!(0, 0);
1228            self.current_page = location;
1229            self.update_results_bar(rq);
1230            self.update_bottom_bar(rq);
1231            self.update(None, hub, rq, context);
1232        }
1233    }
1234
1235    fn update_bottom_bar(&mut self, rq: &mut RenderQueue) {
1236        if let Some(index) = locate::<BottomBar>(self) {
1237            let current_page = self.current_page;
1238            let mut doc = self.doc.lock().unwrap();
1239            let rtoc = self.toc().or_else(|| doc.toc());
1240            let chapter = rtoc.as_ref().and_then(|toc| doc.chapter(current_page, toc));
1241            let title = chapter.map(|(c, _)| c.title.clone()).unwrap_or_default();
1242            let progress = chapter.map(|(_, p)| p).unwrap_or_default();
1243            let bottom_bar = self.children[index]
1244                .as_mut()
1245                .downcast_mut::<BottomBar>()
1246                .unwrap();
1247            let neighbors = Neighbors {
1248                previous_page: doc.resolve_location(Location::Previous(current_page)),
1249                next_page: doc.resolve_location(Location::Next(current_page)),
1250            };
1251            bottom_bar.update_chapter_label(title, progress, rq);
1252            bottom_bar.update_page_label(self.current_page, self.pages_count, rq);
1253            bottom_bar.update_icons(&neighbors, rq);
1254        }
1255    }
1256
1257    fn update_tool_bar(&mut self, rq: &mut RenderQueue, context: &mut Context) {
1258        if let Some(index) = locate::<ToolBar>(self) {
1259            let tool_bar = self.children[index]
1260                .as_mut()
1261                .downcast_mut::<ToolBar>()
1262                .unwrap();
1263            let settings = &context.settings;
1264            if self.reflowable {
1265                let font_family = self
1266                    .info
1267                    .reader
1268                    .as_ref()
1269                    .and_then(|r| r.font_family.clone())
1270                    .unwrap_or_else(|| settings.reader.font_family.clone());
1271                tool_bar.update_font_family(font_family, rq);
1272                let font_size = self
1273                    .info
1274                    .reader
1275                    .as_ref()
1276                    .and_then(|r| r.font_size)
1277                    .unwrap_or(settings.reader.font_size);
1278                tool_bar.update_font_size_slider(font_size, rq);
1279                let text_align = self
1280                    .info
1281                    .reader
1282                    .as_ref()
1283                    .and_then(|r| r.text_align)
1284                    .unwrap_or(settings.reader.text_align);
1285                tool_bar.update_text_align_icon(text_align, rq);
1286                let line_height = self
1287                    .info
1288                    .reader
1289                    .as_ref()
1290                    .and_then(|r| r.line_height)
1291                    .unwrap_or(settings.reader.line_height);
1292                tool_bar.update_line_height(line_height, rq);
1293            } else {
1294                tool_bar.update_contrast_exponent_slider(self.contrast.exponent, rq);
1295                tool_bar.update_contrast_gray_slider(self.contrast.gray, rq);
1296            }
1297            let reflowable = self.reflowable;
1298            let margin_width = self
1299                .info
1300                .reader
1301                .as_ref()
1302                .and_then(|r| {
1303                    if reflowable {
1304                        r.margin_width
1305                    } else {
1306                        r.screen_margin_width
1307                    }
1308                })
1309                .unwrap_or_else(|| {
1310                    if reflowable {
1311                        settings.reader.margin_width
1312                    } else {
1313                        0
1314                    }
1315                });
1316            tool_bar.update_margin_width(margin_width, rq);
1317        }
1318    }
1319
1320    fn update_results_bar(&mut self, rq: &mut RenderQueue) {
1321        if self.search.is_none() {
1322            return;
1323        }
1324        let (count, current_page, pages_count) = {
1325            let s = self.search.as_ref().unwrap();
1326            (s.results_count, s.current_page, s.highlights.len())
1327        };
1328        if let Some(index) = locate::<ResultsBar>(self) {
1329            let results_bar = self.child_mut(index).downcast_mut::<ResultsBar>().unwrap();
1330            results_bar.update_results_label(count, rq);
1331            results_bar.update_page_label(current_page, pages_count, rq);
1332            results_bar.update_icons(current_page, pages_count, rq);
1333        }
1334    }
1335
1336    #[inline]
1337    fn update_noninverted_regions(&mut self, inverted: bool) {
1338        self.noninverted_regions.clear();
1339        if inverted {
1340            for chunk in &self.chunks {
1341                if let Some((images, _)) = self
1342                    .doc
1343                    .lock()
1344                    .unwrap()
1345                    .images(Location::Exact(chunk.location))
1346                {
1347                    self.noninverted_regions.insert(chunk.location, images);
1348                }
1349            }
1350        }
1351    }
1352
1353    #[inline]
1354    fn update_annotations(&mut self) {
1355        self.annotations.clear();
1356        if let Some(annotations) = self
1357            .info
1358            .reader
1359            .as_ref()
1360            .map(|r| &r.annotations)
1361            .filter(|a| !a.is_empty())
1362        {
1363            for chunk in &self.chunks {
1364                let words = &self.text[&chunk.location];
1365                if words.is_empty() {
1366                    continue;
1367                }
1368                for annot in annotations {
1369                    let [start, end] = annot.selection;
1370                    if (start >= words[0].location && start <= words[words.len() - 1].location)
1371                        || (end >= words[0].location && end <= words[words.len() - 1].location)
1372                    {
1373                        self.annotations
1374                            .entry(chunk.location)
1375                            .or_insert_with(Vec::new)
1376                            .push(annot.clone());
1377                    }
1378                }
1379            }
1380        }
1381    }
1382
1383    fn update(
1384        &mut self,
1385        update_mode: Option<UpdateMode>,
1386        hub: &Hub,
1387        rq: &mut RenderQueue,
1388        context: &Context,
1389    ) {
1390        self.page_turns += 1;
1391        let update_mode = update_mode.unwrap_or_else(|| {
1392            let pair = context
1393                .settings
1394                .reader
1395                .refresh_rate
1396                .by_kind
1397                .get(&self.info.file.kind)
1398                .unwrap_or_else(|| &context.settings.reader.refresh_rate.global);
1399            let refresh_rate = if context.fb.inverted() {
1400                pair.inverted
1401            } else {
1402                pair.regular
1403            };
1404            if refresh_rate == 0 || self.page_turns % (refresh_rate as usize) != 0 {
1405                UpdateMode::Partial
1406            } else {
1407                UpdateMode::Full
1408            }
1409        });
1410
1411        self.chunks.clear();
1412        let mut location = self.current_page;
1413        let smw = self.view_port.margin_width;
1414
1415        match self.view_port.zoom_mode {
1416            ZoomMode::FitToPage => {
1417                self.load_pixmap(location);
1418                self.load_text(location);
1419                let Resource { frame, scale, .. } = self.cache[&location];
1420                let dx = smw + ((self.rect.width() - frame.width()) as i32 - 2 * smw) / 2;
1421                let dy = smw + ((self.rect.height() - frame.height()) as i32 - 2 * smw) / 2;
1422                self.chunks.push(RenderChunk {
1423                    frame,
1424                    location,
1425                    position: pt!(dx, dy),
1426                    scale,
1427                });
1428            }
1429            ZoomMode::FitToWidth => match self.view_port.scroll_mode {
1430                ScrollMode::Screen => {
1431                    let available_height = self.rect.height() as i32 - 2 * smw;
1432                    let mut height = 0;
1433                    while height < available_height {
1434                        self.load_pixmap(location);
1435                        self.load_text(location);
1436                        let Resource {
1437                            mut frame, scale, ..
1438                        } = self.cache[&location];
1439                        if location == self.current_page {
1440                            frame.min.y += self.view_port.page_offset.y;
1441                        }
1442                        let position = pt!(smw, smw + height);
1443                        self.chunks.push(RenderChunk {
1444                            frame,
1445                            location,
1446                            position,
1447                            scale,
1448                        });
1449                        height += frame.height() as i32;
1450                        if let Ok(mut doc) = self.doc.lock() {
1451                            if let Some(next_location) =
1452                                doc.resolve_location(Location::Next(location))
1453                            {
1454                                location = next_location;
1455                            } else {
1456                                break;
1457                            }
1458                        }
1459                    }
1460                    if height > available_height {
1461                        if let Some(last_chunk) = self.chunks.last_mut() {
1462                            last_chunk.frame.max.y -= height - available_height;
1463                            let mut doc = self.doc.lock().unwrap();
1464                            if let Some((lines, _)) =
1465                                doc.lines(Location::Exact(last_chunk.location))
1466                            {
1467                                let pixmap_frame = self.cache[&last_chunk.location].frame;
1468                                if let Some(mut y_pos) = find_cut(
1469                                    &pixmap_frame,
1470                                    last_chunk.frame.max.y,
1471                                    last_chunk.scale,
1472                                    LinearDir::Backward,
1473                                    &lines,
1474                                ) {
1475                                    y_pos = y_pos.clamp(pixmap_frame.min.y, pixmap_frame.max.y - 1);
1476                                    last_chunk.frame.max.y = y_pos;
1477                                }
1478                            }
1479                        }
1480                        let actual_height: i32 =
1481                            self.chunks.iter().map(|c| c.frame.height() as i32).sum();
1482                        let dy = (available_height - actual_height) / 2;
1483                        for chunk in &mut self.chunks {
1484                            chunk.position.y += dy;
1485                        }
1486                    }
1487                }
1488                ScrollMode::Page => {
1489                    self.load_pixmap(location);
1490                    self.load_text(location);
1491                    let available_height = self.rect.height() as i32 - 2 * smw;
1492                    let Resource {
1493                        mut frame, scale, ..
1494                    } = self.cache[&location];
1495                    frame.min.y += self.view_port.page_offset.y;
1496                    frame.max.y = (frame.min.y + available_height).min(frame.max.y);
1497                    let position = pt!(smw, smw + (available_height - frame.height() as i32) / 2);
1498                    self.chunks.push(RenderChunk {
1499                        frame,
1500                        location,
1501                        position,
1502                        scale,
1503                    });
1504                }
1505            },
1506            ZoomMode::Custom(_) => {
1507                self.load_pixmap(location);
1508                self.load_text(location);
1509                let Resource { frame, scale, .. } = self.cache[&location];
1510                let vpw = self.rect.width() as i32 - 2 * smw;
1511                let vph = self.rect.height() as i32 - 2 * smw;
1512                let vpr = rect![pt!(0), pt!(vpw, vph)] + self.view_port.page_offset + frame.min;
1513                if let Some(rect) = frame.intersection(&vpr) {
1514                    let position = pt!(smw) + rect.min - vpr.min;
1515                    self.chunks.push(RenderChunk {
1516                        frame: rect,
1517                        location,
1518                        position,
1519                        scale,
1520                    });
1521                }
1522            }
1523        }
1524
1525        rq.add(RenderData::new(self.id, self.rect, update_mode));
1526        let first_location = self.chunks.first().map(|c| c.location).unwrap();
1527        let last_location = self.chunks.last().map(|c| c.location).unwrap();
1528
1529        while self.cache.len() > 3 {
1530            let left_count = self.cache.range(..first_location).count();
1531            let right_count = self.cache.range(last_location + 1..).count();
1532            let extremum = if left_count >= right_count {
1533                self.cache.keys().next().cloned().unwrap()
1534            } else {
1535                self.cache.keys().next_back().cloned().unwrap()
1536            };
1537            self.cache.remove(&extremum);
1538        }
1539
1540        self.update_annotations();
1541        self.update_noninverted_regions(context.fb.inverted());
1542
1543        if self.view_port.zoom_mode == ZoomMode::FitToPage
1544            || self.view_port.zoom_mode == ZoomMode::FitToWidth
1545        {
1546            let doc2 = self.doc.clone();
1547            let hub2 = hub.clone();
1548            thread::spawn(move || {
1549                let mut doc = doc2.lock().unwrap();
1550                if let Some(next_location) = doc.resolve_location(Location::Next(last_location)) {
1551                    hub2.send(Event::LoadPixmap(next_location)).ok();
1552                }
1553            });
1554            let doc3 = self.doc.clone();
1555            let hub3 = hub.clone();
1556            thread::spawn(move || {
1557                let mut doc = doc3.lock().unwrap();
1558                if let Some(previous_location) =
1559                    doc.resolve_location(Location::Previous(first_location))
1560                {
1561                    hub3.send(Event::LoadPixmap(previous_location)).ok();
1562                }
1563            });
1564        }
1565    }
1566
1567    fn search(&mut self, text: &str, query: Regex, hub: &Hub, rq: &mut RenderQueue) {
1568        let s = Search {
1569            query: text.to_string(),
1570            ..Default::default()
1571        };
1572
1573        let hub2 = hub.clone();
1574        let doc2 = Arc::clone(&self.doc);
1575        let running = Arc::clone(&s.running);
1576        let current_page = self.current_page;
1577        let search_direction = self.search_direction;
1578        let ws = word_separator(&self.info.language);
1579
1580        thread::spawn(move || {
1581            let mut loc = Location::Exact(current_page);
1582            let mut started = false;
1583
1584            loop {
1585                if !running.load(AtomicOrdering::Relaxed) {
1586                    break;
1587                }
1588
1589                let mut doc = doc2.lock().unwrap();
1590                let mut text = String::new();
1591                let mut rects = BTreeMap::new();
1592
1593                if let Some(location) = doc.resolve_location(loc) {
1594                    if location == current_page && started {
1595                        break;
1596                    }
1597                    if let Some((ref words, _)) = doc.words(Location::Exact(location)) {
1598                        for word in words {
1599                            if !running.load(AtomicOrdering::Relaxed) {
1600                                break;
1601                            }
1602                            if text.ends_with('\u{00AD}') {
1603                                text.pop();
1604                            } else if !text.ends_with('-') && !text.is_empty() {
1605                                text.push_str(ws);
1606                            }
1607                            rects.insert(text.len(), word.rect);
1608                            text += &word.text;
1609                        }
1610                        for m in query.find_iter(&text) {
1611                            if let Some((first, _)) = rects.range(..=m.start()).next_back() {
1612                                let mut match_rects = Vec::new();
1613                                for (_, rect) in rects.range(*first..m.end()) {
1614                                    match_rects.push(*rect);
1615                                }
1616                                hub2.send(Event::SearchResult(location, match_rects)).ok();
1617                            }
1618                        }
1619                    }
1620                    loc = match search_direction {
1621                        LinearDir::Forward => Location::Next(location),
1622                        LinearDir::Backward => Location::Previous(location),
1623                    };
1624                } else {
1625                    loc = match search_direction {
1626                        LinearDir::Forward => Location::Exact(0),
1627                        LinearDir::Backward => Location::Exact(doc.pages_count() - 1),
1628                    };
1629                }
1630
1631                started = true;
1632            }
1633
1634            running.store(false, AtomicOrdering::Relaxed);
1635            hub2.send(Event::EndOfSearch).ok();
1636        });
1637
1638        if self.search.is_some() {
1639            self.render_results(rq);
1640        }
1641
1642        self.search = Some(s);
1643    }
1644
1645    fn toggle_keyboard(
1646        &mut self,
1647        enable: bool,
1648        id: Option<ViewId>,
1649        hub: &Hub,
1650        rq: &mut RenderQueue,
1651        context: &mut Context,
1652    ) {
1653        if let Some(index) = locate::<Keyboard>(self) {
1654            if enable {
1655                return;
1656            }
1657
1658            let mut rect = *self.child(index).rect();
1659            rect.absorb(self.child(index - 1).rect());
1660
1661            if index == 1 {
1662                rect.absorb(self.child(index + 1).rect());
1663                self.children.drain(index - 1..=index + 1);
1664                rq.add(RenderData::expose(rect, UpdateMode::Gui));
1665            } else {
1666                self.children.drain(index - 1..=index);
1667
1668                let start_index = locate::<TopBar>(self).map(|index| index + 2).unwrap_or(0);
1669                let y_min = self.child(start_index).rect().min.y;
1670                let delta_y = rect.height() as i32;
1671
1672                for i in start_index..index - 1 {
1673                    let shifted_rect = *self.child(i).rect() + pt!(0, delta_y);
1674                    self.child_mut(i).resize(shifted_rect, hub, rq, context);
1675                    rq.add(RenderData::new(
1676                        self.child(i).id(),
1677                        shifted_rect,
1678                        UpdateMode::Gui,
1679                    ));
1680                }
1681
1682                let rect = rect![self.rect.min.x, y_min, self.rect.max.x, y_min + delta_y];
1683                rq.add(RenderData::expose(rect, UpdateMode::Gui));
1684            }
1685
1686            context.kb_rect = Rectangle::default();
1687            hub.send(Event::Focus(None)).ok();
1688        } else {
1689            if !enable {
1690                return;
1691            }
1692
1693            let dpi = CURRENT_DEVICE.dpi;
1694            let (small_height, big_height) = (
1695                scale_by_dpi(SMALL_BAR_HEIGHT, dpi) as i32,
1696                scale_by_dpi(BIG_BAR_HEIGHT, dpi) as i32,
1697            );
1698            let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
1699            let (small_thickness, big_thickness) = halves(thickness);
1700
1701            let mut kb_rect = rect![
1702                self.rect.min.x,
1703                self.rect.max.y - (small_height + 3 * big_height) as i32 + big_thickness,
1704                self.rect.max.x,
1705                self.rect.max.y - small_height - small_thickness
1706            ];
1707
1708            let number = matches!(
1709                id,
1710                Some(ViewId::GoToPageInput)
1711                    | Some(ViewId::GoToResultsPageInput)
1712                    | Some(ViewId::NamePageInput)
1713            );
1714
1715            let index = rlocate::<Filler>(self).unwrap_or(0);
1716
1717            if index == 0 {
1718                let separator = Filler::new(
1719                    rect![
1720                        self.rect.min.x,
1721                        kb_rect.max.y,
1722                        self.rect.max.x,
1723                        kb_rect.max.y + thickness
1724                    ],
1725                    BLACK,
1726                );
1727                self.children
1728                    .insert(index, Box::new(separator) as Box<dyn View>);
1729            }
1730
1731            let keyboard = Keyboard::new(&mut kb_rect, number, context);
1732            self.children
1733                .insert(index, Box::new(keyboard) as Box<dyn View>);
1734
1735            let separator = Filler::new(
1736                rect![
1737                    self.rect.min.x,
1738                    kb_rect.min.y - thickness,
1739                    self.rect.max.x,
1740                    kb_rect.min.y
1741                ],
1742                BLACK,
1743            );
1744            self.children
1745                .insert(index, Box::new(separator) as Box<dyn View>);
1746
1747            if index == 0 {
1748                for i in index..index + 3 {
1749                    rq.add(RenderData::new(
1750                        self.child(i).id(),
1751                        *self.child(i).rect(),
1752                        UpdateMode::Gui,
1753                    ));
1754                }
1755            } else {
1756                for i in index..index + 2 {
1757                    rq.add(RenderData::new(
1758                        self.child(i).id(),
1759                        *self.child(i).rect(),
1760                        UpdateMode::Gui,
1761                    ));
1762                }
1763
1764                let delta_y = kb_rect.height() as i32 + thickness;
1765                let start_index = locate::<TopBar>(self).map(|index| index + 2).unwrap_or(0);
1766
1767                for i in start_index..index {
1768                    let shifted_rect = *self.child(i).rect() + pt!(0, -delta_y);
1769                    self.child_mut(i).resize(shifted_rect, hub, rq, context);
1770                    rq.add(RenderData::new(
1771                        self.child(i).id(),
1772                        shifted_rect,
1773                        UpdateMode::Gui,
1774                    ));
1775                }
1776            }
1777        }
1778    }
1779
1780    fn toggle_tool_bar(&mut self, enable: bool, rq: &mut RenderQueue, context: &mut Context) {
1781        if let Some(index) = locate::<ToolBar>(self) {
1782            if enable {
1783                return;
1784            }
1785
1786            let mut rect = *self.child(index).rect();
1787            rect.absorb(self.child(index - 1).rect());
1788            self.children.drain(index - 1..=index);
1789            rq.add(RenderData::expose(rect, UpdateMode::Gui));
1790        } else {
1791            if !enable {
1792                return;
1793            }
1794
1795            let dpi = CURRENT_DEVICE.dpi;
1796            let big_height = scale_by_dpi(BIG_BAR_HEIGHT, dpi) as i32;
1797            let tb_height = 2 * big_height;
1798
1799            let sp_rect = *self.child(2).rect() - pt!(0, tb_height as i32);
1800
1801            let tool_bar = ToolBar::new(
1802                rect![
1803                    self.rect.min.x,
1804                    sp_rect.max.y,
1805                    self.rect.max.x,
1806                    sp_rect.max.y + tb_height as i32
1807                ],
1808                self.reflowable,
1809                self.info.reader.as_ref(),
1810                &context.settings.reader,
1811            );
1812            self.children.insert(2, Box::new(tool_bar) as Box<dyn View>);
1813
1814            let separator = Filler::new(sp_rect, BLACK);
1815            self.children
1816                .insert(2, Box::new(separator) as Box<dyn View>);
1817        }
1818    }
1819
1820    fn toggle_results_bar(&mut self, enable: bool, rq: &mut RenderQueue, _context: &mut Context) {
1821        if let Some(index) = locate::<ResultsBar>(self) {
1822            if enable {
1823                return;
1824            }
1825
1826            let mut rect = *self.child(index).rect();
1827            rect.absorb(self.child(index - 1).rect());
1828            self.children.drain(index - 1..=index);
1829            rq.add(RenderData::expose(rect, UpdateMode::Gui));
1830        } else {
1831            if !enable {
1832                return;
1833            }
1834
1835            let dpi = CURRENT_DEVICE.dpi;
1836            let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
1837            let small_height = scale_by_dpi(SMALL_BAR_HEIGHT, dpi) as i32;
1838            let index = locate::<TopBar>(self).map(|index| index + 2).unwrap_or(0);
1839
1840            let sp_rect = *self.child(index).rect() - pt!(0, small_height);
1841            let y_min = sp_rect.max.y;
1842            let mut rect = rect![
1843                self.rect.min.x,
1844                y_min,
1845                self.rect.max.x,
1846                y_min + small_height - thickness
1847            ];
1848
1849            if let Some(ref s) = self.search {
1850                let results_bar = ResultsBar::new(
1851                    rect,
1852                    s.current_page,
1853                    s.highlights.len(),
1854                    s.results_count,
1855                    !s.running.load(AtomicOrdering::Relaxed),
1856                );
1857                self.children
1858                    .insert(index, Box::new(results_bar) as Box<dyn View>);
1859                let separator = Filler::new(sp_rect, BLACK);
1860                self.children
1861                    .insert(index, Box::new(separator) as Box<dyn View>);
1862                rect.absorb(&sp_rect);
1863                rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
1864            }
1865        }
1866    }
1867
1868    fn toggle_search_bar(
1869        &mut self,
1870        enable: bool,
1871        hub: &Hub,
1872        rq: &mut RenderQueue,
1873        context: &mut Context,
1874    ) {
1875        if let Some(index) = locate::<SearchBar>(self) {
1876            if enable {
1877                return;
1878            }
1879
1880            if let Some(ViewId::ReaderSearchInput) = self.focus {
1881                self.toggle_keyboard(false, None, hub, rq, context);
1882            }
1883
1884            if self.child(0).is::<TopBar>() {
1885                self.toggle_bars(Some(false), hub, rq, context);
1886            } else {
1887                let mut rect = *self.child(index).rect();
1888                rect.absorb(self.child(index - 1).rect());
1889                rect.absorb(self.child(index + 1).rect());
1890                self.children.drain(index - 1..=index + 1);
1891                rq.add(RenderData::expose(rect, UpdateMode::Gui));
1892            }
1893        } else {
1894            if !enable {
1895                return;
1896            }
1897
1898            self.toggle_tool_bar(false, rq, context);
1899
1900            let dpi = CURRENT_DEVICE.dpi;
1901            let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
1902            let (small_thickness, big_thickness) = halves(thickness);
1903            let small_height = scale_by_dpi(SMALL_BAR_HEIGHT, dpi) as i32;
1904            let index = locate::<TopBar>(self).map(|index| index + 2).unwrap_or(0);
1905
1906            if index == 0 {
1907                let sp_rect = rect![
1908                    self.rect.min.x,
1909                    self.rect.max.y - small_height - small_thickness,
1910                    self.rect.max.x,
1911                    self.rect.max.y - small_height + big_thickness
1912                ];
1913                let separator = Filler::new(sp_rect, BLACK);
1914                self.children
1915                    .insert(index, Box::new(separator) as Box<dyn View>);
1916            }
1917
1918            let sp_rect = rect![
1919                self.rect.min.x,
1920                self.rect.max.y - 2 * small_height - small_thickness,
1921                self.rect.max.x,
1922                self.rect.max.y - 2 * small_height + big_thickness
1923            ];
1924            let y_min = sp_rect.max.y;
1925            let rect = rect![
1926                self.rect.min.x,
1927                y_min,
1928                self.rect.max.x,
1929                y_min + small_height - thickness
1930            ];
1931            let search_bar = SearchBar::new(rect, ViewId::ReaderSearchInput, "", "", context);
1932            self.children
1933                .insert(index, Box::new(search_bar) as Box<dyn View>);
1934
1935            let separator = Filler::new(sp_rect, BLACK);
1936            self.children
1937                .insert(index, Box::new(separator) as Box<dyn View>);
1938
1939            rq.add(RenderData::new(
1940                self.child(index).id(),
1941                *self.child(index).rect(),
1942                UpdateMode::Gui,
1943            ));
1944            rq.add(RenderData::new(
1945                self.child(index + 1).id(),
1946                *self.child(index + 1).rect(),
1947                UpdateMode::Gui,
1948            ));
1949
1950            if index == 0 {
1951                rq.add(RenderData::new(
1952                    self.child(index + 2).id(),
1953                    *self.child(index + 2).rect(),
1954                    UpdateMode::Gui,
1955                ));
1956            }
1957
1958            self.toggle_keyboard(true, Some(ViewId::ReaderSearchInput), hub, rq, context);
1959            hub.send(Event::Focus(Some(ViewId::ReaderSearchInput))).ok();
1960        }
1961    }
1962
1963    fn toggle_bars(
1964        &mut self,
1965        enable: Option<bool>,
1966        hub: &Hub,
1967        rq: &mut RenderQueue,
1968        context: &mut Context,
1969    ) {
1970        if let Some(top_index) = locate::<TopBar>(self) {
1971            if let Some(true) = enable {
1972                return;
1973            }
1974
1975            if let Some(bottom_index) = locate::<BottomBar>(self) {
1976                let mut top_rect = *self.child(top_index).rect();
1977                top_rect.absorb(self.child(top_index + 1).rect());
1978                let mut bottom_rect = *self.child(bottom_index).rect();
1979                for i in top_index + 2..bottom_index {
1980                    bottom_rect.absorb(self.child(i).rect());
1981                }
1982
1983                self.children.drain(top_index..=bottom_index);
1984
1985                rq.add(RenderData::expose(top_rect, UpdateMode::Gui));
1986                rq.add(RenderData::expose(bottom_rect, UpdateMode::Gui));
1987                hub.send(Event::Focus(None)).ok();
1988            }
1989        } else {
1990            if let Some(false) = enable {
1991                return;
1992            }
1993
1994            let dpi = CURRENT_DEVICE.dpi;
1995            let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
1996            let (small_thickness, big_thickness) = halves(thickness);
1997            let (small_height, big_height) = (
1998                scale_by_dpi(SMALL_BAR_HEIGHT, dpi) as i32,
1999                scale_by_dpi(BIG_BAR_HEIGHT, dpi) as i32,
2000            );
2001
2002            let mut doc = self.doc.lock().unwrap();
2003            let mut index = 0;
2004
2005            let top_bar = TopBar::new(
2006                rect![
2007                    self.rect.min.x,
2008                    self.rect.min.y,
2009                    self.rect.max.x,
2010                    self.rect.min.y + small_height - small_thickness
2011                ],
2012                TopBarVariant::Back,
2013                self.info.title(),
2014                context,
2015            );
2016
2017            self.children
2018                .insert(index, Box::new(top_bar) as Box<dyn View>);
2019            index += 1;
2020
2021            let separator = Filler::new(
2022                rect![
2023                    self.rect.min.x,
2024                    self.rect.min.y + small_height - small_thickness,
2025                    self.rect.max.x,
2026                    self.rect.min.y + small_height + big_thickness
2027                ],
2028                BLACK,
2029            );
2030            self.children
2031                .insert(index, Box::new(separator) as Box<dyn View>);
2032            index += 1;
2033
2034            if let Some(ref s) = self.search {
2035                if let Some(sindex) = rlocate::<SearchBar>(self) {
2036                    index = sindex + 2;
2037                } else {
2038                    let separator = Filler::new(
2039                        rect![
2040                            self.rect.min.x,
2041                            self.rect.max.y - 3 * small_height - small_thickness,
2042                            self.rect.max.x,
2043                            self.rect.max.y - 3 * small_height + big_thickness
2044                        ],
2045                        BLACK,
2046                    );
2047                    self.children
2048                        .insert(index, Box::new(separator) as Box<dyn View>);
2049                    index += 1;
2050
2051                    let results_bar = ResultsBar::new(
2052                        rect![
2053                            self.rect.min.x,
2054                            self.rect.max.y - 3 * small_height + big_thickness,
2055                            self.rect.max.x,
2056                            self.rect.max.y - 2 * small_height - small_thickness
2057                        ],
2058                        s.current_page,
2059                        s.highlights.len(),
2060                        s.results_count,
2061                        !s.running.load(AtomicOrdering::Relaxed),
2062                    );
2063                    self.children
2064                        .insert(index, Box::new(results_bar) as Box<dyn View>);
2065                    index += 1;
2066
2067                    let separator = Filler::new(
2068                        rect![
2069                            self.rect.min.x,
2070                            self.rect.max.y - 2 * small_height - small_thickness,
2071                            self.rect.max.x,
2072                            self.rect.max.y - 2 * small_height + big_thickness
2073                        ],
2074                        BLACK,
2075                    );
2076                    self.children
2077                        .insert(index, Box::new(separator) as Box<dyn View>);
2078                    index += 1;
2079
2080                    let search_bar = SearchBar::new(
2081                        rect![
2082                            self.rect.min.x,
2083                            self.rect.max.y - 2 * small_height + big_thickness,
2084                            self.rect.max.x,
2085                            self.rect.max.y - small_height - small_thickness
2086                        ],
2087                        ViewId::ReaderSearchInput,
2088                        "",
2089                        &s.query,
2090                        context,
2091                    );
2092                    self.children
2093                        .insert(index, Box::new(search_bar) as Box<dyn View>);
2094                    index += 1;
2095                }
2096            } else {
2097                let tb_height = 2 * big_height;
2098                let separator = Filler::new(
2099                    rect![
2100                        self.rect.min.x,
2101                        self.rect.max.y - (small_height + tb_height) as i32 - small_thickness,
2102                        self.rect.max.x,
2103                        self.rect.max.y - (small_height + tb_height) as i32 + big_thickness
2104                    ],
2105                    BLACK,
2106                );
2107                self.children
2108                    .insert(index, Box::new(separator) as Box<dyn View>);
2109                index += 1;
2110
2111                let tool_bar = ToolBar::new(
2112                    rect![
2113                        self.rect.min.x,
2114                        self.rect.max.y - (small_height + tb_height) as i32 + big_thickness,
2115                        self.rect.max.x,
2116                        self.rect.max.y - small_height - small_thickness
2117                    ],
2118                    self.reflowable,
2119                    self.info.reader.as_ref(),
2120                    &context.settings.reader,
2121                );
2122                self.children
2123                    .insert(index, Box::new(tool_bar) as Box<dyn View>);
2124                index += 1;
2125            }
2126
2127            let separator = Filler::new(
2128                rect![
2129                    self.rect.min.x,
2130                    self.rect.max.y - small_height - small_thickness,
2131                    self.rect.max.x,
2132                    self.rect.max.y - small_height + big_thickness
2133                ],
2134                BLACK,
2135            );
2136            self.children
2137                .insert(index, Box::new(separator) as Box<dyn View>);
2138            index += 1;
2139
2140            let neighbors = Neighbors {
2141                previous_page: doc.resolve_location(Location::Previous(self.current_page)),
2142                next_page: doc.resolve_location(Location::Next(self.current_page)),
2143            };
2144
2145            let bottom_bar = BottomBar::new(
2146                rect![
2147                    self.rect.min.x,
2148                    self.rect.max.y - small_height + big_thickness,
2149                    self.rect.max.x,
2150                    self.rect.max.y
2151                ],
2152                doc.as_mut(),
2153                self.toc(),
2154                self.current_page,
2155                self.pages_count,
2156                &neighbors,
2157                self.synthetic,
2158            );
2159            self.children
2160                .insert(index, Box::new(bottom_bar) as Box<dyn View>);
2161
2162            for i in 0..=index {
2163                rq.add(RenderData::new(
2164                    self.child(i).id(),
2165                    *self.child(i).rect(),
2166                    UpdateMode::Gui,
2167                ));
2168            }
2169        }
2170    }
2171
2172    fn toggle_margin_cropper(
2173        &mut self,
2174        enable: bool,
2175        hub: &Hub,
2176        rq: &mut RenderQueue,
2177        context: &mut Context,
2178    ) {
2179        if let Some(index) = locate::<MarginCropper>(self) {
2180            if enable {
2181                return;
2182            }
2183
2184            rq.add(RenderData::expose(
2185                *self.child(index).rect(),
2186                UpdateMode::Gui,
2187            ));
2188            self.children.remove(index);
2189        } else {
2190            if !enable {
2191                return;
2192            }
2193
2194            self.toggle_bars(Some(false), hub, rq, context);
2195
2196            let dpi = CURRENT_DEVICE.dpi;
2197            let padding = scale_by_dpi(BUTTON_DIAMETER / 2.0, dpi) as i32;
2198            let pixmap_rect = rect![self.rect.min + pt!(padding), self.rect.max - pt!(padding)];
2199
2200            let margin = self
2201                .info
2202                .reader
2203                .as_ref()
2204                .and_then(|r| {
2205                    r.cropping_margins
2206                        .as_ref()
2207                        .map(|c| c.margin(self.current_page))
2208                })
2209                .cloned()
2210                .unwrap_or_default();
2211
2212            let mut doc = self.doc.lock().unwrap();
2213            let (pixmap, _) = build_pixmap(&pixmap_rect, doc.as_mut(), self.current_page);
2214
2215            let margin_cropper = MarginCropper::new(self.rect, pixmap, &margin, context);
2216            rq.add(RenderData::new(
2217                margin_cropper.id(),
2218                *margin_cropper.rect(),
2219                UpdateMode::Gui,
2220            ));
2221            self.children
2222                .push(Box::new(margin_cropper) as Box<dyn View>);
2223        }
2224    }
2225
2226    fn toggle_edit_note(
2227        &mut self,
2228        text: Option<String>,
2229        enable: Option<bool>,
2230        hub: &Hub,
2231        rq: &mut RenderQueue,
2232        context: &mut Context,
2233    ) {
2234        if let Some(index) = locate_by_id(self, ViewId::EditNote) {
2235            if let Some(true) = enable {
2236                return;
2237            }
2238
2239            rq.add(RenderData::expose(
2240                *self.child(index).rect(),
2241                UpdateMode::Gui,
2242            ));
2243            self.children.remove(index);
2244
2245            if self
2246                .focus
2247                .map(|focus_id| focus_id == ViewId::EditNoteInput)
2248                .unwrap_or(false)
2249            {
2250                self.toggle_keyboard(false, None, hub, rq, context);
2251            }
2252        } else {
2253            if let Some(false) = enable {
2254                return;
2255            }
2256
2257            let mut edit_note = NamedInput::new(
2258                "Note".to_string(),
2259                ViewId::EditNote,
2260                ViewId::EditNoteInput,
2261                32,
2262                context,
2263            );
2264            if let Some(text) = text.as_ref() {
2265                edit_note.set_text(text, &mut RenderQueue::new(), context);
2266            }
2267
2268            rq.add(RenderData::new(
2269                edit_note.id(),
2270                *edit_note.rect(),
2271                UpdateMode::Gui,
2272            ));
2273            hub.send(Event::Focus(Some(ViewId::EditNoteInput))).ok();
2274
2275            self.children.push(Box::new(edit_note) as Box<dyn View>);
2276        }
2277    }
2278
2279    fn toggle_name_page(
2280        &mut self,
2281        enable: Option<bool>,
2282        hub: &Hub,
2283        rq: &mut RenderQueue,
2284        context: &mut Context,
2285    ) {
2286        if let Some(index) = locate_by_id(self, ViewId::NamePage) {
2287            if let Some(true) = enable {
2288                return;
2289            }
2290
2291            rq.add(RenderData::expose(
2292                *self.child(index).rect(),
2293                UpdateMode::Gui,
2294            ));
2295            self.children.remove(index);
2296
2297            if self
2298                .focus
2299                .map(|focus_id| focus_id == ViewId::NamePageInput)
2300                .unwrap_or(false)
2301            {
2302                self.toggle_keyboard(false, None, hub, rq, context);
2303            }
2304        } else {
2305            if let Some(false) = enable {
2306                return;
2307            }
2308
2309            let name_page = NamedInput::new(
2310                "Name page".to_string(),
2311                ViewId::NamePage,
2312                ViewId::NamePageInput,
2313                4,
2314                context,
2315            );
2316            rq.add(RenderData::new(
2317                name_page.id(),
2318                *name_page.rect(),
2319                UpdateMode::Gui,
2320            ));
2321            hub.send(Event::Focus(Some(ViewId::NamePageInput))).ok();
2322
2323            self.children.push(Box::new(name_page) as Box<dyn View>);
2324        }
2325    }
2326
2327    fn toggle_go_to_page(
2328        &mut self,
2329        enable: Option<bool>,
2330        id: ViewId,
2331        hub: &Hub,
2332        rq: &mut RenderQueue,
2333        context: &mut Context,
2334    ) {
2335        let (text, input_id) = if id == ViewId::GoToPage {
2336            ("Go to page", ViewId::GoToPageInput)
2337        } else {
2338            ("Go to results page", ViewId::GoToResultsPageInput)
2339        };
2340
2341        if let Some(index) = locate_by_id(self, id) {
2342            if let Some(true) = enable {
2343                return;
2344            }
2345
2346            rq.add(RenderData::expose(
2347                *self.child(index).rect(),
2348                UpdateMode::Gui,
2349            ));
2350            self.children.remove(index);
2351
2352            if self
2353                .focus
2354                .map(|focus_id| focus_id == input_id)
2355                .unwrap_or(false)
2356            {
2357                self.toggle_keyboard(false, None, hub, rq, context);
2358            }
2359        } else {
2360            if let Some(false) = enable {
2361                return;
2362            }
2363
2364            let go_to_page = NamedInput::new(text.to_string(), id, input_id, 4, context);
2365            rq.add(RenderData::new(
2366                go_to_page.id(),
2367                *go_to_page.rect(),
2368                UpdateMode::Gui,
2369            ));
2370            hub.send(Event::Focus(Some(input_id))).ok();
2371
2372            self.children.push(Box::new(go_to_page) as Box<dyn View>);
2373        }
2374    }
2375
2376    pub fn toggle_annotation_menu(
2377        &mut self,
2378        annot: &Annotation,
2379        rect: Rectangle,
2380        enable: Option<bool>,
2381        rq: &mut RenderQueue,
2382        context: &mut Context,
2383    ) {
2384        if let Some(index) = locate_by_id(self, ViewId::AnnotationMenu) {
2385            if let Some(true) = enable {
2386                return;
2387            }
2388
2389            rq.add(RenderData::expose(
2390                *self.child(index).rect(),
2391                UpdateMode::Gui,
2392            ));
2393            self.children.remove(index);
2394        } else {
2395            if let Some(false) = enable {
2396                return;
2397            }
2398
2399            let sel = annot.selection;
2400            let mut entries = Vec::new();
2401
2402            if annot.note.is_empty() {
2403                entries.push(EntryKind::Command(
2404                    "Remove Highlight".to_string(),
2405                    EntryId::RemoveAnnotation(sel),
2406                ));
2407                entries.push(EntryKind::Separator);
2408                entries.push(EntryKind::Command(
2409                    "Add Note".to_string(),
2410                    EntryId::EditAnnotationNote(sel),
2411                ));
2412            } else {
2413                entries.push(EntryKind::Command(
2414                    "Remove Annotation".to_string(),
2415                    EntryId::RemoveAnnotation(sel),
2416                ));
2417                entries.push(EntryKind::Separator);
2418                entries.push(EntryKind::Command(
2419                    "Edit Note".to_string(),
2420                    EntryId::EditAnnotationNote(sel),
2421                ));
2422                entries.push(EntryKind::Command(
2423                    "Remove Note".to_string(),
2424                    EntryId::RemoveAnnotationNote(sel),
2425                ));
2426            }
2427
2428            let selection_menu = Menu::new(
2429                rect,
2430                ViewId::AnnotationMenu,
2431                MenuKind::Contextual,
2432                entries,
2433                context,
2434            );
2435            rq.add(RenderData::new(
2436                selection_menu.id(),
2437                *selection_menu.rect(),
2438                UpdateMode::Gui,
2439            ));
2440            self.children
2441                .push(Box::new(selection_menu) as Box<dyn View>);
2442        }
2443    }
2444
2445    pub fn toggle_selection_menu(
2446        &mut self,
2447        rect: Rectangle,
2448        enable: Option<bool>,
2449        rq: &mut RenderQueue,
2450        context: &mut Context,
2451    ) {
2452        if let Some(index) = locate_by_id(self, ViewId::SelectionMenu) {
2453            if let Some(true) = enable {
2454                return;
2455            }
2456
2457            rq.add(RenderData::expose(
2458                *self.child(index).rect(),
2459                UpdateMode::Gui,
2460            ));
2461            self.children.remove(index);
2462        } else {
2463            if let Some(false) = enable {
2464                return;
2465            }
2466            let mut entries = vec![
2467                EntryKind::Command("Highlight".to_string(), EntryId::HighlightSelection),
2468                EntryKind::Command("Add Note".to_string(), EntryId::AnnotateSelection),
2469            ];
2470
2471            entries.push(EntryKind::Separator);
2472            entries.push(EntryKind::Command(
2473                "Define".to_string(),
2474                EntryId::DefineSelection,
2475            ));
2476            entries.push(EntryKind::Command(
2477                "Search".to_string(),
2478                EntryId::SearchForSelection,
2479            ));
2480
2481            if self
2482                .info
2483                .reader
2484                .as_ref()
2485                .map_or(false, |r| !r.page_names.is_empty())
2486            {
2487                entries.push(EntryKind::Command(
2488                    "Go To".to_string(),
2489                    EntryId::GoToSelectedPageName,
2490                ));
2491            }
2492
2493            entries.push(EntryKind::Separator);
2494            entries.push(EntryKind::Command(
2495                "Adjust Selection".to_string(),
2496                EntryId::AdjustSelection,
2497            ));
2498
2499            let selection_menu = Menu::new(
2500                rect,
2501                ViewId::SelectionMenu,
2502                MenuKind::Contextual,
2503                entries,
2504                context,
2505            );
2506            rq.add(RenderData::new(
2507                selection_menu.id(),
2508                *selection_menu.rect(),
2509                UpdateMode::Gui,
2510            ));
2511            self.children
2512                .push(Box::new(selection_menu) as Box<dyn View>);
2513        }
2514    }
2515
2516    pub fn toggle_title_menu(
2517        &mut self,
2518        rect: Rectangle,
2519        enable: Option<bool>,
2520        rq: &mut RenderQueue,
2521        context: &mut Context,
2522    ) {
2523        if let Some(index) = locate_by_id(self, ViewId::TitleMenu) {
2524            if let Some(true) = enable {
2525                return;
2526            }
2527
2528            rq.add(RenderData::expose(
2529                *self.child(index).rect(),
2530                UpdateMode::Gui,
2531            ));
2532            self.children.remove(index);
2533        } else {
2534            if let Some(false) = enable {
2535                return;
2536            }
2537
2538            let zoom_mode = self.view_port.zoom_mode;
2539            let scroll_mode = self.view_port.scroll_mode;
2540            let sf = if let ZoomMode::Custom(sf) = zoom_mode {
2541                sf
2542            } else {
2543                1.0
2544            };
2545
2546            let mut entries = if self.reflowable {
2547                vec![EntryKind::SubMenu(
2548                    "Zoom Mode".to_string(),
2549                    vec![
2550                        EntryKind::RadioButton(
2551                            "Fit to Page".to_string(),
2552                            EntryId::SetZoomMode(ZoomMode::FitToPage),
2553                            zoom_mode == ZoomMode::FitToPage,
2554                        ),
2555                        EntryKind::RadioButton(
2556                            format!("Custom ({:.1}%)", 100.0 * sf),
2557                            EntryId::SetZoomMode(ZoomMode::Custom(sf)),
2558                            zoom_mode == ZoomMode::Custom(sf),
2559                        ),
2560                    ],
2561                )]
2562            } else {
2563                vec![EntryKind::SubMenu(
2564                    "Zoom Mode".to_string(),
2565                    vec![
2566                        EntryKind::RadioButton(
2567                            "Fit to Page".to_string(),
2568                            EntryId::SetZoomMode(ZoomMode::FitToPage),
2569                            zoom_mode == ZoomMode::FitToPage,
2570                        ),
2571                        EntryKind::RadioButton(
2572                            "Fit to Width".to_string(),
2573                            EntryId::SetZoomMode(ZoomMode::FitToWidth),
2574                            zoom_mode == ZoomMode::FitToWidth,
2575                        ),
2576                        EntryKind::RadioButton(
2577                            format!("Custom ({:.1}%)", 100.0 * sf),
2578                            EntryId::SetZoomMode(ZoomMode::Custom(sf)),
2579                            zoom_mode == ZoomMode::Custom(sf),
2580                        ),
2581                    ],
2582                )]
2583            };
2584
2585            entries.push(EntryKind::SubMenu(
2586                "Scroll Mode".to_string(),
2587                vec![
2588                    EntryKind::RadioButton(
2589                        "Screen".to_string(),
2590                        EntryId::SetScrollMode(ScrollMode::Screen),
2591                        scroll_mode == ScrollMode::Screen,
2592                    ),
2593                    EntryKind::RadioButton(
2594                        "Page".to_string(),
2595                        EntryId::SetScrollMode(ScrollMode::Page),
2596                        scroll_mode == ScrollMode::Page,
2597                    ),
2598                ],
2599            ));
2600
2601            if self.ephemeral {
2602                entries.push(EntryKind::Command("Save".to_string(), EntryId::Save));
2603            }
2604
2605            if self
2606                .info
2607                .reader
2608                .as_ref()
2609                .map_or(false, |r| !r.annotations.is_empty())
2610            {
2611                entries.push(EntryKind::Command(
2612                    "Annotations".to_string(),
2613                    EntryId::Annotations,
2614                ));
2615            }
2616
2617            if self
2618                .info
2619                .reader
2620                .as_ref()
2621                .map_or(false, |r| !r.bookmarks.is_empty())
2622            {
2623                entries.push(EntryKind::Command(
2624                    "Bookmarks".to_string(),
2625                    EntryId::Bookmarks,
2626                ));
2627            }
2628
2629            if !entries.is_empty() {
2630                entries.push(EntryKind::Separator);
2631            }
2632
2633            entries.push(EntryKind::CheckBox(
2634                "Apply Dithering".to_string(),
2635                EntryId::ToggleDithered,
2636                context.fb.dithered(),
2637            ));
2638
2639            let mut title_menu = Menu::new(
2640                rect,
2641                ViewId::TitleMenu,
2642                MenuKind::DropDown,
2643                entries,
2644                context,
2645            );
2646            title_menu
2647                .child_mut(1)
2648                .downcast_mut::<MenuEntry>()
2649                .unwrap()
2650                .set_disabled(zoom_mode != ZoomMode::FitToWidth, rq);
2651
2652            rq.add(RenderData::new(
2653                title_menu.id(),
2654                *title_menu.rect(),
2655                UpdateMode::Gui,
2656            ));
2657            self.children.push(Box::new(title_menu) as Box<dyn View>);
2658        }
2659    }
2660
2661    fn toggle_font_family_menu(
2662        &mut self,
2663        rect: Rectangle,
2664        enable: Option<bool>,
2665        rq: &mut RenderQueue,
2666        context: &mut Context,
2667    ) {
2668        if let Some(index) = locate_by_id(self, ViewId::FontFamilyMenu) {
2669            if let Some(true) = enable {
2670                return;
2671            }
2672
2673            rq.add(RenderData::expose(
2674                *self.child(index).rect(),
2675                UpdateMode::Gui,
2676            ));
2677            self.children.remove(index);
2678        } else {
2679            if let Some(false) = enable {
2680                return;
2681            }
2682
2683            let mut families = family_names(&context.settings.reader.font_path)
2684                .map_err(|e| error!("Can't get family names: {:#}.", e))
2685                .unwrap_or_default();
2686            let current_family = self
2687                .info
2688                .reader
2689                .as_ref()
2690                .and_then(|r| r.font_family.clone())
2691                .unwrap_or_else(|| context.settings.reader.font_family.clone());
2692            families.insert(DEFAULT_FONT_FAMILY.to_string());
2693            let entries = families
2694                .iter()
2695                .map(|f| {
2696                    EntryKind::RadioButton(
2697                        f.clone(),
2698                        EntryId::SetFontFamily(f.clone()),
2699                        *f == current_family,
2700                    )
2701                })
2702                .collect();
2703            let font_family_menu = Menu::new(
2704                rect,
2705                ViewId::FontFamilyMenu,
2706                MenuKind::DropDown,
2707                entries,
2708                context,
2709            );
2710            rq.add(RenderData::new(
2711                font_family_menu.id(),
2712                *font_family_menu.rect(),
2713                UpdateMode::Gui,
2714            ));
2715            self.children
2716                .push(Box::new(font_family_menu) as Box<dyn View>);
2717        }
2718    }
2719
2720    fn toggle_font_size_menu(
2721        &mut self,
2722        rect: Rectangle,
2723        enable: Option<bool>,
2724        rq: &mut RenderQueue,
2725        context: &mut Context,
2726    ) {
2727        if let Some(index) = locate_by_id(self, ViewId::FontSizeMenu) {
2728            if let Some(true) = enable {
2729                return;
2730            }
2731
2732            rq.add(RenderData::expose(
2733                *self.child(index).rect(),
2734                UpdateMode::Gui,
2735            ));
2736            self.children.remove(index);
2737        } else {
2738            if let Some(false) = enable {
2739                return;
2740            }
2741
2742            let font_size = self
2743                .info
2744                .reader
2745                .as_ref()
2746                .and_then(|r| r.font_size)
2747                .unwrap_or(context.settings.reader.font_size);
2748            let min_font_size = context.settings.reader.font_size / 2.0;
2749            let max_font_size = 3.0 * context.settings.reader.font_size / 2.0;
2750            let entries = (0..=20)
2751                .filter_map(|v| {
2752                    let fs = font_size - 1.0 + v as f32 / 10.0;
2753                    if fs >= min_font_size && fs <= max_font_size {
2754                        Some(EntryKind::RadioButton(
2755                            format!("{:.1}", fs),
2756                            EntryId::SetFontSize(v),
2757                            (fs - font_size).abs() < 0.05,
2758                        ))
2759                    } else {
2760                        None
2761                    }
2762                })
2763                .collect();
2764            let font_size_menu = Menu::new(
2765                rect,
2766                ViewId::FontSizeMenu,
2767                MenuKind::Contextual,
2768                entries,
2769                context,
2770            );
2771            rq.add(RenderData::new(
2772                font_size_menu.id(),
2773                *font_size_menu.rect(),
2774                UpdateMode::Gui,
2775            ));
2776            self.children
2777                .push(Box::new(font_size_menu) as Box<dyn View>);
2778        }
2779    }
2780
2781    fn toggle_text_align_menu(
2782        &mut self,
2783        rect: Rectangle,
2784        enable: Option<bool>,
2785        rq: &mut RenderQueue,
2786        context: &mut Context,
2787    ) {
2788        if let Some(index) = locate_by_id(self, ViewId::TextAlignMenu) {
2789            if let Some(true) = enable {
2790                return;
2791            }
2792
2793            rq.add(RenderData::expose(
2794                *self.child(index).rect(),
2795                UpdateMode::Gui,
2796            ));
2797            self.children.remove(index);
2798        } else {
2799            if let Some(false) = enable {
2800                return;
2801            }
2802
2803            let text_align = self
2804                .info
2805                .reader
2806                .as_ref()
2807                .and_then(|r| r.text_align)
2808                .unwrap_or(context.settings.reader.text_align);
2809            let choices = [
2810                TextAlign::Justify,
2811                TextAlign::Left,
2812                TextAlign::Right,
2813                TextAlign::Center,
2814            ];
2815            let entries = choices
2816                .iter()
2817                .map(|v| {
2818                    EntryKind::RadioButton(
2819                        v.to_string(),
2820                        EntryId::SetTextAlign(*v),
2821                        text_align == *v,
2822                    )
2823                })
2824                .collect();
2825            let text_align_menu = Menu::new(
2826                rect,
2827                ViewId::TextAlignMenu,
2828                MenuKind::Contextual,
2829                entries,
2830                context,
2831            );
2832            rq.add(RenderData::new(
2833                text_align_menu.id(),
2834                *text_align_menu.rect(),
2835                UpdateMode::Gui,
2836            ));
2837            self.children
2838                .push(Box::new(text_align_menu) as Box<dyn View>);
2839        }
2840    }
2841
2842    fn toggle_line_height_menu(
2843        &mut self,
2844        rect: Rectangle,
2845        enable: Option<bool>,
2846        rq: &mut RenderQueue,
2847        context: &mut Context,
2848    ) {
2849        if let Some(index) = locate_by_id(self, ViewId::LineHeightMenu) {
2850            if let Some(true) = enable {
2851                return;
2852            }
2853
2854            rq.add(RenderData::expose(
2855                *self.child(index).rect(),
2856                UpdateMode::Gui,
2857            ));
2858            self.children.remove(index);
2859        } else {
2860            if let Some(false) = enable {
2861                return;
2862            }
2863
2864            let line_height = self
2865                .info
2866                .reader
2867                .as_ref()
2868                .and_then(|r| r.line_height)
2869                .unwrap_or(context.settings.reader.line_height);
2870            let entries = (0..=10)
2871                .map(|x| {
2872                    let lh = 1.0 + x as f32 / 10.0;
2873                    EntryKind::RadioButton(
2874                        format!("{:.1}", lh),
2875                        EntryId::SetLineHeight(x),
2876                        (lh - line_height).abs() < 0.05,
2877                    )
2878                })
2879                .collect();
2880            let line_height_menu = Menu::new(
2881                rect,
2882                ViewId::LineHeightMenu,
2883                MenuKind::DropDown,
2884                entries,
2885                context,
2886            );
2887            rq.add(RenderData::new(
2888                line_height_menu.id(),
2889                *line_height_menu.rect(),
2890                UpdateMode::Gui,
2891            ));
2892            self.children
2893                .push(Box::new(line_height_menu) as Box<dyn View>);
2894        }
2895    }
2896
2897    fn toggle_contrast_exponent_menu(
2898        &mut self,
2899        rect: Rectangle,
2900        enable: Option<bool>,
2901        rq: &mut RenderQueue,
2902        context: &mut Context,
2903    ) {
2904        if let Some(index) = locate_by_id(self, ViewId::ContrastExponentMenu) {
2905            if let Some(true) = enable {
2906                return;
2907            }
2908
2909            rq.add(RenderData::expose(
2910                *self.child(index).rect(),
2911                UpdateMode::Gui,
2912            ));
2913            self.children.remove(index);
2914        } else {
2915            if let Some(false) = enable {
2916                return;
2917            }
2918
2919            let entries = (0..=8)
2920                .map(|x| {
2921                    let e = 1.0 + x as f32 / 2.0;
2922                    EntryKind::RadioButton(
2923                        format!("{:.1}", e),
2924                        EntryId::SetContrastExponent(x),
2925                        (e - self.contrast.exponent).abs() < f32::EPSILON,
2926                    )
2927                })
2928                .collect();
2929            let contrast_exponent_menu = Menu::new(
2930                rect,
2931                ViewId::ContrastExponentMenu,
2932                MenuKind::DropDown,
2933                entries,
2934                context,
2935            );
2936            rq.add(RenderData::new(
2937                contrast_exponent_menu.id(),
2938                *contrast_exponent_menu.rect(),
2939                UpdateMode::Gui,
2940            ));
2941            self.children
2942                .push(Box::new(contrast_exponent_menu) as Box<dyn View>);
2943        }
2944    }
2945
2946    fn toggle_contrast_gray_menu(
2947        &mut self,
2948        rect: Rectangle,
2949        enable: Option<bool>,
2950        rq: &mut RenderQueue,
2951        context: &mut Context,
2952    ) {
2953        if let Some(index) = locate_by_id(self, ViewId::ContrastGrayMenu) {
2954            if let Some(true) = enable {
2955                return;
2956            }
2957
2958            rq.add(RenderData::expose(
2959                *self.child(index).rect(),
2960                UpdateMode::Gui,
2961            ));
2962            self.children.remove(index);
2963        } else {
2964            if let Some(false) = enable {
2965                return;
2966            }
2967
2968            let entries = (1..=6)
2969                .map(|x| {
2970                    let g = ((1 << 8) - (1 << (8 - x))) as f32;
2971                    EntryKind::RadioButton(
2972                        format!("{:.1}", g),
2973                        EntryId::SetContrastGray(x),
2974                        (g - self.contrast.gray).abs() < f32::EPSILON,
2975                    )
2976                })
2977                .collect();
2978            let contrast_gray_menu = Menu::new(
2979                rect,
2980                ViewId::ContrastGrayMenu,
2981                MenuKind::DropDown,
2982                entries,
2983                context,
2984            );
2985            rq.add(RenderData::new(
2986                contrast_gray_menu.id(),
2987                *contrast_gray_menu.rect(),
2988                UpdateMode::Gui,
2989            ));
2990            self.children
2991                .push(Box::new(contrast_gray_menu) as Box<dyn View>);
2992        }
2993    }
2994
2995    fn toggle_margin_width_menu(
2996        &mut self,
2997        rect: Rectangle,
2998        enable: Option<bool>,
2999        rq: &mut RenderQueue,
3000        context: &mut Context,
3001    ) {
3002        if let Some(index) = locate_by_id(self, ViewId::MarginWidthMenu) {
3003            if let Some(true) = enable {
3004                return;
3005            }
3006
3007            rq.add(RenderData::expose(
3008                *self.child(index).rect(),
3009                UpdateMode::Gui,
3010            ));
3011            self.children.remove(index);
3012        } else {
3013            if let Some(false) = enable {
3014                return;
3015            }
3016
3017            let reflowable = self.reflowable;
3018            let margin_width = self
3019                .info
3020                .reader
3021                .as_ref()
3022                .and_then(|r| {
3023                    if reflowable {
3024                        r.margin_width
3025                    } else {
3026                        r.screen_margin_width
3027                    }
3028                })
3029                .unwrap_or_else(|| {
3030                    if reflowable {
3031                        context.settings.reader.margin_width
3032                    } else {
3033                        0
3034                    }
3035                });
3036            let min_margin_width = context.settings.reader.min_margin_width;
3037            let max_margin_width = context.settings.reader.max_margin_width;
3038            let entries = (min_margin_width..=max_margin_width)
3039                .map(|mw| {
3040                    EntryKind::RadioButton(
3041                        format!("{}", mw),
3042                        EntryId::SetMarginWidth(mw),
3043                        mw == margin_width,
3044                    )
3045                })
3046                .collect();
3047            let margin_width_menu = Menu::new(
3048                rect,
3049                ViewId::MarginWidthMenu,
3050                MenuKind::DropDown,
3051                entries,
3052                context,
3053            );
3054            rq.add(RenderData::new(
3055                margin_width_menu.id(),
3056                *margin_width_menu.rect(),
3057                UpdateMode::Gui,
3058            ));
3059            self.children
3060                .push(Box::new(margin_width_menu) as Box<dyn View>);
3061        }
3062    }
3063
3064    fn toggle_page_menu(
3065        &mut self,
3066        rect: Rectangle,
3067        enable: Option<bool>,
3068        rq: &mut RenderQueue,
3069        context: &mut Context,
3070    ) {
3071        if let Some(index) = locate_by_id(self, ViewId::PageMenu) {
3072            if let Some(true) = enable {
3073                return;
3074            }
3075
3076            rq.add(RenderData::expose(
3077                *self.child(index).rect(),
3078                UpdateMode::Gui,
3079            ));
3080            self.children.remove(index);
3081        } else {
3082            if let Some(false) = enable {
3083                return;
3084            }
3085
3086            let has_name = self
3087                .info
3088                .reader
3089                .as_ref()
3090                .map_or(false, |r| r.page_names.contains_key(&self.current_page));
3091
3092            let mut entries = vec![EntryKind::Command("Name".to_string(), EntryId::SetPageName)];
3093            if has_name {
3094                entries.push(EntryKind::Command(
3095                    "Remove Name".to_string(),
3096                    EntryId::RemovePageName,
3097                ));
3098            }
3099            let names = self
3100                .info
3101                .reader
3102                .as_ref()
3103                .map(|r| {
3104                    r.page_names
3105                        .iter()
3106                        .map(|(i, s)| EntryKind::Command(s.to_string(), EntryId::GoTo(*i)))
3107                        .collect::<Vec<EntryKind>>()
3108                })
3109                .unwrap_or_default();
3110            if !names.is_empty() {
3111                entries.push(EntryKind::Separator);
3112                entries.push(EntryKind::SubMenu("Go To".to_string(), names));
3113            }
3114
3115            let page_menu = Menu::new(rect, ViewId::PageMenu, MenuKind::DropDown, entries, context);
3116            rq.add(RenderData::new(
3117                page_menu.id(),
3118                *page_menu.rect(),
3119                UpdateMode::Gui,
3120            ));
3121            self.children.push(Box::new(page_menu) as Box<dyn View>);
3122        }
3123    }
3124
3125    fn toggle_margin_cropper_menu(
3126        &mut self,
3127        rect: Rectangle,
3128        enable: Option<bool>,
3129        rq: &mut RenderQueue,
3130        context: &mut Context,
3131    ) {
3132        if let Some(index) = locate_by_id(self, ViewId::MarginCropperMenu) {
3133            if let Some(true) = enable {
3134                return;
3135            }
3136
3137            rq.add(RenderData::expose(
3138                *self.child(index).rect(),
3139                UpdateMode::Gui,
3140            ));
3141            self.children.remove(index);
3142        } else {
3143            if let Some(false) = enable {
3144                return;
3145            }
3146
3147            let current_page = self.current_page;
3148            let is_split = self
3149                .info
3150                .reader
3151                .as_ref()
3152                .and_then(|r| r.cropping_margins.as_ref().map(CroppingMargins::is_split));
3153
3154            let mut entries = vec![
3155                EntryKind::RadioButton(
3156                    "Any".to_string(),
3157                    EntryId::ApplyCroppings(current_page, PageScheme::Any),
3158                    is_split.is_some() && !is_split.unwrap(),
3159                ),
3160                EntryKind::RadioButton(
3161                    "Even/Odd".to_string(),
3162                    EntryId::ApplyCroppings(current_page, PageScheme::EvenOdd),
3163                    is_split.is_some() && is_split.unwrap(),
3164                ),
3165            ];
3166
3167            let is_applied = self
3168                .info
3169                .reader
3170                .as_ref()
3171                .map(|r| r.cropping_margins.is_some())
3172                .unwrap_or(false);
3173            if is_applied {
3174                entries.extend_from_slice(&[
3175                    EntryKind::Separator,
3176                    EntryKind::Command("Remove".to_string(), EntryId::RemoveCroppings),
3177                ]);
3178            }
3179
3180            let margin_cropper_menu = Menu::new(
3181                rect,
3182                ViewId::MarginCropperMenu,
3183                MenuKind::DropDown,
3184                entries,
3185                context,
3186            );
3187            rq.add(RenderData::new(
3188                margin_cropper_menu.id(),
3189                *margin_cropper_menu.rect(),
3190                UpdateMode::Gui,
3191            ));
3192            self.children
3193                .push(Box::new(margin_cropper_menu) as Box<dyn View>);
3194        }
3195    }
3196
3197    fn toggle_search_menu(
3198        &mut self,
3199        rect: Rectangle,
3200        enable: Option<bool>,
3201        rq: &mut RenderQueue,
3202        context: &mut Context,
3203    ) {
3204        if let Some(index) = locate_by_id(self, ViewId::SearchMenu) {
3205            if let Some(true) = enable {
3206                return;
3207            }
3208
3209            rq.add(RenderData::expose(
3210                *self.child(index).rect(),
3211                UpdateMode::Gui,
3212            ));
3213            self.children.remove(index);
3214        } else {
3215            if let Some(false) = enable {
3216                return;
3217            }
3218
3219            let entries = vec![
3220                EntryKind::RadioButton(
3221                    "Forward".to_string(),
3222                    EntryId::SearchDirection(LinearDir::Forward),
3223                    self.search_direction == LinearDir::Forward,
3224                ),
3225                EntryKind::RadioButton(
3226                    "Backward".to_string(),
3227                    EntryId::SearchDirection(LinearDir::Backward),
3228                    self.search_direction == LinearDir::Backward,
3229                ),
3230            ];
3231
3232            let search_menu = Menu::new(
3233                rect,
3234                ViewId::SearchMenu,
3235                MenuKind::Contextual,
3236                entries,
3237                context,
3238            );
3239            rq.add(RenderData::new(
3240                search_menu.id(),
3241                *search_menu.rect(),
3242                UpdateMode::Gui,
3243            ));
3244            self.children.push(Box::new(search_menu) as Box<dyn View>);
3245        }
3246    }
3247
3248    fn set_font_size(
3249        &mut self,
3250        font_size: f32,
3251        hub: &Hub,
3252        rq: &mut RenderQueue,
3253        context: &mut Context,
3254    ) {
3255        if Arc::strong_count(&self.doc) > 1 {
3256            return;
3257        }
3258
3259        if let Some(ref mut r) = self.info.reader {
3260            r.font_size = Some(font_size);
3261        }
3262
3263        let (width, height) = context.display.dims;
3264        {
3265            let mut doc = self.doc.lock().unwrap();
3266
3267            doc.layout(width, height, font_size, CURRENT_DEVICE.dpi);
3268
3269            if self.synthetic {
3270                let current_page = self.current_page.min(doc.pages_count() - 1);
3271                if let Some(location) = doc.resolve_location(Location::Exact(current_page)) {
3272                    self.current_page = location;
3273                }
3274            } else {
3275                let ratio = doc.pages_count() / self.pages_count;
3276                self.pages_count = doc.pages_count();
3277                self.current_page = (ratio * self.current_page).min(self.pages_count - 1);
3278            }
3279        }
3280
3281        self.cache.clear();
3282        self.text.clear();
3283        self.update(None, hub, rq, context);
3284        self.update_tool_bar(rq, context);
3285        self.update_bottom_bar(rq);
3286    }
3287
3288    fn set_text_align(
3289        &mut self,
3290        text_align: TextAlign,
3291        hub: &Hub,
3292        rq: &mut RenderQueue,
3293        context: &mut Context,
3294    ) {
3295        if Arc::strong_count(&self.doc) > 1 {
3296            return;
3297        }
3298
3299        if let Some(ref mut r) = self.info.reader {
3300            r.text_align = Some(text_align);
3301        }
3302
3303        {
3304            let mut doc = self.doc.lock().unwrap();
3305            doc.set_text_align(text_align);
3306
3307            if self.synthetic {
3308                let current_page = self.current_page.min(doc.pages_count() - 1);
3309                if let Some(location) = doc.resolve_location(Location::Exact(current_page)) {
3310                    self.current_page = location;
3311                }
3312            } else {
3313                self.pages_count = doc.pages_count();
3314                self.current_page = self.current_page.min(self.pages_count - 1);
3315            }
3316        }
3317
3318        self.cache.clear();
3319        self.text.clear();
3320        self.update(None, hub, rq, context);
3321        self.update_tool_bar(rq, context);
3322        self.update_bottom_bar(rq);
3323    }
3324
3325    fn set_font_family(
3326        &mut self,
3327        font_family: &str,
3328        hub: &Hub,
3329        rq: &mut RenderQueue,
3330        context: &mut Context,
3331    ) {
3332        if Arc::strong_count(&self.doc) > 1 {
3333            return;
3334        }
3335
3336        if let Some(ref mut r) = self.info.reader {
3337            r.font_family = Some(font_family.to_string());
3338        }
3339
3340        {
3341            let mut doc = self.doc.lock().unwrap();
3342            let font_path = if font_family == DEFAULT_FONT_FAMILY {
3343                "fonts"
3344            } else {
3345                &context.settings.reader.font_path
3346            };
3347
3348            doc.set_font_family(font_family, font_path);
3349
3350            if self.synthetic {
3351                let current_page = self.current_page.min(doc.pages_count() - 1);
3352                if let Some(location) = doc.resolve_location(Location::Exact(current_page)) {
3353                    self.current_page = location;
3354                }
3355            } else {
3356                self.pages_count = doc.pages_count();
3357                self.current_page = self.current_page.min(self.pages_count - 1);
3358            }
3359        }
3360
3361        self.cache.clear();
3362        self.text.clear();
3363        self.update(None, hub, rq, context);
3364        self.update_tool_bar(rq, context);
3365        self.update_bottom_bar(rq);
3366    }
3367
3368    fn set_line_height(
3369        &mut self,
3370        line_height: f32,
3371        hub: &Hub,
3372        rq: &mut RenderQueue,
3373        context: &mut Context,
3374    ) {
3375        if Arc::strong_count(&self.doc) > 1 {
3376            return;
3377        }
3378
3379        if let Some(ref mut r) = self.info.reader {
3380            r.line_height = Some(line_height);
3381        }
3382
3383        {
3384            let mut doc = self.doc.lock().unwrap();
3385            doc.set_line_height(line_height);
3386
3387            if self.synthetic {
3388                let current_page = self.current_page.min(doc.pages_count() - 1);
3389                if let Some(location) = doc.resolve_location(Location::Exact(current_page)) {
3390                    self.current_page = location;
3391                }
3392            } else {
3393                self.pages_count = doc.pages_count();
3394                self.current_page = self.current_page.min(self.pages_count - 1);
3395            }
3396        }
3397
3398        self.cache.clear();
3399        self.text.clear();
3400        self.update(None, hub, rq, context);
3401        self.update_tool_bar(rq, context);
3402        self.update_bottom_bar(rq);
3403    }
3404
3405    fn set_margin_width(
3406        &mut self,
3407        width: i32,
3408        hub: &Hub,
3409        rq: &mut RenderQueue,
3410        context: &mut Context,
3411    ) {
3412        if Arc::strong_count(&self.doc) > 1 {
3413            return;
3414        }
3415
3416        if let Some(ref mut r) = self.info.reader {
3417            if self.reflowable {
3418                r.margin_width = Some(width);
3419            } else {
3420                if width == 0 {
3421                    r.screen_margin_width = None;
3422                } else {
3423                    r.screen_margin_width = Some(width);
3424                }
3425            }
3426        }
3427
3428        if self.reflowable {
3429            let mut doc = self.doc.lock().unwrap();
3430            doc.set_margin_width(width);
3431
3432            if self.synthetic {
3433                let current_page = self.current_page.min(doc.pages_count() - 1);
3434                if let Some(location) = doc.resolve_location(Location::Exact(current_page)) {
3435                    self.current_page = location;
3436                }
3437            } else {
3438                self.pages_count = doc.pages_count();
3439                self.current_page = self.current_page.min(self.pages_count - 1);
3440            }
3441        } else {
3442            let next_margin_width = mm_to_px(width as f32, CURRENT_DEVICE.dpi) as i32;
3443            if self.view_port.zoom_mode == ZoomMode::FitToWidth {
3444                // Apply the scale change.
3445                let ratio = (self.rect.width() as i32 - 2 * next_margin_width) as f32
3446                    / (self.rect.width() as i32 - 2 * self.view_port.margin_width) as f32;
3447                self.view_port.page_offset.y = (self.view_port.page_offset.y as f32 * ratio) as i32;
3448            } else {
3449                // Keep the center still.
3450                self.view_port.page_offset += pt!(next_margin_width - self.view_port.margin_width);
3451            }
3452            self.view_port.margin_width = next_margin_width;
3453        }
3454
3455        self.text.clear();
3456        self.cache.clear();
3457        self.update(None, hub, rq, context);
3458        self.update_tool_bar(rq, context);
3459        self.update_bottom_bar(rq);
3460    }
3461
3462    fn toggle_bookmark(&mut self, rq: &mut RenderQueue) {
3463        if let Some(ref mut r) = self.info.reader {
3464            if !r.bookmarks.insert(self.current_page) {
3465                r.bookmarks.remove(&self.current_page);
3466            }
3467        }
3468        let dpi = CURRENT_DEVICE.dpi;
3469        let thickness = scale_by_dpi(3.0, dpi) as u16;
3470        let radius = mm_to_px(0.4, dpi) as i32 + thickness as i32;
3471        let center = pt!(self.rect.max.x - 5 * radius, self.rect.min.y + 5 * radius);
3472        let rect = Rectangle::from_disk(center, radius);
3473        rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
3474    }
3475
3476    fn set_contrast_exponent(
3477        &mut self,
3478        exponent: f32,
3479        hub: &Hub,
3480        rq: &mut RenderQueue,
3481        context: &mut Context,
3482    ) {
3483        if let Some(ref mut r) = self.info.reader {
3484            r.contrast_exponent = Some(exponent);
3485        }
3486        self.contrast.exponent = exponent;
3487        self.update(None, hub, rq, context);
3488        self.update_tool_bar(rq, context);
3489    }
3490
3491    fn set_contrast_gray(
3492        &mut self,
3493        gray: f32,
3494        hub: &Hub,
3495        rq: &mut RenderQueue,
3496        context: &mut Context,
3497    ) {
3498        if let Some(ref mut r) = self.info.reader {
3499            r.contrast_gray = Some(gray);
3500        }
3501        self.contrast.gray = gray;
3502        self.update(None, hub, rq, context);
3503        self.update_tool_bar(rq, context);
3504    }
3505
3506    fn set_zoom_mode(
3507        &mut self,
3508        zoom_mode: ZoomMode,
3509        reset_page_offset: bool,
3510        hub: &Hub,
3511        rq: &mut RenderQueue,
3512        context: &Context,
3513    ) {
3514        if self.view_port.zoom_mode == zoom_mode {
3515            return;
3516        }
3517
3518        if let Some(index) = locate_by_id(self, ViewId::TitleMenu) {
3519            self.child_mut(index)
3520                .child_mut(1)
3521                .downcast_mut::<MenuEntry>()
3522                .unwrap()
3523                .set_disabled(zoom_mode != ZoomMode::FitToWidth, rq);
3524        }
3525
3526        self.view_port.zoom_mode = zoom_mode;
3527        if reset_page_offset {
3528            self.view_port.page_offset = pt!(0, 0);
3529        }
3530        self.cache.clear();
3531        self.update(None, hub, rq, context);
3532    }
3533
3534    fn set_scroll_mode(
3535        &mut self,
3536        scroll_mode: ScrollMode,
3537        hub: &Hub,
3538        rq: &mut RenderQueue,
3539        context: &Context,
3540    ) {
3541        if self.view_port.scroll_mode == scroll_mode
3542            || self.view_port.zoom_mode != ZoomMode::FitToWidth
3543        {
3544            return;
3545        }
3546        self.view_port.scroll_mode = scroll_mode;
3547        self.view_port.page_offset = pt!(0, 0);
3548        self.update(None, hub, rq, context);
3549    }
3550
3551    fn crop_margins(
3552        &mut self,
3553        index: usize,
3554        margin: &Margin,
3555        hub: &Hub,
3556        rq: &mut RenderQueue,
3557        context: &Context,
3558    ) {
3559        if self.view_port.zoom_mode != ZoomMode::FitToPage {
3560            let Resource { pixmap, frame, .. } = self.cache.get(&index).unwrap();
3561            let offset = frame.min + self.view_port.page_offset;
3562            let x_ratio = offset.x as f32 / pixmap.width as f32;
3563            let y_ratio = offset.y as f32 / pixmap.height as f32;
3564            let dims = {
3565                let doc = self.doc.lock().unwrap();
3566                doc.dims(index).unwrap()
3567            };
3568            let scale = scaling_factor(
3569                &self.rect,
3570                margin,
3571                self.view_port.margin_width,
3572                dims,
3573                self.view_port.zoom_mode,
3574            );
3575            if x_ratio >= margin.left && x_ratio <= (1.0 - margin.right) {
3576                self.view_port.page_offset.x = (scale * (x_ratio - margin.left) * dims.0) as i32;
3577            } else {
3578                self.view_port.page_offset.x = 0;
3579            }
3580            if y_ratio >= margin.top && y_ratio <= (1.0 - margin.bottom) {
3581                self.view_port.page_offset.y = (scale * (y_ratio - margin.top) * dims.1) as i32;
3582            } else {
3583                self.view_port.page_offset.y = 0;
3584            }
3585        }
3586        if let Some(r) = self.info.reader.as_mut() {
3587            if r.cropping_margins.is_none() {
3588                r.cropping_margins = Some(CroppingMargins::Any(Margin::default()));
3589            }
3590            for c in r.cropping_margins.iter_mut() {
3591                *c.margin_mut(index) = margin.clone();
3592            }
3593        }
3594        self.cache.clear();
3595        self.update(None, hub, rq, context);
3596    }
3597
3598    fn toc(&self) -> Option<Vec<TocEntry>> {
3599        let mut index = 0;
3600        self.info
3601            .toc
3602            .as_ref()
3603            .map(|simple_toc| self.toc_aux(simple_toc, &mut index))
3604    }
3605
3606    fn toc_aux(&self, simple_toc: &[SimpleTocEntry], index: &mut usize) -> Vec<TocEntry> {
3607        let mut toc = Vec::new();
3608        for entry in simple_toc {
3609            *index += 1;
3610            match entry {
3611                SimpleTocEntry::Leaf(title, location)
3612                | SimpleTocEntry::Container(title, location, _) => {
3613                    let current_title = title.clone();
3614                    let current_location = match location {
3615                        TocLocation::Uri(uri) if uri.starts_with('\'') => self
3616                            .find_page_by_name(&uri[1..])
3617                            .map(Location::Exact)
3618                            .unwrap_or_else(|| location.clone().into()),
3619                        _ => location.clone().into(),
3620                    };
3621                    let current_index = *index;
3622                    let current_children = if let SimpleTocEntry::Container(_, _, children) = entry
3623                    {
3624                        self.toc_aux(children, index)
3625                    } else {
3626                        Vec::new()
3627                    };
3628                    toc.push(TocEntry {
3629                        title: current_title,
3630                        location: current_location,
3631                        index: current_index,
3632                        children: current_children,
3633                    });
3634                }
3635            }
3636        }
3637        toc
3638    }
3639
3640    fn find_page_by_name(&self, name: &str) -> Option<usize> {
3641        self.info.reader.as_ref().and_then(|r| {
3642            if let Ok(a) = name.parse::<u32>() {
3643                r.page_names
3644                    .iter()
3645                    .filter_map(|(i, s)| s.parse::<u32>().ok().map(|b| (b, i)))
3646                    .filter(|(b, _)| *b <= a)
3647                    .max_by(|x, y| x.0.cmp(&y.0))
3648                    .map(|(b, i)| *i + (a - b) as usize)
3649            } else if let Some(a) = name.chars().next().and_then(|c| c.to_alphabetic_digit()) {
3650                r.page_names
3651                    .iter()
3652                    .filter_map(|(i, s)| {
3653                        s.chars()
3654                            .next()
3655                            .and_then(|c| c.to_alphabetic_digit())
3656                            .map(|c| (c, i))
3657                    })
3658                    .filter(|(b, _)| *b <= a)
3659                    .max_by(|x, y| x.0.cmp(&y.0))
3660                    .map(|(b, i)| *i + (a - b) as usize)
3661            } else if let Ok(a) = Roman::from_str(name) {
3662                r.page_names
3663                    .iter()
3664                    .filter_map(|(i, s)| Roman::from_str(s).ok().map(|b| (*b, i)))
3665                    .filter(|(b, _)| *b <= *a)
3666                    .max_by(|x, y| x.0.cmp(&y.0))
3667                    .map(|(b, i)| *i + (*a - b) as usize)
3668            } else {
3669                None
3670            }
3671        })
3672    }
3673
3674    fn text_excerpt(&self, sel: [TextLocation; 2]) -> Option<String> {
3675        let [start, end] = sel;
3676        let parts = self
3677            .text
3678            .values()
3679            .flatten()
3680            .filter(|bnd| bnd.location >= start && bnd.location <= end)
3681            .map(|bnd| bnd.text.as_str())
3682            .collect::<Vec<&str>>();
3683
3684        if parts.is_empty() {
3685            return None;
3686        }
3687
3688        let ws = word_separator(&self.info.language);
3689        let mut text = parts[0].to_string();
3690
3691        for p in &parts[1..] {
3692            if text.ends_with('\u{00AD}') {
3693                text.pop();
3694            } else if !text.ends_with('-') {
3695                text.push_str(ws);
3696            }
3697            text += p;
3698        }
3699
3700        Some(text)
3701    }
3702
3703    fn selected_text(&self) -> Option<String> {
3704        self.selection
3705            .as_ref()
3706            .and_then(|sel| self.text_excerpt([sel.start, sel.end]))
3707    }
3708
3709    fn text_rect(&self, sel: [TextLocation; 2]) -> Option<Rectangle> {
3710        let [start, end] = sel;
3711        let mut result: Option<Rectangle> = None;
3712
3713        for chunk in &self.chunks {
3714            if let Some(words) = self.text.get(&chunk.location) {
3715                for word in words {
3716                    if word.location >= start && word.location <= end {
3717                        let rect =
3718                            (word.rect * chunk.scale).to_rect() - chunk.frame.min + chunk.position;
3719                        if let Some(ref mut r) = result {
3720                            r.absorb(&rect);
3721                        } else {
3722                            result = Some(rect);
3723                        }
3724                    }
3725                }
3726            }
3727        }
3728
3729        result
3730    }
3731
3732    fn render_results(&self, rq: &mut RenderQueue) {
3733        for chunk in &self.chunks {
3734            if let Some(groups) = self
3735                .search
3736                .as_ref()
3737                .and_then(|s| s.highlights.get(&chunk.location))
3738            {
3739                for rects in groups {
3740                    let mut rect_opt: Option<Rectangle> = None;
3741                    for rect in rects {
3742                        let rect =
3743                            (*rect * chunk.scale).to_rect() - chunk.frame.min + chunk.position;
3744                        if let Some(ref mut r) = rect_opt {
3745                            r.absorb(&rect);
3746                        } else {
3747                            rect_opt = Some(rect);
3748                        }
3749                    }
3750                    if let Some(rect) = rect_opt {
3751                        rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
3752                    }
3753                }
3754            }
3755        }
3756    }
3757
3758    fn selection_rect(&self) -> Option<Rectangle> {
3759        self.selection
3760            .as_ref()
3761            .and_then(|sel| self.text_rect([sel.start, sel.end]))
3762    }
3763
3764    fn find_annotation_ref(&mut self, sel: [TextLocation; 2]) -> Option<&Annotation> {
3765        self.info.reader.as_ref().and_then(|r| {
3766            r.annotations
3767                .iter()
3768                .find(|a| a.selection[0] == sel[0] && a.selection[1] == sel[1])
3769        })
3770    }
3771
3772    fn find_annotation_mut(&mut self, sel: [TextLocation; 2]) -> Option<&mut Annotation> {
3773        self.info.reader.as_mut().and_then(|r| {
3774            r.annotations
3775                .iter_mut()
3776                .find(|a| a.selection[0] == sel[0] && a.selection[1] == sel[1])
3777        })
3778    }
3779
3780    fn reseed(&mut self, rq: &mut RenderQueue, context: &mut Context) {
3781        if let Some(index) = locate::<TopBar>(self) {
3782            if let Some(top_bar) = self.child_mut(index).downcast_mut::<TopBar>() {
3783                top_bar.reseed(rq, context);
3784            }
3785        }
3786
3787        rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
3788    }
3789
3790    fn quit(&mut self, context: &mut Context) {
3791        if let Some(ref mut s) = self.search {
3792            s.running.store(false, AtomicOrdering::Relaxed);
3793        }
3794
3795        if self.ephemeral {
3796            return;
3797        }
3798
3799        if let Some(ref mut r) = self.info.reader {
3800            r.current_page = self.current_page;
3801            r.pages_count = self.pages_count;
3802            r.finished = self.finished;
3803            r.dithered = context.fb.dithered();
3804
3805            if self.view_port.zoom_mode == ZoomMode::FitToPage {
3806                r.zoom_mode = None;
3807                r.page_offset = None;
3808            } else {
3809                r.zoom_mode = Some(self.view_port.zoom_mode);
3810                r.page_offset = Some(self.view_port.page_offset);
3811            }
3812
3813            if self.view_port.zoom_mode == ZoomMode::FitToWidth {
3814                r.scroll_mode = Some(self.view_port.scroll_mode);
3815            } else {
3816                r.scroll_mode = None;
3817            }
3818
3819            r.rotation = Some(CURRENT_DEVICE.to_canonical(context.display.rotation));
3820
3821            if (self.contrast.exponent - DEFAULT_CONTRAST_EXPONENT).abs() > f32::EPSILON {
3822                r.contrast_exponent = Some(self.contrast.exponent);
3823                if (self.contrast.gray - DEFAULT_CONTRAST_GRAY).abs() > f32::EPSILON {
3824                    r.contrast_gray = Some(self.contrast.gray);
3825                } else {
3826                    r.contrast_gray = None;
3827                }
3828            } else {
3829                r.contrast_exponent = None;
3830                r.contrast_gray = None;
3831            }
3832
3833            context.library.sync_reader_info(&self.info.file.path, r);
3834        }
3835    }
3836
3837    fn scale_page(
3838        &mut self,
3839        center: Point,
3840        factor: f32,
3841        hub: &Hub,
3842        rq: &mut RenderQueue,
3843        context: &mut Context,
3844    ) {
3845        if self.cache.is_empty() {
3846            return;
3847        }
3848
3849        let current_factor = if let ZoomMode::Custom(sf) = self.view_port.zoom_mode {
3850            sf
3851        } else {
3852            self.cache[&self.current_page].scale
3853        };
3854
3855        if let Some(chunk) = self.chunks.iter().find(|chunk| {
3856            let chunk_rect = chunk.frame - chunk.frame.min + chunk.position;
3857            chunk_rect.includes(center)
3858        }) {
3859            let smw = self.view_port.margin_width;
3860            let frame = self.cache[&chunk.location].frame;
3861            self.current_page = chunk.location;
3862            self.view_port.page_offset = Point::from(
3863                factor * Vec2::from(center - chunk.position + chunk.frame.min - frame.min),
3864            ) - pt!(
3865                self.rect.width() as i32 / 2 - smw,
3866                self.rect.height() as i32 / 2 - smw
3867            );
3868
3869            self.set_zoom_mode(
3870                ZoomMode::Custom(current_factor * factor),
3871                false,
3872                hub,
3873                rq,
3874                context,
3875            );
3876        }
3877    }
3878}
3879
3880impl View for Reader {
3881    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, hub, _bus, rq, context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
3882    fn handle_event(
3883        &mut self,
3884        evt: &Event,
3885        hub: &Hub,
3886        _bus: &mut Bus,
3887        rq: &mut RenderQueue,
3888        context: &mut Context,
3889    ) -> bool {
3890        match *evt {
3891            Event::Gesture(GestureEvent::Rotate { quarter_turns, .. }) if quarter_turns != 0 => {
3892                let (_, dir) = CURRENT_DEVICE.mirroring_scheme();
3893                let n = (4 + (context.display.rotation - dir * quarter_turns)) % 4;
3894                hub.send(Event::Select(EntryId::Rotate(n))).ok();
3895                true
3896            }
3897            Event::Gesture(GestureEvent::Swipe { dir, start, end })
3898                if self.rect.includes(start) =>
3899            {
3900                match self.view_port.zoom_mode {
3901                    ZoomMode::FitToPage | ZoomMode::FitToWidth => {
3902                        match dir {
3903                            Dir::West => self.go_to_neighbor(CycleDir::Next, hub, rq, context),
3904                            Dir::East => self.go_to_neighbor(CycleDir::Previous, hub, rq, context),
3905                            Dir::South | Dir::North => {
3906                                self.vertical_scroll(start.y - end.y, hub, rq, context)
3907                            }
3908                        };
3909                    }
3910                    ZoomMode::Custom(_) => {
3911                        match dir {
3912                            Dir::West | Dir::East => {
3913                                self.directional_scroll(pt!(start.x - end.x, 0), hub, rq, context)
3914                            }
3915                            Dir::South | Dir::North => {
3916                                self.directional_scroll(pt!(0, start.y - end.y), hub, rq, context)
3917                            }
3918                        };
3919                    }
3920                }
3921                true
3922            }
3923            Event::Gesture(GestureEvent::SlantedSwipe { start, end, .. })
3924                if self.rect.includes(start) =>
3925            {
3926                if let ZoomMode::Custom(_) = self.view_port.zoom_mode {
3927                    self.directional_scroll(start - end, hub, rq, context);
3928                }
3929                true
3930            }
3931            Event::Gesture(GestureEvent::Spread {
3932                axis: Axis::Horizontal,
3933                center,
3934                ..
3935            }) if self.rect.includes(center) => {
3936                if !self.reflowable {
3937                    self.set_zoom_mode(ZoomMode::FitToWidth, true, hub, rq, context);
3938                }
3939                true
3940            }
3941            Event::Gesture(GestureEvent::Pinch {
3942                axis: Axis::Horizontal,
3943                center,
3944                ..
3945            }) if self.rect.includes(center) => {
3946                self.set_zoom_mode(ZoomMode::FitToPage, true, hub, rq, context);
3947                true
3948            }
3949            Event::Gesture(GestureEvent::Spread {
3950                axis: Axis::Vertical,
3951                center,
3952                ..
3953            }) if self.rect.includes(center) => {
3954                if !self.reflowable {
3955                    self.set_scroll_mode(ScrollMode::Screen, hub, rq, context);
3956                }
3957                true
3958            }
3959            Event::Gesture(GestureEvent::Pinch {
3960                axis: Axis::Vertical,
3961                center,
3962                ..
3963            }) if self.rect.includes(center) => {
3964                if !self.reflowable {
3965                    self.set_scroll_mode(ScrollMode::Page, hub, rq, context);
3966                }
3967                true
3968            }
3969            Event::Gesture(GestureEvent::Spread {
3970                axis: Axis::Diagonal,
3971                center,
3972                factor,
3973            })
3974            | Event::Gesture(GestureEvent::Pinch {
3975                axis: Axis::Diagonal,
3976                center,
3977                factor,
3978            }) if factor.is_finite() && self.rect.includes(center) => {
3979                self.scale_page(center, factor, hub, rq, context);
3980                true
3981            }
3982            Event::Gesture(GestureEvent::Arrow { dir, .. }) => {
3983                match dir {
3984                    Dir::West => {
3985                        if self.search.is_none() {
3986                            self.go_to_chapter(CycleDir::Previous, hub, rq, context);
3987                        } else {
3988                            self.go_to_results_page(0, hub, rq, context);
3989                        }
3990                    }
3991                    Dir::East => {
3992                        if self.search.is_none() {
3993                            self.go_to_chapter(CycleDir::Next, hub, rq, context);
3994                        } else {
3995                            let last_page = self.search.as_ref().unwrap().highlights.len() - 1;
3996                            self.go_to_results_page(last_page, hub, rq, context);
3997                        }
3998                    }
3999                    Dir::North => {
4000                        self.search_direction = LinearDir::Backward;
4001                        self.toggle_search_bar(true, hub, rq, context);
4002                    }
4003                    Dir::South => {
4004                        self.search_direction = LinearDir::Forward;
4005                        self.toggle_search_bar(true, hub, rq, context);
4006                    }
4007                }
4008                true
4009            }
4010            Event::Gesture(GestureEvent::Corner { dir, .. }) => {
4011                match dir {
4012                    DiagDir::NorthWest => self.go_to_bookmark(CycleDir::Previous, hub, rq, context),
4013                    DiagDir::NorthEast => self.go_to_bookmark(CycleDir::Next, hub, rq, context),
4014                    DiagDir::SouthEast => match context.settings.reader.bottom_right_gesture {
4015                        BottomRightGestureAction::ToggleDithered => {
4016                            hub.send(Event::Select(EntryId::ToggleDithered)).ok();
4017                        }
4018                        BottomRightGestureAction::ToggleInverted => {
4019                            hub.send(Event::Select(EntryId::ToggleInverted)).ok();
4020                        }
4021                    },
4022                    DiagDir::SouthWest => {
4023                        if context.settings.frontlight_presets.len() > 1 {
4024                            if context.settings.frontlight {
4025                                let lightsensor_level = if CURRENT_DEVICE.has_lightsensor() {
4026                                    context.lightsensor.level().ok()
4027                                } else {
4028                                    None
4029                                };
4030                                if let Some(ref frontlight_levels) = guess_frontlight(
4031                                    lightsensor_level,
4032                                    &context.settings.frontlight_presets,
4033                                ) {
4034                                    let LightLevels { intensity, warmth } = *frontlight_levels;
4035                                    context.frontlight.set_intensity(intensity);
4036                                    context.frontlight.set_warmth(warmth);
4037                                }
4038                            }
4039                        } else {
4040                            hub.send(Event::ToggleFrontlight).ok();
4041                        }
4042                    }
4043                };
4044                true
4045            }
4046            Event::Gesture(GestureEvent::MultiCorner { dir, .. }) => {
4047                match dir {
4048                    DiagDir::NorthWest => {
4049                        self.go_to_annotation(CycleDir::Previous, hub, rq, context)
4050                    }
4051                    DiagDir::NorthEast => self.go_to_annotation(CycleDir::Next, hub, rq, context),
4052                    _ => (),
4053                }
4054                true
4055            }
4056            Event::Gesture(GestureEvent::Cross(_)) => {
4057                self.quit(context);
4058                hub.send(Event::Back).ok();
4059                true
4060            }
4061            Event::Gesture(GestureEvent::Diamond(_)) => {
4062                self.toggle_bars(None, hub, rq, context);
4063                true
4064            }
4065            Event::Gesture(GestureEvent::HoldButtonShort(code, ..)) => {
4066                match code {
4067                    ButtonCode::Backward => {
4068                        self.go_to_chapter(CycleDir::Previous, hub, rq, context)
4069                    }
4070                    ButtonCode::Forward => self.go_to_chapter(CycleDir::Next, hub, rq, context),
4071                    _ => (),
4072                }
4073                self.held_buttons.insert(code);
4074                true
4075            }
4076            Event::Device(DeviceEvent::Button {
4077                code,
4078                status: ButtonStatus::Released,
4079                ..
4080            }) => {
4081                if !self.held_buttons.remove(&code) {
4082                    match code {
4083                        ButtonCode::Backward => {
4084                            if self.search.is_none() {
4085                                self.go_to_neighbor(CycleDir::Previous, hub, rq, context);
4086                            } else {
4087                                self.go_to_results_neighbor(CycleDir::Previous, hub, rq, context);
4088                            }
4089                        }
4090                        ButtonCode::Forward => {
4091                            if self.search.is_none() {
4092                                self.go_to_neighbor(CycleDir::Next, hub, rq, context);
4093                            } else {
4094                                self.go_to_results_neighbor(CycleDir::Next, hub, rq, context);
4095                            }
4096                        }
4097                        _ => (),
4098                    }
4099                }
4100                true
4101            }
4102            Event::Device(DeviceEvent::Finger {
4103                position,
4104                status: FingerStatus::Motion,
4105                id,
4106                ..
4107            }) if self.state == State::Selection(id) => {
4108                let mut nearest_word = None;
4109                let mut dmin = u32::MAX;
4110                let dmax =
4111                    (scale_by_dpi(RECT_DIST_JITTER, CURRENT_DEVICE.dpi) as i32).pow(2) as u32;
4112                let mut rects = Vec::new();
4113
4114                for chunk in &self.chunks {
4115                    for word in &self.text[&chunk.location] {
4116                        let rect =
4117                            (word.rect * chunk.scale).to_rect() - chunk.frame.min + chunk.position;
4118                        rects.push((rect, word.location));
4119                        let d = position.rdist2(&rect);
4120                        if d < dmax && d < dmin {
4121                            dmin = d;
4122                            nearest_word = Some(word.clone());
4123                        }
4124                    }
4125                }
4126
4127                let selection = self.selection.as_mut().unwrap();
4128
4129                if let Some(word) = nearest_word {
4130                    let old_start = selection.start;
4131                    let old_end = selection.end;
4132                    let (start, end) = word.location.min_max(selection.anchor);
4133
4134                    if start == old_start && end == old_end {
4135                        return true;
4136                    }
4137
4138                    let (start_low, start_high) = old_start.min_max(start);
4139                    let (end_low, end_high) = old_end.min_max(end);
4140
4141                    if start_low != start_high {
4142                        if let Some(mut i) = rects.iter().position(|(_, loc)| *loc == start_low) {
4143                            let mut rect = rects[i].0;
4144                            while rects[i].1 < start_high {
4145                                let next_rect = rects[i + 1].0;
4146                                if rect.max.y.min(next_rect.max.y) - rect.min.y.max(next_rect.min.y)
4147                                    > rect.height().min(next_rect.height()) as i32 / 2
4148                                {
4149                                    if rects[i + 1].1 == start_high {
4150                                        if rect.min.x < next_rect.min.x {
4151                                            rect.max.x = next_rect.min.x;
4152                                        } else {
4153                                            rect.min.x = next_rect.max.x;
4154                                        }
4155                                        rect.min.y = rect.min.y.min(next_rect.min.y);
4156                                        rect.max.y = rect.max.y.max(next_rect.max.y);
4157                                    } else {
4158                                        rect.absorb(&next_rect);
4159                                    }
4160                                } else {
4161                                    rq.add(RenderData::new(self.id, rect, UpdateMode::Fast));
4162                                    rect = next_rect;
4163                                }
4164                                i += 1;
4165                            }
4166                            rq.add(RenderData::new(self.id, rect, UpdateMode::Fast));
4167                        }
4168                    }
4169
4170                    if end_low != end_high {
4171                        if let Some(mut i) = rects.iter().rposition(|(_, loc)| *loc == end_high) {
4172                            let mut rect = rects[i].0;
4173                            while rects[i].1 > end_low {
4174                                let prev_rect = rects[i - 1].0;
4175                                if rect.max.y.min(prev_rect.max.y) - rect.min.y.max(prev_rect.min.y)
4176                                    > rect.height().min(prev_rect.height()) as i32 / 2
4177                                {
4178                                    if rects[i - 1].1 == end_low {
4179                                        if rect.min.x > prev_rect.min.x {
4180                                            rect.min.x = prev_rect.max.x;
4181                                        } else {
4182                                            rect.max.x = prev_rect.min.x;
4183                                        }
4184                                        rect.min.y = rect.min.y.min(prev_rect.min.y);
4185                                        rect.max.y = rect.max.y.max(prev_rect.max.y);
4186                                    } else {
4187                                        rect.absorb(&prev_rect);
4188                                    }
4189                                } else {
4190                                    rq.add(RenderData::new(self.id, rect, UpdateMode::Fast));
4191                                    rect = prev_rect;
4192                                }
4193                                i -= 1;
4194                            }
4195                            rq.add(RenderData::new(self.id, rect, UpdateMode::Fast));
4196                        }
4197                    }
4198
4199                    selection.start = start;
4200                    selection.end = end;
4201                }
4202                true
4203            }
4204            Event::Device(DeviceEvent::Finger {
4205                status: FingerStatus::Up,
4206                position,
4207                id,
4208                ..
4209            }) if self.state == State::Selection(id) => {
4210                self.state = State::Idle;
4211                let radius = scale_by_dpi(24.0, CURRENT_DEVICE.dpi) as i32;
4212                self.toggle_selection_menu(
4213                    Rectangle::from_disk(position, radius),
4214                    Some(true),
4215                    rq,
4216                    context,
4217                );
4218                true
4219            }
4220            Event::Gesture(GestureEvent::Tap(center))
4221                if self.state == State::AdjustSelection && self.rect.includes(center) =>
4222            {
4223                let mut found = None;
4224                let mut dmin = u32::MAX;
4225                let dmax =
4226                    (scale_by_dpi(RECT_DIST_JITTER, CURRENT_DEVICE.dpi) as i32).pow(2) as u32;
4227                let mut rects = Vec::new();
4228
4229                for chunk in &self.chunks {
4230                    for word in &self.text[&chunk.location] {
4231                        let rect =
4232                            (word.rect * chunk.scale).to_rect() - chunk.frame.min + chunk.position;
4233                        rects.push((rect, word.location));
4234                        let d = center.rdist2(&rect);
4235                        if d < dmax && d < dmin {
4236                            dmin = d;
4237                            found = Some((word.clone(), rects.len() - 1));
4238                        }
4239                    }
4240                }
4241
4242                let selection = self.selection.as_mut().unwrap();
4243
4244                if let Some((word, index)) = found {
4245                    let old_start = selection.start;
4246                    let old_end = selection.end;
4247
4248                    let (start, end) = if word.location <= old_start {
4249                        (word.location, old_end)
4250                    } else if word.location >= old_end {
4251                        (old_start, word.location)
4252                    } else {
4253                        let (start_index, end_index) = (
4254                            rects.iter().position(|(_, loc)| *loc == old_start),
4255                            rects.iter().position(|(_, loc)| *loc == old_end),
4256                        );
4257                        match (start_index, end_index) {
4258                            (Some(s), Some(e)) => {
4259                                if index - s > e - index {
4260                                    (old_start, word.location)
4261                                } else {
4262                                    (word.location, old_end)
4263                                }
4264                            }
4265                            (Some(..), None) => (word.location, old_end),
4266                            (None, Some(..)) => (old_start, word.location),
4267                            (None, None) => (old_start, old_end),
4268                        }
4269                    };
4270
4271                    if start == old_start && end == old_end {
4272                        return true;
4273                    }
4274
4275                    let (start_low, start_high) = old_start.min_max(start);
4276                    let (end_low, end_high) = old_end.min_max(end);
4277
4278                    if start_low != start_high {
4279                        if let Some(mut i) = rects.iter().position(|(_, loc)| *loc == start_low) {
4280                            let mut rect = rects[i].0;
4281                            while i < rects.len() - 1 && rects[i].1 < start_high {
4282                                let next_rect = rects[i + 1].0;
4283                                if rect.min.y < next_rect.max.y && next_rect.min.y < rect.max.y {
4284                                    if rects[i + 1].1 == start_high {
4285                                        if rect.min.x < next_rect.min.x {
4286                                            rect.max.x = next_rect.min.x;
4287                                        } else {
4288                                            rect.min.x = next_rect.max.x;
4289                                        }
4290                                        rect.min.y = rect.min.y.min(next_rect.min.y);
4291                                        rect.max.y = rect.max.y.max(next_rect.max.y);
4292                                    } else {
4293                                        rect.absorb(&next_rect);
4294                                    }
4295                                } else {
4296                                    rq.add(RenderData::new(self.id, rect, UpdateMode::Fast));
4297                                    rect = next_rect;
4298                                }
4299                                i += 1;
4300                            }
4301                            rq.add(RenderData::new(self.id, rect, UpdateMode::Fast));
4302                        }
4303                    }
4304
4305                    if end_low != end_high {
4306                        if let Some(mut i) = rects.iter().rposition(|(_, loc)| *loc == end_high) {
4307                            let mut rect = rects[i].0;
4308                            while i > 0 && rects[i].1 > end_low {
4309                                let prev_rect = rects[i - 1].0;
4310                                if rect.min.y < prev_rect.max.y && prev_rect.min.y < rect.max.y {
4311                                    if rects[i - 1].1 == end_low {
4312                                        if rect.min.x > prev_rect.min.x {
4313                                            rect.min.x = prev_rect.max.x;
4314                                        } else {
4315                                            rect.max.x = prev_rect.min.x;
4316                                        }
4317                                        rect.min.y = rect.min.y.min(prev_rect.min.y);
4318                                        rect.max.y = rect.max.y.max(prev_rect.max.y);
4319                                    } else {
4320                                        rect.absorb(&prev_rect);
4321                                    }
4322                                } else {
4323                                    rq.add(RenderData::new(self.id, rect, UpdateMode::Fast));
4324                                    rect = prev_rect;
4325                                }
4326                                i -= 1;
4327                            }
4328                            rq.add(RenderData::new(self.id, rect, UpdateMode::Fast));
4329                        }
4330                    }
4331
4332                    selection.start = start;
4333                    selection.end = end;
4334                }
4335                true
4336            }
4337            Event::Gesture(GestureEvent::Tap(center)) if self.rect.includes(center) => {
4338                if self.focus.is_some() {
4339                    return true;
4340                }
4341
4342                let mut nearest_link = None;
4343                let mut dmin = u32::MAX;
4344                let dmax =
4345                    (scale_by_dpi(RECT_DIST_JITTER, CURRENT_DEVICE.dpi) as i32).pow(2) as u32;
4346
4347                for chunk in &self.chunks {
4348                    let (links, _) = self
4349                        .doc
4350                        .lock()
4351                        .ok()
4352                        .and_then(|mut doc| doc.links(Location::Exact(chunk.location)))
4353                        .unwrap_or((Vec::new(), 0));
4354                    for link in links {
4355                        let rect =
4356                            (link.rect * chunk.scale).to_rect() - chunk.frame.min + chunk.position;
4357                        let d = center.rdist2(&rect);
4358                        if d < dmax && d < dmin {
4359                            dmin = d;
4360                            nearest_link = Some(link.clone());
4361                        }
4362                    }
4363                }
4364
4365                if let Some(link) = nearest_link.take() {
4366                    let pdf_page = Regex::new(r"^#page=(\d+).*$").unwrap();
4367                    let djvu_page = Regex::new(r"^#([+-])?(\d+)$").unwrap();
4368                    let toc_page = Regex::new(r"^@(.+)$").unwrap();
4369                    if let Some(caps) = toc_page.captures(&link.text) {
4370                        let loc_opt = if caps[1].chars().all(|c| c.is_digit(10)) {
4371                            caps[1].parse::<usize>().map(Location::Exact).ok()
4372                        } else {
4373                            Some(Location::Uri(caps[1].to_string()))
4374                        };
4375                        if let Some(location) = loc_opt {
4376                            self.quit(context);
4377                            hub.send(Event::Back).ok();
4378                            hub.send(Event::GoToLocation(location)).ok();
4379                        }
4380                    } else if let Some(caps) = pdf_page.captures(&link.text) {
4381                        if let Ok(index) = caps[1].parse::<usize>() {
4382                            self.go_to_page(index.saturating_sub(1), true, hub, rq, context);
4383                        }
4384                    } else if let Some(caps) = djvu_page.captures(&link.text) {
4385                        if let Ok(mut index) = caps[2].parse::<usize>() {
4386                            let prefix = caps.get(1).map(|m| m.as_str());
4387                            match prefix {
4388                                Some("-") => index = self.current_page.saturating_sub(index),
4389                                Some("+") => index += self.current_page,
4390                                _ => index = index.saturating_sub(1),
4391                            }
4392                            self.go_to_page(index, true, hub, rq, context);
4393                        }
4394                    } else {
4395                        let mut doc = self.doc.lock().unwrap();
4396                        let loc = Location::LocalUri(self.current_page, link.text.clone());
4397                        if let Some(location) = doc.resolve_location(loc) {
4398                            hub.send(Event::GoTo(location)).ok();
4399                        } else if link.text.starts_with("https:") || link.text.starts_with("http:")
4400                        {
4401                            if let Some(path) = context.settings.external_urls_queue.as_ref() {
4402                                if let Ok(mut file) =
4403                                    OpenOptions::new().create(true).append(true).open(path)
4404                                {
4405                                    if let Err(e) = writeln!(file, "{}", link.text) {
4406                                        error!("Couldn't write to {}: {:#}.", path.display(), e);
4407                                    } else {
4408                                        let message = format!("Queued {}.", link.text);
4409                                        let notif = Notification::new(
4410                                            None, message, false, hub, rq, context,
4411                                        );
4412                                        self.children.push(Box::new(notif) as Box<dyn View>);
4413                                    }
4414                                }
4415                            }
4416                        } else {
4417                            error!("Can't resolve URI: {}.", link.text);
4418                        }
4419                    }
4420                    return true;
4421                }
4422
4423                if let ZoomMode::Custom(_) = self.view_port.zoom_mode {
4424                    let dx = self.rect.width() as i32 - 2 * self.view_port.margin_width;
4425                    let dy = self.rect.height() as i32 - 2 * self.view_port.margin_width;
4426                    match Region::from_point(
4427                        center,
4428                        self.rect,
4429                        context.settings.reader.strip_width,
4430                        context.settings.reader.corner_width,
4431                    ) {
4432                        Region::Corner(diag_dir) => match diag_dir {
4433                            DiagDir::NorthEast => {
4434                                self.directional_scroll(pt!(dx, -dy), hub, rq, context)
4435                            }
4436                            DiagDir::SouthEast => {
4437                                self.directional_scroll(pt!(dx, dy), hub, rq, context)
4438                            }
4439                            DiagDir::SouthWest => {
4440                                self.directional_scroll(pt!(-dx, dy), hub, rq, context)
4441                            }
4442                            DiagDir::NorthWest => {
4443                                self.directional_scroll(pt!(-dx, -dy), hub, rq, context)
4444                            }
4445                        },
4446                        Region::Strip(dir) => match dir {
4447                            Dir::North => self.directional_scroll(pt!(0, -dy), hub, rq, context),
4448                            Dir::East => self.directional_scroll(pt!(dx, 0), hub, rq, context),
4449                            Dir::South => self.directional_scroll(pt!(0, dy), hub, rq, context),
4450                            Dir::West => self.directional_scroll(pt!(-dx, 0), hub, rq, context),
4451                        },
4452                        Region::Center => self.toggle_bars(None, hub, rq, context),
4453                    }
4454
4455                    return true;
4456                }
4457
4458                match Region::from_point(
4459                    center,
4460                    self.rect,
4461                    context.settings.reader.strip_width,
4462                    context.settings.reader.corner_width,
4463                ) {
4464                    Region::Corner(diag_dir) => match diag_dir {
4465                        DiagDir::NorthWest => self.go_to_last_page(hub, rq, context),
4466                        DiagDir::NorthEast => self.toggle_bookmark(rq),
4467                        DiagDir::SouthEast => {
4468                            if self.search.is_none() {
4469                                match context.settings.reader.south_east_corner {
4470                                    SouthEastCornerAction::GoToPage => {
4471                                        hub.send(Event::Toggle(ToggleEvent::View(
4472                                            ViewId::GoToPage,
4473                                        )))
4474                                        .ok();
4475                                    }
4476                                    SouthEastCornerAction::NextPage => {
4477                                        self.go_to_neighbor(CycleDir::Next, hub, rq, context);
4478                                    }
4479                                }
4480                            } else {
4481                                self.go_to_neighbor(CycleDir::Next, hub, rq, context);
4482                            }
4483                        }
4484                        DiagDir::SouthWest => {
4485                            if self.search.is_none() {
4486                                if self.ephemeral
4487                                    && self.info.file.path == PathBuf::from(MEM_SCHEME)
4488                                {
4489                                    self.quit(context);
4490                                    hub.send(Event::Back).ok();
4491                                } else {
4492                                    hub.send(Event::Show(ViewId::TableOfContents)).ok();
4493                                }
4494                            } else {
4495                                self.go_to_neighbor(CycleDir::Previous, hub, rq, context);
4496                            }
4497                        }
4498                    },
4499                    Region::Strip(dir) => match dir {
4500                        Dir::West => {
4501                            if self.search.is_none() {
4502                                match context.settings.reader.west_strip {
4503                                    WestStripAction::PreviousPage => {
4504                                        self.go_to_neighbor(CycleDir::Previous, hub, rq, context);
4505                                    }
4506                                    WestStripAction::NextPage => {
4507                                        self.go_to_neighbor(CycleDir::Next, hub, rq, context);
4508                                    }
4509                                    WestStripAction::None => (),
4510                                }
4511                            } else {
4512                                self.go_to_results_neighbor(CycleDir::Previous, hub, rq, context);
4513                            }
4514                        }
4515                        Dir::East => {
4516                            if self.search.is_none() {
4517                                match context.settings.reader.east_strip {
4518                                    EastStripAction::PreviousPage => {
4519                                        self.go_to_neighbor(CycleDir::Previous, hub, rq, context);
4520                                    }
4521                                    EastStripAction::NextPage => {
4522                                        self.go_to_neighbor(CycleDir::Next, hub, rq, context);
4523                                    }
4524                                    EastStripAction::None => (),
4525                                }
4526                            } else {
4527                                self.go_to_results_neighbor(CycleDir::Next, hub, rq, context);
4528                            }
4529                        }
4530                        Dir::South => match context.settings.reader.south_strip {
4531                            SouthStripAction::ToggleBars => {
4532                                self.toggle_bars(None, hub, rq, context);
4533                            }
4534                            SouthStripAction::NextPage => {
4535                                self.go_to_neighbor(CycleDir::Next, hub, rq, context);
4536                            }
4537                        },
4538                        Dir::North => self.toggle_bars(None, hub, rq, context),
4539                    },
4540                    Region::Center => self.toggle_bars(None, hub, rq, context),
4541                }
4542
4543                true
4544            }
4545            Event::Gesture(GestureEvent::HoldFingerShort(center, id))
4546                if self.rect.includes(center) =>
4547            {
4548                if self.focus.is_some() {
4549                    return true;
4550                }
4551
4552                let mut found = None;
4553                let mut dmin = u32::MAX;
4554                let dmax =
4555                    (scale_by_dpi(RECT_DIST_JITTER, CURRENT_DEVICE.dpi) as i32).pow(2) as u32;
4556
4557                if let Some(rect) = self.selection_rect() {
4558                    let d = center.rdist2(&rect);
4559                    if d < dmax {
4560                        self.state = State::Idle;
4561                        let radius = scale_by_dpi(24.0, CURRENT_DEVICE.dpi) as i32;
4562                        self.toggle_selection_menu(
4563                            Rectangle::from_disk(center, radius),
4564                            Some(true),
4565                            rq,
4566                            context,
4567                        );
4568                    }
4569                    return true;
4570                }
4571
4572                for chunk in &self.chunks {
4573                    for word in &self.text[&chunk.location] {
4574                        let rect =
4575                            (word.rect * chunk.scale).to_rect() - chunk.frame.min + chunk.position;
4576                        let d = center.rdist2(&rect);
4577                        if d < dmax && d < dmin {
4578                            dmin = d;
4579                            found = Some((word.clone(), rect));
4580                        }
4581                    }
4582                }
4583
4584                if let Some((nearest_word, rect)) = found {
4585                    let anchor = nearest_word.location;
4586                    if let Some(annot) = self
4587                        .annotations
4588                        .values()
4589                        .flatten()
4590                        .find(|annot| anchor >= annot.selection[0] && anchor <= annot.selection[1])
4591                        .cloned()
4592                    {
4593                        let radius = scale_by_dpi(24.0, CURRENT_DEVICE.dpi) as i32;
4594                        self.toggle_annotation_menu(
4595                            &annot,
4596                            Rectangle::from_disk(center, radius),
4597                            Some(true),
4598                            rq,
4599                            context,
4600                        );
4601                    } else {
4602                        self.selection = Some(Selection {
4603                            start: anchor,
4604                            end: anchor,
4605                            anchor,
4606                        });
4607                        self.state = State::Selection(id);
4608                        rq.add(RenderData::new(self.id, rect, UpdateMode::Fast));
4609                    }
4610                }
4611
4612                true
4613            }
4614            Event::Gesture(GestureEvent::HoldFingerLong(center, _))
4615                if self.rect.includes(center) =>
4616            {
4617                if let Some(text) = self.selected_text() {
4618                    let query = text
4619                        .trim_matches(|c: char| !c.is_alphanumeric())
4620                        .to_string();
4621                    let language = self.info.language.clone();
4622                    hub.send(Event::Select(EntryId::Launch(AppCmd::Dictionary {
4623                        query,
4624                        language,
4625                    })))
4626                    .ok();
4627                }
4628                self.selection = None;
4629                self.state = State::Idle;
4630                true
4631            }
4632            Event::Update(mode) => {
4633                self.update(Some(mode), hub, rq, context);
4634                true
4635            }
4636            Event::LoadPixmap(location) => {
4637                self.load_pixmap(location);
4638                true
4639            }
4640            Event::Submit(ViewId::GoToPageInput, ref text) => {
4641                let re = Regex::new(r#"^([-+'])?(.+)$"#).unwrap();
4642                if let Some(caps) = re.captures(text) {
4643                    let prefix = caps.get(1).map(|m| m.as_str());
4644                    if prefix == Some("'") {
4645                        if let Some(location) = self.find_page_by_name(&caps[2]) {
4646                            self.go_to_page(location, true, hub, rq, context);
4647                        }
4648                    } else {
4649                        if text == "_" {
4650                            let location =
4651                                (context.rng.next_u64() % self.pages_count as u64) as usize;
4652                            self.go_to_page(location, true, hub, rq, context);
4653                        } else if text == "(" {
4654                            self.go_to_page(0, true, hub, rq, context);
4655                        } else if text == ")" {
4656                            self.go_to_page(
4657                                self.pages_count.saturating_sub(1),
4658                                true,
4659                                hub,
4660                                rq,
4661                                context,
4662                            );
4663                        } else if let Some(percent) = text.strip_suffix('%') {
4664                            if let Ok(number) = percent.parse::<f64>() {
4665                                let location =
4666                                    (number.max(0.0).min(100.0) / 100.0 * self.pages_count as f64)
4667                                        .round() as usize;
4668                                self.go_to_page(location, true, hub, rq, context);
4669                            }
4670                        } else if let Ok(number) = caps[2].parse::<f64>() {
4671                            let location = {
4672                                let bpp = if self.synthetic { BYTES_PER_PAGE } else { 1.0 };
4673                                let mut index = (number * bpp).max(0.0).round() as usize;
4674                                match prefix {
4675                                    Some("-") => index = self.current_page.saturating_sub(index),
4676                                    Some("+") => index += self.current_page,
4677                                    _ => index = index.saturating_sub(1 / (bpp as usize)),
4678                                }
4679                                index
4680                            };
4681                            self.go_to_page(location, true, hub, rq, context);
4682                        }
4683                    }
4684                }
4685                true
4686            }
4687            Event::Submit(ViewId::GoToResultsPageInput, ref text) => {
4688                if let Ok(index) = text.parse::<usize>() {
4689                    self.go_to_results_page(index.saturating_sub(1), hub, rq, context);
4690                }
4691                true
4692            }
4693            Event::Submit(ViewId::NamePageInput, ref text) => {
4694                if !text.is_empty() {
4695                    if let Some(ref mut r) = self.info.reader {
4696                        r.page_names.insert(self.current_page, text.to_string());
4697                    }
4698                }
4699                self.toggle_keyboard(false, None, hub, rq, context);
4700                true
4701            }
4702            Event::Submit(ViewId::EditNoteInput, ref note) => {
4703                let selection = self.selection.take().map(|sel| [sel.start, sel.end]);
4704
4705                if let Some(sel) = selection {
4706                    let text = self.text_excerpt(sel).unwrap();
4707                    if let Some(r) = self.info.reader.as_mut() {
4708                        r.annotations.push(Annotation {
4709                            selection: sel,
4710                            note: note.to_string(),
4711                            text,
4712                            modified: Local::now().naive_local(),
4713                        });
4714                    }
4715                    if let Some(rect) = self.text_rect(sel) {
4716                        rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
4717                    }
4718                } else {
4719                    if let Some(sel) = self.target_annotation.take() {
4720                        if let Some(annot) = self.find_annotation_mut(sel) {
4721                            annot.note = note.to_string();
4722                            annot.modified = Local::now().naive_local();
4723                        }
4724                        if let Some(rect) = self.text_rect(sel) {
4725                            rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
4726                        }
4727                    }
4728                }
4729
4730                self.update_annotations();
4731                self.toggle_keyboard(false, None, hub, rq, context);
4732                true
4733            }
4734            Event::Submit(ViewId::ReaderSearchInput, ref text) => {
4735                match make_query(text) {
4736                    Some(query) => {
4737                        self.search(text, query, hub, rq);
4738                        self.toggle_keyboard(false, None, hub, rq, context);
4739                        self.toggle_results_bar(true, rq, context);
4740                    }
4741                    None => {
4742                        let notif = Notification::new(
4743                            None,
4744                            "Invalid search query.".to_string(),
4745                            false,
4746                            hub,
4747                            rq,
4748                            context,
4749                        );
4750                        self.children.push(Box::new(notif) as Box<dyn View>);
4751                    }
4752                }
4753                true
4754            }
4755            Event::Page(dir) => {
4756                self.go_to_neighbor(dir, hub, rq, context);
4757                true
4758            }
4759            Event::GoTo(location) | Event::Select(EntryId::GoTo(location)) => {
4760                self.go_to_page(location, true, hub, rq, context);
4761                true
4762            }
4763            Event::GoToLocation(ref location) => {
4764                let offset_opt = {
4765                    let mut doc = self.doc.lock().unwrap();
4766                    doc.resolve_location(location.clone())
4767                };
4768                if let Some(offset) = offset_opt {
4769                    self.go_to_page(offset, true, hub, rq, context);
4770                }
4771                true
4772            }
4773            Event::Chapter(dir) => {
4774                self.go_to_chapter(dir, hub, rq, context);
4775                true
4776            }
4777            Event::ResultsPage(dir) => {
4778                self.go_to_results_neighbor(dir, hub, rq, context);
4779                true
4780            }
4781            Event::CropMargins(ref margin) => {
4782                let current_page = self.current_page;
4783                self.crop_margins(current_page, margin.as_ref(), hub, rq, context);
4784                true
4785            }
4786            Event::Toggle(ToggleEvent::View(ViewId::TopBottomBars)) => {
4787                self.toggle_bars(None, hub, rq, context);
4788                true
4789            }
4790            Event::Toggle(ToggleEvent::View(ViewId::GoToPage)) => {
4791                self.toggle_go_to_page(None, ViewId::GoToPage, hub, rq, context);
4792                true
4793            }
4794            Event::Toggle(ToggleEvent::View(ViewId::GoToResultsPage)) => {
4795                self.toggle_go_to_page(None, ViewId::GoToResultsPage, hub, rq, context);
4796                true
4797            }
4798            Event::Slider(SliderId::FontSize, font_size, FingerStatus::Up) => {
4799                self.set_font_size(font_size, hub, rq, context);
4800                true
4801            }
4802            Event::Slider(SliderId::ContrastExponent, exponent, FingerStatus::Up) => {
4803                self.set_contrast_exponent(exponent, hub, rq, context);
4804                true
4805            }
4806            Event::Slider(SliderId::ContrastGray, gray, FingerStatus::Up) => {
4807                self.set_contrast_gray(gray, hub, rq, context);
4808                true
4809            }
4810            Event::ToggleNear(ViewId::TitleMenu, rect) => {
4811                self.toggle_title_menu(rect, None, rq, context);
4812                true
4813            }
4814            Event::ToggleNear(ViewId::MainMenu, rect) => {
4815                toggle_main_menu(self, rect, None, rq, context);
4816                true
4817            }
4818            Event::ToggleNear(ViewId::BatteryMenu, rect) => {
4819                toggle_battery_menu(self, rect, None, rq, context);
4820                true
4821            }
4822            Event::ToggleNear(ViewId::ClockMenu, rect) => {
4823                toggle_clock_menu(self, rect, None, rq, context);
4824                true
4825            }
4826            Event::ToggleNear(ViewId::MarginCropperMenu, rect) => {
4827                self.toggle_margin_cropper_menu(rect, None, rq, context);
4828                true
4829            }
4830            Event::ToggleNear(ViewId::SearchMenu, rect) => {
4831                self.toggle_search_menu(rect, None, rq, context);
4832                true
4833            }
4834            Event::ToggleNear(ViewId::FontFamilyMenu, rect) => {
4835                self.toggle_font_family_menu(rect, None, rq, context);
4836                true
4837            }
4838            Event::ToggleNear(ViewId::FontSizeMenu, rect) => {
4839                self.toggle_font_size_menu(rect, None, rq, context);
4840                true
4841            }
4842            Event::ToggleNear(ViewId::TextAlignMenu, rect) => {
4843                self.toggle_text_align_menu(rect, None, rq, context);
4844                true
4845            }
4846            Event::ToggleNear(ViewId::MarginWidthMenu, rect) => {
4847                self.toggle_margin_width_menu(rect, None, rq, context);
4848                true
4849            }
4850            Event::ToggleNear(ViewId::LineHeightMenu, rect) => {
4851                self.toggle_line_height_menu(rect, None, rq, context);
4852                true
4853            }
4854            Event::ToggleNear(ViewId::ContrastExponentMenu, rect) => {
4855                self.toggle_contrast_exponent_menu(rect, None, rq, context);
4856                true
4857            }
4858            Event::ToggleNear(ViewId::ContrastGrayMenu, rect) => {
4859                self.toggle_contrast_gray_menu(rect, None, rq, context);
4860                true
4861            }
4862            Event::ToggleNear(ViewId::PageMenu, rect) => {
4863                self.toggle_page_menu(rect, None, rq, context);
4864                true
4865            }
4866            Event::Close(ViewId::MainMenu) => {
4867                toggle_main_menu(self, Rectangle::default(), Some(false), rq, context);
4868                true
4869            }
4870            Event::Close(ViewId::SearchBar) => {
4871                self.toggle_results_bar(false, rq, context);
4872                self.toggle_search_bar(false, hub, rq, context);
4873                if let Some(ref mut s) = self.search {
4874                    s.running.store(false, AtomicOrdering::Relaxed);
4875                    self.render_results(rq);
4876                    self.search = None;
4877                }
4878                true
4879            }
4880            Event::Close(ViewId::GoToPage) => {
4881                self.toggle_go_to_page(Some(false), ViewId::GoToPage, hub, rq, context);
4882                true
4883            }
4884            Event::Close(ViewId::GoToResultsPage) => {
4885                self.toggle_go_to_page(Some(false), ViewId::GoToResultsPage, hub, rq, context);
4886                true
4887            }
4888            Event::Close(ViewId::SelectionMenu) => {
4889                if self.state == State::Idle && self.target_annotation.is_none() {
4890                    if let Some(rect) = self.selection_rect() {
4891                        self.selection = None;
4892                        rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
4893                    }
4894                }
4895                false
4896            }
4897            Event::Close(ViewId::EditNote) => {
4898                self.toggle_edit_note(None, Some(false), hub, rq, context);
4899                if let Some(rect) = self.selection_rect() {
4900                    self.selection = None;
4901                    rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
4902                }
4903                self.target_annotation = None;
4904                false
4905            }
4906            Event::Close(ViewId::NamePage) => {
4907                self.toggle_keyboard(false, None, hub, rq, context);
4908                false
4909            }
4910            Event::Show(ViewId::TableOfContents) => {
4911                {
4912                    self.toggle_bars(Some(false), hub, rq, context);
4913                }
4914                let mut doc = self.doc.lock().unwrap();
4915                if let Some(toc) = self
4916                    .toc()
4917                    .or_else(|| doc.toc())
4918                    .filter(|toc| !toc.is_empty())
4919                {
4920                    let chap = doc.chapter(self.current_page, &toc).map(|(c, _)| c);
4921                    let chap_index = chap.map_or(usize::MAX, |chap| chap.index);
4922                    let html = toc_as_html(&toc, chap_index);
4923                    let link_uri = chap.and_then(|chap| match chap.location {
4924                        Location::Uri(ref uri) => Some(format!("@{}", uri)),
4925                        Location::Exact(offset) => Some(format!("@{}", offset)),
4926                        _ => None,
4927                    });
4928                    hub.send(Event::OpenHtml(html, link_uri)).ok();
4929                }
4930                true
4931            }
4932            Event::Select(EntryId::Annotations) => {
4933                self.toggle_bars(Some(false), hub, rq, context);
4934                let mut starts = self
4935                    .annotations
4936                    .values()
4937                    .flatten()
4938                    .map(|annot| annot.selection[0])
4939                    .collect::<Vec<TextLocation>>();
4940                starts.sort();
4941                let active_range = starts.first().cloned().zip(starts.last().cloned());
4942                if let Some(mut annotations) =
4943                    self.info.reader.as_ref().map(|r| &r.annotations).cloned()
4944                {
4945                    annotations.sort_by(|a, b| a.selection[0].cmp(&b.selection[0]));
4946                    let html = annotations_as_html(&annotations, active_range);
4947                    let link_uri = annotations
4948                        .iter()
4949                        .filter(|annot| annot.selection[0].location() <= self.current_page)
4950                        .max_by_key(|annot| annot.selection[0])
4951                        .map(|annot| format!("@{}", annot.selection[0].location()));
4952                    hub.send(Event::OpenHtml(html, link_uri)).ok();
4953                }
4954                true
4955            }
4956            Event::Select(EntryId::Bookmarks) => {
4957                self.toggle_bars(Some(false), hub, rq, context);
4958                if let Some(bookmarks) = self.info.reader.as_ref().map(|r| &r.bookmarks) {
4959                    let html = bookmarks_as_html(bookmarks, self.current_page, self.synthetic);
4960                    let link_uri = bookmarks
4961                        .range(..=self.current_page)
4962                        .next_back()
4963                        .map(|index| format!("@{}", index));
4964                    hub.send(Event::OpenHtml(html, link_uri)).ok();
4965                }
4966                true
4967            }
4968            Event::Show(ViewId::SearchBar) => {
4969                self.toggle_search_bar(true, hub, rq, context);
4970                true
4971            }
4972            Event::Show(ViewId::MarginCropper) => {
4973                self.toggle_margin_cropper(true, hub, rq, context);
4974                true
4975            }
4976            Event::Close(ViewId::MarginCropper) => {
4977                self.toggle_margin_cropper(false, hub, rq, context);
4978                true
4979            }
4980            Event::SearchResult(location, ref rects) => {
4981                if self.search.is_none() {
4982                    return true;
4983                }
4984
4985                let mut results_count = 0;
4986
4987                if let Some(ref mut s) = self.search {
4988                    let pages_count = s.highlights.len();
4989                    s.highlights
4990                        .entry(location)
4991                        .or_insert_with(Vec::new)
4992                        .push(rects.clone());
4993                    s.results_count += 1;
4994                    results_count = s.results_count;
4995                    if results_count > 1
4996                        && location <= self.current_page
4997                        && s.highlights.len() > pages_count
4998                    {
4999                        s.current_page += 1;
5000                    }
5001                }
5002
5003                self.update_results_bar(rq);
5004
5005                if results_count == 1 {
5006                    self.toggle_results_bar(false, rq, context);
5007                    self.toggle_search_bar(false, hub, rq, context);
5008                    self.go_to_page(location, true, hub, rq, context);
5009                } else if location == self.current_page {
5010                    self.update(None, hub, rq, context);
5011                }
5012
5013                true
5014            }
5015            Event::EndOfSearch => {
5016                let results_count = self
5017                    .search
5018                    .as_ref()
5019                    .map(|s| s.results_count)
5020                    .unwrap_or(usize::MAX);
5021                if results_count == 0 {
5022                    let notif = Notification::new(
5023                        None,
5024                        "No search results.".to_string(),
5025                        false,
5026                        hub,
5027                        rq,
5028                        context,
5029                    );
5030                    self.children.push(Box::new(notif) as Box<dyn View>);
5031                    self.toggle_search_bar(true, hub, rq, context);
5032                    hub.send(Event::Focus(Some(ViewId::ReaderSearchInput))).ok();
5033                }
5034                true
5035            }
5036            Event::Select(EntryId::AnnotateSelection) => {
5037                self.toggle_edit_note(None, Some(true), hub, rq, context);
5038                true
5039            }
5040            Event::Select(EntryId::HighlightSelection) => {
5041                if let Some(sel) = self.selection.take() {
5042                    let text = self.text_excerpt([sel.start, sel.end]).unwrap();
5043                    if let Some(r) = self.info.reader.as_mut() {
5044                        r.annotations.push(Annotation {
5045                            selection: [sel.start, sel.end],
5046                            note: String::new(),
5047                            text,
5048                            modified: Local::now().naive_local(),
5049                        });
5050                    }
5051                    if let Some(rect) = self.text_rect([sel.start, sel.end]) {
5052                        rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
5053                    }
5054                    self.update_annotations();
5055                }
5056
5057                true
5058            }
5059            Event::Select(EntryId::DefineSelection) => {
5060                if let Some(text) = self.selected_text() {
5061                    let query = text
5062                        .trim_matches(|c: char| !c.is_alphanumeric())
5063                        .to_string();
5064                    let language = self.info.language.clone();
5065                    hub.send(Event::Select(EntryId::Launch(AppCmd::Dictionary {
5066                        query,
5067                        language,
5068                    })))
5069                    .ok();
5070                }
5071                self.selection = None;
5072                true
5073            }
5074            Event::Select(EntryId::SearchForSelection) => {
5075                if let Some(text) = self.selected_text() {
5076                    let text = text.trim_matches(|c: char| !c.is_alphanumeric());
5077                    match make_query(text) {
5078                        Some(query) => {
5079                            self.search(text, query, hub, rq);
5080                        }
5081                        None => {
5082                            let notif = Notification::new(
5083                                None,
5084                                "Invalid search query.".to_string(),
5085                                false,
5086                                hub,
5087                                rq,
5088                                context,
5089                            );
5090                            self.children.push(Box::new(notif) as Box<dyn View>);
5091                        }
5092                    }
5093                }
5094                if let Some(rect) = self.selection_rect() {
5095                    rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
5096                }
5097                self.selection = None;
5098                true
5099            }
5100            Event::Select(EntryId::GoToSelectedPageName) => {
5101                if let Some(loc) = self.selected_text().and_then(|text| {
5102                    let end = text
5103                        .find(|c: char| {
5104                            !c.is_ascii_digit()
5105                                && Digit::from_char(c).is_err()
5106                                && !c.is_ascii_uppercase()
5107                        })
5108                        .unwrap_or_else(|| text.len());
5109                    self.find_page_by_name(&text[..end])
5110                }) {
5111                    self.go_to_page(loc, true, hub, rq, context);
5112                }
5113                if let Some(rect) = self.selection_rect() {
5114                    rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
5115                }
5116                self.selection = None;
5117                true
5118            }
5119            Event::Select(EntryId::AdjustSelection) => {
5120                self.state = State::AdjustSelection;
5121                true
5122            }
5123            Event::Select(EntryId::EditAnnotationNote(sel)) => {
5124                let text = self
5125                    .find_annotation_ref(sel)
5126                    .map(|annot| annot.note.clone());
5127                self.toggle_edit_note(text, Some(true), hub, rq, context);
5128                self.target_annotation = Some(sel);
5129                true
5130            }
5131            Event::Select(EntryId::RemoveAnnotationNote(sel)) => {
5132                if let Some(annot) = self.find_annotation_mut(sel) {
5133                    annot.note.clear();
5134                    annot.modified = Local::now().naive_local();
5135                    self.update_annotations();
5136                }
5137                if let Some(rect) = self.text_rect(sel) {
5138                    rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
5139                }
5140                true
5141            }
5142            Event::Select(EntryId::RemoveAnnotation(sel)) => {
5143                if let Some(annotations) = self.info.reader.as_mut().map(|r| &mut r.annotations) {
5144                    annotations.retain(|annot| {
5145                        annot.selection[0] != sel[0] || annot.selection[1] != sel[1]
5146                    });
5147                    self.update_annotations();
5148                }
5149                if let Some(rect) = self.text_rect(sel) {
5150                    rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
5151                }
5152                true
5153            }
5154            Event::Select(EntryId::SetZoomMode(zoom_mode)) => {
5155                self.set_zoom_mode(zoom_mode, true, hub, rq, context);
5156                true
5157            }
5158            Event::Select(EntryId::SetScrollMode(scroll_mode)) => {
5159                self.set_scroll_mode(scroll_mode, hub, rq, context);
5160                true
5161            }
5162            Event::Select(EntryId::Save) => {
5163                let name = format!(
5164                    "{}-{}.{}",
5165                    self.info.title.to_lowercase().replace(' ', "_"),
5166                    Local::now().format("%Y%m%d_%H%M%S"),
5167                    self.info.file.kind
5168                );
5169                let doc = self.doc.lock().unwrap();
5170                let msg = match doc.save(&name) {
5171                    Err(e) => format!("{}", e),
5172                    Ok(()) => format!("Saved {}.", name),
5173                };
5174                let notif = Notification::new(None, msg, false, hub, rq, context);
5175                self.children.push(Box::new(notif) as Box<dyn View>);
5176                true
5177            }
5178            Event::Select(EntryId::ApplyCroppings(index, scheme)) => {
5179                self.info.reader.as_mut().map(|r| {
5180                    if r.cropping_margins.is_none() {
5181                        r.cropping_margins = Some(CroppingMargins::Any(Margin::default()));
5182                    }
5183                    r.cropping_margins.as_mut().map(|c| c.apply(index, scheme))
5184                });
5185                true
5186            }
5187            Event::Select(EntryId::RemoveCroppings) => {
5188                if let Some(r) = self.info.reader.as_mut() {
5189                    r.cropping_margins = None;
5190                }
5191                self.cache.clear();
5192                self.update(None, hub, rq, context);
5193                true
5194            }
5195            Event::Select(EntryId::SearchDirection(dir)) => {
5196                self.search_direction = dir;
5197                true
5198            }
5199            Event::Select(EntryId::SetFontFamily(ref font_family)) => {
5200                self.set_font_family(font_family, hub, rq, context);
5201                true
5202            }
5203            Event::Select(EntryId::SetTextAlign(text_align)) => {
5204                self.set_text_align(text_align, hub, rq, context);
5205                true
5206            }
5207            Event::Select(EntryId::SetFontSize(v)) => {
5208                let font_size = self
5209                    .info
5210                    .reader
5211                    .as_ref()
5212                    .and_then(|r| r.font_size)
5213                    .unwrap_or(context.settings.reader.font_size);
5214                let font_size = font_size - 1.0 + v as f32 / 10.0;
5215                self.set_font_size(font_size, hub, rq, context);
5216                true
5217            }
5218            Event::Select(EntryId::SetMarginWidth(width)) => {
5219                self.set_margin_width(width, hub, rq, context);
5220                true
5221            }
5222            Event::Select(EntryId::SetLineHeight(v)) => {
5223                let line_height = 1.0 + v as f32 / 10.0;
5224                self.set_line_height(line_height, hub, rq, context);
5225                true
5226            }
5227            Event::Select(EntryId::SetContrastExponent(v)) => {
5228                let exponent = 1.0 + v as f32 / 2.0;
5229                self.set_contrast_exponent(exponent, hub, rq, context);
5230                true
5231            }
5232            Event::Select(EntryId::SetContrastGray(v)) => {
5233                let gray = ((1 << 8) - (1 << (8 - v))) as f32;
5234                self.set_contrast_gray(gray, hub, rq, context);
5235                true
5236            }
5237            Event::Select(EntryId::SetPageName) => {
5238                self.toggle_name_page(None, hub, rq, context);
5239                true
5240            }
5241            Event::Select(EntryId::RemovePageName) => {
5242                if let Some(ref mut r) = self.info.reader {
5243                    r.page_names.remove(&self.current_page);
5244                }
5245                true
5246            }
5247            Event::Select(EntryId::ToggleInverted) => {
5248                self.update_noninverted_regions(!context.fb.inverted());
5249                false
5250            }
5251            Event::Reseed => {
5252                self.reseed(rq, context);
5253                true
5254            }
5255            Event::ToggleFrontlight => {
5256                if let Some(index) = locate::<TopBar>(self) {
5257                    self.child_mut(index)
5258                        .downcast_mut::<TopBar>()
5259                        .unwrap()
5260                        .update_frontlight_icon(rq, context);
5261                }
5262                true
5263            }
5264            Event::Device(DeviceEvent::Button {
5265                code: ButtonCode::Home,
5266                status: ButtonStatus::Pressed,
5267                ..
5268            }) => {
5269                self.quit(context);
5270                hub.send(Event::Back).ok();
5271                true
5272            }
5273            Event::Select(EntryId::Quit)
5274            | Event::Select(EntryId::Reboot)
5275            | Event::Select(EntryId::Restart)
5276            | Event::Back
5277            | Event::Suspend => {
5278                self.quit(context);
5279                false
5280            }
5281            Event::Focus(v) => {
5282                if self.focus != v {
5283                    if let Some(ViewId::ReaderSearchInput) = v {
5284                        self.toggle_results_bar(false, rq, context);
5285                        if let Some(ref mut s) = self.search {
5286                            s.running.store(false, AtomicOrdering::Relaxed);
5287                        }
5288                        self.render_results(rq);
5289                        self.search = None;
5290                    }
5291                    self.focus = v;
5292                    if v.is_some() {
5293                        self.toggle_keyboard(true, v, hub, rq, context);
5294                    }
5295                }
5296                true
5297            }
5298            _ => false,
5299        }
5300    }
5301
5302    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, fb, _fonts), fields(rect = ?rect)))]
5303    fn render(&self, fb: &mut dyn Framebuffer, rect: Rectangle, _fonts: &mut Fonts) {
5304        fb.draw_rectangle(&rect, WHITE);
5305
5306        for chunk in &self.chunks {
5307            let Resource {
5308                ref pixmap, scale, ..
5309            } = self.cache[&chunk.location];
5310            let chunk_rect = chunk.frame - chunk.frame.min + chunk.position;
5311
5312            if let Some(region_rect) = rect.intersection(&chunk_rect) {
5313                let chunk_frame = region_rect - chunk.position + chunk.frame.min;
5314                let chunk_position = region_rect.min;
5315                fb.draw_framed_pixmap_contrast(
5316                    pixmap,
5317                    &chunk_frame,
5318                    chunk_position,
5319                    self.contrast.exponent,
5320                    self.contrast.gray,
5321                );
5322
5323                if let Some(rects) = self.noninverted_regions.get(&chunk.location) {
5324                    for r in rects {
5325                        let rect = (*r * scale).to_rect() - chunk.frame.min + chunk.position;
5326                        if let Some(ref image_rect) = rect.intersection(&region_rect) {
5327                            fb.invert_region(image_rect);
5328                        }
5329                    }
5330                }
5331
5332                if let Some(groups) = self
5333                    .search
5334                    .as_ref()
5335                    .and_then(|s| s.highlights.get(&chunk.location))
5336                {
5337                    for rects in groups {
5338                        let mut last_rect: Option<Rectangle> = None;
5339                        for r in rects {
5340                            let rect = (*r * scale).to_rect() - chunk.frame.min + chunk.position;
5341                            if let Some(ref search_rect) = rect.intersection(&region_rect) {
5342                                fb.invert_region(search_rect);
5343                            }
5344                            if let Some(last) = last_rect {
5345                                if rect.max.y.min(last.max.y) - rect.min.y.max(last.min.y)
5346                                    > rect.height().min(last.height()) as i32 / 2
5347                                    && (last.max.x < rect.min.x || rect.max.x < last.min.x)
5348                                {
5349                                    let space = if last.max.x < rect.min.x {
5350                                        rect![
5351                                            last.max.x,
5352                                            (last.min.y + rect.min.y) / 2,
5353                                            rect.min.x,
5354                                            (last.max.y + rect.max.y) / 2
5355                                        ]
5356                                    } else {
5357                                        rect![
5358                                            rect.max.x,
5359                                            (last.min.y + rect.min.y) / 2,
5360                                            last.min.x,
5361                                            (last.max.y + rect.max.y) / 2
5362                                        ]
5363                                    };
5364                                    if let Some(ref res_rect) = space.intersection(&region_rect) {
5365                                        fb.invert_region(res_rect);
5366                                    }
5367                                }
5368                            }
5369                            last_rect = Some(rect);
5370                        }
5371                    }
5372                }
5373
5374                if let Some(annotations) = self.annotations.get(&chunk.location) {
5375                    for annot in annotations {
5376                        let drift = if annot.note.is_empty() {
5377                            HIGHLIGHT_DRIFT
5378                        } else {
5379                            ANNOTATION_DRIFT
5380                        };
5381                        let [start, end] = annot.selection;
5382                        if let Some(text) = self.text.get(&chunk.location) {
5383                            let mut last_rect: Option<Rectangle> = None;
5384                            for word in text
5385                                .iter()
5386                                .filter(|w| w.location >= start && w.location <= end)
5387                            {
5388                                let rect = (word.rect * scale).to_rect() - chunk.frame.min
5389                                    + chunk.position;
5390                                if let Some(ref sel_rect) = rect.intersection(&region_rect) {
5391                                    fb.shift_region(sel_rect, drift);
5392                                }
5393                                if let Some(last) = last_rect {
5394                                    // Are `rect` and `last` on the same line?
5395                                    if rect.max.y.min(last.max.y) - rect.min.y.max(last.min.y)
5396                                        > rect.height().min(last.height()) as i32 / 2
5397                                        && (last.max.x < rect.min.x || rect.max.x < last.min.x)
5398                                    {
5399                                        let space = if last.max.x < rect.min.x {
5400                                            rect![
5401                                                last.max.x,
5402                                                (last.min.y + rect.min.y) / 2,
5403                                                rect.min.x,
5404                                                (last.max.y + rect.max.y) / 2
5405                                            ]
5406                                        } else {
5407                                            rect![
5408                                                rect.max.x,
5409                                                (last.min.y + rect.min.y) / 2,
5410                                                last.min.x,
5411                                                (last.max.y + rect.max.y) / 2
5412                                            ]
5413                                        };
5414                                        if let Some(ref sel_rect) = space.intersection(&region_rect)
5415                                        {
5416                                            fb.shift_region(sel_rect, drift);
5417                                        }
5418                                    }
5419                                }
5420                                last_rect = Some(rect);
5421                            }
5422                        }
5423                    }
5424                }
5425
5426                if let Some(sel) = self.selection.as_ref() {
5427                    if let Some(text) = self.text.get(&chunk.location) {
5428                        let mut last_rect: Option<Rectangle> = None;
5429                        for word in text
5430                            .iter()
5431                            .filter(|w| w.location >= sel.start && w.location <= sel.end)
5432                        {
5433                            let rect =
5434                                (word.rect * scale).to_rect() - chunk.frame.min + chunk.position;
5435                            if let Some(ref sel_rect) = rect.intersection(&region_rect) {
5436                                fb.invert_region(sel_rect);
5437                            }
5438                            if let Some(last) = last_rect {
5439                                if rect.max.y.min(last.max.y) - rect.min.y.max(last.min.y)
5440                                    > rect.height().min(last.height()) as i32 / 2
5441                                    && (last.max.x < rect.min.x || rect.max.x < last.min.x)
5442                                {
5443                                    let space = if last.max.x < rect.min.x {
5444                                        rect![
5445                                            last.max.x,
5446                                            (last.min.y + rect.min.y) / 2,
5447                                            rect.min.x,
5448                                            (last.max.y + rect.max.y) / 2
5449                                        ]
5450                                    } else {
5451                                        rect![
5452                                            rect.max.x,
5453                                            (last.min.y + rect.min.y) / 2,
5454                                            last.min.x,
5455                                            (last.max.y + rect.max.y) / 2
5456                                        ]
5457                                    };
5458                                    if let Some(ref sel_rect) = space.intersection(&region_rect) {
5459                                        fb.invert_region(sel_rect);
5460                                    }
5461                                }
5462                            }
5463                            last_rect = Some(rect);
5464                        }
5465                    }
5466                }
5467            }
5468        }
5469
5470        if self
5471            .info
5472            .reader
5473            .as_ref()
5474            .map_or(false, |r| r.bookmarks.contains(&self.current_page))
5475        {
5476            let dpi = CURRENT_DEVICE.dpi;
5477            let thickness = scale_by_dpi(3.0, dpi) as u16;
5478            let radius = mm_to_px(0.4, dpi) as i32 + thickness as i32;
5479            let center = pt!(self.rect.max.x - 5 * radius, self.rect.min.y + 5 * radius);
5480            fb.draw_rounded_rectangle_with_border(
5481                &Rectangle::from_disk(center, radius),
5482                &CornerSpec::Uniform(radius),
5483                &BorderSpec {
5484                    thickness,
5485                    color: WHITE,
5486                },
5487                &BLACK,
5488            );
5489        }
5490    }
5491
5492    fn render_rect(&self, rect: &Rectangle) -> Rectangle {
5493        rect.intersection(&self.rect).unwrap_or(self.rect)
5494    }
5495
5496    fn resize(&mut self, rect: Rectangle, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) {
5497        if !self.children.is_empty() {
5498            let dpi = CURRENT_DEVICE.dpi;
5499            let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
5500            let (small_thickness, big_thickness) = halves(thickness);
5501            let (small_height, big_height) = (
5502                scale_by_dpi(SMALL_BAR_HEIGHT, dpi) as i32,
5503                scale_by_dpi(BIG_BAR_HEIGHT, dpi) as i32,
5504            );
5505            let mut floating_layer_start = 0;
5506
5507            self.children.retain(|child| !child.is::<Menu>());
5508
5509            if self.children[0].is::<TopBar>() {
5510                let top_bar_rect = rect![
5511                    rect.min.x,
5512                    rect.min.y,
5513                    rect.max.x,
5514                    small_height - small_thickness
5515                ];
5516                self.children[0].resize(top_bar_rect, hub, rq, context);
5517                let separator_rect = rect![
5518                    rect.min.x,
5519                    small_height - small_thickness,
5520                    rect.max.x,
5521                    small_height + big_thickness
5522                ];
5523                self.children[1].resize(separator_rect, hub, rq, context);
5524            } else if self.children[0].is::<Filler>() {
5525                let mut index = 1;
5526                if self.children[index].is::<SearchBar>() {
5527                    let sb_rect = rect![
5528                        rect.min.x,
5529                        rect.max.y - (3 * big_height + 2 * small_height) as i32 + big_thickness,
5530                        rect.max.x,
5531                        rect.max.y - (3 * big_height + small_height) as i32 - small_thickness
5532                    ];
5533                    self.children[index].resize(sb_rect, hub, rq, context);
5534                    self.children[index - 1].resize(
5535                        rect![
5536                            rect.min.x,
5537                            sb_rect.min.y - thickness,
5538                            rect.max.x,
5539                            sb_rect.min.y
5540                        ],
5541                        hub,
5542                        rq,
5543                        context,
5544                    );
5545                    index += 2;
5546                }
5547                if self.children[index].is::<Keyboard>() {
5548                    let kb_rect = rect![
5549                        rect.min.x,
5550                        rect.max.y - (small_height + 3 * big_height) as i32 + big_thickness,
5551                        rect.max.x,
5552                        rect.max.y - small_height - small_thickness
5553                    ];
5554                    self.children[index].resize(kb_rect, hub, rq, context);
5555                    self.children[index + 1].resize(
5556                        rect![
5557                            rect.min.x,
5558                            kb_rect.max.y,
5559                            rect.max.x,
5560                            kb_rect.max.y + thickness
5561                        ],
5562                        hub,
5563                        rq,
5564                        context,
5565                    );
5566                    let kb_rect = *self.children[index].rect();
5567                    self.children[index - 1].resize(
5568                        rect![
5569                            rect.min.x,
5570                            kb_rect.min.y - thickness,
5571                            rect.max.x,
5572                            kb_rect.min.y
5573                        ],
5574                        hub,
5575                        rq,
5576                        context,
5577                    );
5578                    index += 2;
5579                }
5580                floating_layer_start = index;
5581            }
5582
5583            if let Some(mut index) = locate::<BottomBar>(self) {
5584                floating_layer_start = index + 1;
5585                let separator_rect = rect![
5586                    rect.min.x,
5587                    rect.max.y - small_height - small_thickness,
5588                    rect.max.x,
5589                    rect.max.y - small_height + big_thickness
5590                ];
5591                self.children[index - 1].resize(separator_rect, hub, rq, context);
5592                let bottom_bar_rect = rect![
5593                    rect.min.x,
5594                    rect.max.y - small_height + big_thickness,
5595                    rect.max.x,
5596                    rect.max.y
5597                ];
5598                self.children[index].resize(bottom_bar_rect, hub, rq, context);
5599
5600                index -= 2;
5601
5602                while index > 2 {
5603                    let bar_height = if self.children[index].is::<ToolBar>() {
5604                        2 * big_height
5605                    } else if self.children[index].is::<Keyboard>() {
5606                        3 * big_height
5607                    } else {
5608                        small_height
5609                    } as i32;
5610
5611                    let y_max = self.children[index + 1].rect().min.y;
5612                    let bar_rect = rect![
5613                        rect.min.x,
5614                        y_max - bar_height + thickness,
5615                        rect.max.x,
5616                        y_max
5617                    ];
5618                    self.children[index].resize(bar_rect, hub, rq, context);
5619                    let y_max = self.children[index].rect().min.y;
5620                    let sp_rect = rect![rect.min.x, y_max - thickness, rect.max.x, y_max];
5621                    self.children[index - 1].resize(sp_rect, hub, rq, context);
5622
5623                    index -= 2;
5624                }
5625            }
5626
5627            for i in floating_layer_start..self.children.len() {
5628                self.children[i].resize(rect, hub, rq, context);
5629            }
5630        }
5631
5632        match self.view_port.zoom_mode {
5633            ZoomMode::FitToWidth => {
5634                // Apply the scale change.
5635                let ratio = (rect.width() as i32 - 2 * self.view_port.margin_width) as f32
5636                    / (self.rect.width() as i32 - 2 * self.view_port.margin_width) as f32;
5637                self.view_port.page_offset.y = (self.view_port.page_offset.y as f32 * ratio) as i32;
5638            }
5639            ZoomMode::Custom(_) => {
5640                // Keep the center still.
5641                self.view_port.page_offset += pt!(
5642                    self.rect.width() as i32 - rect.width() as i32,
5643                    self.rect.height() as i32 - rect.height() as i32
5644                ) / 2;
5645            }
5646            _ => (),
5647        }
5648
5649        self.rect = rect;
5650
5651        if self.reflowable {
5652            let font_size = self
5653                .info
5654                .reader
5655                .as_ref()
5656                .and_then(|r| r.font_size)
5657                .unwrap_or(context.settings.reader.font_size);
5658            let mut doc = self.doc.lock().unwrap();
5659            doc.layout(rect.width(), rect.height(), font_size, CURRENT_DEVICE.dpi);
5660            let current_page = self.current_page.min(doc.pages_count() - 1);
5661            if let Some(location) = doc.resolve_location(Location::Exact(current_page)) {
5662                self.current_page = location;
5663            }
5664            self.text.clear();
5665        }
5666
5667        self.cache.clear();
5668        self.update(Some(UpdateMode::Full), hub, rq, context);
5669    }
5670
5671    fn might_rotate(&self) -> bool {
5672        self.search.is_none()
5673    }
5674
5675    fn is_background(&self) -> bool {
5676        true
5677    }
5678
5679    fn rect(&self) -> &Rectangle {
5680        &self.rect
5681    }
5682
5683    fn rect_mut(&mut self) -> &mut Rectangle {
5684        &mut self.rect
5685    }
5686
5687    fn children(&self) -> &Vec<Box<dyn View>> {
5688        &self.children
5689    }
5690
5691    fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
5692        &mut self.children
5693    }
5694
5695    fn id(&self) -> Id {
5696        self.id
5697    }
5698}