summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-11-03 11:44:53 +0100
committerLaurenz <laurmaedje@gmail.com>2022-11-03 13:35:39 +0100
commit37a7afddfaffd44cb9bc013c9506599267e08983 (patch)
tree20e7d62d3c5418baff01a21d0406b91bf3096214
parent56342bd972a13ffe21beaf2b87ab7eb1597704b4 (diff)
Split crates
-rw-r--r--Cargo.lock61
-rw-r--r--Cargo.toml88
-rw-r--r--benches/oneshot.rs18
-rw-r--r--cli/Cargo.toml26
-rw-r--r--cli/src/main.rs (renamed from src/main.rs)17
-rw-r--r--library/Cargo.toml26
-rw-r--r--library/src/ext.rs (renamed from src/library/ext.rs)14
-rw-r--r--library/src/graphics/hide.rs (renamed from src/library/graphics/hide.rs)2
-rw-r--r--library/src/graphics/image.rs (renamed from src/library/graphics/image.rs)7
-rw-r--r--library/src/graphics/line.rs (renamed from src/library/graphics/line.rs)16
-rw-r--r--library/src/graphics/mod.rs (renamed from src/library/graphics/mod.rs)0
-rw-r--r--library/src/graphics/shape.rs (renamed from src/library/graphics/shape.rs)10
-rw-r--r--library/src/layout/align.rs (renamed from src/library/layout/align.rs)8
-rw-r--r--library/src/layout/columns.rs (renamed from src/library/layout/columns.rs)4
-rw-r--r--library/src/layout/container.rs (renamed from src/library/layout/container.rs)2
-rw-r--r--library/src/layout/flow.rs (renamed from src/library/layout/flow.rs)6
-rw-r--r--library/src/layout/grid.rs (renamed from src/library/layout/grid.rs)37
-rw-r--r--library/src/layout/mod.rs (renamed from src/library/layout/mod.rs)63
-rw-r--r--library/src/layout/pad.rs (renamed from src/library/layout/pad.rs)2
-rw-r--r--library/src/layout/page.rs (renamed from src/library/layout/page.rs)2
-rw-r--r--library/src/layout/place.rs (renamed from src/library/layout/place.rs)4
-rw-r--r--library/src/layout/spacing.rs (renamed from src/library/layout/spacing.rs)4
-rw-r--r--library/src/layout/stack.rs (renamed from src/library/layout/stack.rs)7
-rw-r--r--library/src/layout/transform.rs (renamed from src/library/layout/transform.rs)7
-rw-r--r--library/src/lib.rs (renamed from src/library/mod.rs)46
-rw-r--r--library/src/math/mod.rs188
-rw-r--r--library/src/math/tex.rs164
-rw-r--r--library/src/prelude.rs27
-rw-r--r--library/src/structure/doc.rs (renamed from src/library/structure/doc.rs)4
-rw-r--r--library/src/structure/heading.rs (renamed from src/library/structure/heading.rs)6
-rw-r--r--library/src/structure/list.rs (renamed from src/library/structure/list.rs)8
-rw-r--r--library/src/structure/mod.rs (renamed from src/library/structure/mod.rs)0
-rw-r--r--library/src/structure/reference.rs (renamed from src/library/structure/reference.rs)2
-rw-r--r--library/src/structure/table.rs (renamed from src/library/structure/table.rs)18
-rw-r--r--library/src/text/deco.rs (renamed from src/library/text/deco.rs)7
-rw-r--r--library/src/text/link.rs (renamed from src/library/text/link.rs)14
-rw-r--r--library/src/text/mod.rs (renamed from src/library/text/mod.rs)130
-rw-r--r--library/src/text/par.rs (renamed from src/library/text/par.rs)41
-rw-r--r--library/src/text/quotes.rs (renamed from src/library/text/quotes.rs)3
-rw-r--r--library/src/text/raw.rs (renamed from src/library/text/raw.rs)13
-rw-r--r--library/src/text/shaping.rs (renamed from src/library/text/shaping.rs)13
-rw-r--r--library/src/text/shift.rs (renamed from src/library/text/shift.rs)9
-rw-r--r--library/src/utility/color.rs (renamed from src/library/utility/color.rs)2
-rw-r--r--library/src/utility/data.rs (renamed from src/library/utility/data.rs)5
-rw-r--r--library/src/utility/math.rs (renamed from src/library/utility/math.rs)2
-rw-r--r--library/src/utility/mod.rs (renamed from src/library/utility/mod.rs)8
-rw-r--r--library/src/utility/string.rs (renamed from src/library/utility/string.rs)5
-rw-r--r--macros/src/lib.rs20
-rw-r--r--src/diag.rs18
-rw-r--r--src/export/pdf/font.rs2
-rw-r--r--src/export/pdf/outline.rs2
-rw-r--r--src/export/pdf/page.rs1
-rw-r--r--src/export/render.rs24
-rw-r--r--src/font/book.rs2
-rw-r--r--src/font/variant.rs2
-rw-r--r--src/frame.rs69
-rw-r--r--src/geom/align.rs37
-rw-r--r--src/geom/angle.rs2
-rw-r--r--src/geom/mod.rs2
-rw-r--r--src/geom/paint.rs18
-rw-r--r--src/geom/stroke.rs61
-rw-r--r--src/lib.rs57
-rw-r--r--src/library/math/frac.rs17
-rw-r--r--src/library/math/mod.rs295
-rw-r--r--src/library/math/script.rs31
-rw-r--r--src/library/prelude.rs27
-rw-r--r--src/library/raw.rs149
-rw-r--r--src/model/args.rs2
-rw-r--r--src/model/array.rs10
-rw-r--r--src/model/cast.rs142
-rw-r--r--src/model/content.rs10
-rw-r--r--src/model/dict.rs8
-rw-r--r--src/model/eval.rs183
-rw-r--r--src/model/func.rs4
-rw-r--r--src/model/items.rs123
-rw-r--r--src/model/methods.rs2
-rw-r--r--src/model/mod.rs8
-rw-r--r--src/model/ops.rs21
-rw-r--r--src/model/str.rs18
-rw-r--r--src/model/styles.rs118
-rw-r--r--src/model/value.rs8
-rw-r--r--src/model/vm.rs23
-rw-r--r--src/syntax/ast.rs26
-rw-r--r--src/syntax/highlight.rs2
-rw-r--r--src/syntax/incremental.rs26
-rw-r--r--src/syntax/node.rs70
-rw-r--r--src/syntax/parser.rs2
-rw-r--r--src/syntax/span.rs4
-rw-r--r--src/syntax/tokens.rs2
-rw-r--r--src/util/eco.rs13
-rw-r--r--src/util/mod.rs12
-rw-r--r--tests/typ/math/basic.typ6
-rw-r--r--tests/typeset.rs26
93 files changed, 1518 insertions, 1333 deletions
diff --git a/Cargo.lock b/Cargo.lock
index e1eabd01..aabc95e1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1110,49 +1110,78 @@ version = "0.1.0"
dependencies = [
"bitflags",
"bytemuck",
- "chrono",
- "codespan-reporting",
"comemo",
- "csv",
- "dirs",
"elsa",
"flate2",
- "hypher",
"iai",
"image",
- "kurbo",
- "lipsum",
- "memmap2",
"miniz_oxide",
- "notify",
"once_cell",
"pdf-writer",
- "pico-args",
"pixglyph",
"regex",
"resvg",
"rex",
"roxmltree",
"rustybuzz",
- "same-file",
"serde",
- "serde_json",
"siphasher",
"subsetter",
"svg2pdf",
"syntect",
"tiny-skia",
"ttf-parser 0.17.1",
- "typed-arena",
+ "typst-library",
"typst-macros",
- "unicode-bidi",
- "unicode-math",
- "unicode-script",
"unicode-segmentation",
"unicode-xid",
"unscanny",
"usvg",
"walkdir",
+]
+
+[[package]]
+name = "typst-cli"
+version = "0.1.0"
+dependencies = [
+ "chrono",
+ "codespan-reporting",
+ "comemo",
+ "dirs",
+ "elsa",
+ "memmap2",
+ "notify",
+ "once_cell",
+ "pico-args",
+ "same-file",
+ "siphasher",
+ "typst",
+ "typst-library",
+ "walkdir",
+]
+
+[[package]]
+name = "typst-library"
+version = "0.1.0"
+dependencies = [
+ "comemo",
+ "csv",
+ "hypher",
+ "kurbo",
+ "lipsum",
+ "once_cell",
+ "rex",
+ "roxmltree",
+ "rustybuzz",
+ "serde_json",
+ "syntect",
+ "ttf-parser 0.17.1",
+ "typed-arena",
+ "typst",
+ "unicode-bidi",
+ "unicode-math",
+ "unicode-script",
+ "unscanny",
"xi-unicode",
]
diff --git a/Cargo.toml b/Cargo.toml
index f08f9056..0e964877 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,97 +5,47 @@ authors = ["The Typst Project Developers"]
edition = "2021"
[workspace]
-members = ["macros"]
+members = ["cli", "library", "macros"]
[dependencies]
-# Workspace
-typst-macros = { path = "./macros" }
-
-# Utilities
+typst-macros = { path = "macros" }
bitflags = "1"
bytemuck = "1"
comemo = "0.1"
+flate2 = "1"
+image = { version = "0.24", default-features = false, features = ["png", "jpeg", "gif"] }
+miniz_oxide = "0.5"
once_cell = "1"
+pdf-writer = "0.6"
+pixglyph = { git = "https://github.com/typst/pixglyph" }
regex = "1"
+resvg = { version = "0.22", default-features = false }
+rex = { git = "https://github.com/laurmaedje/ReX" }
+roxmltree = "0.14"
+rustybuzz = "0.5"
serde = { version = "1", features = ["derive"] }
siphasher = "0.3"
-typed-arena = "2"
-unscanny = "0.1"
-
-# Text and font handling
-hypher = "0.1"
-kurbo = "0.8"
-rustybuzz = "0.5"
+subsetter = "0.1"
+svg2pdf = "0.4"
+syntect = { version = "5", default-features = false, features = ["default-syntaxes", "regex-fancy"] }
+tiny-skia = "0.6.2"
ttf-parser = "0.17"
-unicode-bidi = "0.3.5"
-unicode-script = "0.5"
unicode-segmentation = "1"
unicode-xid = "0.2"
-xi-unicode = "0.3"
-
-# Raster and vector graphics handling
-image = { version = "0.24", default-features = false, features = ["png", "jpeg", "gif"] }
+unscanny = "0.1"
usvg = { version = "0.22", default-features = false }
-# External implementation of user-facing features
-csv = "1"
-lipsum = { git = "https://github.com/reknih/lipsum" }
-rex = { git = "https://github.com/laurmaedje/ReX" }
-serde_json = "1"
-syntect = { version = "5", default-features = false, features = ["default-syntaxes", "regex-fancy"] }
-unicode-math = { git = "https://github.com/s3bk/unicode-math/" }
-
-# PDF export
-miniz_oxide = "0.5"
-pdf-writer = "0.6"
-subsetter = "0.1"
-svg2pdf = "0.4"
-
-# Rendering
-flate2 = "1"
-pixglyph = { git = "https://github.com/typst/pixglyph" }
-resvg = { version = "0.22", default-features = false }
-roxmltree = "0.14"
-tiny-skia = "0.6.2"
-
-# Command line interface
-chrono = { version = "0.4", default-features = false, features = ["clock", "std"], optional = true }
-codespan-reporting = { version = "0.11", optional = true }
-dirs = { version = "4", optional = true }
-elsa = { version = "1.7", optional = true }
-memmap2 = { version = "0.5", optional = true }
-notify = { version = "5", optional = true }
-pico-args = { version = "0.4", optional = true }
-same-file = { version = "1", optional = true }
-walkdir = { version = "2", optional = true }
-
[dev-dependencies]
+typst-library = { path = "library" }
iai = { git = "https://github.com/reknih/iai" }
elsa = "1.7"
walkdir = "2"
-[features]
-cli = [
- "chrono",
- "codespan-reporting",
- "dirs",
- "elsa",
- "memmap2",
- "notify",
- "pico-args",
- "same-file",
- "walkdir",
-]
-
[profile.dev]
-debug = 0 # Faster compilation
+debug = 0
[profile.dev.package."*"]
-opt-level = 2 # Faster test execution
-
-[[bin]]
-name = "typst"
-required-features = ["cli"]
+opt-level = 2
[[test]]
name = "typeset"
diff --git a/benches/oneshot.rs b/benches/oneshot.rs
index 2437b723..965d0c78 100644
--- a/benches/oneshot.rs
+++ b/benches/oneshot.rs
@@ -1,4 +1,4 @@
-use std::path::Path;
+use std::path::{Path, PathBuf};
use comemo::{Prehashed, Track, Tracked};
use iai::{black_box, main, Iai};
@@ -20,7 +20,7 @@ main!(
bench_parse,
bench_edit,
bench_eval,
- bench_layout,
+ bench_typeset,
bench_highlight,
bench_render,
);
@@ -80,12 +80,10 @@ fn bench_eval(iai: &mut Iai) {
iai.run(|| typst::model::eval(world.track(), route.track(), id).unwrap());
}
-fn bench_layout(iai: &mut Iai) {
+fn bench_typeset(iai: &mut Iai) {
let world = BenchWorld::new();
let id = world.source.id();
- let route = typst::model::Route::default();
- let module = typst::model::eval(world.track(), route.track(), id).unwrap();
- iai.run(|| typst::library::layout::Layout::layout(&module.content, world.track()));
+ iai.run(|| typst::typeset(&world, id));
}
fn bench_render(iai: &mut Iai) {
@@ -104,7 +102,13 @@ struct BenchWorld {
impl BenchWorld {
fn new() -> Self {
- let config = Config::default();
+ let config = Config {
+ root: PathBuf::new(),
+ scope: typst_library::scope(),
+ styles: typst_library::styles(),
+ items: typst_library::items(),
+ };
+
let font = Font::new(FONT.into(), 0).unwrap();
let book = FontBook::from_fonts([&font]);
let id = SourceId::from_u16(0);
diff --git a/cli/Cargo.toml b/cli/Cargo.toml
new file mode 100644
index 00000000..5e3f227a
--- /dev/null
+++ b/cli/Cargo.toml
@@ -0,0 +1,26 @@
+[package]
+name = "typst-cli"
+version = "0.1.0"
+authors = ["The Typst Project Developers"]
+edition = "2021"
+
+[[bin]]
+name = "typst"
+path = "src/main.rs"
+doc = false
+
+[dependencies]
+typst = { path = ".." }
+typst-library = { path = "../library" }
+chrono = { version = "0.4", default-features = false, features = ["clock", "std"] }
+codespan-reporting = "0.11"
+comemo = "0.1"
+dirs = "4"
+elsa = "1.7"
+memmap2 = "0.5"
+notify = "5"
+once_cell = "1"
+pico-args = "0.4"
+same-file = "1"
+siphasher = "0.3"
+walkdir = "2"
diff --git a/src/main.rs b/cli/src/main.rs
index e32bb8c6..62cad8ab 100644
--- a/src/main.rs
+++ b/cli/src/main.rs
@@ -174,13 +174,20 @@ fn dispatch(command: Command) -> StrResult<()> {
/// Execute a typesetting command.
fn typeset(command: TypesetCommand) -> StrResult<()> {
- let mut config = Config::default();
- if let Some(root) = &command.root {
- config.root = root.clone();
+ let root = if let Some(root) = &command.root {
+ root.clone()
} else if let Some(dir) = command.input.parent() {
- config.root = dir.into();
- }
+ dir.into()
+ } else {
+ PathBuf::new()
+ };
+ let config = Config {
+ root,
+ scope: typst_library::scope(),
+ styles: typst_library::styles(),
+ items: typst_library::items(),
+ };
// Create the world that serves sources, fonts and files.
let mut world = SystemWorld::new(config);
diff --git a/library/Cargo.toml b/library/Cargo.toml
new file mode 100644
index 00000000..18f4779c
--- /dev/null
+++ b/library/Cargo.toml
@@ -0,0 +1,26 @@
+[package]
+name = "typst-library"
+version = "0.1.0"
+authors = ["The Typst Project Developers"]
+edition = "2021"
+
+[dependencies]
+typst = { path = ".." }
+comemo = "0.1"
+csv = "1"
+hypher = "0.1"
+kurbo = "0.8"
+lipsum = { git = "https://github.com/reknih/lipsum" }
+once_cell = "1"
+rex = { git = "https://github.com/laurmaedje/ReX" }
+roxmltree = "0.14"
+rustybuzz = "0.5"
+serde_json = "1"
+syntect = { version = "5", default-features = false, features = ["default-syntaxes", "regex-fancy"] }
+ttf-parser = "0.17"
+typed-arena = "2"
+unicode-bidi = "0.3.5"
+unicode-math = { git = "https://github.com/s3bk/unicode-math/" }
+unicode-script = "0.5"
+unscanny = "0.1"
+xi-unicode = "0.3"
diff --git a/src/library/ext.rs b/library/src/ext.rs
index 07b55a7b..72ef484b 100644
--- a/src/library/ext.rs
+++ b/library/src/ext.rs
@@ -1,5 +1,5 @@
use super::*;
-use crate::library::prelude::*;
+use crate::prelude::*;
/// Additional methods on content.
pub trait ContentExt {
@@ -19,7 +19,7 @@ pub trait ContentExt {
fn boxed(self, sizing: Axes<Option<Rel<Length>>>) -> Self;
/// Set alignments for this content.
- fn aligned(self, aligns: Axes<Option<RawAlign>>) -> Self;
+ fn aligned(self, aligns: Axes<Option<GenAlign>>) -> Self;
/// Pad this content at the sides.
fn padded(self, padding: Sides<Rel<Length>>) -> Self;
@@ -83,7 +83,7 @@ impl ContentExt for Content {
layout::BoxNode { sizing, child: self }.pack()
}
- fn aligned(self, aligns: Axes<Option<RawAlign>>) -> Self {
+ fn aligned(self, aligns: Axes<Option<GenAlign>>) -> Self {
layout::AlignNode { aligns, child: self }.pack()
}
@@ -115,9 +115,11 @@ impl StyleMapExt for StyleMap {
fn set_family(&mut self, preferred: text::FontFamily, existing: StyleChain) {
self.set(
text::TextNode::FAMILY,
- std::iter::once(preferred)
- .chain(existing.get(text::TextNode::FAMILY).iter().cloned())
- .collect(),
+ FallbackList(
+ std::iter::once(preferred)
+ .chain(existing.get(text::TextNode::FAMILY).0.iter().cloned())
+ .collect(),
+ ),
);
}
}
diff --git a/src/library/graphics/hide.rs b/library/src/graphics/hide.rs
index d320b06c..3affd809 100644
--- a/src/library/graphics/hide.rs
+++ b/library/src/graphics/hide.rs
@@ -1,4 +1,4 @@
-use crate::library::prelude::*;
+use crate::prelude::*;
/// Hide content without affecting layout.
#[derive(Debug, Hash)]
diff --git a/src/library/graphics/image.rs b/library/src/graphics/image.rs
index e27ea488..6bf02265 100644
--- a/src/library/graphics/image.rs
+++ b/library/src/graphics/image.rs
@@ -1,8 +1,9 @@
use std::ffi::OsStr;
-use crate::image::{Image, ImageFormat, RasterFormat, VectorFormat};
-use crate::library::prelude::*;
-use crate::library::text::TextNode;
+use typst::image::{Image, ImageFormat, RasterFormat, VectorFormat};
+
+use crate::prelude::*;
+use crate::text::TextNode;
/// Show a raster or vector graphic.
#[derive(Debug, Hash)]
diff --git a/src/library/graphics/line.rs b/library/src/graphics/line.rs
index ee7813a5..df427171 100644
--- a/src/library/graphics/line.rs
+++ b/library/src/graphics/line.rs
@@ -1,4 +1,4 @@
-use crate::library::prelude::*;
+use crate::prelude::*;
/// Display a line without affecting the layout.
#[derive(Debug, Hash)]
@@ -13,7 +13,7 @@ pub struct LineNode {
impl LineNode {
/// How to stroke the line.
#[property(resolve, fold)]
- pub const STROKE: RawStroke = RawStroke::default();
+ pub const STROKE: PartialStroke = PartialStroke::default();
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
let origin = args.named("origin")?.unwrap_or_default();
@@ -66,15 +66,3 @@ impl LayoutInline for LineNode {
Ok(vec![frame])
}
}
-
-castable! {
- Axes<Rel<Length>>,
- Expected: "array of two relative lengths",
- Value::Array(array) => {
- let mut iter = array.into_iter();
- match (iter.next(), iter.next(), iter.next()) {
- (Some(a), Some(b), None) => Axes::new(a.cast()?, b.cast()?),
- _ => Err("point array must contain exactly two entries")?,
- }
- },
-}
diff --git a/src/library/graphics/mod.rs b/library/src/graphics/mod.rs
index 34182121..34182121 100644
--- a/src/library/graphics/mod.rs
+++ b/library/src/graphics/mod.rs
diff --git a/src/library/graphics/shape.rs b/library/src/graphics/shape.rs
index 4804cd68..d980b64a 100644
--- a/src/library/graphics/shape.rs
+++ b/library/src/graphics/shape.rs
@@ -1,7 +1,7 @@
use std::f64::consts::SQRT_2;
-use crate::library::prelude::*;
-use crate::library::text::TextNode;
+use crate::prelude::*;
+use crate::text::TextNode;
/// A sizable and fillable shape with optional content.
#[derive(Debug, Hash)]
@@ -25,7 +25,7 @@ impl<const S: ShapeKind> ShapeNode<S> {
pub const FILL: Option<Paint> = None;
/// How to stroke the shape.
#[property(skip, resolve, fold)]
- pub const STROKE: Smart<Sides<Option<RawStroke>>> = Smart::Auto;
+ pub const STROKE: Smart<Sides<Option<PartialStroke>>> = Smart::Auto;
/// How much to pad the shape's content.
#[property(resolve, fold)]
@@ -62,7 +62,7 @@ impl<const S: ShapeKind> ShapeNode<S> {
if is_round(S) {
styles.set_opt(
Self::STROKE,
- args.named::<Smart<Option<RawStroke>>>("stroke")?
+ args.named::<Smart<Option<PartialStroke>>>("stroke")?
.map(|some| some.map(Sides::splat)),
);
} else {
@@ -140,7 +140,7 @@ impl<const S: ShapeKind> LayoutInline for ShapeNode<S> {
Smart::Auto if fill.is_none() => Sides::splat(Some(Stroke::default())),
Smart::Auto => Sides::splat(None),
Smart::Custom(strokes) => {
- strokes.map(|s| s.map(RawStroke::unwrap_or_default))
+ strokes.map(|s| s.map(PartialStroke::unwrap_or_default))
}
};
diff --git a/src/library/layout/align.rs b/library/src/layout/align.rs
index 2ee565cc..5e1b3555 100644
--- a/src/library/layout/align.rs
+++ b/library/src/layout/align.rs
@@ -1,11 +1,11 @@
-use crate::library::prelude::*;
-use crate::library::text::{HorizontalAlign, ParNode};
+use crate::prelude::*;
+use crate::text::{HorizontalAlign, ParNode};
/// Align content along the layouting axes.
#[derive(Debug, Hash)]
pub struct AlignNode {
/// How to align the content horizontally and vertically.
- pub aligns: Axes<Option<RawAlign>>,
+ pub aligns: Axes<Option<GenAlign>>,
/// The content to be aligned.
pub child: Content,
}
@@ -13,7 +13,7 @@ pub struct AlignNode {
#[node(LayoutBlock)]
impl AlignNode {
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- let aligns: Axes<Option<RawAlign>> = args.find()?.unwrap_or_default();
+ let aligns: Axes<Option<GenAlign>> = args.find()?.unwrap_or_default();
let body: Content = args.expect("body")?;
if let Axes { x: Some(x), y: None } = aligns {
diff --git a/src/library/layout/columns.rs b/library/src/layout/columns.rs
index df259eab..8eae922b 100644
--- a/src/library/layout/columns.rs
+++ b/library/src/layout/columns.rs
@@ -1,5 +1,5 @@
-use crate::library::prelude::*;
-use crate::library::text::TextNode;
+use crate::prelude::*;
+use crate::text::TextNode;
/// Separate a region into multiple equally sized columns.
#[derive(Debug, Hash)]
diff --git a/src/library/layout/container.rs b/library/src/layout/container.rs
index 023809d0..d65b78b6 100644
--- a/src/library/layout/container.rs
+++ b/library/src/layout/container.rs
@@ -1,4 +1,4 @@
-use crate::library::prelude::*;
+use crate::prelude::*;
/// An inline-level container that sizes content.
#[derive(Debug, Clone, Hash)]
diff --git a/src/library/layout/flow.rs b/library/src/layout/flow.rs
index f4d18699..a5992796 100644
--- a/src/library/layout/flow.rs
+++ b/library/src/layout/flow.rs
@@ -1,8 +1,8 @@
use std::cmp::Ordering;
use super::{AlignNode, PlaceNode, Spacing};
-use crate::library::prelude::*;
-use crate::library::text::ParNode;
+use crate::prelude::*;
+use crate::text::ParNode;
/// Arrange spacing, paragraphs and block-level nodes into a flow.
///
@@ -256,7 +256,7 @@ impl FlowLayouter {
/// Finish layouting and return the resulting frames.
pub fn finish(mut self) -> Vec<Frame> {
if self.expand.y {
- while self.regions.backlog.len() > 0 {
+ while !self.regions.backlog.is_empty() {
self.finish_region();
}
}
diff --git a/src/library/layout/grid.rs b/library/src/layout/grid.rs
index 1bb67691..f6610d78 100644
--- a/src/library/layout/grid.rs
+++ b/library/src/layout/grid.rs
@@ -1,4 +1,4 @@
-use crate::library::prelude::*;
+use crate::prelude::*;
/// Arrange content in a grid.
#[derive(Debug, Hash)]
@@ -14,11 +14,11 @@ pub struct GridNode {
#[node(LayoutBlock)]
impl GridNode {
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- let columns = args.named("columns")?.unwrap_or_default();
- let rows = args.named("rows")?.unwrap_or_default();
- let base_gutter: Vec<TrackSizing> = args.named("gutter")?.unwrap_or_default();
- let column_gutter = args.named("column-gutter")?;
- let row_gutter = args.named("row-gutter")?;
+ let TrackSizings(columns) = args.named("columns")?.unwrap_or_default();
+ let TrackSizings(rows) = args.named("rows")?.unwrap_or_default();
+ let TrackSizings(base_gutter) = args.named("gutter")?.unwrap_or_default();
+ let column_gutter = args.named("column-gutter")?.map(|TrackSizings(v)| v);
+ let row_gutter = args.named("row-gutter")?.map(|TrackSizings(v)| v);
Ok(Self {
tracks: Axes::new(columns, rows),
gutter: Axes::new(
@@ -66,19 +66,26 @@ pub enum TrackSizing {
Fractional(Fr),
}
+/// Track sizing definitions.
+#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
+pub struct TrackSizings(pub Vec<TrackSizing>);
+
castable! {
- Vec<TrackSizing>,
+ TrackSizings,
Expected: "integer, auto, relative length, fraction, or array of the latter three",
- Value::Auto => vec![TrackSizing::Auto],
- Value::Length(v) => vec![TrackSizing::Relative(v.into())],
- Value::Ratio(v) => vec![TrackSizing::Relative(v.into())],
- Value::Relative(v) => vec![TrackSizing::Relative(v)],
- Value::Fraction(v) => vec![TrackSizing::Fractional(v)],
- Value::Int(v) => vec![TrackSizing::Auto; Value::Int(v).cast::<NonZeroUsize>()?.get()],
- Value::Array(values) => values
+ Value::Auto => Self(vec![TrackSizing::Auto]),
+ Value::Length(v) => Self(vec![TrackSizing::Relative(v.into())]),
+ Value::Ratio(v) => Self(vec![TrackSizing::Relative(v.into())]),
+ Value::Relative(v) => Self(vec![TrackSizing::Relative(v)]),
+ Value::Fraction(v) => Self(vec![TrackSizing::Fractional(v)]),
+ Value::Int(v) => Self(vec![
+ TrackSizing::Auto;
+ Value::Int(v).cast::<NonZeroUsize>()?.get()
+ ]),
+ Value::Array(values) => Self(values
.into_iter()
.filter_map(|v| v.cast().ok())
- .collect(),
+ .collect()),
}
castable! {
diff --git a/src/library/layout/mod.rs b/library/src/layout/mod.rs
index 000cb212..ddfaa351 100644
--- a/src/library/layout/mod.rs
+++ b/library/src/layout/mod.rs
@@ -28,19 +28,21 @@ use std::mem;
use comemo::Tracked;
use typed_arena::Arena;
+use typst::diag::SourceResult;
+use typst::frame::Frame;
+use typst::geom::*;
+use typst::model::{
+ capability, Barrier, Content, Node, SequenceNode, Show, StyleChain, StyleEntry,
+ StyleMap, StyleVec, StyleVecBuilder, StyledNode, Target,
+};
+use typst::World;
-use crate::diag::SourceResult;
-use crate::frame::Frame;
-use crate::geom::*;
-use crate::library::structure::{DocNode, ListItem, ListNode, DESC, ENUM, LIST};
-use crate::library::text::{
- LinebreakNode, ParChild, ParNode, ParbreakNode, SmartQuoteNode, SpaceNode, TextNode,
+use crate::structure::{
+ DescNode, DocNode, EnumNode, ListItem, ListNode, DESC, ENUM, LIST,
};
-use crate::model::{
- capability, Barrier, Content, Interruption, Node, SequenceNode, Show, StyleChain,
- StyleEntry, StyleMap, StyleVec, StyleVecBuilder, StyledNode, Target,
+use crate::text::{
+ LinebreakNode, ParChild, ParNode, ParbreakNode, SmartQuoteNode, SpaceNode, TextNode,
};
-use crate::World;
/// The root-level layout.
#[capability]
@@ -204,7 +206,7 @@ impl Regions {
///
/// If this is true, calling `next()` will have no effect.
pub fn in_last(&self) -> bool {
- self.backlog.len() == 0 && self.last.map_or(true, |height| self.first.y == height)
+ self.backlog.is_empty() && self.last.map_or(true, |height| self.first.y == height)
}
/// Advance to the next region if there is any.
@@ -255,6 +257,17 @@ struct Scratch<'a> {
templates: Arena<Content>,
}
+/// Determines whether a style could interrupt some composable structure.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
+pub enum Interruption {
+ /// The style forces a list break.
+ List,
+ /// The style forces a paragraph break.
+ Par,
+ /// The style forces a page break.
+ Page,
+}
+
impl<'a> Builder<'a> {
pub fn new(
world: Tracked<'a, dyn World>,
@@ -303,10 +316,8 @@ impl<'a> Builder<'a> {
return self.styled(styled, styles);
} else if let Some(seq) = content.downcast::<SequenceNode>() {
return self.sequence(seq, styles);
- } else if content.has::<dyn Show>() {
- if self.show(&content, styles)? {
- return Ok(());
- }
+ } else if content.has::<dyn Show>() && self.show(content, styles)? {
+ return Ok(());
}
if self.list.accept(content, styles) {
@@ -371,7 +382,19 @@ impl<'a> Builder<'a> {
) -> SourceResult<()> {
let stored = self.scratch.styles.alloc(styles);
let styles = styled.map.chain(stored);
- let intr = styled.map.interruption();
+
+ let intr = if styled.map.interrupts::<PageNode>() {
+ Some(Interruption::Page)
+ } else if styled.map.interrupts::<ParNode>() {
+ Some(Interruption::Par)
+ } else if styled.map.interrupts::<ListNode>()
+ || styled.map.interrupts::<EnumNode>()
+ || styled.map.interrupts::<DescNode>()
+ {
+ Some(Interruption::List)
+ } else {
+ None
+ };
if let Some(intr) = intr {
self.interrupt(intr, styles, false)?;
@@ -396,10 +419,8 @@ impl<'a> Builder<'a> {
mem::take(&mut self.list).finish(self)?;
}
- if intr >= Interruption::Par {
- if !self.par.is_empty() {
- mem::take(&mut self.par).finish(self);
- }
+ if intr >= Interruption::Par && !self.par.is_empty() {
+ mem::take(&mut self.par).finish(self);
}
if intr >= Interruption::Page {
@@ -471,7 +492,7 @@ impl<'a> FlowBuilder<'a> {
// 4 | generated weak fractional spacing
// 5 | par spacing
- if let Some(_) = content.downcast::<ParbreakNode>() {
+ if content.is::<ParbreakNode>() {
/* Nothing to do */
} else if let Some(colbreak) = content.downcast::<ColbreakNode>() {
if colbreak.weak {
diff --git a/src/library/layout/pad.rs b/library/src/layout/pad.rs
index 920660d6..318d9f27 100644
--- a/src/library/layout/pad.rs
+++ b/library/src/layout/pad.rs
@@ -1,4 +1,4 @@
-use crate::library::prelude::*;
+use crate::prelude::*;
/// Pad content at the sides.
#[derive(Debug, Hash)]
diff --git a/src/library/layout/page.rs b/library/src/layout/page.rs
index 8d081749..53a8cbc7 100644
--- a/src/library/layout/page.rs
+++ b/library/src/layout/page.rs
@@ -1,7 +1,7 @@
use std::str::FromStr;
use super::ColumnsNode;
-use crate::library::prelude::*;
+use crate::prelude::*;
/// Layouts its child onto one or multiple pages.
#[derive(PartialEq, Clone, Hash)]
diff --git a/src/library/layout/place.rs b/library/src/layout/place.rs
index ee38ebe6..7d760ab6 100644
--- a/src/library/layout/place.rs
+++ b/library/src/layout/place.rs
@@ -1,5 +1,5 @@
use super::AlignNode;
-use crate::library::prelude::*;
+use crate::prelude::*;
/// Place content at an absolute position.
#[derive(Debug, Hash)]
@@ -8,7 +8,7 @@ pub struct PlaceNode(pub Content);
#[node(LayoutBlock)]
impl PlaceNode {
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- let aligns = args.find()?.unwrap_or(Axes::with_x(Some(RawAlign::Start)));
+ let aligns = args.find()?.unwrap_or(Axes::with_x(Some(GenAlign::Start)));
let dx = args.named("dx")?.unwrap_or_default();
let dy = args.named("dy")?.unwrap_or_default();
let body = args.expect::<Content>("body")?;
diff --git a/src/library/layout/spacing.rs b/library/src/layout/spacing.rs
index c410eee7..67fff5db 100644
--- a/src/library/layout/spacing.rs
+++ b/library/src/layout/spacing.rs
@@ -1,7 +1,7 @@
use std::cmp::Ordering;
-use crate::library::prelude::*;
-use crate::library::text::ParNode;
+use crate::prelude::*;
+use crate::text::ParNode;
/// Horizontal spacing.
#[derive(Debug, Clone, Hash)]
diff --git a/src/library/layout/stack.rs b/library/src/layout/stack.rs
index e1e70de9..44bcbf67 100644
--- a/src/library/layout/stack.rs
+++ b/library/src/layout/stack.rs
@@ -1,7 +1,8 @@
+use typst::model::StyledNode;
+
use super::{AlignNode, Spacing};
-use crate::library::prelude::*;
-use crate::library::text::ParNode;
-use crate::model::StyledNode;
+use crate::prelude::*;
+use crate::text::ParNode;
/// Arrange content and spacing along an axis.
#[derive(Debug, Hash)]
diff --git a/src/library/layout/transform.rs b/library/src/layout/transform.rs
index a73a1827..4e0b8ac2 100644
--- a/src/library/layout/transform.rs
+++ b/library/src/layout/transform.rs
@@ -1,5 +1,6 @@
-use crate::geom::Transform;
-use crate::library::prelude::*;
+use typst::geom::Transform;
+
+use crate::prelude::*;
/// Move content without affecting layout.
#[derive(Debug, Hash)]
@@ -61,7 +62,7 @@ pub type ScaleNode = TransformNode<SCALE>;
impl<const T: TransformKind> TransformNode<T> {
/// The origin of the transformation.
#[property(resolve)]
- pub const ORIGIN: Axes<Option<RawAlign>> = Axes::default();
+ pub const ORIGIN: Axes<Option<GenAlign>> = Axes::default();
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
let transform = match T {
diff --git a/src/library/mod.rs b/library/src/lib.rs
index 184c515e..ed332a06 100644
--- a/src/library/mod.rs
+++ b/library/src/lib.rs
@@ -1,7 +1,4 @@
-//! The standard library.
-//!
-//! Call [`scope`] to obtain a [`Scope`] containing all standard library
-//! definitions.
+//! Typst's standard library.
pub mod graphics;
pub mod layout;
@@ -12,15 +9,13 @@ pub mod text;
pub mod utility;
mod ext;
-mod raw;
-pub use raw::*;
+use typst::geom::{Align, Color, Dir, GenAlign};
+use typst::model::{LangItems, Node, Scope, StyleMap};
-use crate::geom::{Align, Color, Dir};
-use crate::model::{Node, Scope};
-use crate::LangItems;
+use self::layout::Layout;
-/// Construct a scope containing all standard library definitions.
+/// Construct the standard library scope.
pub fn scope() -> Scope {
let mut std = Scope::new();
@@ -140,21 +135,29 @@ pub fn scope() -> Scope {
std.define("rtl", Dir::RTL);
std.define("ttb", Dir::TTB);
std.define("btt", Dir::BTT);
- std.define("start", RawAlign::Start);
- std.define("end", RawAlign::End);
- std.define("left", RawAlign::Specific(Align::Left));
- std.define("center", RawAlign::Specific(Align::Center));
- std.define("right", RawAlign::Specific(Align::Right));
- std.define("top", RawAlign::Specific(Align::Top));
- std.define("horizon", RawAlign::Specific(Align::Horizon));
- std.define("bottom", RawAlign::Specific(Align::Bottom));
+ std.define("start", GenAlign::Start);
+ std.define("end", GenAlign::End);
+ std.define("left", GenAlign::Specific(Align::Left));
+ std.define("center", GenAlign::Specific(Align::Center));
+ std.define("right", GenAlign::Specific(Align::Right));
+ std.define("top", GenAlign::Specific(Align::Top));
+ std.define("horizon", GenAlign::Specific(Align::Horizon));
+ std.define("bottom", GenAlign::Specific(Align::Bottom));
std
}
-/// Construct the language map.
+/// Construct the standard style map.
+pub fn styles() -> StyleMap {
+ StyleMap::new()
+}
+
+/// Construct the standard lang item mapping.
pub fn items() -> LangItems {
LangItems {
+ root: |world, content| content.layout(world),
+ em: |styles| styles.get(text::TextNode::SIZE),
+ dir: |styles| styles.get(text::TextNode::DIR),
space: || text::SpaceNode.pack(),
linebreak: |justify| text::LinebreakNode { justify }.pack(),
text: |text| text::TextNode(text).pack(),
@@ -179,5 +182,10 @@ pub fn items() -> LangItems {
desc_item: |term, body| {
structure::ListItem::Desc(Box::new(structure::DescItem { term, body })).pack()
},
+ math: |children, display| math::MathNode { children, display }.pack(),
+ math_atom: |atom| math::AtomNode(atom).pack(),
+ math_script: |base, sub, sup| math::ScriptNode { base, sub, sup }.pack(),
+ math_frac: |num, denom| math::FracNode { num, denom }.pack(),
+ math_align: |count| math::AlignNode(count).pack(),
}
}
diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs
new file mode 100644
index 00000000..e46ba040
--- /dev/null
+++ b/library/src/math/mod.rs
@@ -0,0 +1,188 @@
+//! Mathematical formulas.
+
+mod tex;
+
+use std::fmt::Write;
+
+use self::tex::{layout_tex, Texify};
+use crate::layout::BlockSpacing;
+use crate::prelude::*;
+use crate::text::FontFamily;
+
+/// A piece of a mathematical formula.
+#[derive(Debug, Clone, Hash)]
+pub struct MathNode {
+ /// The pieces of the formula.
+ pub children: Vec<Content>,
+ /// Whether the formula is display-level.
+ pub display: bool,
+}
+
+#[node(Show, LayoutInline, Texify)]
+impl MathNode {
+ /// The math font family.
+ #[property(referenced)]
+ pub const FAMILY: FontFamily = FontFamily::new("NewComputerModernMath");
+ /// The spacing above display math.
+ #[property(resolve, shorthand(around))]
+ pub const ABOVE: Option<BlockSpacing> = Some(Ratio::one().into());
+ /// The spacing below display math.
+ #[property(resolve, shorthand(around))]
+ pub const BELOW: Option<BlockSpacing> = Some(Ratio::one().into());
+}
+
+impl Show for MathNode {
+ fn unguard_parts(&self, _: Selector) -> Content {
+ self.clone().pack()
+ }
+
+ fn field(&self, _: &str) -> Option<Value> {
+ None
+ }
+
+ fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
+ Ok(if self.display {
+ self.clone().pack().aligned(Axes::with_x(Some(Align::Center.into())))
+ } else {
+ self.clone().pack()
+ })
+ }
+
+ fn finalize(
+ &self,
+ _: Tracked<dyn World>,
+ styles: StyleChain,
+ realized: Content,
+ ) -> SourceResult<Content> {
+ Ok(if self.display {
+ realized.spaced(styles.get(Self::ABOVE), styles.get(Self::BELOW))
+ } else {
+ realized
+ })
+ }
+}
+
+impl LayoutInline for MathNode {
+ fn layout_inline(
+ &self,
+ world: Tracked<dyn World>,
+ _: &Regions,
+ styles: StyleChain,
+ ) -> SourceResult<Vec<Frame>> {
+ Ok(vec![layout_tex(
+ &self.texify(),
+ self.display,
+ world,
+ styles,
+ )?])
+ }
+}
+
+impl Texify for MathNode {
+ fn texify(&self) -> EcoString {
+ self.children.iter().map(Texify::texify).collect()
+ }
+}
+
+/// An atom in a math formula: `x`, `+`, `12`.
+#[derive(Debug, Hash)]
+pub struct AtomNode(pub EcoString);
+
+#[node(Texify)]
+impl AtomNode {}
+
+impl Texify for AtomNode {
+ fn texify(&self) -> EcoString {
+ self.0.chars().map(escape_char).collect()
+ }
+}
+
+/// A fraction in a mathematical formula.
+#[derive(Debug, Hash)]
+pub struct FracNode {
+ /// The numerator.
+ pub num: Content,
+ /// The denominator.
+ pub denom: Content,
+}
+
+#[node(Texify)]
+impl FracNode {}
+
+impl Texify for FracNode {
+ fn texify(&self) -> EcoString {
+ format_eco!(
+ "\\frac{{{}}}{{{}}}",
+ unparen(self.num.texify()),
+ unparen(self.denom.texify())
+ )
+ }
+}
+
+/// A sub- and/or superscript in a mathematical formula.
+#[derive(Debug, Hash)]
+pub struct ScriptNode {
+ /// The base.
+ pub base: Content,
+ /// The subscript.
+ pub sub: Option<Content>,
+ /// The superscript.
+ pub sup: Option<Content>,
+}
+
+#[node(Texify)]
+impl ScriptNode {}
+
+impl Texify for ScriptNode {
+ fn texify(&self) -> EcoString {
+ let mut tex = self.base.texify();
+
+ if let Some(sub) = &self.sub {
+ write!(tex, "_{{{}}}", unparen(sub.texify())).unwrap();
+ }
+
+ if let Some(sup) = &self.sup {
+ write!(tex, "^{{{}}}", unparen(sup.texify())).unwrap();
+ }
+
+ tex
+ }
+}
+
+/// A math alignment indicator: `&`, `&&`.
+#[derive(Debug, Hash)]
+pub struct AlignNode(pub usize);
+
+#[node(Texify)]
+impl AlignNode {}
+
+impl Texify for AlignNode {
+ fn texify(&self) -> EcoString {
+ EcoString::new()
+ }
+}
+
+/// Escape a char for TeX usage.
+#[rustfmt::skip]
+fn escape_char(c: char) -> EcoString {
+ match c {
+ '{' | '}' | '%' | '&' | '$' | '#' => format_eco!(" \\{c} "),
+ 'a' ..= 'z' | 'A' ..= 'Z' | '0' ..= '9' | 'Α' ..= 'Ω' | 'α' ..= 'ω' |
+ '*' | '+' | '-' | '[' | '(' | ']' | ')' | '?' | '!' | '=' | '<' | '>' |
+ ':' | ',' | ';' | '|' | '/' | '@' | '.' | '"' => c.into(),
+ c => unicode_math::SYMBOLS
+ .iter()
+ .find(|sym| sym.codepoint == c)
+ .map(|sym| format_eco!("\\{} ", sym.name))
+ .unwrap_or_default(),
+ }
+}
+
+/// Trim grouping parenthesis≤.
+fn unparen(s: EcoString) -> EcoString {
+ if s.starts_with('(') && s.ends_with(')') {
+ s[1 .. s.len() - 1].into()
+ } else {
+ s
+ }
+}
diff --git a/library/src/math/tex.rs b/library/src/math/tex.rs
new file mode 100644
index 00000000..7b40aa2b
--- /dev/null
+++ b/library/src/math/tex.rs
@@ -0,0 +1,164 @@
+use rex::error::{Error, LayoutError};
+use rex::font::FontContext;
+use rex::layout::{LayoutSettings, Style};
+use rex::parser::color::RGBA;
+use rex::render::{Backend, Cursor, Renderer};
+use typst::font::Font;
+
+use super::*;
+use crate::prelude::*;
+use crate::text::{variant, LinebreakNode, SpaceNode, TextNode};
+
+/// Turn a math node into TeX math code.
+#[capability]
+pub trait Texify: 'static + Sync + Send {
+ /// Perform the conversion.
+ fn texify(&self) -> EcoString;
+}
+
+impl Texify for Content {
+ fn texify(&self) -> EcoString {
+ if self.is::<SpaceNode>() {
+ return EcoString::new();
+ }
+
+ if self.is::<LinebreakNode>() {
+ return r"\\".into();
+ }
+
+ if let Some(node) = self.to::<dyn Texify>() {
+ return node.texify();
+ }
+
+ panic!("{self:?} is not math");
+ }
+}
+
+/// Layout a TeX formula into a frame.
+pub fn layout_tex(
+ tex: &str,
+ display: bool,
+ world: Tracked<dyn World>,
+ styles: StyleChain,
+) -> SourceResult<Frame> {
+ // Load the font.
+ let font = world
+ .book()
+ .select(styles.get(MathNode::FAMILY).as_str(), variant(styles))
+ .and_then(|id| world.font(id))
+ .expect("failed to find math font");
+
+ // Prepare the font context.
+ let ctx = font
+ .math()
+ .map(|math| FontContext::new(font.ttf(), math))
+ .expect("font is not suitable for math");
+
+ // Layout the formula.
+ let em = styles.get(TextNode::SIZE);
+ let style = if display { Style::Display } else { Style::Text };
+ let settings = LayoutSettings::new(&ctx, em.to_pt(), style);
+ let renderer = Renderer::new();
+ let layout = renderer
+ .layout(&tex, settings)
+ .map_err(|err| match err {
+ Error::Parse(err) => err.to_string(),
+ Error::Layout(LayoutError::Font(err)) => err.to_string(),
+ })
+ .expect("failed to layout with rex");
+
+ // Determine the metrics.
+ let (x0, y0, x1, y1) = renderer.size(&layout);
+ let width = Abs::pt(x1 - x0);
+ let mut top = Abs::pt(y1);
+ let mut bottom = Abs::pt(-y0);
+ if style != Style::Display {
+ let metrics = font.metrics();
+ top = styles.get(TextNode::TOP_EDGE).resolve(styles, metrics);
+ bottom = -styles.get(TextNode::BOTTOM_EDGE).resolve(styles, metrics);
+ };
+
+ // Prepare a frame rendering backend.
+ let size = Size::new(width, top + bottom);
+ let mut backend = FrameBackend {
+ frame: {
+ let mut frame = Frame::new(size);
+ frame.set_baseline(top);
+ frame.apply_role(Role::Formula);
+ frame
+ },
+ baseline: top,
+ font: font.clone(),
+ fill: styles.get(TextNode::FILL),
+ lang: styles.get(TextNode::LANG),
+ colors: vec![],
+ };
+
+ // Render into the frame.
+ renderer.render(&layout, &mut backend);
+ Ok(backend.frame)
+}
+
+/// A ReX rendering backend that renders into a frame.
+struct FrameBackend {
+ frame: Frame,
+ baseline: Abs,
+ font: Font,
+ 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(Abs::pt(cursor.x), self.baseline + Abs::pt(cursor.y))
+ }
+}
+
+impl Backend for FrameBackend {
+ fn symbol(&mut self, pos: Cursor, gid: u16, scale: f64) {
+ self.frame.push(
+ self.transform(pos),
+ Element::Text(Text {
+ font: self.font.clone(),
+ size: Abs::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(Abs::pt(width), Abs::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/library/src/prelude.rs b/library/src/prelude.rs
new file mode 100644
index 00000000..0c3b0eb1
--- /dev/null
+++ b/library/src/prelude.rs
@@ -0,0 +1,27 @@
+//! Helpful imports for creating library functionality.
+
+pub use std::fmt::{self, Debug, Formatter};
+pub use std::hash::Hash;
+pub use std::io;
+pub use std::num::NonZeroUsize;
+pub use std::sync::Arc;
+
+pub use comemo::Tracked;
+pub use typst::diag::{
+ bail, error, with_alternative, At, FileError, FileResult, SourceError, SourceResult,
+ StrResult,
+};
+pub use typst::frame::*;
+pub use typst::geom::*;
+pub use typst::model::{
+ array, capability, castable, dict, dynamic, format_str, node, Args, Array,
+ Capability, Cast, Content, Dict, Dynamic, Fold, Func, Key, LangItems, Node, Resolve,
+ Scope, Selector, Show, Smart, Str, StyleChain, StyleMap, StyleVec, Value, Vm,
+};
+pub use typst::syntax::{Span, Spanned};
+pub use typst::util::{format_eco, EcoString};
+pub use typst::World;
+
+pub use super::ext::{ContentExt, StyleMapExt};
+pub use super::layout::{Layout, LayoutBlock, LayoutInline, Regions};
+pub use super::text::{FallbackList, TextNode};
diff --git a/src/library/structure/doc.rs b/library/src/structure/doc.rs
index c3af3f1c..ac12c3ab 100644
--- a/src/library/structure/doc.rs
+++ b/library/src/structure/doc.rs
@@ -1,5 +1,5 @@
-use crate::library::layout::PageNode;
-use crate::library::prelude::*;
+use crate::layout::PageNode;
+use crate::prelude::*;
/// A sequence of page runs.
#[derive(Hash)]
diff --git a/src/library/structure/heading.rs b/library/src/structure/heading.rs
index 5b056c30..62a67000 100644
--- a/src/library/structure/heading.rs
+++ b/library/src/structure/heading.rs
@@ -1,6 +1,6 @@
-use crate::library::layout::{BlockNode, BlockSpacing};
-use crate::library::prelude::*;
-use crate::library::text::{FontFamily, TextNode, TextSize};
+use crate::layout::{BlockNode, BlockSpacing};
+use crate::prelude::*;
+use crate::text::{FontFamily, TextNode, TextSize};
/// A section heading.
#[derive(Debug, Hash)]
diff --git a/src/library/structure/list.rs b/library/src/structure/list.rs
index f061c5f8..2015f19b 100644
--- a/src/library/structure/list.rs
+++ b/library/src/structure/list.rs
@@ -1,9 +1,9 @@
use unscanny::Scanner;
-use crate::library::layout::{BlockSpacing, GridNode, HNode, TrackSizing};
-use crate::library::prelude::*;
-use crate::library::text::{ParNode, SpaceNode};
-use crate::library::utility::Numbering;
+use crate::layout::{BlockSpacing, GridNode, HNode, TrackSizing};
+use crate::prelude::*;
+use crate::text::{ParNode, SpaceNode};
+use crate::utility::Numbering;
/// An unordered (bulleted) or ordered (numbered) list.
#[derive(Debug, Hash)]
diff --git a/src/library/structure/mod.rs b/library/src/structure/mod.rs
index 088d1e6c..088d1e6c 100644
--- a/src/library/structure/mod.rs
+++ b/library/src/structure/mod.rs
diff --git a/src/library/structure/reference.rs b/library/src/structure/reference.rs
index b4e8b047..632ecba5 100644
--- a/src/library/structure/reference.rs
+++ b/library/src/structure/reference.rs
@@ -1,4 +1,4 @@
-use crate::library::prelude::*;
+use crate::prelude::*;
/// A reference to a label.
#[derive(Debug, Hash)]
diff --git a/src/library/structure/table.rs b/library/src/structure/table.rs
index 8a4eb302..722f11e6 100644
--- a/src/library/structure/table.rs
+++ b/library/src/structure/table.rs
@@ -1,5 +1,5 @@
-use crate::library::layout::{BlockSpacing, GridNode, TrackSizing};
-use crate::library::prelude::*;
+use crate::layout::{BlockSpacing, GridNode, TrackSizing, TrackSizings};
+use crate::prelude::*;
/// A table of items.
#[derive(Debug, Hash)]
@@ -19,7 +19,7 @@ impl TableNode {
pub const FILL: Celled<Option<Paint>> = Celled::Value(None);
/// How to stroke the cells.
#[property(resolve, fold)]
- pub const STROKE: Option<RawStroke> = Some(RawStroke::default());
+ pub const STROKE: Option<PartialStroke> = Some(PartialStroke::default());
/// How much to pad the cells's content.
pub const PADDING: Rel<Length> = Abs::pt(5.0).into();
@@ -31,11 +31,11 @@ impl TableNode {
pub const BELOW: Option<BlockSpacing> = Some(Ratio::one().into());
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- let columns = args.named("columns")?.unwrap_or_default();
- let rows = args.named("rows")?.unwrap_or_default();
- let base_gutter: Vec<TrackSizing> = args.named("gutter")?.unwrap_or_default();
- let column_gutter = args.named("column-gutter")?;
- let row_gutter = args.named("row-gutter")?;
+ let TrackSizings(columns) = args.named("columns")?.unwrap_or_default();
+ let TrackSizings(rows) = args.named("rows")?.unwrap_or_default();
+ let TrackSizings(base_gutter) = args.named("gutter")?.unwrap_or_default();
+ let column_gutter = args.named("column-gutter")?.map(|TrackSizings(v)| v);
+ let row_gutter = args.named("row-gutter")?.map(|TrackSizings(v)| v);
Ok(Self {
tracks: Axes::new(columns, rows),
gutter: Axes::new(
@@ -73,7 +73,7 @@ impl Show for TableNode {
styles: StyleChain,
) -> SourceResult<Content> {
let fill = styles.get(Self::FILL);
- let stroke = styles.get(Self::STROKE).map(RawStroke::unwrap_or_default);
+ let stroke = styles.get(Self::STROKE).map(PartialStroke::unwrap_or_default);
let padding = styles.get(Self::PADDING);
let cols = self.tracks.x.len().max(1);
diff --git a/src/library/text/deco.rs b/library/src/text/deco.rs
index 158647f2..cd3acef5 100644
--- a/src/library/text/deco.rs
+++ b/library/src/text/deco.rs
@@ -2,7 +2,7 @@ use kurbo::{BezPath, Line, ParamCurve};
use ttf_parser::{GlyphId, OutlineBuilder};
use super::TextNode;
-use crate::library::prelude::*;
+use crate::prelude::*;
/// Typeset underline, stricken-through or overlined text.
#[derive(Debug, Hash)]
@@ -22,7 +22,7 @@ impl<const L: DecoLine> DecoNode<L> {
/// How to stroke the line. The text color and thickness are read from the
/// font tables if `auto`.
#[property(shorthand, resolve, fold)]
- pub const STROKE: Smart<RawStroke> = Smart::Auto;
+ pub const STROKE: Smart<PartialStroke> = Smart::Auto;
/// Position of the line relative to the baseline, read from the font tables
/// if `auto`.
#[property(resolve)]
@@ -72,7 +72,7 @@ impl<const L: DecoLine> Show for DecoNode<L> {
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Decoration {
pub line: DecoLine,
- pub stroke: RawStroke<Abs>,
+ pub stroke: PartialStroke<Abs>,
pub offset: Smart<Abs>,
pub extent: Abs,
pub evade: bool,
@@ -157,7 +157,6 @@ pub fn decorate(
if bbox.map_or(false, |bbox| {
let y_min = -text.font.to_em(bbox.y_max).at(text.size);
let y_max = -text.font.to_em(bbox.y_min).at(text.size);
-
offset >= y_min && offset <= y_max
}) {
// Find all intersections of segments with the line.
diff --git a/src/library/text/link.rs b/library/src/text/link.rs
index 1e9adc3e..82abe5cd 100644
--- a/src/library/text/link.rs
+++ b/library/src/text/link.rs
@@ -1,5 +1,5 @@
use super::TextNode;
-use crate::library::prelude::*;
+use crate::prelude::*;
/// Link text and other elements to a destination.
#[derive(Debug, Hash)]
@@ -35,18 +35,6 @@ impl LinkNode {
}
}
-castable! {
- Destination,
- Expected: "string or dictionary with `page`, `x`, and `y` keys",
- Value::Str(string) => Self::Url(string.into()),
- Value::Dict(dict) => {
- let page = dict.get("page")?.clone().cast()?;
- let x: Length = dict.get("x")?.clone().cast()?;
- let y: Length = dict.get("y")?.clone().cast()?;
- Self::Internal(Location { page, pos: Point::new(x.abs, y.abs) })
- },
-}
-
impl Show for LinkNode {
fn unguard_parts(&self, sel: Selector) -> Content {
Self {
diff --git a/src/library/text/mod.rs b/library/src/text/mod.rs
index 18e747d0..d793f614 100644
--- a/src/library/text/mod.rs
+++ b/library/src/text/mod.rs
@@ -19,10 +19,10 @@ pub use shift::*;
use std::borrow::Cow;
use rustybuzz::Tag;
+use typst::font::{FontMetrics, FontStretch, FontStyle, FontWeight, VerticalFontMetric};
+use typst::util::EcoString;
-use crate::font::{FontMetrics, FontStretch, FontStyle, FontWeight, VerticalFontMetric};
-use crate::library::prelude::*;
-use crate::util::EcoString;
+use crate::prelude::*;
/// A single run of text with the same style.
#[derive(Debug, Clone, Hash)]
@@ -32,7 +32,7 @@ pub struct TextNode(pub EcoString);
impl TextNode {
/// A prioritized sequence of font families.
#[property(skip, referenced)]
- pub const FAMILY: Vec<FontFamily> = vec![FontFamily::new("IBM Plex Sans")];
+ pub const FAMILY: FallbackList = FallbackList(vec![FontFamily::new("IBM Plex Sans")]);
/// Whether to allow font fallback when the primary font list contains no
/// match.
pub const FALLBACK: bool = true;
@@ -73,11 +73,11 @@ impl TextNode {
/// The direction for text and inline objects. When `auto`, the direction is
/// automatically inferred from the language.
#[property(resolve)]
- pub const DIR: Smart<HorizontalDir> = Smart::Auto;
+ pub const DIR: HorizontalDir = HorizontalDir(Smart::Auto);
/// Whether to hyphenate text to improve line breaking. When `auto`, words
/// will will be hyphenated if and only if justification is enabled.
#[property(resolve)]
- pub const HYPHENATE: Smart<Hyphenate> = Smart::Auto;
+ pub const HYPHENATE: Hyphenate = Hyphenate(Smart::Auto);
/// Whether to apply smart quotes.
pub const SMART_QUOTES: bool = true;
@@ -103,7 +103,7 @@ impl TextNode {
pub const FRACTIONS: bool = false;
/// Raw OpenType features to apply.
#[property(fold)]
- pub const FEATURES: Vec<(Tag, u32)> = vec![];
+ pub const FEATURES: FontFeatures = FontFeatures(vec![]);
/// Whether the font weight should be increased by 300.
#[property(skip, fold)]
@@ -156,7 +156,7 @@ impl TextNode {
list.push(args.find()?.unwrap());
}
- styles.set(Self::FAMILY, list);
+ styles.set(Self::FAMILY, FallbackList(list));
}
}
}
@@ -190,53 +190,19 @@ castable! {
Value::Str(string) => Self::new(&string),
}
+/// Font family fallback list.
+#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
+pub struct FallbackList(pub Vec<FontFamily>);
+
castable! {
- Vec<FontFamily>,
+ FallbackList,
Expected: "string or array of strings",
- Value::Str(string) => vec![FontFamily::new(&string)],
- Value::Array(values) => values
+ Value::Str(string) => Self(vec![FontFamily::new(&string)]),
+ Value::Array(values) => Self(values
.into_iter()
.filter_map(|v| v.cast().ok())
.map(|string: EcoString| FontFamily::new(&string))
- .collect(),
-}
-
-castable! {
- FontStyle,
- Expected: "string",
- Value::Str(string) => match string.as_str() {
- "normal" => Self::Normal,
- "italic" => Self::Italic,
- "oblique" => Self::Oblique,
- _ => Err(r#"expected "normal", "italic" or "oblique""#)?,
- },
-}
-
-castable! {
- FontWeight,
- Expected: "integer or string",
- Value::Int(v) => Value::Int(v)
- .cast::<usize>()?
- .try_into()
- .map_or(Self::BLACK, Self::from_number),
- Value::Str(string) => match string.as_str() {
- "thin" => Self::THIN,
- "extralight" => Self::EXTRALIGHT,
- "light" => Self::LIGHT,
- "regular" => Self::REGULAR,
- "medium" => Self::MEDIUM,
- "semibold" => Self::SEMIBOLD,
- "bold" => Self::BOLD,
- "extrabold" => Self::EXTRABOLD,
- "black" => Self::BLACK,
- _ => Err("unknown font weight")?,
- },
-}
-
-castable! {
- FontStretch,
- Expected: "ratio",
- Value::Ratio(v) => Self::from_ratio(v.get() as f32),
+ .collect()),
}
/// The size of text.
@@ -286,57 +252,49 @@ castable! {
}),
}
-castable! {
- Lang,
- Expected: "string",
- Value::Str(string) => Self::from_str(&string)
- .ok_or("expected two or three letter language code (ISO 639-1/2/3)")?,
-}
-
-castable! {
- Region,
- Expected: "string",
- Value::Str(string) => Self::from_str(&string)
- .ok_or("expected two letter region code (ISO 3166-1 alpha-2)")?,
-}
-
/// The direction of text and inline objects in their line.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub struct HorizontalDir(pub Dir);
+pub struct HorizontalDir(pub Smart<Dir>);
castable! {
HorizontalDir,
- Expected: "direction",
+ Expected: "direction or auto",
+ Value::Auto => Self(Smart::Auto),
@dir: Dir => match dir.axis() {
- Axis::X => Self(*dir),
+ Axis::X => Self(Smart::Custom(*dir)),
Axis::Y => Err("must be horizontal")?,
},
}
-impl Resolve for Smart<HorizontalDir> {
+impl Resolve for HorizontalDir {
type Output = Dir;
fn resolve(self, styles: StyleChain) -> Self::Output {
- match self {
+ match self.0 {
Smart::Auto => styles.get(TextNode::LANG).dir(),
- Smart::Custom(dir) => dir.0,
+ Smart::Custom(dir) => dir,
}
}
}
/// Whether to hyphenate text.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub struct Hyphenate(pub bool);
+pub struct Hyphenate(pub Smart<bool>);
-castable!(Hyphenate: bool);
+castable! {
+ Hyphenate,
+ Expected: "boolean or auto",
+ Value::Auto => Self(Smart::Auto),
+ Value::Bool(v) => Self(Smart::Custom(v)),
+}
-impl Resolve for Smart<Hyphenate> {
+impl Resolve for Hyphenate {
type Output = bool;
fn resolve(self, styles: StyleChain) -> Self::Output {
- match self {
+ match self.0 {
Smart::Auto => styles.get(ParNode::JUSTIFY),
- Smart::Custom(v) => v.0,
+ Smart::Custom(v) => v,
}
}
}
@@ -404,29 +362,33 @@ castable! {
},
}
+/// OpenType font features settings.
+#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
+pub struct FontFeatures(pub Vec<(Tag, u32)>);
+
castable! {
- Vec<(Tag, u32)>,
+ FontFeatures,
Expected: "array of strings or dictionary mapping tags to integers",
- Value::Array(values) => values
+ Value::Array(values) => Self(values
.into_iter()
.filter_map(|v| v.cast().ok())
.map(|string: EcoString| (Tag::from_bytes_lossy(string.as_bytes()), 1))
- .collect(),
- Value::Dict(values) => values
+ .collect()),
+ Value::Dict(values) => Self(values
.into_iter()
.filter_map(|(k, v)| {
let tag = Tag::from_bytes_lossy(k.as_bytes());
let num = v.cast::<i64>().ok()?.try_into().ok()?;
Some((tag, num))
})
- .collect(),
+ .collect()),
}
-impl Fold for Vec<(Tag, u32)> {
+impl Fold for FontFeatures {
type Output = Self;
fn fold(mut self, outer: Self::Output) -> Self::Output {
- self.extend(outer);
+ self.0.extend(outer.0);
self
}
}
@@ -515,7 +477,7 @@ pub fn smallcaps(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
Ok(Value::Content(body.styled(TextNode::SMALLCAPS, true)))
}
-/// Strong text, rendered in boldface by default.
+/// Strong content, rendered in boldface by default.
#[derive(Debug, Hash)]
pub struct StrongNode(pub Content);
@@ -543,7 +505,7 @@ impl Show for StrongNode {
}
}
-/// Emphasized text, rendered with an italic font by default.
+/// Emphasized content, rendered with an italic font by default.
#[derive(Debug, Hash)]
pub struct EmphNode(pub Content);
diff --git a/src/library/text/par.rs b/library/src/text/par.rs
index 50089b20..95371e1a 100644
--- a/src/library/text/par.rs
+++ b/library/src/text/par.rs
@@ -1,13 +1,13 @@
use std::cmp::Ordering;
+use typst::util::EcoString;
use unicode_bidi::{BidiInfo, Level as BidiLevel};
use unicode_script::{Script, UnicodeScript};
use xi_unicode::LineBreakIterator;
use super::{shape, Lang, Quoter, Quotes, ShapedText, TextNode};
-use crate::library::layout::Spacing;
-use crate::library::prelude::*;
-use crate::util::EcoString;
+use crate::layout::Spacing;
+use crate::prelude::*;
/// Arrange text, spacing and inline-level nodes into a paragraph.
#[derive(Hash)]
@@ -42,11 +42,10 @@ impl ParNode {
/// How to align text and inline objects in their line.
#[property(resolve)]
- pub const ALIGN: HorizontalAlign = HorizontalAlign(RawAlign::Start);
+ pub const ALIGN: HorizontalAlign = HorizontalAlign(GenAlign::Start);
/// Whether to justify text in its line.
pub const JUSTIFY: bool = false;
/// How to determine line breaks.
- #[property(resolve)]
pub const LINEBREAKS: Smart<Linebreaks> = Smart::Auto;
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
@@ -113,12 +112,12 @@ impl PartialOrd for ParChild {
/// A horizontal alignment.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub struct HorizontalAlign(pub RawAlign);
+pub struct HorizontalAlign(pub GenAlign);
castable! {
HorizontalAlign,
Expected: "alignment",
- @align: RawAlign => match align.axis() {
+ @align: GenAlign => match align.axis() {
Axis::X => Self(*align),
Axis::Y => Err("must be horizontal")?,
},
@@ -151,20 +150,6 @@ castable! {
},
}
-impl Resolve for Smart<Linebreaks> {
- type Output = Linebreaks;
-
- fn resolve(self, styles: StyleChain) -> Self::Output {
- self.unwrap_or_else(|| {
- if styles.get(ParNode::JUSTIFY) {
- Linebreaks::Optimized
- } else {
- Linebreaks::Simple
- }
- })
- }
-}
-
/// A paragraph break.
#[derive(Debug, Clone, Hash)]
pub struct ParbreakNode;
@@ -446,7 +431,7 @@ fn collect<'a>(
let mut iter = par.0.iter().peekable();
while let Some((child, map)) = iter.next() {
- let styles = map.chain(&styles);
+ let styles = map.chain(styles);
let segment = match child {
ParChild::Text(text) => {
let prev = full.len();
@@ -515,7 +500,7 @@ fn prepare<'a>(
regions: &Regions,
styles: StyleChain<'a>,
) -> SourceResult<Preparation<'a>> {
- let bidi = BidiInfo::new(&text, match styles.get(TextNode::DIR) {
+ let bidi = BidiInfo::new(text, match styles.get(TextNode::DIR) {
Dir::LTR => Some(BidiLevel::ltr()),
Dir::RTL => Some(BidiLevel::rtl()),
_ => None,
@@ -642,7 +627,15 @@ fn linebreak<'a>(
world: Tracked<dyn World>,
width: Abs,
) -> Vec<Line<'a>> {
- match p.styles.get(ParNode::LINEBREAKS) {
+ let linebreaks = p.styles.get(ParNode::LINEBREAKS).unwrap_or_else(|| {
+ if p.styles.get(ParNode::JUSTIFY) {
+ Linebreaks::Optimized
+ } else {
+ Linebreaks::Simple
+ }
+ });
+
+ match linebreaks {
Linebreaks::Simple => linebreak_simple(p, world, width),
Linebreaks::Optimized => linebreak_optimized(p, world, width),
}
diff --git a/src/library/text/quotes.rs b/library/src/text/quotes.rs
index 0a22646a..ab4d3f9d 100644
--- a/src/library/text/quotes.rs
+++ b/library/src/text/quotes.rs
@@ -1,5 +1,6 @@
+use typst::syntax::is_newline;
+
use super::{Lang, Region};
-use crate::syntax::is_newline;
/// State machine for smart quote subtitution.
#[derive(Debug, Clone)]
diff --git a/src/library/text/raw.rs b/library/src/text/raw.rs
index 0c769636..31f1517e 100644
--- a/src/library/text/raw.rs
+++ b/library/src/text/raw.rs
@@ -4,10 +4,11 @@ use syntect::highlighting::{
Color, FontStyle, Style, StyleModifier, Theme, ThemeItem, ThemeSettings,
};
use syntect::parsing::SyntaxSet;
+use typst::syntax;
use super::{FontFamily, Hyphenate, LinebreakNode, TextNode};
-use crate::library::layout::{BlockNode, BlockSpacing};
-use crate::library::prelude::*;
+use crate::layout::{BlockNode, BlockSpacing};
+use crate::prelude::*;
/// Monospaced text with optional syntax highlighting.
#[derive(Debug, Hash)]
@@ -70,12 +71,12 @@ impl Show for RawNode {
let mut realized = if matches!(lang.as_deref(), Some("typ" | "typst" | "typc")) {
let root = match lang.as_deref() {
- Some("typc") => crate::syntax::parse_code(&self.text),
- _ => crate::syntax::parse(&self.text),
+ Some("typc") => syntax::parse_code(&self.text),
+ _ => syntax::parse(&self.text),
};
let mut seq = vec![];
- crate::syntax::highlight::highlight_themed(&root, &THEME, |range, style| {
+ syntax::highlight::highlight_themed(&root, &THEME, |range, style| {
seq.push(styled(&self.text[range], foreground, style));
});
@@ -108,7 +109,7 @@ impl Show for RawNode {
let mut map = StyleMap::new();
map.set(TextNode::OVERHANG, false);
- map.set(TextNode::HYPHENATE, Smart::Custom(Hyphenate(false)));
+ map.set(TextNode::HYPHENATE, Hyphenate(Smart::Custom(false)));
map.set(TextNode::SMART_QUOTES, false);
Ok(realized.styled_with_map(map))
diff --git a/src/library/text/shaping.rs b/library/src/text/shaping.rs
index 9ac3db1c..32143862 100644
--- a/src/library/text/shaping.rs
+++ b/library/src/text/shaping.rs
@@ -2,11 +2,11 @@ use std::ops::Range;
use std::str::FromStr;
use rustybuzz::{Feature, Tag, UnicodeBuffer};
+use typst::font::{Font, FontVariant};
+use typst::util::SliceExt;
use super::*;
-use crate::font::{Font, FontVariant};
-use crate::library::prelude::*;
-use crate::util::SliceExt;
+use crate::prelude::*;
/// The result of shaping text.
///
@@ -128,7 +128,7 @@ impl<'a> ShapedText<'a> {
// Apply line decorations.
for deco in &decos {
- decorate(&mut frame, &deco, &text, shift, pos, width);
+ decorate(&mut frame, deco, &text, shift, pos, width);
}
frame.insert(text_layer, pos, Element::Text(text));
@@ -339,7 +339,7 @@ pub fn shape<'a>(
};
if !text.is_empty() {
- shape_segment(&mut ctx, 0, &text, families(styles));
+ shape_segment(&mut ctx, 0, text, families(styles));
}
track_and_space(&mut ctx);
@@ -570,6 +570,7 @@ fn families(styles: StyleChain) -> impl Iterator<Item = &str> + Clone {
let tail = if styles.get(TextNode::FALLBACK) { FALLBACKS } else { &[] };
styles
.get(TextNode::FAMILY)
+ .0
.iter()
.map(|family| family.as_str())
.chain(tail.iter().copied())
@@ -635,7 +636,7 @@ fn tags(styles: StyleChain) -> Vec<Feature> {
feat(b"frac", 1);
}
- for (tag, value) in styles.get(TextNode::FEATURES) {
+ for (tag, value) in styles.get(TextNode::FEATURES).0 {
tags.push(Feature::new(tag, value, ..))
}
diff --git a/src/library/text/shift.rs b/library/src/text/shift.rs
index c3cf8b03..e5f142dd 100644
--- a/src/library/text/shift.rs
+++ b/library/src/text/shift.rs
@@ -1,7 +1,8 @@
+use typst::model::SequenceNode;
+use typst::util::EcoString;
+
use super::{variant, SpaceNode, TextNode, TextSize};
-use crate::library::prelude::*;
-use crate::model::SequenceNode;
-use crate::util::EcoString;
+use crate::prelude::*;
/// Sub or superscript text.
///
@@ -98,7 +99,7 @@ fn search_text(content: &Content, mode: ScriptKind) -> Option<EcoString> {
/// Checks whether the first retrievable family contains all code points of the
/// given string.
fn is_shapable(world: Tracked<dyn World>, text: &str, styles: StyleChain) -> bool {
- for family in styles.get(TextNode::FAMILY).iter() {
+ for family in styles.get(TextNode::FAMILY).0.iter() {
if let Some(font) = world
.book()
.select(family.as_str(), variant(styles))
diff --git a/src/library/utility/color.rs b/library/src/utility/color.rs
index a5a5704d..8bb12334 100644
--- a/src/library/utility/color.rs
+++ b/library/src/utility/color.rs
@@ -1,6 +1,6 @@
use std::str::FromStr;
-use crate::library::prelude::*;
+use crate::prelude::*;
/// Create a grayscale color.
pub fn luma(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
diff --git a/src/library/utility/data.rs b/library/src/utility/data.rs
index 0cff42c1..3edade55 100644
--- a/src/library/utility/data.rs
+++ b/library/src/utility/data.rs
@@ -1,7 +1,8 @@
use std::fmt::Write;
-use crate::diag::format_xml_like_error;
-use crate::library::prelude::*;
+use typst::diag::format_xml_like_error;
+
+use crate::prelude::*;
/// Read structured data from a CSV file.
pub fn csv(vm: &mut Vm, args: &mut Args) -> SourceResult<Value> {
diff --git a/src/library/utility/math.rs b/library/src/utility/math.rs
index 7c3af490..dd37e8e7 100644
--- a/src/library/utility/math.rs
+++ b/library/src/utility/math.rs
@@ -1,6 +1,6 @@
use std::cmp::Ordering;
-use crate::library::prelude::*;
+use crate::prelude::*;
/// Convert a value to an integer.
pub fn int(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
diff --git a/src/library/utility/mod.rs b/library/src/utility/mod.rs
index 2d637d29..402944cd 100644
--- a/src/library/utility/mod.rs
+++ b/library/src/utility/mod.rs
@@ -11,10 +11,10 @@ pub use math::*;
pub use string::*;
use comemo::Track;
+use typst::model::{Eval, Route, Scopes, Vm};
+use typst::syntax::Source;
-use crate::library::prelude::*;
-use crate::model::{Eval, Route, Scopes, Vm};
-use crate::syntax::Source;
+use crate::prelude::*;
/// The name of a value's type.
pub fn type_(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
@@ -39,7 +39,7 @@ pub fn eval(vm: &mut Vm, args: &mut Args) -> SourceResult<Value> {
let ast = source.ast()?;
// Evaluate the source.
- let std = &vm.world.config().std;
+ let std = &vm.world.config().scope;
let scopes = Scopes::new(Some(std));
let route = Route::default();
let mut sub = Vm::new(vm.world, route.track(), None, scopes);
diff --git a/src/library/utility/string.rs b/library/src/utility/string.rs
index 66f127d1..ed444d35 100644
--- a/src/library/utility/string.rs
+++ b/library/src/utility/string.rs
@@ -1,5 +1,6 @@
-use crate::library::prelude::*;
-use crate::model::Regex;
+use typst::model::Regex;
+
+use crate::prelude::*;
/// The string representation of a value.
pub fn repr(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
diff --git a/macros/src/lib.rs b/macros/src/lib.rs
index 823dbdc8..d3b3b30a 100644
--- a/macros/src/lib.rs
+++ b/macros/src/lib.rs
@@ -15,7 +15,7 @@ pub fn capability(_: TokenStream, item: TokenStream) -> TokenStream {
let name = &item_trait.ident;
quote! {
#item_trait
- impl crate::model::Capability for dyn #name {}
+ impl ::typst::model::Capability for dyn #name {}
}.into()
}
@@ -70,7 +70,7 @@ fn expand_node(
fn construct(
_: &mut model::Vm,
_: &mut model::Args,
- ) -> crate::diag::SourceResult<model::Content> {
+ ) -> typst::diag::SourceResult<model::Content> {
unimplemented!()
}
}
@@ -84,7 +84,7 @@ fn expand_node(
let checks = items.iter().map(|cap| {
quote! {
if id == TypeId::of::<dyn #cap>() {
- return Some(unsafe { crate::util::fat::vtable(self as &dyn #cap) });
+ return Some(unsafe { typst::util::fat::vtable(self as &dyn #cap) });
}
}
});
@@ -101,10 +101,10 @@ fn expand_node(
Ok(quote! {
#[allow(non_snake_case)]
mod #module {
- use std::any::TypeId;
- use std::marker::PhantomData;
- use once_cell::sync::Lazy;
- use crate::model;
+ use ::std::any::TypeId;
+ use ::std::marker::PhantomData;
+ use ::once_cell::sync::Lazy;
+ use ::typst::model;
use super::*;
#impl_block
@@ -370,7 +370,7 @@ fn generate_set(
) -> syn::ImplItemMethod {
let user = user.map(|method| {
let block = &method.block;
- quote! { (|| -> crate::diag::SourceResult<()> { #block; Ok(()) } )()?; }
+ quote! { (|| -> typst::diag::SourceResult<()> { #block; Ok(()) } )()?; }
});
let mut shorthands = vec![];
@@ -379,7 +379,7 @@ fn generate_set(
.filter(|p| !p.skip)
.map(|property| {
let name = &property.name;
- let string = name.to_string().replace("_", "-").to_lowercase();
+ let string = name.to_string().replace('_', "-").to_lowercase();
let value = if let Some(short) = &property.shorthand {
match short {
@@ -409,7 +409,7 @@ fn generate_set(
fn set(
args: &mut model::Args,
constructor: bool,
- ) -> crate::diag::SourceResult<model::StyleMap> {
+ ) -> typst::diag::SourceResult<model::StyleMap> {
let mut styles = model::StyleMap::new();
#user
#(#bindings)*
diff --git a/src/diag.rs b/src/diag.rs
index f4725f00..0a532713 100644
--- a/src/diag.rs
+++ b/src/diag.rs
@@ -10,33 +10,41 @@ use std::string::FromUtf8Error;
use comemo::Tracked;
use crate::syntax::{ErrorPos, Span, Spanned};
-use crate::util::EcoString;
+use crate::util::{format_eco, EcoString};
use crate::World;
/// Early-return with a [`SourceError`].
#[macro_export]
-macro_rules! bail {
+#[doc(hidden)]
+macro_rules! __bail {
($error:expr) => {
return Err(Box::new(vec![$error]))
};
($($tts:tt)*) => {
- $crate::bail!($crate::error!($($tts)*))
+ $crate::diag::bail!($crate::diag::error!($($tts)*))
};
}
+#[doc(inline)]
+pub use crate::__bail as bail;
+
/// Construct a [`SourceError`].
#[macro_export]
-macro_rules! error {
+#[doc(hidden)]
+macro_rules! __error {
($span:expr, $message:expr $(,)?) => {
$crate::diag::SourceError::new($span, $message)
};
($span:expr, $fmt:expr, $($arg:expr),+ $(,)?) => {
- $crate::error!($span, format!($fmt, $($arg),+))
+ $crate::diag::error!($span, format!($fmt, $($arg),+))
};
}
+#[doc(inline)]
+pub use crate::__error as error;
+
/// A result that can carry multiple source errors.
pub type SourceResult<T> = Result<T, Box<Vec<SourceError>>>;
diff --git a/src/export/pdf/font.rs b/src/export/pdf/font.rs
index 3398478a..075e3490 100644
--- a/src/export/pdf/font.rs
+++ b/src/export/pdf/font.rs
@@ -5,7 +5,7 @@ use pdf_writer::{Filter, Finish, Name, Rect, Str};
use ttf_parser::{name_id, GlyphId, Tag};
use super::{deflate, EmExt, PdfContext, RefExt};
-use crate::util::SliceExt;
+use crate::util::{format_eco, SliceExt};
/// Embed all used fonts into the PDF.
pub fn write_fonts(ctx: &mut PdfContext) {
diff --git a/src/export/pdf/outline.rs b/src/export/pdf/outline.rs
index 73dd9e96..add167b4 100644
--- a/src/export/pdf/outline.rs
+++ b/src/export/pdf/outline.rs
@@ -71,7 +71,7 @@ pub fn write_outline_item(
let current_child = Ref::new(id.get() + 1);
outline.first(current_child);
outline.last(Ref::new(next_ref.get() - 1));
- outline.count(-1 * node.children.len() as i32);
+ outline.count(-(node.children.len() as i32));
}
outline.title(TextStr(&node.heading.content));
diff --git a/src/export/pdf/page.rs b/src/export/pdf/page.rs
index 7fbf7833..3167989c 100644
--- a/src/export/pdf/page.rs
+++ b/src/export/pdf/page.rs
@@ -12,6 +12,7 @@ use crate::geom::{
Transform,
};
use crate::image::Image;
+use crate::util::format_eco;
/// Construct page objects.
pub fn construct_pages(ctx: &mut PdfContext, frames: &[Frame]) {
diff --git a/src/export/render.rs b/src/export/render.rs
index df8f512f..41fff863 100644
--- a/src/export/render.rs
+++ b/src/export/render.rs
@@ -312,8 +312,10 @@ fn render_shape(
if let Some(Stroke { paint, thickness }) = shape.stroke {
let paint = paint.into();
- let mut stroke = sk::Stroke::default();
- stroke.width = thickness.to_f32();
+ let stroke = sk::Stroke {
+ width: thickness.to_f32(),
+ ..Default::default()
+ };
canvas.stroke_path(&path, &paint, &stroke, ts, mask);
}
@@ -364,14 +366,16 @@ fn render_image(
let scale_x = view_width / pixmap.width() as f32;
let scale_y = view_height / pixmap.height() as f32;
- let mut paint = sk::Paint::default();
- paint.shader = sk::Pattern::new(
- pixmap.as_ref(),
- sk::SpreadMode::Pad,
- sk::FilterQuality::Nearest,
- 1.0,
- sk::Transform::from_scale(scale_x, scale_y),
- );
+ let paint = sk::Paint {
+ shader: sk::Pattern::new(
+ pixmap.as_ref(),
+ sk::SpreadMode::Pad,
+ sk::FilterQuality::Nearest,
+ 1.0,
+ sk::Transform::from_scale(scale_x, scale_y),
+ ),
+ ..Default::default()
+ };
let rect = sk::Rect::from_xywh(0.0, 0.0, view_width, view_height)?;
canvas.fill_rect(rect, &paint, ts, mask);
diff --git a/src/font/book.rs b/src/font/book.rs
index d900d3c4..49225c04 100644
--- a/src/font/book.rs
+++ b/src/font/book.rs
@@ -170,7 +170,7 @@ bitflags::bitflags! {
impl FontInfo {
/// Compute metadata for all fonts in the given data.
- pub fn from_data<'a>(data: &'a [u8]) -> impl Iterator<Item = FontInfo> + 'a {
+ pub fn from_data(data: &[u8]) -> impl Iterator<Item = FontInfo> + '_ {
let count = ttf_parser::fonts_in_collection(data).unwrap_or(1);
(0 .. count).filter_map(move |index| {
let ttf = ttf_parser::Face::parse(data, index).ok()?;
diff --git a/src/font/variant.rs b/src/font/variant.rs
index 9e16afc8..085f2ae7 100644
--- a/src/font/variant.rs
+++ b/src/font/variant.rs
@@ -111,7 +111,7 @@ impl FontWeight {
/// The absolute number distance between this and another font weight.
pub fn distance(self, other: Self) -> u16 {
- (self.0 as i16 - other.0 as i16).abs() as u16
+ (self.0 as i16 - other.0 as i16).unsigned_abs()
}
}
diff --git a/src/frame.rs b/src/frame.rs
index e85256f5..2eac6879 100644
--- a/src/frame.rs
+++ b/src/frame.rs
@@ -2,6 +2,7 @@
use std::fmt::{self, Debug, Formatter, Write};
use std::num::NonZeroUsize;
+use std::str::FromStr;
use std::sync::Arc;
use crate::font::Font;
@@ -9,7 +10,7 @@ use crate::geom::{
Abs, Align, Axes, Dir, Em, Numeric, Paint, Point, Shape, Size, Transform,
};
use crate::image::Image;
-use crate::model::{Dict, Value};
+use crate::model::{dict, Dict, Value};
use crate::util::EcoString;
/// A finished layout with elements at fixed positions.
@@ -396,7 +397,7 @@ pub struct Glyph {
pub c: char,
}
-/// A code for a natural language.
+/// An identifier for a natural language.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Lang([u8; 3], u8);
@@ -404,19 +405,6 @@ impl Lang {
/// The code for the english language.
pub const ENGLISH: Self = Self(*b"en ", 2);
- /// Construct a language from a two- or three-byte ISO 639-1/2/3 code.
- pub fn from_str(iso: &str) -> Option<Self> {
- let len = iso.len();
- if matches!(len, 2 ..= 3) && iso.is_ascii() {
- let mut bytes = [b' '; 3];
- bytes[.. len].copy_from_slice(iso.as_bytes());
- bytes.make_ascii_lowercase();
- Some(Self(bytes, len as u8))
- } else {
- None
- }
- }
-
/// Return the language code as an all lowercase string slice.
pub fn as_str(&self) -> &str {
std::str::from_utf8(&self.0[.. usize::from(self.1)]).unwrap_or_default()
@@ -432,28 +420,49 @@ impl Lang {
}
}
-/// A code for a region somewhere in the world.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
-pub struct Region([u8; 2]);
+impl FromStr for Lang {
+ type Err = &'static str;
-impl Region {
- /// Construct a region from its two-byte ISO 3166-1 alpha-2 code.
- pub fn from_str(iso: &str) -> Option<Self> {
- if iso.is_ascii() {
- let mut bytes: [u8; 2] = iso.as_bytes().try_into().ok()?;
- bytes.make_ascii_uppercase();
- Some(Self(bytes))
+ /// Construct a language from a two- or three-byte ISO 639-1/2/3 code.
+ fn from_str(iso: &str) -> Result<Self, Self::Err> {
+ let len = iso.len();
+ if matches!(len, 2 ..= 3) && iso.is_ascii() {
+ let mut bytes = [b' '; 3];
+ bytes[.. len].copy_from_slice(iso.as_bytes());
+ bytes.make_ascii_lowercase();
+ Ok(Self(bytes, len as u8))
} else {
- None
+ Err("expected two or three letter language code (ISO 639-1/2/3)")
}
}
+}
+/// An identifier for a region somewhere in the world.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub struct Region([u8; 2]);
+
+impl Region {
/// Return the region code as an all uppercase string slice.
pub fn as_str(&self) -> &str {
std::str::from_utf8(&self.0).unwrap_or_default()
}
}
+impl FromStr for Region {
+ type Err = &'static str;
+
+ /// Construct a region from its two-byte ISO 3166-1 alpha-2 code.
+ fn from_str(iso: &str) -> Result<Self, Self::Err> {
+ if iso.len() == 2 && iso.is_ascii() {
+ let mut bytes: [u8; 2] = iso.as_bytes().try_into().unwrap();
+ bytes.make_ascii_uppercase();
+ Ok(Self(bytes))
+ } else {
+ Err("expected two letter region code (ISO 3166-1 alpha-2)")
+ }
+ }
+}
+
/// A link destination.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum Destination {
@@ -529,9 +538,9 @@ impl Role {
pub fn is_weak(self) -> bool {
// In Typst, all text is in a paragraph, so paragraph isn't very
// descriptive.
- match self {
- Self::Paragraph | Self::GenericBlock | Self::GenericInline => true,
- _ => false,
- }
+ matches!(
+ self,
+ Self::Paragraph | Self::GenericBlock | Self::GenericInline
+ )
}
}
diff --git a/src/geom/align.rs b/src/geom/align.rs
index a7ee2763..25409e3a 100644
--- a/src/geom/align.rs
+++ b/src/geom/align.rs
@@ -78,3 +78,40 @@ impl Debug for Align {
})
}
}
+
+/// The generic alignment representation.
+#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub enum GenAlign {
+ /// Align at the start side of the text direction.
+ Start,
+ /// Align at the end side of the text direction.
+ End,
+ /// Align at a specific alignment.
+ Specific(Align),
+}
+
+impl GenAlign {
+ /// The axis this alignment belongs to.
+ pub const fn axis(self) -> Axis {
+ match self {
+ Self::Start | Self::End => Axis::X,
+ Self::Specific(align) => align.axis(),
+ }
+ }
+}
+
+impl From<Align> for GenAlign {
+ fn from(align: Align) -> Self {
+ Self::Specific(align)
+ }
+}
+
+impl Debug for GenAlign {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ match self {
+ Self::Start => f.pad("start"),
+ Self::End => f.pad("end"),
+ Self::Specific(align) => align.fmt(f),
+ }
+ }
+}
diff --git a/src/geom/angle.rs b/src/geom/angle.rs
index 33a864ca..8e80d72b 100644
--- a/src/geom/angle.rs
+++ b/src/geom/angle.rs
@@ -178,6 +178,6 @@ mod tests {
#[test]
fn test_angle_unit_conversion() {
assert!((Angle::rad(2.0 * PI).to_deg() - 360.0) < 1e-4);
- assert!((Angle::deg(45.0).to_rad() - 0.7854) < 1e-4);
+ assert!((Angle::deg(45.0).to_rad() - std::f64::consts::FRAC_PI_4) < 1e-4);
}
}
diff --git a/src/geom/mod.rs b/src/geom/mod.rs
index 6e2b8f9b..c1469b26 100644
--- a/src/geom/mod.rs
+++ b/src/geom/mod.rs
@@ -21,6 +21,7 @@ mod rounded;
mod scalar;
mod sides;
mod size;
+mod stroke;
mod transform;
pub use abs::*;
@@ -42,6 +43,7 @@ pub use rounded::*;
pub use scalar::*;
pub use sides::*;
pub use size::*;
+pub use stroke::*;
pub use transform::*;
use std::cmp::Ordering;
diff --git a/src/geom/paint.rs b/src/geom/paint.rs
index c5bbefa4..b07f09af 100644
--- a/src/geom/paint.rs
+++ b/src/geom/paint.rs
@@ -388,24 +388,6 @@ impl From<CmykColor> for Color {
}
}
-/// A stroke of a geometric shape.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub struct Stroke {
- /// The stroke's paint.
- pub paint: Paint,
- /// The stroke's thickness.
- pub thickness: Abs,
-}
-
-impl Default for Stroke {
- fn default() -> Self {
- Self {
- paint: Paint::Solid(Color::BLACK.into()),
- thickness: Abs::pt(1.0),
- }
- }
-}
-
/// Convert to the closest u8.
fn round_u8(value: f64) -> u8 {
value.round() as u8
diff --git a/src/geom/stroke.rs b/src/geom/stroke.rs
new file mode 100644
index 00000000..eae43c24
--- /dev/null
+++ b/src/geom/stroke.rs
@@ -0,0 +1,61 @@
+use super::*;
+use crate::model::Smart;
+
+/// A stroke of a geometric shape.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub struct Stroke {
+ /// The stroke's paint.
+ pub paint: Paint,
+ /// The stroke's thickness.
+ pub thickness: Abs,
+}
+
+impl Default for Stroke {
+ fn default() -> Self {
+ Self {
+ paint: Paint::Solid(Color::BLACK),
+ thickness: Abs::pt(1.0),
+ }
+ }
+}
+
+/// A partial stroke representation.
+///
+/// In this representation, both fields are optional so that you can pass either
+/// just a paint (`red`), just a thickness (`0.1em`) or both (`2pt + red`) where
+/// this is expected.
+#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
+pub struct PartialStroke<T = Length> {
+ /// The stroke's paint.
+ pub paint: Smart<Paint>,
+ /// The stroke's thickness.
+ pub thickness: Smart<T>,
+}
+
+impl PartialStroke<Abs> {
+ /// Unpack the stroke, filling missing fields from the `default`.
+ pub fn unwrap_or(self, default: Stroke) -> Stroke {
+ Stroke {
+ paint: self.paint.unwrap_or(default.paint),
+ thickness: self.thickness.unwrap_or(default.thickness),
+ }
+ }
+
+ /// Unpack the stroke, filling missing fields with the default values.
+ pub fn unwrap_or_default(self) -> Stroke {
+ self.unwrap_or(Stroke::default())
+ }
+}
+
+impl<T: Debug> Debug for PartialStroke<T> {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ match (self.paint, &self.thickness) {
+ (Smart::Custom(paint), Smart::Custom(thickness)) => {
+ write!(f, "{thickness:?} + {paint:?}")
+ }
+ (Smart::Custom(paint), Smart::Auto) => paint.fmt(f),
+ (Smart::Auto, Smart::Custom(thickness)) => thickness.fmt(f),
+ (Smart::Auto, Smart::Auto) => f.pad("<stroke>"),
+ }
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 051a58f3..dbef46e7 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -26,9 +26,7 @@
//! [content]: model::Content
//! [PDF]: export::pdf
-#![allow(clippy::len_without_is_empty)]
-#![allow(clippy::or_fun_call)]
-#![allow(clippy::try_err)]
+extern crate self as typst;
#[macro_use]
pub mod util;
@@ -42,10 +40,8 @@ pub mod export;
pub mod font;
pub mod frame;
pub mod image;
-pub mod library;
pub mod syntax;
-use std::num::NonZeroUsize;
use std::path::{Path, PathBuf};
use comemo::{Prehashed, Track};
@@ -53,9 +49,9 @@ use comemo::{Prehashed, Track};
use crate::diag::{FileResult, SourceResult};
use crate::font::{Font, FontBook};
use crate::frame::Frame;
-use crate::model::{Content, Route, Scope, StyleMap};
+use crate::model::{LangItems, Route, Scope, StyleMap};
use crate::syntax::{Source, SourceId};
-use crate::util::{Buffer, EcoString};
+use crate::util::Buffer;
/// Typeset a source file into a collection of layouted frames.
///
@@ -66,9 +62,10 @@ pub fn typeset(
world: &(dyn World + 'static),
main: SourceId,
) -> SourceResult<Vec<Frame>> {
+ crate::model::set_lang_items(world.config().items);
let route = Route::default();
let module = model::eval(world.track(), route.track(), main)?;
- library::layout::Layout::layout(&module.content, world.track())
+ item!(root)(world.track(), &module.content)
}
/// The environment in which typesetting occurs.
@@ -97,49 +94,11 @@ pub trait World {
#[derive(Debug, Clone, Hash)]
pub struct Config {
/// The compilation root, relative to which absolute paths are.
- ///
- /// Default: Empty path.
pub root: PathBuf,
/// The scope containing definitions that are available everywhere.
- ///
- /// Default: Typst's standard library.
- pub std: Scope,
- /// Defines which standard library items fulfill which syntactical roles.
- ///
- /// Default: Typst's standard library's language map.
- pub items: LangItems,
+ pub scope: Scope,
/// The default properties for page size, font selection and so on.
- ///
- /// Default: Empty style map.
pub styles: StyleMap,
-}
-
-impl Default for Config {
- fn default() -> Self {
- Self {
- root: PathBuf::new(),
- std: library::scope(),
- items: library::items(),
- styles: StyleMap::new(),
- }
- }
-}
-
-/// Definition of certain standard library items the language is aware of.
-#[derive(Debug, Clone, Hash)]
-pub struct LangItems {
- pub space: fn() -> Content,
- pub linebreak: fn(justify: bool) -> Content,
- pub text: fn(text: EcoString) -> Content,
- pub smart_quote: fn(double: bool) -> Content,
- pub parbreak: fn() -> Content,
- pub strong: fn(body: Content) -> Content,
- pub emph: fn(body: Content) -> Content,
- pub raw: fn(text: EcoString, tag: Option<EcoString>, block: bool) -> Content,
- pub link: fn(label: EcoString) -> Content,
- pub ref_: fn(target: EcoString) -> Content,
- pub heading: fn(level: NonZeroUsize, body: Content) -> Content,
- pub list_item: fn(body: Content) -> Content,
- pub enum_item: fn(number: Option<usize>, body: Content) -> Content,
- pub desc_item: fn(term: Content, body: Content) -> Content,
+ /// Defines which standard library items fulfill which syntactical roles.
+ pub items: LangItems,
}
diff --git a/src/library/math/frac.rs b/src/library/math/frac.rs
deleted file mode 100644
index 791fd19a..00000000
--- a/src/library/math/frac.rs
+++ /dev/null
@@ -1,17 +0,0 @@
-use super::*;
-use crate::library::prelude::*;
-
-/// A fraction in a mathematical formula.
-#[derive(Debug, Hash)]
-pub struct FracNode {
- /// The numerator.
- pub num: MathNode,
- /// The denominator.
- pub denom: MathNode,
-}
-
-impl Texify for FracNode {
- fn texify(&self) -> EcoString {
- format_eco!("\\frac{{{}}}{{{}}}", self.num.texify(), self.denom.texify())
- }
-}
diff --git a/src/library/math/mod.rs b/src/library/math/mod.rs
deleted file mode 100644
index 5bb5054d..00000000
--- a/src/library/math/mod.rs
+++ /dev/null
@@ -1,295 +0,0 @@
-//! Mathematical formulas.
-
-mod frac;
-mod script;
-
-pub use frac::*;
-pub use script::*;
-
-use rex::error::{Error, LayoutError};
-use rex::font::FontContext;
-use rex::layout::{LayoutSettings, Style};
-use rex::parser::color::RGBA;
-use rex::render::{Backend, Cursor, Renderer};
-
-use crate::font::Font;
-use crate::library::layout::BlockSpacing;
-use crate::library::prelude::*;
-use crate::library::text::{variant, FontFamily, TextNode};
-
-/// A piece of a mathematical formula.
-#[derive(Debug, Clone, Hash)]
-pub enum MathNode {
- /// Whitespace.
- Space,
- /// A forced line break.
- Linebreak,
- /// An atom in a math formula: `x`, `+`, `12`.
- Atom(EcoString),
- /// A base with optional sub and superscripts: `a_1^2`.
- Script(Arc<ScriptNode>),
- /// A fraction: `x/2`.
- Frac(Arc<FracNode>),
- /// A numbered math alignment indicator: `&`, `&&`.
- Align(usize),
- /// A row of mathematical material.
- Row(Arc<Vec<MathNode>>, Span),
-}
-
-#[node(Show, LayoutInline)]
-impl MathNode {
- /// The math font family.
- #[property(referenced)]
- pub const FAMILY: FontFamily = FontFamily::new("NewComputerModernMath");
- /// The spacing above display math.
- #[property(resolve, shorthand(around))]
- pub const ABOVE: Option<BlockSpacing> = Some(Ratio::one().into());
- /// The spacing below display math.
- #[property(resolve, shorthand(around))]
- pub const BELOW: Option<BlockSpacing> = Some(Ratio::one().into());
-
- fn construct(_: &mut Vm, _: &mut Args) -> SourceResult<Content> {
- todo!()
- }
-}
-
-impl MathNode {
- /// Strip parentheses from the node.
- pub fn unparen(self) -> Self {
- if let Self::Row(row, span) = &self {
- if let [MathNode::Atom(l), .., MathNode::Atom(r)] = row.as_slice() {
- if l == "(" && r == ")" {
- let inner = row[1 .. row.len() - 1].to_vec();
- return Self::Row(Arc::new(inner), *span);
- }
- }
- }
-
- self
- }
-
- /// Whether the formula is display level.
- pub fn display(&self) -> bool {
- if let Self::Row(row, _) = self {
- matches!(row.as_slice(), [MathNode::Space, .., MathNode::Space])
- } else {
- false
- }
- }
-}
-
-impl Show for MathNode {
- fn unguard_parts(&self, _: Selector) -> Content {
- self.clone().pack()
- }
-
- fn field(&self, _: &str) -> Option<Value> {
- None
- }
-
- fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
- Ok(if self.display() {
- self.clone().pack().aligned(Axes::with_x(Some(Align::Center.into())))
- } else {
- self.clone().pack()
- })
- }
-
- fn finalize(
- &self,
- _: Tracked<dyn World>,
- styles: StyleChain,
- realized: Content,
- ) -> SourceResult<Content> {
- Ok(if self.display() {
- realized.spaced(styles.get(Self::ABOVE), styles.get(Self::BELOW))
- } else {
- realized
- })
- }
-}
-
-impl LayoutInline for MathNode {
- fn layout_inline(
- &self,
- world: Tracked<dyn World>,
- _: &Regions,
- styles: StyleChain,
- ) -> SourceResult<Vec<Frame>> {
- let style = if self.display() { Style::Display } else { Style::Text };
- let span = match self {
- &Self::Row(_, span) => span,
- _ => Span::detached(),
- };
-
- Ok(vec![layout_tex(world, self, span, style, styles)?])
- }
-}
-
-/// Layout a TeX formula into a frame.
-fn layout_tex(
- world: Tracked<dyn World>,
- node: &dyn Texify,
- span: Span,
- style: Style,
- styles: StyleChain,
-) -> SourceResult<Frame> {
- let tex = node.texify();
-
- // Load the font.
- let font = world
- .book()
- .select(styles.get(MathNode::FAMILY).as_str(), variant(styles))
- .and_then(|id| world.font(id))
- .ok_or("failed to find math font")
- .at(span)?;
-
- // Prepare the font context.
- let ctx = font
- .math()
- .map(|math| FontContext::new(font.ttf(), math))
- .ok_or("font is not suitable for math")
- .at(span)?;
-
- // Layout the formula.
- let em = styles.get(TextNode::SIZE);
- let settings = LayoutSettings::new(&ctx, em.to_pt(), style);
- let renderer = Renderer::new();
- let layout = renderer
- .layout(&tex, settings)
- .map_err(|err| match err {
- Error::Parse(err) => err.to_string(),
- Error::Layout(LayoutError::Font(err)) => err.to_string(),
- })
- .at(span)?;
-
- // Determine the metrics.
- let (x0, y0, x1, y1) = renderer.size(&layout);
- let width = Abs::pt(x1 - x0);
- let mut top = Abs::pt(y1);
- let mut bottom = Abs::pt(-y0);
- if style != Style::Display {
- let metrics = font.metrics();
- top = styles.get(TextNode::TOP_EDGE).resolve(styles, metrics);
- bottom = -styles.get(TextNode::BOTTOM_EDGE).resolve(styles, metrics);
- };
-
- // Prepare a frame rendering backend.
- let size = Size::new(width, top + bottom);
- let mut backend = FrameBackend {
- frame: {
- let mut frame = Frame::new(size);
- frame.set_baseline(top);
- frame.apply_role(Role::Formula);
- frame
- },
- baseline: top,
- font: font.clone(),
- fill: styles.get(TextNode::FILL),
- lang: styles.get(TextNode::LANG),
- colors: vec![],
- };
-
- // Render into the frame.
- renderer.render(&layout, &mut backend);
- Ok(backend.frame)
-}
-
-/// A ReX rendering backend that renders into a frame.
-struct FrameBackend {
- frame: Frame,
- baseline: Abs,
- font: Font,
- 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(Abs::pt(cursor.x), self.baseline + Abs::pt(cursor.y))
- }
-}
-
-impl Backend for FrameBackend {
- fn symbol(&mut self, pos: Cursor, gid: u16, scale: f64) {
- self.frame.push(
- self.transform(pos),
- Element::Text(Text {
- font: self.font.clone(),
- size: Abs::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(Abs::pt(width), Abs::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();
- }
-}
-
-/// Turn a math node into TeX math code.
-trait Texify {
- /// Perform the conversion.
- fn texify(&self) -> EcoString;
-}
-
-impl Texify for MathNode {
- fn texify(&self) -> EcoString {
- match self {
- Self::Space => "".into(),
- Self::Linebreak => r"\\".into(),
- Self::Atom(atom) => atom.chars().map(escape_char).collect(),
- Self::Script(script) => script.texify(),
- Self::Frac(frac) => frac.texify(),
- Self::Align(_) => "".into(),
- Self::Row(row, _) => row.iter().map(Texify::texify).collect(),
- }
- }
-}
-
-#[rustfmt::skip]
-fn escape_char(c: char) -> EcoString {
- match c {
- '{' | '}' | '%' | '&' | '$' | '#' => format_eco!(" \\{c} "),
- 'a' ..= 'z' | 'A' ..= 'Z' | '0' ..= '9' | 'Α' ..= 'Ω' | 'α' ..= 'ω' |
- '*' | '+' | '-' | '[' | '(' | ']' | ')' | '?' | '!' | '=' | '<' | '>' |
- ':' | ',' | ';' | '|' | '/' | '@' | '.' | '"' => c.into(),
- c => unicode_math::SYMBOLS
- .iter()
- .find(|sym| sym.codepoint == c)
- .map(|sym| format_eco!("\\{} ", sym.name))
- .unwrap_or_default(),
- }
-}
diff --git a/src/library/math/script.rs b/src/library/math/script.rs
deleted file mode 100644
index 09f52164..00000000
--- a/src/library/math/script.rs
+++ /dev/null
@@ -1,31 +0,0 @@
-use std::fmt::Write;
-
-use super::*;
-use crate::library::prelude::*;
-
-/// A sub- and/or superscript in a mathematical formula.
-#[derive(Debug, Hash)]
-pub struct ScriptNode {
- /// The base.
- pub base: MathNode,
- /// The subscript.
- pub sub: Option<MathNode>,
- /// The superscript.
- pub sup: Option<MathNode>,
-}
-
-impl Texify for ScriptNode {
- fn texify(&self) -> EcoString {
- let mut tex = self.base.texify();
-
- if let Some(sub) = &self.sub {
- write!(tex, "_{{{}}}", sub.texify()).unwrap();
- }
-
- if let Some(sup) = &self.sup {
- write!(tex, "^{{{}}}", sup.texify()).unwrap();
- }
-
- tex
- }
-}
diff --git a/src/library/prelude.rs b/src/library/prelude.rs
deleted file mode 100644
index 66e35e68..00000000
--- a/src/library/prelude.rs
+++ /dev/null
@@ -1,27 +0,0 @@
-//! Helpful imports for creating library functionality.
-
-pub use std::fmt::{self, Debug, Formatter};
-pub use std::hash::Hash;
-pub use std::io;
-pub use std::num::NonZeroUsize;
-pub use std::sync::Arc;
-
-pub use comemo::Tracked;
-
-pub use super::ext::{ContentExt, StyleMapExt};
-pub use super::layout::{Layout, LayoutBlock, LayoutInline, Regions};
-pub use super::text::TextNode;
-pub use super::{RawAlign, RawStroke};
-pub use crate::diag::{
- with_alternative, At, FileError, FileResult, SourceError, SourceResult, StrResult,
-};
-pub use crate::frame::*;
-pub use crate::geom::*;
-pub use crate::model::{
- capability, node, Arg, Args, Array, Capability, Cast, Content, Dict, Dynamic, Fold,
- Func, Key, Node, Resolve, Scope, Selector, Show, Smart, Str, StyleChain, StyleMap,
- StyleVec, Value, Vm,
-};
-pub use crate::syntax::{Span, Spanned};
-pub use crate::util::EcoString;
-pub use crate::{LangItems, World};
diff --git a/src/library/raw.rs b/src/library/raw.rs
deleted file mode 100644
index 67aa651d..00000000
--- a/src/library/raw.rs
+++ /dev/null
@@ -1,149 +0,0 @@
-use std::fmt::{self, Debug, Formatter};
-
-use crate::geom::{Abs, Align, Axes, Axis, Get, Length, Paint, Stroke};
-use crate::library::text::TextNode;
-use crate::model::{Fold, Resolve, Smart, StyleChain, Value};
-
-/// The unresolved alignment representation.
-#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
-pub enum RawAlign {
- /// Align at the start side of the text direction.
- Start,
- /// Align at the end side of the text direction.
- End,
- /// Align at a specific alignment.
- Specific(Align),
-}
-
-impl Resolve for RawAlign {
- type Output = Align;
-
- fn resolve(self, styles: StyleChain) -> Self::Output {
- let dir = styles.get(TextNode::DIR);
- match self {
- Self::Start => dir.start().into(),
- Self::End => dir.end().into(),
- Self::Specific(align) => align,
- }
- }
-}
-
-impl RawAlign {
- /// The axis this alignment belongs to.
- pub const fn axis(self) -> Axis {
- match self {
- Self::Start | Self::End => Axis::X,
- Self::Specific(align) => align.axis(),
- }
- }
-}
-
-impl From<Align> for RawAlign {
- fn from(align: Align) -> Self {
- Self::Specific(align)
- }
-}
-
-impl Debug for RawAlign {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- match self {
- Self::Start => f.pad("start"),
- Self::End => f.pad("end"),
- Self::Specific(align) => align.fmt(f),
- }
- }
-}
-
-dynamic! {
- RawAlign: "alignment",
-}
-
-dynamic! {
- Axes<RawAlign>: "2d alignment",
-}
-
-castable! {
- Axes<Option<RawAlign>>,
- Expected: "1d or 2d alignment",
- @align: RawAlign => {
- let mut aligns = Axes::default();
- aligns.set(align.axis(), Some(*align));
- aligns
- },
- @aligns: Axes<RawAlign> => aligns.map(Some),
-}
-
-/// The unresolved stroke representation.
-///
-/// In this representation, both fields are optional so that you can pass either
-/// just a paint (`red`), just a thickness (`0.1em`) or both (`2pt + red`) where
-/// this is expected.
-#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
-pub struct RawStroke<T = Length> {
- /// The stroke's paint.
- pub paint: Smart<Paint>,
- /// The stroke's thickness.
- pub thickness: Smart<T>,
-}
-
-impl RawStroke<Abs> {
- /// Unpack the stroke, filling missing fields from the `default`.
- pub fn unwrap_or(self, default: Stroke) -> Stroke {
- Stroke {
- paint: self.paint.unwrap_or(default.paint),
- thickness: self.thickness.unwrap_or(default.thickness),
- }
- }
-
- /// Unpack the stroke, filling missing fields with the default values.
- pub fn unwrap_or_default(self) -> Stroke {
- self.unwrap_or(Stroke::default())
- }
-}
-
-impl Resolve for RawStroke {
- type Output = RawStroke<Abs>;
-
- fn resolve(self, styles: StyleChain) -> Self::Output {
- RawStroke {
- paint: self.paint,
- thickness: self.thickness.resolve(styles),
- }
- }
-}
-
-impl Fold for RawStroke<Abs> {
- type Output = Self;
-
- fn fold(self, outer: Self::Output) -> Self::Output {
- Self {
- paint: self.paint.or(outer.paint),
- thickness: self.thickness.or(outer.thickness),
- }
- }
-}
-
-impl<T: Debug> Debug for RawStroke<T> {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- match (self.paint, &self.thickness) {
- (Smart::Custom(paint), Smart::Custom(thickness)) => {
- write!(f, "{thickness:?} + {paint:?}")
- }
- (Smart::Custom(paint), Smart::Auto) => paint.fmt(f),
- (Smart::Auto, Smart::Custom(thickness)) => thickness.fmt(f),
- (Smart::Auto, Smart::Auto) => f.pad("<stroke>"),
- }
- }
-}
-
-dynamic! {
- RawStroke: "stroke",
- Value::Length(thickness) => Self {
- paint: Smart::Auto,
- thickness: Smart::Custom(thickness),
- },
- Value::Color(color) => Self {
- paint: Smart::Custom(color.into()),
- thickness: Smart::Auto,
- },
-}
diff --git a/src/model/args.rs b/src/model/args.rs
index f95fbf08..9fb30b9c 100644
--- a/src/model/args.rs
+++ b/src/model/args.rs
@@ -1,7 +1,7 @@
use std::fmt::{self, Debug, Formatter, Write};
use super::{Array, Cast, Dict, Str, Value};
-use crate::diag::{At, SourceResult};
+use crate::diag::{bail, At, SourceResult};
use crate::syntax::{Span, Spanned};
/// Evaluated arguments to a function.
diff --git a/src/model/array.rs b/src/model/array.rs
index 196f02ec..053248ec 100644
--- a/src/model/array.rs
+++ b/src/model/array.rs
@@ -9,8 +9,9 @@ use crate::syntax::Spanned;
use crate::util::ArcExt;
/// Create a new [`Array`] from values.
-#[allow(unused_macros)]
-macro_rules! array {
+#[macro_export]
+#[doc(hidden)]
+macro_rules! __array {
($value:expr; $count:expr) => {
$crate::model::Array::from_vec(vec![$value.into(); $count])
};
@@ -20,6 +21,9 @@ macro_rules! array {
};
}
+#[doc(inline)]
+pub use crate::__array as array;
+
/// A reference counted array with value semantics.
#[derive(Default, Clone, PartialEq, Hash)]
pub struct Array(Arc<Vec<Value>>);
@@ -97,7 +101,7 @@ impl Array {
.ok_or_else(|| out_of_bounds(index, len))?;
Arc::make_mut(&mut self.0).remove(i);
- return Ok(());
+ Ok(())
}
/// Extract a contigous subregion of the array.
diff --git a/src/model/cast.rs b/src/model/cast.rs
index 7356ef70..cbb2952d 100644
--- a/src/model/cast.rs
+++ b/src/model/cast.rs
@@ -1,8 +1,13 @@
use std::num::NonZeroUsize;
+use std::str::FromStr;
use super::{Pattern, Regex, Value};
use crate::diag::{with_alternative, StrResult};
-use crate::geom::{Corners, Dir, Paint, Sides};
+use crate::font::{FontStretch, FontStyle, FontWeight};
+use crate::frame::{Destination, Lang, Location, Region};
+use crate::geom::{
+ Axes, Corners, Dir, GenAlign, Get, Length, Paint, PartialStroke, Point, Rel, Sides,
+};
use crate::syntax::Spanned;
use crate::util::EcoString;
@@ -16,7 +21,9 @@ pub trait Cast<V = Value>: Sized {
}
/// Implement traits for dynamic types.
-macro_rules! dynamic {
+#[macro_export]
+#[doc(hidden)]
+macro_rules! __dynamic {
($type:ty: $name:literal, $($tts:tt)*) => {
impl $crate::model::Type for $type {
const TYPE_NAME: &'static str = $name;
@@ -37,8 +44,13 @@ macro_rules! dynamic {
};
}
+#[doc(inline)]
+pub use crate::__dynamic as dynamic;
+
/// Make a type castable from a value.
-macro_rules! castable {
+#[macro_export]
+#[doc(hidden)]
+macro_rules! __castable {
($type:ty: $inner:ty) => {
impl $crate::model::Cast<$crate::model::Value> for $type {
fn is(value: &$crate::model::Value) -> bool {
@@ -88,6 +100,9 @@ macro_rules! castable {
};
}
+#[doc(inline)]
+pub use crate::__castable as castable;
+
impl Cast for Value {
fn is(_: &Value) -> bool {
true
@@ -119,14 +134,6 @@ impl<T: Cast> Cast<Spanned<Value>> for Spanned<T> {
}
}
-dynamic! {
- Dir: "direction",
-}
-
-dynamic! {
- Regex: "regular expression",
-}
-
castable! {
usize,
Expected: "non-negative integer",
@@ -170,6 +177,10 @@ castable! {
Value::Str(string) => string.into(),
}
+dynamic! {
+ Regex: "regular expression",
+}
+
castable! {
Pattern,
Expected: "function, string or regular expression",
@@ -178,6 +189,115 @@ castable! {
@regex: Regex => Self::Regex(regex.clone()),
}
+dynamic! {
+ Dir: "direction",
+}
+
+dynamic! {
+ GenAlign: "alignment",
+}
+
+dynamic! {
+ Axes<GenAlign>: "2d alignment",
+}
+
+castable! {
+ Axes<Option<GenAlign>>,
+ Expected: "1d or 2d alignment",
+ @align: GenAlign => {
+ let mut aligns = Axes::default();
+ aligns.set(align.axis(), Some(*align));
+ aligns
+ },
+ @aligns: Axes<GenAlign> => aligns.map(Some),
+}
+
+dynamic! {
+ PartialStroke: "stroke",
+ Value::Length(thickness) => Self {
+ paint: Smart::Auto,
+ thickness: Smart::Custom(thickness),
+ },
+ Value::Color(color) => Self {
+ paint: Smart::Custom(color.into()),
+ thickness: Smart::Auto,
+ },
+}
+
+castable! {
+ Axes<Rel<Length>>,
+ Expected: "array of two relative lengths",
+ Value::Array(array) => {
+ let mut iter = array.into_iter();
+ match (iter.next(), iter.next(), iter.next()) {
+ (Some(a), Some(b), None) => Axes::new(a.cast()?, b.cast()?),
+ _ => Err("point array must contain exactly two entries")?,
+ }
+ },
+}
+
+castable! {
+ Destination,
+ Expected: "string or dictionary with `page`, `x`, and `y` keys",
+ Value::Str(string) => Self::Url(string.into()),
+ Value::Dict(dict) => {
+ let page = dict.get("page")?.clone().cast()?;
+ let x: Length = dict.get("x")?.clone().cast()?;
+ let y: Length = dict.get("y")?.clone().cast()?;
+ Self::Internal(Location { page, pos: Point::new(x.abs, y.abs) })
+ },
+}
+
+castable! {
+ FontStyle,
+ Expected: "string",
+ Value::Str(string) => match string.as_str() {
+ "normal" => Self::Normal,
+ "italic" => Self::Italic,
+ "oblique" => Self::Oblique,
+ _ => Err(r#"expected "normal", "italic" or "oblique""#)?,
+ },
+}
+
+castable! {
+ FontWeight,
+ Expected: "integer or string",
+ Value::Int(v) => Value::Int(v)
+ .cast::<usize>()?
+ .try_into()
+ .map_or(Self::BLACK, Self::from_number),
+ Value::Str(string) => match string.as_str() {
+ "thin" => Self::THIN,
+ "extralight" => Self::EXTRALIGHT,
+ "light" => Self::LIGHT,
+ "regular" => Self::REGULAR,
+ "medium" => Self::MEDIUM,
+ "semibold" => Self::SEMIBOLD,
+ "bold" => Self::BOLD,
+ "extrabold" => Self::EXTRABOLD,
+ "black" => Self::BLACK,
+ _ => Err("unknown font weight")?,
+ },
+}
+
+castable! {
+ FontStretch,
+ Expected: "ratio",
+ Value::Ratio(v) => Self::from_ratio(v.get() as f32),
+}
+
+castable! {
+ Lang,
+ Expected: "string",
+ Value::Str(string) => Self::from_str(&string)?,
+}
+
+castable! {
+ Region,
+ Expected: "string",
+ Value::Str(string) => Self::from_str(&string)?,
+}
+
impl<T: Cast> Cast for Option<T> {
fn is(value: &Value) -> bool {
matches!(value, Value::None) || T::is(value)
diff --git a/src/model/content.rs b/src/model/content.rs
index 1cffa773..372f6ff6 100644
--- a/src/model/content.rs
+++ b/src/model/content.rs
@@ -9,8 +9,9 @@ use siphasher::sip128::{Hasher128, SipHasher};
use typst_macros::node;
use super::{Args, Key, Property, Selector, StyleEntry, StyleMap, Vm};
+use crate as typst;
use crate::diag::{SourceResult, StrResult};
-use crate::util::ReadableTypeId;
+use crate::util::{EcoString, ReadableTypeId};
/// Composable representation of styled content.
///
@@ -26,6 +27,11 @@ impl Content {
SequenceNode(vec![]).pack()
}
+ /// Create content from a string of text.
+ pub fn text(text: impl Into<EcoString>) -> Self {
+ item!(text)(text.into())
+ }
+
/// Create a new sequence node from multiples nodes.
pub fn sequence(seq: Vec<Self>) -> Self {
match seq.as_slice() {
@@ -146,7 +152,7 @@ impl Add for Content {
let mut lhs = self;
if let Some(lhs_mut) = lhs.try_downcast_mut::<SequenceNode>() {
if let Some(rhs_mut) = rhs.try_downcast_mut::<SequenceNode>() {
- lhs_mut.0.extend(rhs_mut.0.drain(..));
+ lhs_mut.0.append(&mut rhs_mut.0);
} else if let Some(rhs) = rhs.downcast::<SequenceNode>() {
lhs_mut.0.extend(rhs.0.iter().cloned());
} else {
diff --git a/src/model/dict.rs b/src/model/dict.rs
index 3e4fd956..49e50aa0 100644
--- a/src/model/dict.rs
+++ b/src/model/dict.rs
@@ -10,8 +10,9 @@ use crate::syntax::Spanned;
use crate::util::ArcExt;
/// Create a new [`Dict`] from key-value pairs.
-#[allow(unused_macros)]
-macro_rules! dict {
+#[macro_export]
+#[doc(hidden)]
+macro_rules! __dict {
($($key:expr => $value:expr),* $(,)?) => {{
#[allow(unused_mut)]
let mut map = std::collections::BTreeMap::new();
@@ -20,6 +21,9 @@ macro_rules! dict {
}};
}
+#[doc(inline)]
+pub use crate::__dict as dict;
+
/// A reference-counted dictionary with value semantics.
#[derive(Default, Clone, PartialEq, Hash)]
pub struct Dict(Arc<BTreeMap<Str, Value>>);
diff --git a/src/model/eval.rs b/src/model/eval.rs
index 02617ed6..8e287f14 100644
--- a/src/model/eval.rs
+++ b/src/model/eval.rs
@@ -1,22 +1,19 @@
//! Evaluation of markup into modules.
use std::collections::BTreeMap;
-use std::sync::Arc;
use comemo::{Track, Tracked};
use unicode_segmentation::UnicodeSegmentation;
use super::{
methods, ops, Arg, Args, Array, CapturesVisitor, Closure, Content, Dict, Flow, Func,
- Node, Pattern, Recipe, Scope, Scopes, Show, StyleEntry, StyleMap, Value, Vm,
+ Pattern, Recipe, Scope, Scopes, Show, StyleEntry, StyleMap, Value, Vm,
};
-use crate::diag::{At, SourceResult, StrResult, Trace, Tracepoint};
+use crate::diag::{bail, error, At, SourceResult, StrResult, Trace, Tracepoint};
use crate::geom::{Abs, Angle, Em, Fr, Ratio};
-use crate::library::math;
-use crate::library::text::TextNode;
use crate::syntax::ast::TypedNode;
use crate::syntax::{ast, SourceId, Span, Spanned, Unit};
-use crate::util::EcoString;
+use crate::util::{format_eco, EcoString};
use crate::World;
/// Evaluate a source file and return the resulting module.
@@ -39,7 +36,7 @@ pub fn eval(
// Evaluate the module.
let route = unsafe { Route::insert(route, id) };
let ast = world.source(id).ast()?;
- let std = &world.config().std;
+ let std = &world.config().scope;
let scopes = Scopes::new(Some(std));
let mut vm = Vm::new(world, route.track(), Some(id), scopes);
let result = ast.eval(&mut vm);
@@ -136,8 +133,7 @@ fn eval_markup(
break;
}
- eval_markup(vm, nodes)?
- .styled_with_entry(StyleEntry::Recipe(recipe).into())
+ eval_markup(vm, nodes)?.styled_with_entry(StyleEntry::Recipe(recipe))
}
ast::MarkupNode::Expr(ast::Expr::Wrap(wrap)) => {
let tail = eval_markup(vm, nodes)?;
@@ -165,10 +161,13 @@ impl Eval for ast::MarkupNode {
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
match self {
- Self::Space(v) => v.eval(vm),
+ Self::Space(v) => Ok(match v.newlines() {
+ 0 ..= 1 => (vm.items.space)(),
+ _ => (vm.items.parbreak)(),
+ }),
Self::Linebreak(v) => v.eval(vm),
Self::Text(v) => v.eval(vm),
- Self::Escape(v) => v.eval(vm),
+ Self::Escape(v) => Ok((vm.items.text)(v.get().into())),
Self::Shorthand(v) => v.eval(vm),
Self::SmartQuote(v) => v.eval(vm),
Self::Strong(v) => v.eval(vm),
@@ -187,23 +186,11 @@ impl Eval for ast::MarkupNode {
}
}
-impl Eval for ast::Space {
- type Output = Content;
-
- fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok(if self.newlines() < 2 {
- (vm.items().space)()
- } else {
- (vm.items().parbreak)()
- })
- }
-}
-
impl Eval for ast::Linebreak {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok((vm.items().linebreak)(false))
+ Ok((vm.items.linebreak)(false))
}
}
@@ -211,15 +198,7 @@ impl Eval for ast::Text {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok(vm.text(self.get().clone()))
- }
-}
-
-impl Eval for ast::Escape {
- type Output = Content;
-
- fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok(vm.text(self.get()))
+ Ok((vm.items.text)(self.get().clone()))
}
}
@@ -227,7 +206,7 @@ impl Eval for ast::Shorthand {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok(vm.text(self.get()))
+ Ok((vm.items.text)(self.get().into()))
}
}
@@ -235,7 +214,7 @@ impl Eval for ast::SmartQuote {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok((vm.items().smart_quote)(self.double()))
+ Ok((vm.items.smart_quote)(self.double()))
}
}
@@ -243,7 +222,7 @@ impl Eval for ast::Strong {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok((vm.items().strong)(self.body().eval(vm)?))
+ Ok((vm.items.strong)(self.body().eval(vm)?))
}
}
@@ -251,7 +230,7 @@ impl Eval for ast::Emph {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok((vm.items().emph)(self.body().eval(vm)?))
+ Ok((vm.items.emph)(self.body().eval(vm)?))
}
}
@@ -262,7 +241,7 @@ impl Eval for ast::Raw {
let text = self.text().clone();
let lang = self.lang().cloned();
let block = self.block();
- Ok((vm.items().raw)(text, lang, block))
+ Ok((vm.items.raw)(text, lang, block))
}
}
@@ -270,7 +249,7 @@ impl Eval for ast::Link {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok((vm.items().link)(self.url().clone()))
+ Ok((vm.items.link)(self.url().clone()))
}
}
@@ -286,7 +265,7 @@ impl Eval for ast::Ref {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok((vm.items().ref_)(self.get().clone()))
+ Ok((vm.items.ref_)(self.get().clone()))
}
}
@@ -296,7 +275,7 @@ impl Eval for ast::Heading {
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let level = self.level();
let body = self.body().eval(vm)?;
- Ok((vm.items().heading)(level, body))
+ Ok((vm.items.heading)(level, body))
}
}
@@ -304,7 +283,7 @@ impl Eval for ast::ListItem {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok((vm.items().list_item)(self.body().eval(vm)?))
+ Ok((vm.items.list_item)(self.body().eval(vm)?))
}
}
@@ -314,7 +293,7 @@ impl Eval for ast::EnumItem {
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let number = self.number();
let body = self.body().eval(vm)?;
- Ok((vm.items().enum_item)(number, body))
+ Ok((vm.items.enum_item)(number, body))
}
}
@@ -324,7 +303,7 @@ impl Eval for ast::DescItem {
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let term = self.term().eval(vm)?;
let body = self.body().eval(vm)?;
- Ok((vm.items().desc_item)(term, body))
+ Ok((vm.items.desc_item)(term, body))
}
}
@@ -332,82 +311,76 @@ impl Eval for ast::Math {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- let nodes = self
- .children()
- .map(|node| node.eval(vm))
- .collect::<SourceResult<_>>()?;
- Ok(math::MathNode::Row(Arc::new(nodes), self.span()).pack())
+ Ok((vm.items.math)(
+ self.children()
+ .map(|node| node.eval(vm))
+ .collect::<SourceResult<_>>()?,
+ self.display(),
+ ))
}
}
impl Eval for ast::MathNode {
- type Output = math::MathNode;
+ type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
Ok(match self {
- Self::Space(_) => math::MathNode::Space,
- Self::Linebreak(_) => math::MathNode::Linebreak,
- Self::Escape(c) => math::MathNode::Atom(c.get().into()),
- Self::Atom(atom) => math::MathNode::Atom(atom.get().clone()),
- Self::Script(node) => node.eval(vm)?,
- Self::Frac(node) => node.eval(vm)?,
- Self::Align(node) => node.eval(vm)?,
- Self::Group(node) => math::MathNode::Row(
- Arc::new(
- node.children()
- .map(|node| node.eval(vm))
- .collect::<SourceResult<_>>()?,
- ),
- node.span(),
- ),
- Self::Expr(expr) => {
- let content = expr.eval(vm)?.display(vm.world);
- if let Some(node) = content.downcast::<TextNode>() {
- math::MathNode::Atom(node.0.clone())
- } else {
- bail!(expr.span(), "expected text")
- }
- }
+ Self::Space(_) => (vm.items.space)(),
+ Self::Linebreak(v) => v.eval(vm)?,
+ Self::Escape(v) => (vm.items.math_atom)(v.get().into()),
+ Self::Atom(v) => v.eval(vm)?,
+ Self::Script(v) => v.eval(vm)?,
+ Self::Frac(v) => v.eval(vm)?,
+ Self::Align(v) => v.eval(vm)?,
+ Self::Group(v) => v.eval(vm)?,
+ Self::Expr(v) => match v.eval(vm)? {
+ Value::None => Content::empty(),
+ Value::Int(v) => (vm.items.math_atom)(format_eco!("{}", v)),
+ Value::Float(v) => (vm.items.math_atom)(format_eco!("{}", v)),
+ Value::Str(v) => (vm.items.math_atom)(v.into()),
+ Value::Content(v) => v,
+ _ => bail!(v.span(), "unexpected garbage"),
+ },
})
}
}
+impl Eval for ast::Atom {
+ type Output = Content;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ Ok((vm.items.math_atom)(self.get().clone()))
+ }
+}
+
impl Eval for ast::Script {
- type Output = math::MathNode;
+ type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok(math::MathNode::Script(Arc::new(math::ScriptNode {
- base: self.base().eval(vm)?,
- sub: self
- .sub()
- .map(|node| node.eval(vm))
- .transpose()?
- .map(|node| node.unparen()),
- sup: self
- .sup()
- .map(|node| node.eval(vm))
- .transpose()?
- .map(|node| node.unparen()),
- })))
+ Ok((vm.items.math_script)(
+ self.base().eval(vm)?,
+ self.sub().map(|node| node.eval(vm)).transpose()?,
+ self.sup().map(|node| node.eval(vm)).transpose()?,
+ ))
}
}
impl Eval for ast::Frac {
- type Output = math::MathNode;
+ type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok(math::MathNode::Frac(Arc::new(math::FracNode {
- num: self.num().eval(vm)?.unparen(),
- denom: self.denom().eval(vm)?.unparen(),
- })))
+ Ok((vm.items.math_frac)(
+ self.num().eval(vm)?,
+ self.denom().eval(vm)?,
+ ))
}
}
impl Eval for ast::Align {
- type Output = math::MathNode;
+ type Output = Content;
- fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
- Ok(math::MathNode::Align(self.count()))
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ Ok((vm.items.math_align)(self.count()))
}
}
@@ -515,7 +488,7 @@ fn eval_code(
}
ast::Expr::Show(show) => {
let recipe = show.eval(vm)?;
- let entry = StyleEntry::Recipe(recipe).into();
+ let entry = StyleEntry::Recipe(recipe);
if vm.flow.is_some() {
break;
}
@@ -627,7 +600,7 @@ impl Eval for ast::Unary {
ast::UnOp::Neg => ops::neg(value),
ast::UnOp::Not => ops::not(value),
};
- Ok(result.at(self.span())?)
+ result.at(self.span())
}
}
@@ -676,7 +649,7 @@ impl ast::Binary {
}
let rhs = self.rhs().eval(vm)?;
- Ok(op(lhs, rhs).at(self.span())?)
+ op(lhs, rhs).at(self.span())
}
/// Apply an assignment operation.
@@ -708,8 +681,7 @@ impl Eval for ast::FieldAccess {
.to::<dyn Show>()
.and_then(|node| node.field(&field))
.ok_or_else(|| format!("unknown field {field:?}"))
- .at(span)?
- .clone(),
+ .at(span)?,
v => bail!(
self.target().span(),
@@ -754,9 +726,8 @@ impl Eval for ast::MethodCall {
Ok(if methods::is_mutating(&method) {
let args = self.args().eval(vm)?;
- let mut value = self.target().access(vm)?;
- methods::call_mut(&mut value, &method, args, span)
- .trace(vm.world, point, span)?;
+ let value = self.target().access(vm)?;
+ methods::call_mut(value, &method, args, span).trace(vm.world, point, span)?;
Value::None
} else {
let value = self.target().eval(vm)?;
@@ -882,7 +853,7 @@ impl Eval for ast::SetRule {
let target = self.target();
let target = target.eval(vm)?.cast::<Func>().at(target.span())?;
let args = self.args().eval(vm)?;
- Ok(target.set(args)?)
+ target.set(args)
}
}
@@ -1085,14 +1056,14 @@ impl Eval for ast::ModuleInclude {
let span = self.path().span();
let path = self.path().eval(vm)?.cast::<EcoString>().at(span)?;
let module = import(vm, &path, span)?;
- Ok(module.content.clone())
+ Ok(module.content)
}
}
/// Process an import of a module relative to the current location.
fn import(vm: &mut Vm, path: &str, span: Span) -> SourceResult<Module> {
// Load the source file.
- let full = vm.locate(&path).at(span)?;
+ let full = vm.locate(path).at(span)?;
let id = vm.world.resolve(&full).at(span)?;
// Prevent cyclic importing.
diff --git a/src/model/func.rs b/src/model/func.rs
index dff58233..5be1aae3 100644
--- a/src/model/func.rs
+++ b/src/model/func.rs
@@ -5,7 +5,7 @@ use std::sync::Arc;
use comemo::{Track, Tracked};
use super::{Args, Eval, Flow, Node, NodeId, Route, Scope, Scopes, StyleMap, Value, Vm};
-use crate::diag::{SourceResult, StrResult};
+use crate::diag::{bail, SourceResult, StrResult};
use crate::syntax::ast::{self, Expr, TypedNode};
use crate::syntax::{SourceId, SyntaxNode};
use crate::util::EcoString;
@@ -229,7 +229,7 @@ impl Closure {
}
/// A visitor that determines which variables to capture for a closure.
-pub struct CapturesVisitor<'a> {
+pub(super) struct CapturesVisitor<'a> {
external: &'a Scopes<'a>,
internal: Scopes<'a>,
captures: Scope,
diff --git a/src/model/items.rs b/src/model/items.rs
new file mode 100644
index 00000000..164d9602
--- /dev/null
+++ b/src/model/items.rs
@@ -0,0 +1,123 @@
+use std::fmt::{self, Debug, Formatter};
+use std::hash::{Hash, Hasher};
+use std::num::NonZeroUsize;
+
+use comemo::Tracked;
+use once_cell::sync::OnceCell;
+
+use super::{Content, StyleChain};
+use crate::diag::SourceResult;
+use crate::frame::Frame;
+use crate::geom::{Abs, Dir};
+use crate::util::{hash128, EcoString};
+use crate::World;
+
+/// Global storage for lang items.
+#[doc(hidden)]
+pub static LANG_ITEMS: OnceCell<LangItems> = OnceCell::new();
+
+/// Set the lang items. This is a hack :(
+///
+/// Passing the lang items everywhere they are needed (especially the text node
+/// related things) is very painful. By storing them globally, in theory, we
+/// break incremental, but only when different sets of lang items are used in
+/// the same program. For this reason, if this function is called multiple
+/// times, the items must be the same.
+pub fn set_lang_items(items: LangItems) {
+ if LANG_ITEMS.set(items).is_err() {
+ let first = hash128(LANG_ITEMS.get().unwrap());
+ let second = hash128(&items);
+ assert_eq!(first, second, "set differing lang items");
+ }
+}
+
+/// Access a lang item.
+macro_rules! item {
+ ($name:ident) => {
+ $crate::model::LANG_ITEMS.get().unwrap().$name
+ };
+}
+
+/// Definition of certain standard library items the language is aware of.
+#[derive(Copy, Clone)]
+pub struct LangItems {
+ /// The root layout function.
+ pub root:
+ fn(world: Tracked<dyn World>, document: &Content) -> SourceResult<Vec<Frame>>,
+ /// Access the em size.
+ pub em: fn(StyleChain) -> Abs,
+ /// Access the text direction.
+ pub dir: fn(StyleChain) -> Dir,
+ /// A space.
+ pub space: fn() -> Content,
+ /// A forced line break.
+ pub linebreak: fn(justify: bool) -> Content,
+ /// Plain text.
+ pub text: fn(text: EcoString) -> Content,
+ /// A smart quote: `'` or `"`.
+ pub smart_quote: fn(double: bool) -> Content,
+ /// A paragraph break.
+ pub parbreak: fn() -> Content,
+ /// Strong content: `*Strong*`.
+ pub strong: fn(body: Content) -> Content,
+ /// Emphasized content: `_Emphasized_`.
+ pub emph: fn(body: Content) -> Content,
+ /// A raw block with optional syntax highlighting: `` `...` ``.
+ pub raw: fn(text: EcoString, tag: Option<EcoString>, block: bool) -> Content,
+ /// A hyperlink: `https://typst.org`.
+ pub link: fn(url: EcoString) -> Content,
+ /// A reference: `@target`.
+ pub ref_: fn(target: EcoString) -> Content,
+ /// A section heading: `= Introduction`.
+ pub heading: fn(level: NonZeroUsize, body: Content) -> Content,
+ /// An item in an unordered list: `- ...`.
+ pub list_item: fn(body: Content) -> Content,
+ /// An item in an enumeration (ordered list): `1. ...`.
+ pub enum_item: fn(number: Option<usize>, body: Content) -> Content,
+ /// An item in a description list: `/ Term: Details`.
+ pub desc_item: fn(term: Content, body: Content) -> Content,
+ /// A math formula: `$x$`, `$ x^2 $`.
+ pub math: fn(children: Vec<Content>, display: bool) -> Content,
+ /// A atom in a formula: `x`, `+`, `12`.
+ pub math_atom: fn(atom: EcoString) -> Content,
+ /// A base with an optional sub- and superscript in a formula: `a_1^2`.
+ pub math_script:
+ fn(base: Content, sub: Option<Content>, sup: Option<Content>) -> Content,
+ /// A fraction in a formula: `x/2`
+ pub math_frac: fn(num: Content, denom: Content) -> Content,
+ /// An alignment indicator in a formula: `&`, `&&`.
+ pub math_align: fn(count: usize) -> Content,
+}
+
+impl Debug for LangItems {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad("LangItems { .. }")
+ }
+}
+
+impl Hash for LangItems {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ (self.root as usize).hash(state);
+ (self.em as usize).hash(state);
+ (self.dir as usize).hash(state);
+ self.space.hash(state);
+ self.linebreak.hash(state);
+ self.text.hash(state);
+ self.smart_quote.hash(state);
+ self.parbreak.hash(state);
+ self.strong.hash(state);
+ self.emph.hash(state);
+ self.raw.hash(state);
+ self.link.hash(state);
+ self.ref_.hash(state);
+ self.heading.hash(state);
+ self.list_item.hash(state);
+ self.enum_item.hash(state);
+ self.desc_item.hash(state);
+ self.math.hash(state);
+ self.math_atom.hash(state);
+ self.math_script.hash(state);
+ self.math_frac.hash(state);
+ self.math_align.hash(state);
+ }
+}
diff --git a/src/model/methods.rs b/src/model/methods.rs
index 07cbb822..26d27dfa 100644
--- a/src/model/methods.rs
+++ b/src/model/methods.rs
@@ -97,7 +97,7 @@ pub fn call(
},
Value::Func(func) => match method {
- "with" => Value::Func(func.clone().with(args.take())),
+ "with" => Value::Func(func.with(args.take())),
_ => return missing(),
},
diff --git a/src/model/mod.rs b/src/model/mod.rs
index b4f8f653..fdebce0a 100644
--- a/src/model/mod.rs
+++ b/src/model/mod.rs
@@ -1,6 +1,8 @@
//! Layout and computation model.
#[macro_use]
+mod items;
+#[macro_use]
mod cast;
#[macro_use]
mod array;
@@ -16,12 +18,11 @@ mod args;
mod content;
mod eval;
mod func;
+mod methods;
+mod ops;
mod scope;
mod vm;
-pub mod methods;
-pub mod ops;
-
pub use self::str::*;
pub use args::*;
pub use array::*;
@@ -30,6 +31,7 @@ pub use content::*;
pub use dict::*;
pub use eval::*;
pub use func::*;
+pub use items::*;
pub use scope::*;
pub use styles::*;
pub use value::*;
diff --git a/src/model/ops.rs b/src/model/ops.rs
index 7eb814c1..ee126b03 100644
--- a/src/model/ops.rs
+++ b/src/model/ops.rs
@@ -1,12 +1,9 @@
//! Operations on values.
-use std::cmp::Ordering;
-
-use super::{Node, Regex, Smart, Value};
+use super::{Regex, Smart, Value};
use crate::diag::StrResult;
-use crate::geom::{Axes, Axis, Length, Numeric, Rel};
-use crate::library::text::TextNode;
-use crate::library::{RawAlign, RawStroke};
+use crate::geom::{Axes, Axis, GenAlign, Length, Numeric, PartialStroke, Rel};
+use std::cmp::Ordering;
use Value::*;
/// Bail with a type mismatch error.
@@ -22,8 +19,8 @@ pub fn join(lhs: Value, rhs: Value) -> StrResult<Value> {
(a, None) => a,
(None, b) => b,
(Str(a), Str(b)) => Str(a + b),
- (Str(a), Content(b)) => Content(TextNode(a.into()).pack() + b),
- (Content(a), Str(b)) => Content(a + TextNode(b.into()).pack()),
+ (Str(a), Content(b)) => Content(super::Content::text(a) + b),
+ (Content(a), Str(b)) => Content(a + super::Content::text(b)),
(Content(a), Content(b)) => Content(a + b),
(Array(a), Array(b)) => Array(a + b),
(Dict(a), Dict(b)) => Dict(a + b),
@@ -88,14 +85,14 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> {
(Str(a), Str(b)) => Str(a + b),
(Content(a), Content(b)) => Content(a + b),
- (Content(a), Str(b)) => Content(a + TextNode(b.into()).pack()),
- (Str(a), Content(b)) => Content(TextNode(a.into()).pack() + b),
+ (Content(a), Str(b)) => Content(a + super::Content::text(b)),
+ (Str(a), Content(b)) => Content(super::Content::text(a) + b),
(Array(a), Array(b)) => Array(a + b),
(Dict(a), Dict(b)) => Dict(a + b),
(Color(color), Length(thickness)) | (Length(thickness), Color(color)) => {
- Value::dynamic(RawStroke {
+ Value::dynamic(PartialStroke {
paint: Smart::Custom(color.into()),
thickness: Smart::Custom(thickness),
})
@@ -104,7 +101,7 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> {
(Dyn(a), Dyn(b)) => {
// 1D alignments can be summed into 2D alignments.
if let (Some(&a), Some(&b)) =
- (a.downcast::<RawAlign>(), b.downcast::<RawAlign>())
+ (a.downcast::<GenAlign>(), b.downcast::<GenAlign>())
{
if a.axis() != b.axis() {
Value::dynamic(match a.axis() {
diff --git a/src/model/str.rs b/src/model/str.rs
index 843da9a8..4aa40c54 100644
--- a/src/model/str.rs
+++ b/src/model/str.rs
@@ -5,19 +5,23 @@ use std::ops::{Add, AddAssign, Deref};
use unicode_segmentation::UnicodeSegmentation;
-use super::{Array, Dict, Value};
+use super::{castable, dict, Array, Dict, Value};
use crate::diag::StrResult;
-use crate::library::RawAlign;
+use crate::geom::GenAlign;
use crate::util::EcoString;
/// Create a new [`Str`] from a format string.
-#[allow(unused_macros)]
-macro_rules! format_str {
+#[macro_export]
+#[doc(hidden)]
+macro_rules! __format_str {
($($tts:tt)*) => {{
$crate::model::Str::from(format_eco!($($tts)*))
}};
}
+#[doc(inline)]
+pub use crate::__format_str as format_str;
+
/// An immutable reference counted string.
#[derive(Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Str(EcoString);
@@ -463,9 +467,9 @@ pub enum StrSide {
castable! {
StrSide,
Expected: "start or end",
- @align: RawAlign => match align {
- RawAlign::Start => Self::Start,
- RawAlign::End => Self::End,
+ @align: GenAlign => match align {
+ GenAlign::Start => Self::Start,
+ GenAlign::End => Self::End,
_ => Err("expected either `start` or `end`")?,
},
}
diff --git a/src/model/styles.rs b/src/model/styles.rs
index c58a1beb..24566b09 100644
--- a/src/model/styles.rs
+++ b/src/model/styles.rs
@@ -7,12 +7,11 @@ use std::sync::Arc;
use comemo::{Prehashed, Tracked};
-use super::{capability, Args, Content, Func, Node, NodeId, Regex, Smart, Value};
+use super::{capability, Args, Content, Func, NodeId, Regex, Smart, Value};
use crate::diag::SourceResult;
-use crate::geom::{Abs, Axes, Corners, Em, Length, Numeric, Rel, Sides};
-use crate::library::layout::PageNode;
-use crate::library::structure::{DescNode, EnumNode, ListNode};
-use crate::library::text::{ParNode, TextNode};
+use crate::geom::{
+ Abs, Align, Axes, Corners, Em, GenAlign, Length, Numeric, PartialStroke, Rel, Sides,
+};
use crate::syntax::Spanned;
use crate::util::ReadableTypeId;
use crate::World;
@@ -111,9 +110,9 @@ impl StyleMap {
self
}
- /// The highest-level kind of of structure the map interrupts.
- pub fn interruption(&self) -> Option<Interruption> {
- self.0.iter().filter_map(|entry| entry.interruption()).max()
+ /// Whether this map contains styles for the given `node.`
+ pub fn interrupts<T: 'static>(&self) -> bool {
+ self.0.iter().any(|entry| entry.is_of(NodeId::of::<T>()))
}
}
@@ -132,17 +131,6 @@ impl Debug for StyleMap {
}
}
-/// Determines whether a style could interrupt some composable structure.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
-pub enum Interruption {
- /// The style forces a list break.
- List,
- /// The style forces a paragraph break.
- Par,
- /// The style forces a page break.
- Page,
-}
-
/// An entry for a single style property, recipe or barrier.
#[derive(Clone, PartialEq, Hash)]
pub enum StyleEntry {
@@ -193,12 +181,12 @@ impl StyleEntry {
}
}
- /// The highest-level kind of structure the entry interrupts.
- pub fn interruption(&self) -> Option<Interruption> {
+ /// Whether this entry contains styles for the given `node.`
+ pub fn is_of(&self, node: NodeId) -> bool {
match self {
- Self::Property(property) => property.interruption(),
- Self::Recipe(recipe) => recipe.interruption(),
- _ => None,
+ Self::Property(property) => property.is_of(node),
+ Self::Recipe(recipe) => recipe.is_of(node),
+ _ => false,
}
}
}
@@ -397,7 +385,7 @@ impl<'a, K: Key<'a>> Iterator for Values<'a, K> {
type Item = &'a K::Value;
fn next(&mut self) -> Option<Self::Item> {
- while let Some(entry) = self.entries.next() {
+ for entry in &mut self.entries {
match entry {
StyleEntry::Property(property) => {
if let Some(value) = property.downcast::<K>() {
@@ -662,9 +650,9 @@ impl Property {
self.key == KeyId::of::<K>()
}
- /// Whether this property belongs to the node `T`.
- pub fn is_of<T: 'static>(&self) -> bool {
- self.node == NodeId::of::<T>()
+ /// Whether this property belongs to the node with the given id.
+ pub fn is_of(&self, node: NodeId) -> bool {
+ self.node == node
}
/// Access the property's value if it is of the given key.
@@ -690,22 +678,6 @@ impl Property {
pub fn make_scoped(&mut self) {
self.scoped = true;
}
-
- /// What kind of structure the property interrupts.
- pub fn interruption(&self) -> Option<Interruption> {
- if self.is_of::<PageNode>() {
- Some(Interruption::Page)
- } else if self.is_of::<ParNode>() {
- Some(Interruption::Par)
- } else if self.is_of::<ListNode>()
- || self.is_of::<EnumNode>()
- || self.is_of::<DescNode>()
- {
- Some(Interruption::List)
- } else {
- None
- }
- }
}
impl Debug for Property {
@@ -826,7 +798,7 @@ impl Resolve for Em {
if self.is_zero() {
Abs::zero()
} else {
- self.at(styles.get(TextNode::SIZE))
+ self.at(item!(em)(styles))
}
}
}
@@ -891,6 +863,30 @@ where
}
}
+impl Resolve for GenAlign {
+ type Output = Align;
+
+ fn resolve(self, styles: StyleChain) -> Self::Output {
+ let dir = item!(dir)(styles);
+ match self {
+ Self::Start => dir.start().into(),
+ Self::End => dir.end().into(),
+ Self::Specific(align) => align,
+ }
+ }
+}
+
+impl Resolve for PartialStroke {
+ type Output = PartialStroke<Abs>;
+
+ fn resolve(self, styles: StyleChain) -> Self::Output {
+ PartialStroke {
+ paint: self.paint,
+ thickness: self.thickness.resolve(styles),
+ }
+ }
+}
+
/// A property that is folded to determine its final value.
pub trait Fold {
/// The type of the folded output.
@@ -970,6 +966,17 @@ impl Fold for Corners<Option<Rel<Abs>>> {
}
}
+impl Fold for PartialStroke<Abs> {
+ type Output = Self;
+
+ fn fold(self, outer: Self::Output) -> Self::Output {
+ Self {
+ paint: self.paint.or(outer.paint),
+ thickness: self.thickness.or(outer.thickness),
+ }
+ }
+}
+
/// A show rule recipe.
#[derive(Clone, PartialEq, Hash)]
pub struct Recipe {
@@ -1003,13 +1010,14 @@ impl Recipe {
}
(Target::Text(text), Pattern::Regex(regex)) => {
+ let make = world.config().items.text;
let mut result = vec![];
let mut cursor = 0;
for mat in regex.find_iter(text) {
let start = mat.start();
if cursor < start {
- result.push(TextNode(text[cursor .. start].into()).pack());
+ result.push(make(text[cursor .. start].into()));
}
result.push(self.call(world, || Value::Str(mat.as_str().into()))?);
@@ -1021,7 +1029,7 @@ impl Recipe {
}
if cursor < text.len() {
- result.push(TextNode(text[cursor ..].into()).pack());
+ result.push(make(text[cursor ..].into()));
}
Content::sequence(result)
@@ -1047,18 +1055,12 @@ impl Recipe {
Ok(self.func.v.call_detached(world, args)?.display(world))
}
- /// What kind of structure the property interrupts.
- pub fn interruption(&self) -> Option<Interruption> {
- if let Pattern::Node(id) = self.pattern {
- if id == NodeId::of::<ListNode>()
- || id == NodeId::of::<EnumNode>()
- || id == NodeId::of::<DescNode>()
- {
- return Some(Interruption::List);
- }
+ /// Whether this recipe is for the given node.
+ pub fn is_of(&self, node: NodeId) -> bool {
+ match self.pattern {
+ Pattern::Node(id) => id == node,
+ _ => false,
}
-
- None
}
}
diff --git a/src/model/value.rs b/src/model/value.rs
index d68f42a0..07719883 100644
--- a/src/model/value.rs
+++ b/src/model/value.rs
@@ -7,11 +7,10 @@ use std::sync::Arc;
use comemo::Tracked;
use siphasher::sip128::{Hasher128, SipHasher};
-use super::{ops, Args, Array, Cast, Content, Dict, Func, Node, Str};
+use super::{format_str, ops, Args, Array, Cast, Content, Dict, Func, Str};
use crate::diag::StrResult;
use crate::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel, RgbaColor};
-use crate::library::text::TextNode;
-use crate::util::EcoString;
+use crate::util::{format_eco, EcoString};
use crate::World;
/// A computational value.
@@ -385,7 +384,7 @@ primitive! { Str: "string", Str }
primitive! { Content: "content",
Content,
None => Content::empty(),
- Str(text) => TextNode(text.into()).pack()
+ Str(text) => Content::text(text)
}
primitive! { Array: "array", Array }
primitive! { Dict: "dictionary", Dict }
@@ -395,6 +394,7 @@ primitive! { Args: "arguments", Args }
#[cfg(test)]
mod tests {
use super::*;
+ use crate::model::{array, dict};
#[track_caller]
fn test(value: impl Into<Value>, exp: &str) {
diff --git a/src/model/vm.rs b/src/model/vm.rs
index 4de57d1c..db0bf77c 100644
--- a/src/model/vm.rs
+++ b/src/model/vm.rs
@@ -2,11 +2,11 @@ use std::path::PathBuf;
use comemo::Tracked;
-use super::{Content, Route, Scopes, Value};
-use crate::diag::{SourceError, StrResult};
+use super::{LangItems, Route, Scopes, Value};
+use crate::diag::{error, SourceError, StrResult};
use crate::syntax::{SourceId, Span};
-use crate::util::{EcoString, PathExt};
-use crate::{LangItems, World};
+use crate::util::PathExt;
+use crate::World;
/// A virtual machine.
pub struct Vm<'a> {
@@ -20,6 +20,8 @@ pub struct Vm<'a> {
pub scopes: Scopes<'a>,
/// A control flow event that is currently happening.
pub flow: Option<Flow>,
+ /// The language items.
+ pub items: LangItems,
}
impl<'a> Vm<'a> {
@@ -36,6 +38,7 @@ impl<'a> Vm<'a> {
location,
scopes,
flow: None,
+ items: world.config().items,
}
}
@@ -54,18 +57,6 @@ impl<'a> Vm<'a> {
Err("cannot access file system from here".into())
}
-
- /// The language items.
- pub fn items(&self) -> &LangItems {
- &self.world.config().items
- }
-
- /// Create text content.
- ///
- /// This is a shorthand for `(vm.items().text)(..)`.
- pub fn text(&self, text: impl Into<EcoString>) -> Content {
- (self.items().text)(text.into())
- }
}
/// A control flow event that occurred during evaluation.
diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs
index ecfa9a5b..06e41fa0 100644
--- a/src/syntax/ast.rs
+++ b/src/syntax/ast.rs
@@ -55,7 +55,7 @@ node! {
impl Markup {
/// The children.
- pub fn children(&self) -> impl Iterator<Item = MarkupNode> + '_ {
+ pub fn children(&self) -> impl DoubleEndedIterator<Item = MarkupNode> + '_ {
self.0.children().filter_map(SyntaxNode::cast)
}
}
@@ -166,7 +166,7 @@ impl Space {
}
node! {
- /// A forced line break.
+ /// A forced line break: `\`.
Linebreak
}
@@ -414,9 +414,15 @@ node! {
impl Math {
/// The children.
- pub fn children(&self) -> impl Iterator<Item = MathNode> + '_ {
+ pub fn children(&self) -> impl DoubleEndedIterator<Item = MathNode> + '_ {
self.0.children().filter_map(SyntaxNode::cast)
}
+
+ /// Whether this is a display-level math formula.
+ pub fn display(&self) -> bool {
+ matches!(self.children().next(), Some(MathNode::Space(_)))
+ && matches!(self.children().last(), Some(MathNode::Space(_)))
+ }
}
/// A single piece of a math formula.
@@ -424,7 +430,7 @@ impl Math {
pub enum MathNode {
/// Whitespace.
Space(Space),
- /// A forced line break.
+ /// A forced line break: `\`.
Linebreak(Linebreak),
/// An escape sequence: `\#`, `\u{1F5FA}`.
Escape(Escape),
@@ -535,7 +541,7 @@ impl Frac {
}
node! {
- /// A math alignment indicator: `&`, `&&`.
+ /// An alignment indicator in a formula: `&`, `&&`.
Align
}
@@ -736,7 +742,7 @@ node! {
impl CodeBlock {
/// The list of expressions contained in the block.
- pub fn exprs(&self) -> impl Iterator<Item = Expr> + '_ {
+ pub fn exprs(&self) -> impl DoubleEndedIterator<Item = Expr> + '_ {
self.0.children().filter_map(SyntaxNode::cast)
}
}
@@ -774,7 +780,7 @@ node! {
impl Array {
/// The array's items.
- pub fn items(&self) -> impl Iterator<Item = ArrayItem> + '_ {
+ pub fn items(&self) -> impl DoubleEndedIterator<Item = ArrayItem> + '_ {
self.0.children().filter_map(SyntaxNode::cast)
}
}
@@ -811,7 +817,7 @@ node! {
impl Dict {
/// The dictionary's items.
- pub fn items(&self) -> impl Iterator<Item = DictItem> + '_ {
+ pub fn items(&self) -> impl DoubleEndedIterator<Item = DictItem> + '_ {
self.0.children().filter_map(SyntaxNode::cast)
}
}
@@ -1204,7 +1210,7 @@ node! {
impl Args {
/// The positional and named arguments.
- pub fn items(&self) -> impl Iterator<Item = Arg> + '_ {
+ pub fn items(&self) -> impl DoubleEndedIterator<Item = Arg> + '_ {
self.0.children().filter_map(SyntaxNode::cast)
}
}
@@ -1252,7 +1258,7 @@ impl Closure {
}
/// The parameter bindings.
- pub fn params(&self) -> impl Iterator<Item = Param> + '_ {
+ pub fn params(&self) -> impl DoubleEndedIterator<Item = Param> + '_ {
self.0
.children()
.find(|x| x.kind() == &NodeKind::Params)
diff --git a/src/syntax/highlight.rs b/src/syntax/highlight.rs
index 325b7274..0db45785 100644
--- a/src/syntax/highlight.rs
+++ b/src/syntax/highlight.rs
@@ -97,7 +97,7 @@ where
}
}
- let highlighter = Highlighter::new(&theme);
+ let highlighter = Highlighter::new(theme);
process(0, root, vec![], &highlighter, &mut f);
}
diff --git a/src/syntax/incremental.rs b/src/syntax/incremental.rs
index 529defd7..15c0df0c 100644
--- a/src/syntax/incremental.rs
+++ b/src/syntax/incremental.rs
@@ -235,17 +235,17 @@ fn replace(
let (newborns, terminated, amount) = match mode {
ReparseMode::Code => reparse_code_block(
- &prefix,
+ prefix,
&change.text[newborn_span.start ..],
newborn_span.len(),
),
ReparseMode::Content => reparse_content_block(
- &prefix,
+ prefix,
&change.text[newborn_span.start ..],
newborn_span.len(),
),
ReparseMode::MarkupElements { at_start, min_indent } => reparse_markup_elements(
- &prefix,
+ prefix,
&change.text[newborn_span.start ..],
newborn_span.len(),
differential,
@@ -385,17 +385,17 @@ enum ReparseMode {
/// Whether changes _inside_ this node are safely encapsulated, so that only
/// this node must be reparsed.
fn is_bounded(kind: &NodeKind) -> bool {
- match kind {
+ matches!(
+ kind,
NodeKind::CodeBlock
- | NodeKind::ContentBlock
- | NodeKind::Linebreak
- | NodeKind::SmartQuote { .. }
- | NodeKind::BlockComment
- | NodeKind::Space { .. }
- | NodeKind::Escape(_)
- | NodeKind::Shorthand(_) => true,
- _ => false,
- }
+ | NodeKind::ContentBlock
+ | NodeKind::Linebreak
+ | NodeKind::SmartQuote { .. }
+ | NodeKind::BlockComment
+ | NodeKind::Space { .. }
+ | NodeKind::Escape(_)
+ | NodeKind::Shorthand(_)
+ )
}
/// Whether `at_start` would still be true after this node given the
diff --git a/src/syntax/node.rs b/src/syntax/node.rs
index 6a7d424a..4ec4abdf 100644
--- a/src/syntax/node.rs
+++ b/src/syntax/node.rs
@@ -99,8 +99,22 @@ impl SyntaxNode {
self.children().rev().find_map(Self::cast)
}
+ /// Returns all leaf descendants of this node (may include itself).
+ ///
+ /// This method is slow and only intended for testing.
+ pub fn leafs(&self) -> Vec<Self> {
+ if match self {
+ Self::Inner(inner) => inner.children.is_empty(),
+ Self::Leaf(_) => true,
+ } {
+ vec![self.clone()]
+ } else {
+ self.children().flat_map(Self::leafs).collect()
+ }
+ }
+
/// Change the type of the node.
- pub fn convert(&mut self, kind: NodeKind) {
+ pub(super) fn convert(&mut self, kind: NodeKind) {
match self {
Self::Inner(inner) => {
let node = Arc::make_mut(inner);
@@ -112,7 +126,7 @@ impl SyntaxNode {
}
/// Set a synthetic span for the node and all its descendants.
- pub fn synthesize(&mut self, span: Span) {
+ pub(super) fn synthesize(&mut self, span: Span) {
match self {
Self::Inner(inner) => Arc::make_mut(inner).synthesize(span),
Self::Leaf(leaf) => leaf.synthesize(span),
@@ -120,40 +134,30 @@ impl SyntaxNode {
}
/// Assign spans to each node.
- pub fn numberize(&mut self, id: SourceId, within: Range<u64>) -> NumberingResult {
+ pub(super) fn numberize(
+ &mut self,
+ id: SourceId,
+ within: Range<u64>,
+ ) -> NumberingResult {
match self {
Self::Inner(inner) => Arc::make_mut(inner).numberize(id, None, within),
Self::Leaf(leaf) => leaf.numberize(id, within),
}
}
- /// The upper bound of assigned numbers in this subtree.
- pub fn upper(&self) -> u64 {
- match self {
- Self::Inner(inner) => inner.upper(),
- Self::Leaf(leaf) => leaf.span().number() + 1,
- }
- }
-
/// If the span points into this node, convert it to a byte range.
- pub fn range(&self, span: Span, offset: usize) -> Option<Range<usize>> {
+ pub(super) fn range(&self, span: Span, offset: usize) -> Option<Range<usize>> {
match self {
Self::Inner(inner) => inner.range(span, offset),
Self::Leaf(leaf) => leaf.range(span, offset),
}
}
- /// Returns all leaf descendants of this node (may include itself).
- ///
- /// This method is slow and only intended for testing.
- pub fn leafs(&self) -> Vec<Self> {
- if match self {
- Self::Inner(inner) => inner.children.is_empty(),
- Self::Leaf(_) => true,
- } {
- vec![self.clone()]
- } else {
- self.children().flat_map(Self::leafs).collect()
+ /// The upper bound of assigned numbers in this subtree.
+ fn upper(&self) -> u64 {
+ match self {
+ Self::Inner(inner) => inner.upper(),
+ Self::Leaf(leaf) => leaf.span().number() + 1,
}
}
}
@@ -246,7 +250,7 @@ impl InnerNode {
}
/// Set a synthetic span for the node and all its descendants.
- pub fn synthesize(&mut self, span: Span) {
+ fn synthesize(&mut self, span: Span) {
self.data.synthesize(span);
for child in &mut self.children {
child.synthesize(span);
@@ -255,7 +259,7 @@ impl InnerNode {
/// Assign span numbers `within` an interval to this node's subtree or just
/// a `range` of its children.
- pub fn numberize(
+ fn numberize(
&mut self,
id: SourceId,
range: Option<Range<usize>>,
@@ -304,12 +308,12 @@ impl InnerNode {
}
/// The upper bound of assigned numbers in this subtree.
- pub fn upper(&self) -> u64 {
+ fn upper(&self) -> u64 {
self.upper
}
/// If the span points into this node, convert it to a byte range.
- pub fn range(&self, span: Span, mut offset: usize) -> Option<Range<usize>> {
+ fn range(&self, span: Span, mut offset: usize) -> Option<Range<usize>> {
// Check whether we found it.
if let Some(range) = self.data.range(span, offset) {
return Some(range);
@@ -343,14 +347,14 @@ impl InnerNode {
}
/// The node's children, mutably.
- pub(crate) fn children_mut(&mut self) -> &mut [SyntaxNode] {
+ pub(super) fn children_mut(&mut self) -> &mut [SyntaxNode] {
&mut self.children
}
/// Replaces a range of children with a replacement.
///
/// May have mutated the children if it returns `Err(_)`.
- pub(crate) fn replace_children(
+ pub(super) fn replace_children(
&mut self,
mut range: Range<usize>,
replacement: Vec<SyntaxNode>,
@@ -430,7 +434,7 @@ impl InnerNode {
}
/// Update this node after changes were made to one of its children.
- pub(crate) fn update_parent(
+ pub(super) fn update_parent(
&mut self,
prev_len: usize,
new_len: usize,
@@ -509,12 +513,12 @@ impl NodeData {
}
/// Set a synthetic span for the node.
- pub fn synthesize(&mut self, span: Span) {
+ fn synthesize(&mut self, span: Span) {
self.span = span;
}
/// Assign a span to the node.
- pub fn numberize(&mut self, id: SourceId, within: Range<u64>) -> NumberingResult {
+ fn numberize(&mut self, id: SourceId, within: Range<u64>) -> NumberingResult {
if within.start < within.end {
self.span = Span::new(id, (within.start + within.end) / 2);
Ok(())
@@ -524,7 +528,7 @@ impl NodeData {
}
/// If the span points into this node, convert it to a byte range.
- pub fn range(&self, span: Span, offset: usize) -> Option<Range<usize>> {
+ fn range(&self, span: Span, offset: usize) -> Option<Range<usize>> {
(self.span == span).then(|| offset .. offset + self.len())
}
}
diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs
index 83b333f4..4c8e1013 100644
--- a/src/syntax/parser.rs
+++ b/src/syntax/parser.rs
@@ -3,7 +3,7 @@ use std::mem;
use std::ops::Range;
use super::{ErrorPos, InnerNode, NodeData, NodeKind, SyntaxNode, TokenMode, Tokens};
-use crate::util::EcoString;
+use crate::util::{format_eco, EcoString};
/// A convenient token-based parser.
pub struct Parser<'s> {
diff --git a/src/syntax/span.rs b/src/syntax/span.rs
index d4d9a8f6..e3ff67b8 100644
--- a/src/syntax/span.rs
+++ b/src/syntax/span.rs
@@ -110,11 +110,11 @@ const fn to_non_zero(v: u64) -> NonZeroU64 {
}
/// Result of numbering a node within an interval.
-pub type NumberingResult = Result<(), Unnumberable>;
+pub(super) type NumberingResult = Result<(), Unnumberable>;
/// Indicates that a node cannot be numbered within a given interval.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
-pub struct Unnumberable;
+pub(super) struct Unnumberable;
impl Display for Unnumberable {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs
index 59e6cd3f..c787fa69 100644
--- a/src/syntax/tokens.rs
+++ b/src/syntax/tokens.rs
@@ -6,7 +6,7 @@ use unscanny::Scanner;
use super::resolve::{resolve_hex, resolve_raw, resolve_string};
use super::{ErrorPos, NodeKind, RawKind, Unit};
use crate::geom::{AbsUnit, AngleUnit};
-use crate::util::EcoString;
+use crate::util::{format_eco, EcoString};
/// An iterator over the tokens of a string of source code.
#[derive(Clone)]
diff --git a/src/util/eco.rs b/src/util/eco.rs
index 645880c4..800760e2 100644
--- a/src/util/eco.rs
+++ b/src/util/eco.rs
@@ -8,7 +8,9 @@ use std::sync::Arc;
use super::ArcExt;
/// Create a new [`EcoString`] from a format string.
-macro_rules! format_eco {
+#[macro_export]
+#[doc(hidden)]
+macro_rules! __format_eco {
($($tts:tt)*) => {{
use std::fmt::Write;
let mut s = $crate::util::EcoString::new();
@@ -17,6 +19,9 @@ macro_rules! format_eco {
}};
}
+#[doc(inline)]
+pub use crate::__format_eco as format_eco;
+
/// An economical string with inline storage and clone-on-write semantics.
#[derive(Clone)]
pub struct EcoString(Repr);
@@ -55,7 +60,7 @@ impl EcoString {
}
/// Create an instance from an existing string-like type.
- pub fn from_str<S>(s: S) -> Self
+ pub fn from_str_like<S>(s: S) -> Self
where
S: AsRef<str> + Into<String>,
{
@@ -324,13 +329,13 @@ impl From<char> for EcoString {
impl From<&str> for EcoString {
fn from(s: &str) -> Self {
- Self::from_str(s)
+ Self::from_str_like(s)
}
}
impl From<String> for EcoString {
fn from(s: String) -> Self {
- Self::from_str(s)
+ Self::from_str_like(s)
}
}
diff --git a/src/util/mod.rs b/src/util/mod.rs
index bc7aa250..df3c446e 100644
--- a/src/util/mod.rs
+++ b/src/util/mod.rs
@@ -3,7 +3,7 @@
pub mod fat;
pub use buffer::Buffer;
-pub use eco::EcoString;
+pub use eco::{format_eco, EcoString};
#[macro_use]
mod eco;
@@ -11,9 +11,12 @@ mod buffer;
use std::any::TypeId;
use std::fmt::{self, Debug, Formatter};
+use std::hash::Hash;
use std::path::{Component, Path, PathBuf};
use std::sync::Arc;
+use siphasher::sip128::{Hasher128, SipHasher};
+
/// Turn a closure into a struct implementing [`Debug`].
pub fn debug<F>(f: F) -> impl Debug
where
@@ -33,6 +36,13 @@ where
Wrapper(f)
}
+/// Calculate a 128-bit siphash of a value.
+pub fn hash128<T: Hash>(value: &T) -> u128 {
+ let mut state = SipHasher::new();
+ value.hash(&mut state);
+ state.finish128().as_u128()
+}
+
/// Extra methods for [`str`].
pub trait StrExt {
/// The number of code units this string would use if it was encoded in
diff --git a/tests/typ/math/basic.typ b/tests/typ/math/basic.typ
index a1a9d32f..55a853cf 100644
--- a/tests/typ/math/basic.typ
+++ b/tests/typ/math/basic.typ
@@ -16,11 +16,5 @@ $ sum_(k=0)^n k = (n(n+1))/2 $
$ f: NN arrow RR $
---
-#set math(family: "IBM Plex Sans")
-
-// Error: 1-4 font is not suitable for math
-$a$
-
----
// Error: 1:3 expected dollar sign
$a
diff --git a/tests/typeset.rs b/tests/typeset.rs
index f3f5bfbe..7c0a6e69 100644
--- a/tests/typeset.rs
+++ b/tests/typeset.rs
@@ -14,16 +14,16 @@ use tiny_skia as sk;
use unscanny::Scanner;
use walkdir::WalkDir;
-use typst::diag::{FileError, FileResult};
+use typst::diag::{bail, FileError, FileResult};
use typst::font::{Font, FontBook};
use typst::frame::{Element, Frame};
use typst::geom::{Abs, RgbaColor, Sides};
-use typst::library::layout::PageNode;
-use typst::library::text::{TextNode, TextSize};
-use typst::model::{Smart, StyleMap, Value};
+use typst::model::{Smart, Value};
use typst::syntax::{Source, SourceId, SyntaxNode};
use typst::util::{Buffer, PathExt};
-use typst::{bail, Config, World};
+use typst::{Config, World};
+use typst_library::layout::PageNode;
+use typst_library::text::{TextNode, TextSize};
const TYP_DIR: &str = "./typ";
const REF_DIR: &str = "./ref";
@@ -149,7 +149,7 @@ fn config() -> Config {
// Set page width to 120pt with 10pt margins, so that the inner page is
// exactly 100pt wide. Page height is unbounded and font size is 10pt so
// that it multiplies to nice round numbers.
- let mut styles = StyleMap::new();
+ let mut styles = typst_library::styles();
styles.set(PageNode::WIDTH, Smart::Custom(Abs::pt(120.0).into()));
styles.set(PageNode::HEIGHT, Smart::Auto);
styles.set(
@@ -159,10 +159,10 @@ fn config() -> Config {
styles.set(TextNode::SIZE, TextSize(Abs::pt(10.0).into()));
// Hook up helpers into the global scope.
- let mut std = typst::library::scope();
- std.define("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF));
- std.define("forest", RgbaColor::new(0x43, 0xA1, 0x27, 0xFF));
- std.def_fn("test", move |_, args| {
+ let mut scope = typst_library::scope();
+ scope.define("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF));
+ scope.define("forest", RgbaColor::new(0x43, 0xA1, 0x27, 0xFF));
+ scope.def_fn("test", move |_, args| {
let lhs = args.expect::<Value>("left-hand side")?;
let rhs = args.expect::<Value>("right-hand side")?;
if lhs != rhs {
@@ -170,7 +170,7 @@ fn config() -> Config {
}
Ok(Value::None)
});
- std.def_fn("print", move |_, args| {
+ scope.def_fn("print", move |_, args| {
print!("> ");
for (i, value) in args.all::<Value>()?.into_iter().enumerate() {
if i > 0 {
@@ -184,9 +184,9 @@ fn config() -> Config {
Config {
root: PathBuf::new(),
- items: typst::library::items(),
- std,
+ scope,
styles,
+ items: typst_library::items(),
}
}