diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-05-18 19:09:19 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-05-18 19:09:57 +0200 |
| commit | 3965e10281ea3c8754a1877c9f7e71c1930bf4c3 (patch) | |
| tree | e221b11692ac3af9f97c66625824e1c4e345c2eb /src/library/math | |
| parent | 486f7b1bca15af3805ae1a8f63d72827f227a3d2 (diff) | |
Hack in ReX for now
Diffstat (limited to 'src/library/math')
| -rw-r--r-- | src/library/math/mod.rs | 25 | ||||
| -rw-r--r-- | src/library/math/rex.rs | 148 |
2 files changed, 165 insertions, 8 deletions
diff --git a/src/library/math/mod.rs b/src/library/math/mod.rs index 5656890d..ce41bd49 100644 --- a/src/library/math/mod.rs +++ b/src/library/math/mod.rs @@ -1,14 +1,17 @@ //! Mathematical formulas. +mod rex; + use crate::library::layout::BlockSpacing; use crate::library::prelude::*; use crate::library::text::FontFamily; +use crate::syntax::Spanned; /// A mathematical formula. #[derive(Debug, Hash)] pub struct MathNode { /// The formula. - pub formula: EcoString, + pub formula: Spanned<EcoString>, /// Whether the formula is display-level. pub display: bool, } @@ -40,17 +43,23 @@ impl Show for MathNode { fn encode(&self, _: StyleChain) -> Dict { dict! { - "formula" => Value::Str(self.formula.clone()), + "formula" => Value::Str(self.formula.v.clone()), "display" => Value::Bool(self.display) } } - fn realize(&self, _: &mut Context, _: StyleChain) -> TypResult<Content> { - let mut realized = Content::Text(self.formula.trim().into()); - if self.display { - realized = Content::block(realized); - } - Ok(realized) + fn realize(&self, _: &mut Context, styles: StyleChain) -> TypResult<Content> { + let node = self::rex::RexNode { + tex: self.formula.clone(), + display: self.display, + family: styles.get(Self::FAMILY).clone(), + }; + + Ok(if self.display { + Content::block(node) + } else { + Content::inline(node) + }) } fn finalize( diff --git a/src/library/math/rex.rs b/src/library/math/rex.rs new file mode 100644 index 00000000..43e2c015 --- /dev/null +++ b/src/library/math/rex.rs @@ -0,0 +1,148 @@ +use rex::font::{FontContext, MathFont}; +use rex::layout::{LayoutSettings, Style}; +use rex::parser::color::RGBA; +use rex::render::{Backend, Cursor, Renderer}; +use rex::error::{Error, LayoutError}; + +use crate::font::FaceId; +use crate::library::prelude::*; +use crate::library::text::{variant, FontFamily, Lang, TextNode}; + +/// A layout node that renders with ReX. +#[derive(Debug, Hash)] +pub struct RexNode { + /// The TeX formula. + pub tex: Spanned<EcoString>, + /// Whether the formula is display-level. + pub display: bool, + /// The font family. + pub family: FontFamily, +} + +impl Layout for RexNode { + fn layout( + &self, + ctx: &mut Context, + _: &Regions, + styles: StyleChain, + ) -> TypResult<Vec<Arc<Frame>>> { + // Load the font. + let face_id = match ctx.fonts.select(self.family.as_str(), variant(styles)) { + Some(id) => id, + None => return Ok(vec![]), + }; + + // Prepare the font. + let data = ctx.fonts.get(face_id).buffer(); + let font = match MathFont::parse(data) { + Ok(font) => font, + Err(_) => return Ok(vec![]), + }; + + // Layout the formula. + let ctx = FontContext::new(&font); + let em = styles.get(TextNode::SIZE); + let style = if self.display { Style::Display } else { Style::Text }; + let settings = LayoutSettings::new(&ctx, em.to_pt(), style); + let renderer = Renderer::new(); + let layout = renderer.layout(&self.tex.v, settings) + .map_err(|err| match err { + Error::Parse(err) => err.to_string(), + Error::Layout(LayoutError::Font(err)) => err.to_string(), + }) + .at(self.tex.span)?; + + // Determine the metrics. + let (x0, y0, x1, y1) = renderer.size(&layout); + let width = Length::pt(x1 - x0); + let height = Length::pt(y1 - y0); + let size = Size::new(width, height); + let baseline = Length::pt(y1); + + // Prepare a frame rendering backend. + let mut backend = FrameBackend { + frame: { + let mut frame = Frame::new(size); + frame.baseline = Some(baseline); + frame + }, + baseline, + face_id, + fill: styles.get(TextNode::FILL), + lang: styles.get(TextNode::LANG), + colors: vec![], + }; + + // Render into the frame. + renderer.render(&layout, &mut backend); + + Ok(vec![Arc::new(backend.frame)]) + } +} + +/// A ReX rendering backend that renders into a frame. +struct FrameBackend { + frame: Frame, + baseline: Length, + face_id: FaceId, + fill: Paint, + lang: Lang, + colors: Vec<RGBA>, +} + +impl FrameBackend { + /// The currently active fill paint. + fn fill(&self) -> Paint { + self.colors + .last() + .map(|&RGBA(r, g, b, a)| RgbaColor::new(r, g, b, a).into()) + .unwrap_or(self.fill) + } + + /// Convert a cursor to a point. + fn transform(&self, cursor: Cursor) -> Point { + Point::new(Length::pt(cursor.x), self.baseline + Length::pt(cursor.y)) + } +} + +impl Backend for FrameBackend { + fn symbol(&mut self, pos: Cursor, gid: u16, scale: f64, _: &MathFont) { + self.frame.push( + self.transform(pos), + Element::Text(Text { + face_id: self.face_id, + size: Length::pt(scale), + fill: self.fill(), + lang: self.lang, + glyphs: vec![Glyph { + id: gid, + x_advance: Em::new(0.0), + x_offset: Em::new(0.0), + c: ' ', + }], + }), + ); + } + + fn rule(&mut self, pos: Cursor, width: f64, height: f64) { + self.frame.push( + self.transform(pos), + Element::Shape(Shape { + geometry: Geometry::Rect(Size::new( + Length::pt(width), + Length::pt(height), + )), + fill: Some(self.fill()), + stroke: None, + }), + ); + } + + fn begin_color(&mut self, color: RGBA) { + self.colors.push(color); + } + + fn end_color(&mut self) { + self.colors.pop(); + } +} |
