use mathtex_editor_core::{CaretPath, Editor, Point, RenderOutput, Source};
use mathtex_engine::font::FontLoader;
use mathtex_engine::{Diagnostic, HostBoxes, MathMode, Typeset, TypesetError, Typesetter};
use mathtex_ir::Fragment;
#[derive(Debug, Clone, PartialEq)]
pub struct View {
pub fragment: Fragment,
pub render: RenderOutput,
pub tex: String,
pub warnings: Vec<Diagnostic>,
}
type Layout = Result<(Source, Typeset), TypesetError>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Key {
revision: u64,
epoch: u64,
}
#[derive(Debug, Default)]
pub struct RenderCache {
epoch: u64,
layout: Option<(Key, Layout)>,
view: Option<(Key, u64, View)>,
}
impl RenderCache {
pub fn new() -> Self {
Self::default()
}
pub fn invalidate(&mut self) {
self.epoch += 1;
self.layout = None;
self.view = None;
}
pub fn view<L: FontLoader>(
&mut self,
editor: &Editor,
typesetter: &mut Typesetter<L>,
mode: MathMode,
boxes: &dyn HostBoxes,
) -> Result<&View, TypesetError> {
let key = self.key(editor);
let serial = editor.input_context().serial;
let view = match self.view.take() {
Some((k, s, view)) if (k, s) == (key, serial) => view,
_ => self.draw(editor, typesetter, mode, boxes)?,
};
Ok(&self.view.insert((key, serial, view)).2)
}
pub fn hit_test<L: FontLoader>(
&mut self,
editor: &Editor,
typesetter: &mut Typesetter<L>,
mode: MathMode,
boxes: &dyn HostBoxes,
at: Point,
) -> Result<Option<CaretPath>, TypesetError> {
let (source, typeset) = self.layout(editor, typesetter, mode, boxes)?;
Ok(editor.hit_test(source, &typeset.fragment, at).ok().flatten())
}
fn key(&self, editor: &Editor) -> Key {
Key { revision: editor.revision(), epoch: self.epoch }
}
fn draw<L: FontLoader>(
&mut self,
editor: &Editor,
typesetter: &mut Typesetter<L>,
mode: MathMode,
boxes: &dyn HostBoxes,
) -> Result<View, TypesetError> {
for _ in 0..2 {
let (source, typeset) = self.layout(editor, typesetter, mode, boxes)?;
if let Ok(render) = editor.render(source, &typeset.fragment) {
return Ok(View {
fragment: typeset.fragment.clone(),
render,
tex: source.tex.clone(),
warnings: typeset.warnings.clone(),
});
}
self.invalidate();
}
Err(TypesetError::Format { message: "the exported source does not match its editor".into() })
}
fn layout<L: FontLoader>(
&mut self,
editor: &Editor,
typesetter: &mut Typesetter<L>,
mode: MathMode,
boxes: &dyn HostBoxes,
) -> Result<&(Source, Typeset), TypesetError> {
let key = self.key(editor);
if self.layout.as_ref().is_some_and(|(k, _)| *k != key) {
self.layout = None;
}
let (_, result) = self.layout.get_or_insert_with(|| {
let source = editor.source();
(key, typesetter.typeset(&source.tex, mode, boxes).map(|t| (source, t)))
});
result.as_ref().map_err(Clone::clone)
}
}