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