summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/eval/mod.rs6
-rw-r--r--src/library/math/mod.rs25
-rw-r--r--src/library/math/rex.rs148
-rw-r--r--src/library/text/shaping.rs2
-rw-r--r--src/syntax/ast.rs6
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: `- ...`.