diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/eval/mod.rs | 6 | ||||
| -rw-r--r-- | src/library/math/mod.rs | 25 | ||||
| -rw-r--r-- | src/library/math/rex.rs | 148 | ||||
| -rw-r--r-- | src/library/text/shaping.rs | 2 | ||||
| -rw-r--r-- | src/syntax/ast.rs | 6 |
5 files changed, 172 insertions, 15 deletions
diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 79060137..12e2970e 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -211,13 +211,13 @@ impl Eval for RawNode { } } -impl Eval for MathNode { +impl Eval for Spanned<MathNode> { type Output = Content; fn eval(&self, _: &mut Context, _: &mut Scopes) -> TypResult<Self::Output> { Ok(Content::show(library::math::MathNode { - formula: self.formula.clone(), - display: self.display, + formula: self.clone().map(|math| math.formula), + display: self.v.display, })) } } 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(); + } +} diff --git a/src/library/text/shaping.rs b/src/library/text/shaping.rs index 66a9f7c2..29973bc7 100644 --- a/src/library/text/shaping.rs +++ b/src/library/text/shaping.rs @@ -510,7 +510,7 @@ fn track_and_space(ctx: &mut ShapingContext) { } /// Resolve the font variant with `STRONG` and `EMPH` factored in. -fn variant(styles: StyleChain) -> FontVariant { +pub fn variant(styles: StyleChain) -> FontVariant { let mut variant = FontVariant::new( styles.get(TextNode::STYLE), styles.get(TextNode::WEIGHT), diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index 95421213..0f575f31 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -5,7 +5,7 @@ use std::num::NonZeroUsize; use std::ops::Deref; -use super::{Green, GreenData, NodeKind, RedNode, RedRef, Span}; +use super::{Green, GreenData, NodeKind, RedNode, RedRef, Span, Spanned}; use crate::geom::{AngleUnit, LengthUnit}; use crate::util::EcoString; @@ -77,7 +77,7 @@ impl Markup { NodeKind::Strong => node.cast().map(MarkupNode::Strong), NodeKind::Emph => node.cast().map(MarkupNode::Emph), NodeKind::Raw(raw) => Some(MarkupNode::Raw(raw.as_ref().clone())), - NodeKind::Math(math) => Some(MarkupNode::Math(math.as_ref().clone())), + NodeKind::Math(math) => Some(MarkupNode::Math(Spanned::new(math.as_ref().clone(), node.span()))), NodeKind::Heading => node.cast().map(MarkupNode::Heading), NodeKind::List => node.cast().map(MarkupNode::List), NodeKind::Enum => node.cast().map(MarkupNode::Enum), @@ -106,7 +106,7 @@ pub enum MarkupNode { /// A raw block with optional syntax highlighting: `` `...` ``. Raw(RawNode), /// A math formula: `$a^2 = b^2 + c^2$`. - Math(MathNode), + Math(Spanned<MathNode>), /// A section heading: `= Introduction`. Heading(HeadingNode), /// An item in an unordered list: `- ...`. |
