From 3965e10281ea3c8754a1877c9f7e71c1930bf4c3 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Wed, 18 May 2022 19:09:19 +0200 Subject: [PATCH] Hack in ReX for now --- Cargo.lock | 350 +++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + src/eval/mod.rs | 6 +- src/library/math/mod.rs | 25 ++- src/library/math/rex.rs | 148 +++++++++++++++ src/library/text/shaping.rs | 2 +- src/syntax/ast.rs | 6 +- tests/ref/math/basic.png | Bin 3639 -> 5949 bytes tests/ref/text/par.png | Bin 30060 -> 30191 bytes tests/typ/math/basic.typ | 8 + 10 files changed, 525 insertions(+), 21 deletions(-) create mode 100644 src/library/math/rex.rs diff --git a/Cargo.lock b/Cargo.lock index 1c157a688..1472a2a01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "approx" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0e60b75072ecd4168020818c0107f2857bb6c4e64252d8d3983f6263b40a5c3" +dependencies = [ + "num-traits", +] + [[package]] name = "arrayref" version = "0.3.6" @@ -83,6 +92,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bumpalo" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" + [[package]] name = "bytemuck" version = "1.7.3" @@ -135,6 +150,18 @@ dependencies = [ "matches", ] +[[package]] +name = "decorum" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "281759d3c8a14f5c3f0c49363be56810fcd7f910422f97f2db850c2920fde5cf" +dependencies = [ + "approx", + "num-traits", + "serde", + "serde_derive", +] + [[package]] name = "deflate" version = "0.8.6" @@ -214,6 +241,25 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "font" +version = "0.1.0" +source = "git+https://github.com/pdf-rs/font#f0ea791543140ccbe0d4fb20bac363ab66c53f68" +dependencies = [ + "decorum", + "indexmap", + "itertools", + "log", + "nom", + "pathfinder_color", + "pathfinder_content", + "pathfinder_geometry", + "pdf_encoding", + "rand 0.7.3", + "slotmap", + "tuple", +] + [[package]] name = "fxhash" version = "0.2.1" @@ -223,6 +269,19 @@ dependencies = [ "byteorder", ] +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + [[package]] name = "getrandom" version = "0.2.4" @@ -231,7 +290,7 @@ checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.10.2+wasi-snapshot-preview1", ] [[package]] @@ -280,6 +339,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "itertools" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.8" @@ -298,6 +366,15 @@ version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" +[[package]] +name = "js-sys" +version = "0.3.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "kurbo" version = "0.8.3" @@ -319,6 +396,19 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "lexical-core" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" +dependencies = [ + "arrayvec 0.5.2", + "bitflags", + "cfg-if", + "ryu", + "static_assertions", +] + [[package]] name = "libc" version = "0.2.118" @@ -339,8 +429,8 @@ name = "lipsum" version = "0.8.0" source = "git+https://github.com/reknih/lipsum#c97ce95ba01ed2cce1d1b0b230b6b78295b0720b" dependencies = [ - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", ] [[package]] @@ -410,6 +500,17 @@ dependencies = [ "adler", ] +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "lexical-core", + "memchr", + "version_check", +] + [[package]] name = "num-integer" version = "0.1.44" @@ -489,6 +590,46 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "pathfinder_color" +version = "0.5.0" +source = "git+https://github.com/servo/pathfinder/#038a3476d803fd77a6e66a74117b5b8803a2cb49" +dependencies = [ + "pathfinder_simd", +] + +[[package]] +name = "pathfinder_content" +version = "0.5.0" +source = "git+https://github.com/servo/pathfinder/#038a3476d803fd77a6e66a74117b5b8803a2cb49" +dependencies = [ + "arrayvec 0.5.2", + "bitflags", + "image", + "log", + "pathfinder_color", + "pathfinder_geometry", + "pathfinder_simd", + "smallvec", +] + +[[package]] +name = "pathfinder_geometry" +version = "0.5.1" +source = "git+https://github.com/servo/pathfinder/#038a3476d803fd77a6e66a74117b5b8803a2cb49" +dependencies = [ + "log", + "pathfinder_simd", +] + +[[package]] +name = "pathfinder_simd" +version = "0.5.1" +source = "git+https://github.com/servo/pathfinder/#038a3476d803fd77a6e66a74117b5b8803a2cb49" +dependencies = [ + "rustc_version", +] + [[package]] name = "pdf-writer" version = "0.4.1" @@ -500,6 +641,24 @@ dependencies = [ "ryu", ] +[[package]] +name = "pdf_encoding" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7468173909bb32dbc74ca454c82dfdfe994ad1133ddf78d6c31715c9b88c40" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + [[package]] name = "pico-args" version = "0.4.2" @@ -576,13 +735,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ - "rand_core", + "rand_core 0.6.3", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", ] [[package]] @@ -592,7 +774,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.3", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", ] [[package]] @@ -601,6 +792,15 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + [[package]] name = "rctree" version = "0.4.0" @@ -622,7 +822,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" dependencies = [ - "getrandom", + "getrandom 0.2.4", "redox_syscall", ] @@ -659,6 +859,15 @@ dependencies = [ "usvg", ] +[[package]] +name = "rex" +version = "0.1.2" +source = "git+https://github.com/laurmaedje/ReX#b44e59bfa68cc4b0288bd2be80e093ed3b279af5" +dependencies = [ + "font", + "unicode-math", +] + [[package]] name = "rgb" version = "0.8.31" @@ -677,6 +886,15 @@ dependencies = [ "xmlparser", ] +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver", +] + [[package]] name = "rustybuzz" version = "0.4.0" @@ -729,6 +947,24 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + [[package]] name = "serde" version = "1.0.136" @@ -775,12 +1011,24 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a86232ab60fa71287d7f2ddae4a7073f6b7aac33631c3015abb556f08c6d0a3e" +[[package]] +name = "slotmap" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bf34684c5767b87de9119790e92e9a1d60056be2ceeaf16a8e6ef13082aeab1" + [[package]] name = "smallvec" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "svg2pdf" version = "0.2.1" @@ -874,6 +1122,15 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ae2f58a822f08abdaf668897e96a5656fe72f5a9ce66422423e8849384872e6" +[[package]] +name = "tuple" +version = "0.5.1" +source = "git+https://github.com/s3bk/tuple/#fdf8b4400ffb10506c711018a3cb918412a3c8c1" +dependencies = [ + "num-traits", + "serde", +] + [[package]] name = "typed-arena" version = "2.0.1" @@ -904,6 +1161,7 @@ dependencies = [ "pixglyph", "regex", "resvg", + "rex", "roxmltree", "rustybuzz", "same-file", @@ -933,6 +1191,12 @@ dependencies = [ "syn", ] +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + [[package]] name = "unicode-bidi" version = "0.3.7" @@ -957,6 +1221,14 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07547e3ee45e28326cc23faac56d44f58f16ab23e413db526debce3b0bfd2742" +[[package]] +name = "unicode-math" +version = "0.1.0" +source = "git+https://github.com/s3bk/unicode-math/#8ab17ee2b125747876f48dfb579d58482bf876e5" +dependencies = [ + "regex", +] + [[package]] name = "unicode-script" version = "0.5.4" @@ -1006,6 +1278,12 @@ dependencies = [ "svgtypes", ] +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "walkdir" version = "2.3.2" @@ -1017,12 +1295,72 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +[[package]] +name = "wasm-bindgen" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 2bf6c58c7..585e85ee0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ usvg = { version = "0.20", default-features = false } # External implementation of user-facing features syntect = { version = "4.6", default-features = false, features = ["dump-load", "parsing", "regex-fancy", "assets"] } +rex = { git = "https://github.com/laurmaedje/ReX" } # PDF export miniz_oxide = "0.4" diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 790601373..12e2970ed 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 { type Output = Content; fn eval(&self, _: &mut Context, _: &mut Scopes) -> TypResult { 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 5656890d6..ce41bd49a 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, /// 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 { - 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 { + 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 000000000..43e2c015c --- /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, + /// 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>> { + // 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, +} + +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 66a9f7c26..29973bc79 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 95421213c..0f575f31a 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), /// A section heading: `= Introduction`. Heading(HeadingNode), /// An item in an unordered list: `- ...`. diff --git a/tests/ref/math/basic.png b/tests/ref/math/basic.png index 381e92c462490b289325eb9f5b559aedf60c5cd0..42723bc515d2e2ab862f7c3039fe31d5ed7a14dc 100644 GIT binary patch literal 5949 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 A>i_@% literal 3639 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 diff --git a/tests/ref/text/par.png b/tests/ref/text/par.png index 6ee4aa9cfd3f4237ce0acb8ff6ec7733fb3c526f..18908448917ac306b8af3f7dded2e5221e03b5cb 100644 GIT binary patch delta 9475 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 WRIcE<2pn}Hx~p*iX0E)^^Zx_NxQ*5T delta 9378 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 diff --git a/tests/typ/math/basic.typ b/tests/typ/math/basic.typ index cad01d107..33246261d 100644 --- a/tests/typ/math/basic.typ +++ b/tests/typ/math/basic.typ @@ -7,6 +7,14 @@ The sum of $a$ and $b$ is $a + b$. We will show that: $[ a^2 + b^2 = c^2 ]$ +--- +Prove by induction: +$[ \sum_{k=0}^n k = \frac{n(n+1)}{2} ]$ + +--- +// Error: 1-10 expected '}' found EOF +$\sqrt{x$ + --- // Error: 2:1 expected closing bracket and dollar sign $[a