///| /// Markup the host typeset, drawn in a nested `` or in a ``. pub(all) enum MathBody { Svg(String) Html(String) } ///| /// Typeset math that passed pele's check, with the size of the box it is drawn in. pub(all) struct MathMarkup { body : MathBody width : Double height : Double } ///| /// The typesetter of the render in progress. /// pele keeps it in module state rather than passing it down, as every label path reaches it. let resolver : Ref[((String, Double) -> Json?)?] = { val: None, } ///| /// What the typesetter answered in the render in progress, keyed by size and TeX. let found : Ref[Map[String, MathMarkup?]] = { val: Map([]), } ///| /// Runs `run` with `math` as the typesetter that labels ask, and with nothing typeset yet. pub fn[T] with_math( math : ((String, Double) -> Json?)?, run : () -> T raise?, ) -> T raise? { let outer = resolver.val let outer_found = found.val resolver.val = math found.val = Map([]) defer { resolver.val = outer found.val = outer_found } run() } ///| /// Whether labels are laid out with a typesetter. pub fn math_active() -> Bool { resolver.val is Some(_) } ///| fn valid(size : Json?) -> Double? { match size { Some(Number(size, ..)) if !size.is_nan() && !size.is_inf() && size > 0 => Some(size) _ => None } } ///| /// The markup in what the host returned, when pele would draw it. fn checked(result : Json?) -> MathMarkup? { guard result is Some(Object(fields)) else { return None } guard valid(fields.get("width")) is Some(width) && valid(fields.get("height")) is Some(height) else { return None } let body = match fields.get("svg") { Some(String(svg)) => Svg(svg) Some(_) => return None None => match fields.get("html") { Some(String(html)) => Html(html) _ => return None } } Some({ body, width, height, }) } ///| /// The host's markup for `tex` at font size `size`, or `None` when there is no typesetter or the /// markup fails pele's check. pub fn typeset(tex : String, size : Double) -> MathMarkup? { guard resolver.val is Some(resolve) else { return None } let key = @util.js_string(Json::number(size)) + "\n" + tex if found.val.get(key) is Some(markup) { return markup } let markup = checked(resolve(tex, size)) found.val[key] = markup markup } ///| /// Runs `run` with no typesetter, as Mermaid draws diagram titles as text. pub fn[T] without_math(run : () -> T raise?) -> T raise? { let outer = resolver.val resolver.val = None defer { resolver.val = outer } run() }