1use mathtex_editor_core::{CaretPath, Editor, Point, RenderOutput, Source};
2use mathtex_engine::font::FontLoader;
3use mathtex_engine::{Diagnostic, HostBoxes, MathMode, Typeset, TypesetError, Typesetter};
4use mathtex_ir::Fragment;
5
6#[derive(Debug, Clone, PartialEq)]
8pub struct View {
9 pub fragment: Fragment,
11 pub render: RenderOutput,
13 pub tex: String,
15 pub warnings: Vec<Diagnostic>,
17}
18
19type Layout = Result<(Source, Typeset), TypesetError>;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24struct Key {
25 revision: u64,
26 epoch: u64,
27}
28
29#[derive(Debug, Default)]
31pub struct RenderCache {
32 epoch: u64,
33 layout: Option<(Key, Layout)>,
34 view: Option<(Key, u64, View)>,
36}
37
38impl RenderCache {
39 pub fn new() -> Self {
41 Self::default()
42 }
43
44 pub fn invalidate(&mut self) {
46 self.epoch += 1;
47 self.layout = None;
48 self.view = None;
49 }
50
51 pub fn view<L: FontLoader>(
53 &mut self,
54 editor: &Editor,
55 typesetter: &mut Typesetter<L>,
56 mode: MathMode,
57 boxes: &dyn HostBoxes,
58 ) -> Result<&View, TypesetError> {
59 let key = self.key(editor);
60 let serial = editor.input_context().serial;
61 let view = match self.view.take() {
62 Some((k, s, view)) if (k, s) == (key, serial) => view,
63 _ => self.draw(editor, typesetter, mode, boxes)?,
64 };
65 Ok(&self.view.insert((key, serial, view)).2)
66 }
67
68 pub fn hit_test<L: FontLoader>(
70 &mut self,
71 editor: &Editor,
72 typesetter: &mut Typesetter<L>,
73 mode: MathMode,
74 boxes: &dyn HostBoxes,
75 at: Point,
76 ) -> Result<Option<CaretPath>, TypesetError> {
77 let (source, typeset) = self.layout(editor, typesetter, mode, boxes)?;
78 Ok(editor.hit_test(source, &typeset.fragment, at).ok().flatten())
79 }
80
81 fn key(&self, editor: &Editor) -> Key {
82 Key { revision: editor.revision(), epoch: self.epoch }
83 }
84
85 fn draw<L: FontLoader>(
86 &mut self,
87 editor: &Editor,
88 typesetter: &mut Typesetter<L>,
89 mode: MathMode,
90 boxes: &dyn HostBoxes,
91 ) -> Result<View, TypesetError> {
92 for _ in 0..2 {
93 let (source, typeset) = self.layout(editor, typesetter, mode, boxes)?;
94 if let Ok(render) = editor.render(source, &typeset.fragment) {
95 return Ok(View {
96 fragment: typeset.fragment.clone(),
97 render,
98 tex: source.tex.clone(),
99 warnings: typeset.warnings.clone(),
100 });
101 }
102 self.invalidate();
104 }
105 Err(TypesetError::Format { message: "the exported source does not match its editor".into() })
106 }
107
108 fn layout<L: FontLoader>(
109 &mut self,
110 editor: &Editor,
111 typesetter: &mut Typesetter<L>,
112 mode: MathMode,
113 boxes: &dyn HostBoxes,
114 ) -> Result<&(Source, Typeset), TypesetError> {
115 let key = self.key(editor);
116 if self.layout.as_ref().is_some_and(|(k, _)| *k != key) {
117 self.layout = None;
118 }
119 let (_, result) = self.layout.get_or_insert_with(|| {
120 let source = editor.source();
121 (key, typesetter.typeset(&source.tex, mode, boxes).map(|t| (source, t)))
122 });
123 result.as_ref().map_err(Clone::clone)
124 }
125}