From 37a7afddfaffd44cb9bc013c9506599267e08983 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 3 Nov 2022 11:44:53 +0100 Subject: [PATCH] Split crates --- Cargo.lock | 61 +++- Cargo.toml | 90 ++---- benches/oneshot.rs | 18 +- cli/Cargo.toml | 26 ++ {src => cli/src}/main.rs | 17 +- library/Cargo.toml | 26 ++ {src/library => library/src}/ext.rs | 14 +- {src/library => library/src}/graphics/hide.rs | 2 +- .../library => library/src}/graphics/image.rs | 7 +- {src/library => library/src}/graphics/line.rs | 16 +- {src/library => library/src}/graphics/mod.rs | 0 .../library => library/src}/graphics/shape.rs | 10 +- {src/library => library/src}/layout/align.rs | 8 +- .../library => library/src}/layout/columns.rs | 4 +- .../src}/layout/container.rs | 2 +- {src/library => library/src}/layout/flow.rs | 6 +- {src/library => library/src}/layout/grid.rs | 37 ++- {src/library => library/src}/layout/mod.rs | 63 ++-- {src/library => library/src}/layout/pad.rs | 2 +- {src/library => library/src}/layout/page.rs | 2 +- {src/library => library/src}/layout/place.rs | 4 +- .../library => library/src}/layout/spacing.rs | 4 +- {src/library => library/src}/layout/stack.rs | 7 +- .../src}/layout/transform.rs | 7 +- src/library/mod.rs => library/src/lib.rs | 46 +-- library/src/math/mod.rs | 188 +++++++++++ library/src/math/tex.rs | 164 ++++++++++ library/src/prelude.rs | 27 ++ {src/library => library/src}/structure/doc.rs | 4 +- .../src}/structure/heading.rs | 6 +- .../library => library/src}/structure/list.rs | 8 +- {src/library => library/src}/structure/mod.rs | 0 .../src}/structure/reference.rs | 2 +- .../src}/structure/table.rs | 18 +- {src/library => library/src}/text/deco.rs | 7 +- {src/library => library/src}/text/link.rs | 14 +- {src/library => library/src}/text/mod.rs | 130 +++----- {src/library => library/src}/text/par.rs | 41 +-- {src/library => library/src}/text/quotes.rs | 3 +- {src/library => library/src}/text/raw.rs | 13 +- {src/library => library/src}/text/shaping.rs | 13 +- {src/library => library/src}/text/shift.rs | 9 +- {src/library => library/src}/utility/color.rs | 2 +- {src/library => library/src}/utility/data.rs | 5 +- {src/library => library/src}/utility/math.rs | 2 +- {src/library => library/src}/utility/mod.rs | 8 +- .../library => library/src}/utility/string.rs | 5 +- macros/src/lib.rs | 20 +- src/diag.rs | 18 +- src/export/pdf/font.rs | 2 +- src/export/pdf/outline.rs | 2 +- src/export/pdf/page.rs | 1 + src/export/render.rs | 24 +- src/font/book.rs | 2 +- src/font/variant.rs | 2 +- src/frame.rs | 71 +++-- src/geom/align.rs | 37 +++ src/geom/angle.rs | 2 +- src/geom/mod.rs | 2 + src/geom/paint.rs | 18 -- src/geom/stroke.rs | 61 ++++ src/lib.rs | 57 +--- src/library/math/frac.rs | 17 - src/library/math/mod.rs | 295 ------------------ src/library/math/script.rs | 31 -- src/library/prelude.rs | 27 -- src/library/raw.rs | 149 --------- src/model/args.rs | 2 +- src/model/array.rs | 10 +- src/model/cast.rs | 142 ++++++++- src/model/content.rs | 10 +- src/model/dict.rs | 8 +- src/model/eval.rs | 185 +++++------ src/model/func.rs | 4 +- src/model/items.rs | 123 ++++++++ src/model/methods.rs | 2 +- src/model/mod.rs | 8 +- src/model/ops.rs | 21 +- src/model/str.rs | 18 +- src/model/styles.rs | 118 +++---- src/model/value.rs | 8 +- src/model/vm.rs | 23 +- src/syntax/ast.rs | 26 +- src/syntax/highlight.rs | 2 +- src/syntax/incremental.rs | 26 +- src/syntax/node.rs | 112 +++---- src/syntax/parser.rs | 2 +- src/syntax/span.rs | 4 +- src/syntax/tokens.rs | 2 +- src/util/eco.rs | 13 +- src/util/mod.rs | 12 +- tests/typ/math/basic.typ | 6 - tests/typeset.rs | 26 +- 93 files changed, 1542 insertions(+), 1357 deletions(-) create mode 100644 cli/Cargo.toml rename {src => cli/src}/main.rs (98%) create mode 100644 library/Cargo.toml rename {src/library => library/src}/ext.rs (93%) rename {src/library => library/src}/graphics/hide.rs (95%) rename {src/library => library/src}/graphics/image.rs (96%) rename {src/library => library/src}/graphics/line.rs (80%) rename {src/library => library/src}/graphics/mod.rs (100%) rename {src/library => library/src}/graphics/shape.rs (95%) rename {src/library => library/src}/layout/align.rs (90%) rename {src/library => library/src}/layout/columns.rs (98%) rename {src/library => library/src}/layout/container.rs (98%) rename {src/library => library/src}/layout/flow.rs (98%) rename {src/library => library/src}/layout/grid.rs (94%) rename {src/library => library/src}/layout/mod.rs (94%) rename {src/library => library/src}/layout/pad.rs (98%) rename {src/library => library/src}/layout/page.rs (99%) rename {src/library => library/src}/layout/place.rs (94%) rename {src/library => library/src}/layout/spacing.rs (97%) rename {src/library => library/src}/layout/stack.rs (99%) rename {src/library => library/src}/layout/transform.rs (96%) rename src/library/mod.rs => library/src/lib.rs (82%) create mode 100644 library/src/math/mod.rs create mode 100644 library/src/math/tex.rs create mode 100644 library/src/prelude.rs rename {src/library => library/src}/structure/doc.rs (90%) rename {src/library => library/src}/structure/heading.rs (97%) rename {src/library => library/src}/structure/list.rs (98%) rename {src/library => library/src}/structure/mod.rs (100%) rename {src/library => library/src}/structure/reference.rs (95%) rename {src/library => library/src}/structure/table.rs (87%) rename {src/library => library/src}/text/deco.rs (98%) rename {src/library => library/src}/text/link.rs (87%) rename {src/library => library/src}/text/mod.rs (85%) rename {src/library => library/src}/text/par.rs (98%) rename {src/library => library/src}/text/quotes.rs (99%) rename {src/library => library/src}/text/raw.rs (94%) rename {src/library => library/src}/text/shaping.rs (98%) rename {src/library => library/src}/text/shift.rs (97%) rename {src/library => library/src}/utility/color.rs (98%) rename {src/library => library/src}/utility/data.rs (98%) rename {src/library => library/src}/utility/math.rs (99%) rename {src/library => library/src}/utility/mod.rs (89%) rename {src/library => library/src}/utility/string.rs (98%) create mode 100644 src/geom/stroke.rs delete mode 100644 src/library/math/frac.rs delete mode 100644 src/library/math/mod.rs delete mode 100644 src/library/math/script.rs delete mode 100644 src/library/prelude.rs delete mode 100644 src/library/raw.rs create mode 100644 src/model/items.rs diff --git a/Cargo.lock b/Cargo.lock index e1eabd019..aabc95e1a 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 f08f9056f..0e9648771 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" -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"] } -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" +syntect = { version = "5", default-features = false, features = ["default-syntaxes", "regex-fancy"] } 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 } +ttf-parser = "0.17" +unicode-segmentation = "1" +unicode-xid = "0.2" +unscanny = "0.1" +usvg = { version = "0.22", default-features = false } [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 2437b7236..965d0c788 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 000000000..5e3f227ad --- /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 similarity index 98% rename from src/main.rs rename to cli/src/main.rs index e32bb8c60..62cad8ab5 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 000000000..18f4779c4 --- /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 similarity index 93% rename from src/library/ext.rs rename to library/src/ext.rs index 07b55a7bb..72ef484b6 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>>) -> Self; /// Set alignments for this content. - fn aligned(self, aligns: Axes>) -> Self; + fn aligned(self, aligns: Axes>) -> Self; /// Pad this content at the sides. fn padded(self, padding: Sides>) -> Self; @@ -83,7 +83,7 @@ impl ContentExt for Content { layout::BoxNode { sizing, child: self }.pack() } - fn aligned(self, aligns: Axes>) -> Self { + fn aligned(self, aligns: Axes>) -> 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 similarity index 95% rename from src/library/graphics/hide.rs rename to library/src/graphics/hide.rs index d320b06c5..3affd8099 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 similarity index 96% rename from src/library/graphics/image.rs rename to library/src/graphics/image.rs index e27ea4884..6bf02265f 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 similarity index 80% rename from src/library/graphics/line.rs rename to library/src/graphics/line.rs index ee7813a51..df4271717 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 { let origin = args.named("origin")?.unwrap_or_default(); @@ -66,15 +66,3 @@ impl LayoutInline for LineNode { Ok(vec![frame]) } } - -castable! { - Axes>, - 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 similarity index 100% rename from src/library/graphics/mod.rs rename to library/src/graphics/mod.rs diff --git a/src/library/graphics/shape.rs b/library/src/graphics/shape.rs similarity index 95% rename from src/library/graphics/shape.rs rename to library/src/graphics/shape.rs index 4804cd681..d980b64ac 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 ShapeNode { pub const FILL: Option = None; /// How to stroke the shape. #[property(skip, resolve, fold)] - pub const STROKE: Smart>> = Smart::Auto; + pub const STROKE: Smart>> = Smart::Auto; /// How much to pad the shape's content. #[property(resolve, fold)] @@ -62,7 +62,7 @@ impl ShapeNode { if is_round(S) { styles.set_opt( Self::STROKE, - args.named::>>("stroke")? + args.named::>>("stroke")? .map(|some| some.map(Sides::splat)), ); } else { @@ -140,7 +140,7 @@ impl LayoutInline for ShapeNode { 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 similarity index 90% rename from src/library/layout/align.rs rename to library/src/layout/align.rs index 2ee565cc5..5e1b3555b 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>, + pub aligns: Axes>, /// 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 { - let aligns: Axes> = args.find()?.unwrap_or_default(); + let aligns: Axes> = 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 similarity index 98% rename from src/library/layout/columns.rs rename to library/src/layout/columns.rs index df259eab2..8eae922b1 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 similarity index 98% rename from src/library/layout/container.rs rename to library/src/layout/container.rs index 023809d04..d65b78b63 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 similarity index 98% rename from src/library/layout/flow.rs rename to library/src/layout/flow.rs index f4d18699f..a59927962 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 { 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 similarity index 94% rename from src/library/layout/grid.rs rename to library/src/layout/grid.rs index 1bb676918..f6610d78e 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 { - let columns = args.named("columns")?.unwrap_or_default(); - let rows = args.named("rows")?.unwrap_or_default(); - let base_gutter: Vec = 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); + castable! { - Vec, + 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::()?.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::()?.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 similarity index 94% rename from src/library/layout/mod.rs rename to library/src/layout/mod.rs index 000cb2127..ddfaa3513 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::{ +use crate::structure::{ + DescNode, DocNode, EnumNode, ListItem, ListNode, DESC, ENUM, LIST, +}; +use crate::text::{ LinebreakNode, ParChild, ParNode, ParbreakNode, SmartQuoteNode, SpaceNode, TextNode, }; -use crate::model::{ - capability, Barrier, Content, Interruption, Node, SequenceNode, Show, StyleChain, - StyleEntry, StyleMap, StyleVec, StyleVecBuilder, StyledNode, Target, -}; -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, } +/// 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::() { return self.sequence(seq, styles); - } else if content.has::() { - if self.show(&content, styles)? { - return Ok(()); - } + } else if content.has::() && 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::() { + Some(Interruption::Page) + } else if styled.map.interrupts::() { + Some(Interruption::Par) + } else if styled.map.interrupts::() + || styled.map.interrupts::() + || styled.map.interrupts::() + { + 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::() { + if content.is::() { /* Nothing to do */ } else if let Some(colbreak) = content.downcast::() { if colbreak.weak { diff --git a/src/library/layout/pad.rs b/library/src/layout/pad.rs similarity index 98% rename from src/library/layout/pad.rs rename to library/src/layout/pad.rs index 920660d68..318d9f270 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 similarity index 99% rename from src/library/layout/page.rs rename to library/src/layout/page.rs index 8d0817499..53a8cbc7b 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 similarity index 94% rename from src/library/layout/place.rs rename to library/src/layout/place.rs index ee38ebe60..7d760ab6e 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 { - 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::("body")?; diff --git a/src/library/layout/spacing.rs b/library/src/layout/spacing.rs similarity index 97% rename from src/library/layout/spacing.rs rename to library/src/layout/spacing.rs index c410eee73..67fff5dba 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 similarity index 99% rename from src/library/layout/stack.rs rename to library/src/layout/stack.rs index e1e70de97..44bcbf670 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 similarity index 96% rename from src/library/layout/transform.rs rename to library/src/layout/transform.rs index a73a1827e..4e0b8ac21 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; impl TransformNode { /// The origin of the transformation. #[property(resolve)] - pub const ORIGIN: Axes> = Axes::default(); + pub const ORIGIN: Axes> = Axes::default(); fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let transform = match T { diff --git a/src/library/mod.rs b/library/src/lib.rs similarity index 82% rename from src/library/mod.rs rename to library/src/lib.rs index 184c515e2..ed332a063 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 000000000..e46ba0409 --- /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, + /// 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 = Some(Ratio::one().into()); + /// The spacing below display math. + #[property(resolve, shorthand(around))] + pub const BELOW: Option = Some(Ratio::one().into()); +} + +impl Show for MathNode { + fn unguard_parts(&self, _: Selector) -> Content { + self.clone().pack() + } + + fn field(&self, _: &str) -> Option { + None + } + + fn realize(&self, _: Tracked, _: StyleChain) -> SourceResult { + Ok(if self.display { + self.clone().pack().aligned(Axes::with_x(Some(Align::Center.into()))) + } else { + self.clone().pack() + }) + } + + fn finalize( + &self, + _: Tracked, + styles: StyleChain, + realized: Content, + ) -> SourceResult { + 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, + _: &Regions, + styles: StyleChain, + ) -> SourceResult> { + 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, + /// The superscript. + pub sup: Option, +} + +#[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 000000000..7b40aa2bc --- /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::() { + return EcoString::new(); + } + + if self.is::() { + return r"\\".into(); + } + + if let Some(node) = self.to::() { + 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, + styles: StyleChain, +) -> SourceResult { + // 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, +} + +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 000000000..0c3b0eb19 --- /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 similarity index 90% rename from src/library/structure/doc.rs rename to library/src/structure/doc.rs index c3af3f1ca..ac12c3ab9 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 similarity index 97% rename from src/library/structure/heading.rs rename to library/src/structure/heading.rs index 5b056c306..62a670009 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 similarity index 98% rename from src/library/structure/list.rs rename to library/src/structure/list.rs index f061c5f84..2015f19ba 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 similarity index 100% rename from src/library/structure/mod.rs rename to library/src/structure/mod.rs diff --git a/src/library/structure/reference.rs b/library/src/structure/reference.rs similarity index 95% rename from src/library/structure/reference.rs rename to library/src/structure/reference.rs index b4e8b0474..632ecba5f 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 similarity index 87% rename from src/library/structure/table.rs rename to library/src/structure/table.rs index 8a4eb3021..722f11e6a 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> = Celled::Value(None); /// How to stroke the cells. #[property(resolve, fold)] - pub const STROKE: Option = Some(RawStroke::default()); + pub const STROKE: Option = Some(PartialStroke::default()); /// How much to pad the cells's content. pub const PADDING: Rel = Abs::pt(5.0).into(); @@ -31,11 +31,11 @@ impl TableNode { pub const BELOW: Option = Some(Ratio::one().into()); fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { - let columns = args.named("columns")?.unwrap_or_default(); - let rows = args.named("rows")?.unwrap_or_default(); - let base_gutter: Vec = 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 { 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 similarity index 98% rename from src/library/text/deco.rs rename to library/src/text/deco.rs index 158647f28..cd3acef54 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 DecoNode { /// 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 = Smart::Auto; + pub const STROKE: Smart = Smart::Auto; /// Position of the line relative to the baseline, read from the font tables /// if `auto`. #[property(resolve)] @@ -72,7 +72,7 @@ impl Show for DecoNode { #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct Decoration { pub line: DecoLine, - pub stroke: RawStroke, + pub stroke: PartialStroke, pub offset: Smart, 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 similarity index 87% rename from src/library/text/link.rs rename to library/src/text/link.rs index 1e9adc3e4..82abe5cd4 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 similarity index 85% rename from src/library/text/mod.rs rename to library/src/text/mod.rs index 18e747d07..d793f6148 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 = 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 = 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 = 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); + castable! { - Vec, + 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::()? - .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); 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 { +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); -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 { +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::().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 { 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 similarity index 98% rename from src/library/text/par.rs rename to library/src/text/par.rs index 50089b20f..95371e1aa 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 = Smart::Auto; fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { @@ -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 { - 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> { - 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, width: Abs, ) -> Vec> { - 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 similarity index 99% rename from src/library/text/quotes.rs rename to library/src/text/quotes.rs index 0a22646a1..ab4d3f9dd 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 similarity index 94% rename from src/library/text/raw.rs rename to library/src/text/raw.rs index 0c7696361..31f1517e3 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 similarity index 98% rename from src/library/text/shaping.rs rename to library/src/text/shaping.rs index 9ac3db1c1..32143862c 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 + 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 { 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 similarity index 97% rename from src/library/text/shift.rs rename to library/src/text/shift.rs index c3cf8b03b..e5f142dde 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 { /// Checks whether the first retrievable family contains all code points of the /// given string. fn is_shapable(world: Tracked, 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 similarity index 98% rename from src/library/utility/color.rs rename to library/src/utility/color.rs index a5a5704d3..8bb123349 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 { diff --git a/src/library/utility/data.rs b/library/src/utility/data.rs similarity index 98% rename from src/library/utility/data.rs rename to library/src/utility/data.rs index 0cff42c1f..3edade55c 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 { diff --git a/src/library/utility/math.rs b/library/src/utility/math.rs similarity index 99% rename from src/library/utility/math.rs rename to library/src/utility/math.rs index 7c3af490f..dd37e8e73 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 { diff --git a/src/library/utility/mod.rs b/library/src/utility/mod.rs similarity index 89% rename from src/library/utility/mod.rs rename to library/src/utility/mod.rs index 2d637d298..402944cdf 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 { @@ -39,7 +39,7 @@ pub fn eval(vm: &mut Vm, args: &mut Args) -> SourceResult { 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 similarity index 98% rename from src/library/utility/string.rs rename to library/src/utility/string.rs index 66f127d10..ed444d354 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 { diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 823dbdc8d..d3b3b30ad 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 { + ) -> typst::diag::SourceResult { unimplemented!() } } @@ -84,7 +84,7 @@ fn expand_node( let checks = items.iter().map(|cap| { quote! { if id == TypeId::of::() { - 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 { + ) -> typst::diag::SourceResult { let mut styles = model::StyleMap::new(); #user #(#bindings)* diff --git a/src/diag.rs b/src/diag.rs index f4725f00a..0a5327133 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 = Result>>; diff --git a/src/export/pdf/font.rs b/src/export/pdf/font.rs index 3398478af..075e34908 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 73dd9e96e..add167b42 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 7fbf78338..3167989c4 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 df8f512f2..41fff8637 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 d900d3c44..49225c047 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 + 'a { + pub fn from_data(data: &[u8]) -> impl Iterator + '_ { 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 9e16afc88..085f2ae75 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 e85256f52..2eac68793 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 { - 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. +impl FromStr for Lang { + type Err = &'static str; + + /// Construct a language from a two- or three-byte ISO 639-1/2/3 code. + fn from_str(iso: &str) -> Result { + 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 { + 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 { - /// Construct a region from its two-byte ISO 3166-1 alpha-2 code. - pub fn from_str(iso: &str) -> Option { - if iso.is_ascii() { - let mut bytes: [u8; 2] = iso.as_bytes().try_into().ok()?; - bytes.make_ascii_uppercase(); - Some(Self(bytes)) - } else { - None - } - } - /// 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 { + 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 a7ee27639..25409e3a8 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 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 33a864ca2..8e80d72be 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 6e2b8f9ba..c1469b26b 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 c5bbefa4c..b07f09af1 100644 --- a/src/geom/paint.rs +++ b/src/geom/paint.rs @@ -388,24 +388,6 @@ impl From 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 000000000..eae43c243 --- /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 { + /// The stroke's paint. + pub paint: Smart, + /// The stroke's thickness. + pub thickness: Smart, +} + +impl PartialStroke { + /// 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 Debug for PartialStroke { + 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(""), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 051a58f3c..dbef46e74 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> { + 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, 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, 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 791fd19a2..000000000 --- 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 5bb5054da..000000000 --- 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), - /// A fraction: `x/2`. - Frac(Arc), - /// A numbered math alignment indicator: `&`, `&&`. - Align(usize), - /// A row of mathematical material. - Row(Arc>, 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 = Some(Ratio::one().into()); - /// The spacing below display math. - #[property(resolve, shorthand(around))] - pub const BELOW: Option = Some(Ratio::one().into()); - - fn construct(_: &mut Vm, _: &mut Args) -> SourceResult { - 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 { - None - } - - fn realize(&self, _: Tracked, _: StyleChain) -> SourceResult { - Ok(if self.display() { - self.clone().pack().aligned(Axes::with_x(Some(Align::Center.into()))) - } else { - self.clone().pack() - }) - } - - fn finalize( - &self, - _: Tracked, - styles: StyleChain, - realized: Content, - ) -> SourceResult { - 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, - _: &Regions, - styles: StyleChain, - ) -> SourceResult> { - 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, - node: &dyn Texify, - span: Span, - style: Style, - styles: StyleChain, -) -> SourceResult { - 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, -} - -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 09f52164f..000000000 --- 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, - /// The superscript. - pub sup: Option, -} - -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 66e35e68d..000000000 --- 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 67aa651d7..000000000 --- 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 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: "2d alignment", -} - -castable! { - Axes>, - Expected: "1d or 2d alignment", - @align: RawAlign => { - let mut aligns = Axes::default(); - aligns.set(align.axis(), Some(*align)); - aligns - }, - @aligns: Axes => 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 { - /// The stroke's paint. - pub paint: Smart, - /// The stroke's thickness. - pub thickness: Smart, -} - -impl RawStroke { - /// 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; - - fn resolve(self, styles: StyleChain) -> Self::Output { - RawStroke { - paint: self.paint, - thickness: self.thickness.resolve(styles), - } - } -} - -impl Fold for RawStroke { - 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 Debug for RawStroke { - 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(""), - } - } -} - -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 f95fbf08c..9fb30b9cd 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 196f02ecf..053248ec3 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>); @@ -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 7356ef70d..cbb2952d6 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: 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 Cast> for Spanned { } } -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: "2d alignment", +} + +castable! { + Axes>, + Expected: "1d or 2d alignment", + @align: GenAlign => { + let mut aligns = Axes::default(); + aligns.set(align.axis(), Some(*align)); + aligns + }, + @aligns: Axes => 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>, + 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::()? + .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 Cast for Option { 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 1cffa7731..372f6ff68 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) -> Self { + item!(text)(text.into()) + } + /// Create a new sequence node from multiples nodes. pub fn sequence(seq: Vec) -> 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::() { if let Some(rhs_mut) = rhs.try_downcast_mut::() { - lhs_mut.0.extend(rhs_mut.0.drain(..)); + lhs_mut.0.append(&mut rhs_mut.0); } else if let Some(rhs) = rhs.downcast::() { lhs_mut.0.extend(rhs.0.iter().cloned()); } else { diff --git a/src/model/dict.rs b/src/model/dict.rs index 3e4fd956c..49e50aa0d 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>); diff --git a/src/model/eval.rs b/src/model/eval.rs index 02617ed6f..8e287f146 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 { 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 { - 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 { - 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 { - Ok(vm.text(self.get().clone())) - } -} - -impl Eval for ast::Escape { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { 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 { - 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 { 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 { 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 { - let nodes = self - .children() - .map(|node| node.eval(vm)) - .collect::>()?; - Ok(math::MathNode::Row(Arc::new(nodes), self.span()).pack()) + Ok((vm.items.math)( + self.children() + .map(|node| node.eval(vm)) + .collect::>()?, + self.display(), + )) } } impl Eval for ast::MathNode { - type Output = math::MathNode; + type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult { 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::>()?, - ), - node.span(), - ), - Self::Expr(expr) => { - let content = expr.eval(vm)?.display(vm.world); - if let Some(node) = content.downcast::() { - 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::Script { - type Output = math::MathNode; +impl Eval for ast::Atom { + type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult { - 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_atom)(self.get().clone())) + } +} + +impl Eval for ast::Script { + type Output = Content; + + fn eval(&self, vm: &mut Vm) -> SourceResult { + 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 { - 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 { - Ok(math::MathNode::Align(self.count())) + fn eval(&self, vm: &mut Vm) -> SourceResult { + 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::() .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::().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::().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 { // 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 dff58233e..5be1aae3e 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 000000000..164d9602a --- /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 = 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, document: &Content) -> SourceResult>, + /// 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, 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, 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, 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, sup: Option) -> 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(&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 07cbb8221..26d27dfa4 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 b4f8f6534..fdebce0aa 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -1,5 +1,7 @@ //! Layout and computation model. +#[macro_use] +mod items; #[macro_use] mod cast; #[macro_use] @@ -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 7eb814c15..ee126b038 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 { (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 { (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 { (Dyn(a), Dyn(b)) => { // 1D alignments can be summed into 2D alignments. if let (Some(&a), Some(&b)) = - (a.downcast::(), b.downcast::()) + (a.downcast::(), b.downcast::()) { if a.axis() != b.axis() { Value::dynamic(match a.axis() { diff --git a/src/model/str.rs b/src/model/str.rs index 843da9a86..4aa40c549 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 c58a1beb6..24566b096 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 { - self.0.iter().filter_map(|entry| entry.interruption()).max() + /// Whether this map contains styles for the given `node.` + pub fn interrupts(&self) -> bool { + self.0.iter().any(|entry| entry.is_of(NodeId::of::())) } } @@ -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 { + /// 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 { - while let Some(entry) = self.entries.next() { + for entry in &mut self.entries { match entry { StyleEntry::Property(property) => { if let Some(value) = property.downcast::() { @@ -662,9 +650,9 @@ impl Property { self.key == KeyId::of::() } - /// Whether this property belongs to the node `T`. - pub fn is_of(&self) -> bool { - self.node == NodeId::of::() + /// 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 { - if self.is_of::() { - Some(Interruption::Page) - } else if self.is_of::() { - Some(Interruption::Par) - } else if self.is_of::() - || self.is_of::() - || self.is_of::() - { - 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; + + 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>> { } } +impl Fold for PartialStroke { + 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 { - if let Pattern::Node(id) = self.pattern { - if id == NodeId::of::() - || id == NodeId::of::() - || id == NodeId::of::() - { - 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 d68f42a08..077198835 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, exp: &str) { diff --git a/src/model/vm.rs b/src/model/vm.rs index 4de57d1c8..db0bf77cb 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, + /// 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) -> 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 ecfa9a5b8..06e41fa02 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 + '_ { + pub fn children(&self) -> impl DoubleEndedIterator + '_ { 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 + '_ { + pub fn children(&self) -> impl DoubleEndedIterator + '_ { 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 + '_ { + pub fn exprs(&self) -> impl DoubleEndedIterator + '_ { self.0.children().filter_map(SyntaxNode::cast) } } @@ -774,7 +780,7 @@ node! { impl Array { /// The array's items. - pub fn items(&self) -> impl Iterator + '_ { + pub fn items(&self) -> impl DoubleEndedIterator + '_ { self.0.children().filter_map(SyntaxNode::cast) } } @@ -811,7 +817,7 @@ node! { impl Dict { /// The dictionary's items. - pub fn items(&self) -> impl Iterator + '_ { + pub fn items(&self) -> impl DoubleEndedIterator + '_ { 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 + '_ { + pub fn items(&self) -> impl DoubleEndedIterator + '_ { self.0.children().filter_map(SyntaxNode::cast) } } @@ -1252,7 +1258,7 @@ impl Closure { } /// The parameter bindings. - pub fn params(&self) -> impl Iterator + '_ { + pub fn params(&self) -> impl DoubleEndedIterator + '_ { self.0 .children() .find(|x| x.kind() == &NodeKind::Params) diff --git a/src/syntax/highlight.rs b/src/syntax/highlight.rs index 325b72742..0db457855 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 529defd76..15c0df0c5 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 6a7d424aa..4ec4abdf4 100644 --- a/src/syntax/node.rs +++ b/src/syntax/node.rs @@ -99,50 +99,6 @@ impl SyntaxNode { self.children().rev().find_map(Self::cast) } - /// Change the type of the node. - pub fn convert(&mut self, kind: NodeKind) { - match self { - Self::Inner(inner) => { - let node = Arc::make_mut(inner); - node.erroneous |= kind.is_error(); - node.data.kind = kind; - } - Self::Leaf(leaf) => leaf.kind = kind, - } - } - - /// Set a synthetic span for the node and all its descendants. - pub fn synthesize(&mut self, span: Span) { - match self { - Self::Inner(inner) => Arc::make_mut(inner).synthesize(span), - Self::Leaf(leaf) => leaf.synthesize(span), - } - } - - /// Assign spans to each node. - pub fn numberize(&mut self, id: SourceId, within: Range) -> 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> { - 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. @@ -156,6 +112,54 @@ impl SyntaxNode { self.children().flat_map(Self::leafs).collect() } } + + /// Change the type of the node. + pub(super) fn convert(&mut self, kind: NodeKind) { + match self { + Self::Inner(inner) => { + let node = Arc::make_mut(inner); + node.erroneous |= kind.is_error(); + node.data.kind = kind; + } + Self::Leaf(leaf) => leaf.kind = kind, + } + } + + /// Set a synthetic span for the node and all its descendants. + 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), + } + } + + /// Assign spans to each node. + pub(super) fn numberize( + &mut self, + id: SourceId, + within: Range, + ) -> NumberingResult { + match self { + Self::Inner(inner) => Arc::make_mut(inner).numberize(id, None, within), + Self::Leaf(leaf) => leaf.numberize(id, within), + } + } + + /// If the span points into this node, convert it to a byte range. + pub(super) fn range(&self, span: Span, offset: usize) -> Option> { + match self { + Self::Inner(inner) => inner.range(span, offset), + Self::Leaf(leaf) => leaf.range(span, offset), + } + } + + /// 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, + } + } } impl Default for SyntaxNode { @@ -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>, @@ -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> { + fn range(&self, span: Span, mut offset: usize) -> Option> { // 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, replacement: Vec, @@ -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) -> NumberingResult { + fn numberize(&mut self, id: SourceId, within: Range) -> 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> { + fn range(&self, span: Span, offset: usize) -> Option> { (self.span == span).then(|| offset .. offset + self.len()) } } diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs index 83b333f4f..4c8e10138 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 d4d9a8f61..e3ff67b8d 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 59e6cd3f8..c787fa697 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 645880c4e..800760e2f 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) -> Self + pub fn from_str_like(s: S) -> Self where S: AsRef + Into, { @@ -324,13 +329,13 @@ impl From for EcoString { impl From<&str> for EcoString { fn from(s: &str) -> Self { - Self::from_str(s) + Self::from_str_like(s) } } impl From 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 bc7aa250c..df3c446e8 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) -> impl Debug where @@ -33,6 +36,13 @@ where Wrapper(f) } +/// Calculate a 128-bit siphash of a value. +pub fn hash128(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 a1a9d32fd..55a853cff 100644 --- a/tests/typ/math/basic.typ +++ b/tests/typ/math/basic.typ @@ -15,12 +15,6 @@ $ sum_(k=0)^n k = (n(n+1))/2 $ // Test that blackboard style looks nice. $ 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 f3f5bfbeb..7c0a6e698 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::("left-hand side")?; let rhs = args.expect::("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::()?.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(), } }