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