Split crates

This commit is contained in:
Laurenz 2022-11-03 11:44:53 +01:00
parent 56342bd972
commit 37a7afddfa
93 changed files with 1542 additions and 1357 deletions

61
Cargo.lock generated
View File

@ -1110,49 +1110,78 @@ version = "0.1.0"
dependencies = [
"bitflags",
"bytemuck",
"chrono",
"codespan-reporting",
"comemo",
"csv",
"dirs",
"elsa",
"flate2",
"hypher",
"iai",
"image",
"kurbo",
"lipsum",
"memmap2",
"miniz_oxide",
"notify",
"once_cell",
"pdf-writer",
"pico-args",
"pixglyph",
"regex",
"resvg",
"rex",
"roxmltree",
"rustybuzz",
"same-file",
"serde",
"serde_json",
"siphasher",
"subsetter",
"svg2pdf",
"syntect",
"tiny-skia",
"ttf-parser 0.17.1",
"typed-arena",
"typst-library",
"typst-macros",
"unicode-bidi",
"unicode-math",
"unicode-script",
"unicode-segmentation",
"unicode-xid",
"unscanny",
"usvg",
"walkdir",
]
[[package]]
name = "typst-cli"
version = "0.1.0"
dependencies = [
"chrono",
"codespan-reporting",
"comemo",
"dirs",
"elsa",
"memmap2",
"notify",
"once_cell",
"pico-args",
"same-file",
"siphasher",
"typst",
"typst-library",
"walkdir",
]
[[package]]
name = "typst-library"
version = "0.1.0"
dependencies = [
"comemo",
"csv",
"hypher",
"kurbo",
"lipsum",
"once_cell",
"rex",
"roxmltree",
"rustybuzz",
"serde_json",
"syntect",
"ttf-parser 0.17.1",
"typed-arena",
"typst",
"unicode-bidi",
"unicode-math",
"unicode-script",
"unscanny",
"xi-unicode",
]

View File

@ -5,97 +5,47 @@ authors = ["The Typst Project Developers"]
edition = "2021"
[workspace]
members = ["macros"]
members = ["cli", "library", "macros"]
[dependencies]
# Workspace
typst-macros = { path = "./macros" }
# Utilities
typst-macros = { path = "macros" }
bitflags = "1"
bytemuck = "1"
comemo = "0.1"
flate2 = "1"
image = { version = "0.24", default-features = false, features = ["png", "jpeg", "gif"] }
miniz_oxide = "0.5"
once_cell = "1"
pdf-writer = "0.6"
pixglyph = { git = "https://github.com/typst/pixglyph" }
regex = "1"
resvg = { version = "0.22", default-features = false }
rex = { git = "https://github.com/laurmaedje/ReX" }
roxmltree = "0.14"
rustybuzz = "0.5"
serde = { version = "1", features = ["derive"] }
siphasher = "0.3"
typed-arena = "2"
unscanny = "0.1"
# Text and font handling
hypher = "0.1"
kurbo = "0.8"
rustybuzz = "0.5"
ttf-parser = "0.17"
unicode-bidi = "0.3.5"
unicode-script = "0.5"
unicode-segmentation = "1"
unicode-xid = "0.2"
xi-unicode = "0.3"
# Raster and vector graphics handling
image = { version = "0.24", default-features = false, features = ["png", "jpeg", "gif"] }
usvg = { version = "0.22", default-features = false }
# External implementation of user-facing features
csv = "1"
lipsum = { git = "https://github.com/reknih/lipsum" }
rex = { git = "https://github.com/laurmaedje/ReX" }
serde_json = "1"
syntect = { version = "5", default-features = false, features = ["default-syntaxes", "regex-fancy"] }
unicode-math = { git = "https://github.com/s3bk/unicode-math/" }
# PDF export
miniz_oxide = "0.5"
pdf-writer = "0.6"
subsetter = "0.1"
svg2pdf = "0.4"
# Rendering
flate2 = "1"
pixglyph = { git = "https://github.com/typst/pixglyph" }
resvg = { version = "0.22", default-features = false }
roxmltree = "0.14"
syntect = { version = "5", default-features = false, features = ["default-syntaxes", "regex-fancy"] }
tiny-skia = "0.6.2"
# Command line interface
chrono = { version = "0.4", default-features = false, features = ["clock", "std"], optional = true }
codespan-reporting = { version = "0.11", optional = true }
dirs = { version = "4", optional = true }
elsa = { version = "1.7", optional = true }
memmap2 = { version = "0.5", optional = true }
notify = { version = "5", optional = true }
pico-args = { version = "0.4", optional = true }
same-file = { version = "1", optional = true }
walkdir = { version = "2", optional = true }
ttf-parser = "0.17"
unicode-segmentation = "1"
unicode-xid = "0.2"
unscanny = "0.1"
usvg = { version = "0.22", default-features = false }
[dev-dependencies]
typst-library = { path = "library" }
iai = { git = "https://github.com/reknih/iai" }
elsa = "1.7"
walkdir = "2"
[features]
cli = [
"chrono",
"codespan-reporting",
"dirs",
"elsa",
"memmap2",
"notify",
"pico-args",
"same-file",
"walkdir",
]
[profile.dev]
debug = 0 # Faster compilation
debug = 0
[profile.dev.package."*"]
opt-level = 2 # Faster test execution
[[bin]]
name = "typst"
required-features = ["cli"]
opt-level = 2
[[test]]
name = "typeset"

View File

@ -1,4 +1,4 @@
use std::path::Path;
use std::path::{Path, PathBuf};
use comemo::{Prehashed, Track, Tracked};
use iai::{black_box, main, Iai};
@ -20,7 +20,7 @@ main!(
bench_parse,
bench_edit,
bench_eval,
bench_layout,
bench_typeset,
bench_highlight,
bench_render,
);
@ -80,12 +80,10 @@ fn bench_eval(iai: &mut Iai) {
iai.run(|| typst::model::eval(world.track(), route.track(), id).unwrap());
}
fn bench_layout(iai: &mut Iai) {
fn bench_typeset(iai: &mut Iai) {
let world = BenchWorld::new();
let id = world.source.id();
let route = typst::model::Route::default();
let module = typst::model::eval(world.track(), route.track(), id).unwrap();
iai.run(|| typst::library::layout::Layout::layout(&module.content, world.track()));
iai.run(|| typst::typeset(&world, id));
}
fn bench_render(iai: &mut Iai) {
@ -104,7 +102,13 @@ struct BenchWorld {
impl BenchWorld {
fn new() -> Self {
let config = Config::default();
let config = Config {
root: PathBuf::new(),
scope: typst_library::scope(),
styles: typst_library::styles(),
items: typst_library::items(),
};
let font = Font::new(FONT.into(), 0).unwrap();
let book = FontBook::from_fonts([&font]);
let id = SourceId::from_u16(0);

26
cli/Cargo.toml Normal file
View 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"

View File

@ -174,13 +174,20 @@ fn dispatch(command: Command) -> StrResult<()> {
/// Execute a typesetting command.
fn typeset(command: TypesetCommand) -> StrResult<()> {
let mut config = Config::default();
if let Some(root) = &command.root {
config.root = root.clone();
let root = if let Some(root) = &command.root {
root.clone()
} else if let Some(dir) = command.input.parent() {
config.root = dir.into();
}
dir.into()
} else {
PathBuf::new()
};
let config = Config {
root,
scope: typst_library::scope(),
styles: typst_library::styles(),
items: typst_library::items(),
};
// Create the world that serves sources, fonts and files.
let mut world = SystemWorld::new(config);

26
library/Cargo.toml Normal file
View 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"

View File

@ -1,5 +1,5 @@
use super::*;
use crate::library::prelude::*;
use crate::prelude::*;
/// Additional methods on content.
pub trait ContentExt {
@ -19,7 +19,7 @@ pub trait ContentExt {
fn boxed(self, sizing: Axes<Option<Rel<Length>>>) -> Self;
/// Set alignments for this content.
fn aligned(self, aligns: Axes<Option<RawAlign>>) -> Self;
fn aligned(self, aligns: Axes<Option<GenAlign>>) -> Self;
/// Pad this content at the sides.
fn padded(self, padding: Sides<Rel<Length>>) -> Self;
@ -83,7 +83,7 @@ impl ContentExt for Content {
layout::BoxNode { sizing, child: self }.pack()
}
fn aligned(self, aligns: Axes<Option<RawAlign>>) -> Self {
fn aligned(self, aligns: Axes<Option<GenAlign>>) -> Self {
layout::AlignNode { aligns, child: self }.pack()
}
@ -115,9 +115,11 @@ impl StyleMapExt for StyleMap {
fn set_family(&mut self, preferred: text::FontFamily, existing: StyleChain) {
self.set(
text::TextNode::FAMILY,
std::iter::once(preferred)
.chain(existing.get(text::TextNode::FAMILY).iter().cloned())
.collect(),
FallbackList(
std::iter::once(preferred)
.chain(existing.get(text::TextNode::FAMILY).0.iter().cloned())
.collect(),
),
);
}
}

View File

@ -1,4 +1,4 @@
use crate::library::prelude::*;
use crate::prelude::*;
/// Hide content without affecting layout.
#[derive(Debug, Hash)]

View File

@ -1,8 +1,9 @@
use std::ffi::OsStr;
use crate::image::{Image, ImageFormat, RasterFormat, VectorFormat};
use crate::library::prelude::*;
use crate::library::text::TextNode;
use typst::image::{Image, ImageFormat, RasterFormat, VectorFormat};
use crate::prelude::*;
use crate::text::TextNode;
/// Show a raster or vector graphic.
#[derive(Debug, Hash)]

View File

@ -1,4 +1,4 @@
use crate::library::prelude::*;
use crate::prelude::*;
/// Display a line without affecting the layout.
#[derive(Debug, Hash)]
@ -13,7 +13,7 @@ pub struct LineNode {
impl LineNode {
/// How to stroke the line.
#[property(resolve, fold)]
pub const STROKE: RawStroke = RawStroke::default();
pub const STROKE: PartialStroke = PartialStroke::default();
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
let origin = args.named("origin")?.unwrap_or_default();
@ -66,15 +66,3 @@ impl LayoutInline for LineNode {
Ok(vec![frame])
}
}
castable! {
Axes<Rel<Length>>,
Expected: "array of two relative lengths",
Value::Array(array) => {
let mut iter = array.into_iter();
match (iter.next(), iter.next(), iter.next()) {
(Some(a), Some(b), None) => Axes::new(a.cast()?, b.cast()?),
_ => Err("point array must contain exactly two entries")?,
}
},
}

View File

@ -1,7 +1,7 @@
use std::f64::consts::SQRT_2;
use crate::library::prelude::*;
use crate::library::text::TextNode;
use crate::prelude::*;
use crate::text::TextNode;
/// A sizable and fillable shape with optional content.
#[derive(Debug, Hash)]
@ -25,7 +25,7 @@ impl<const S: ShapeKind> ShapeNode<S> {
pub const FILL: Option<Paint> = None;
/// How to stroke the shape.
#[property(skip, resolve, fold)]
pub const STROKE: Smart<Sides<Option<RawStroke>>> = Smart::Auto;
pub const STROKE: Smart<Sides<Option<PartialStroke>>> = Smart::Auto;
/// How much to pad the shape's content.
#[property(resolve, fold)]
@ -62,7 +62,7 @@ impl<const S: ShapeKind> ShapeNode<S> {
if is_round(S) {
styles.set_opt(
Self::STROKE,
args.named::<Smart<Option<RawStroke>>>("stroke")?
args.named::<Smart<Option<PartialStroke>>>("stroke")?
.map(|some| some.map(Sides::splat)),
);
} else {
@ -140,7 +140,7 @@ impl<const S: ShapeKind> LayoutInline for ShapeNode<S> {
Smart::Auto if fill.is_none() => Sides::splat(Some(Stroke::default())),
Smart::Auto => Sides::splat(None),
Smart::Custom(strokes) => {
strokes.map(|s| s.map(RawStroke::unwrap_or_default))
strokes.map(|s| s.map(PartialStroke::unwrap_or_default))
}
};

View File

@ -1,11 +1,11 @@
use crate::library::prelude::*;
use crate::library::text::{HorizontalAlign, ParNode};
use crate::prelude::*;
use crate::text::{HorizontalAlign, ParNode};
/// Align content along the layouting axes.
#[derive(Debug, Hash)]
pub struct AlignNode {
/// How to align the content horizontally and vertically.
pub aligns: Axes<Option<RawAlign>>,
pub aligns: Axes<Option<GenAlign>>,
/// The content to be aligned.
pub child: Content,
}
@ -13,7 +13,7 @@ pub struct AlignNode {
#[node(LayoutBlock)]
impl AlignNode {
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
let aligns: Axes<Option<RawAlign>> = args.find()?.unwrap_or_default();
let aligns: Axes<Option<GenAlign>> = args.find()?.unwrap_or_default();
let body: Content = args.expect("body")?;
if let Axes { x: Some(x), y: None } = aligns {

View File

@ -1,5 +1,5 @@
use crate::library::prelude::*;
use crate::library::text::TextNode;
use crate::prelude::*;
use crate::text::TextNode;
/// Separate a region into multiple equally sized columns.
#[derive(Debug, Hash)]

View File

@ -1,4 +1,4 @@
use crate::library::prelude::*;
use crate::prelude::*;
/// An inline-level container that sizes content.
#[derive(Debug, Clone, Hash)]

View File

@ -1,8 +1,8 @@
use std::cmp::Ordering;
use super::{AlignNode, PlaceNode, Spacing};
use crate::library::prelude::*;
use crate::library::text::ParNode;
use crate::prelude::*;
use crate::text::ParNode;
/// Arrange spacing, paragraphs and block-level nodes into a flow.
///
@ -256,7 +256,7 @@ impl FlowLayouter {
/// Finish layouting and return the resulting frames.
pub fn finish(mut self) -> Vec<Frame> {
if self.expand.y {
while self.regions.backlog.len() > 0 {
while !self.regions.backlog.is_empty() {
self.finish_region();
}
}

View File

@ -1,4 +1,4 @@
use crate::library::prelude::*;
use crate::prelude::*;
/// Arrange content in a grid.
#[derive(Debug, Hash)]
@ -14,11 +14,11 @@ pub struct GridNode {
#[node(LayoutBlock)]
impl GridNode {
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
let columns = args.named("columns")?.unwrap_or_default();
let rows = args.named("rows")?.unwrap_or_default();
let base_gutter: Vec<TrackSizing> = args.named("gutter")?.unwrap_or_default();
let column_gutter = args.named("column-gutter")?;
let row_gutter = args.named("row-gutter")?;
let TrackSizings(columns) = args.named("columns")?.unwrap_or_default();
let TrackSizings(rows) = args.named("rows")?.unwrap_or_default();
let TrackSizings(base_gutter) = args.named("gutter")?.unwrap_or_default();
let column_gutter = args.named("column-gutter")?.map(|TrackSizings(v)| v);
let row_gutter = args.named("row-gutter")?.map(|TrackSizings(v)| v);
Ok(Self {
tracks: Axes::new(columns, rows),
gutter: Axes::new(
@ -66,19 +66,26 @@ pub enum TrackSizing {
Fractional(Fr),
}
/// Track sizing definitions.
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
pub struct TrackSizings(pub Vec<TrackSizing>);
castable! {
Vec<TrackSizing>,
TrackSizings,
Expected: "integer, auto, relative length, fraction, or array of the latter three",
Value::Auto => vec![TrackSizing::Auto],
Value::Length(v) => vec![TrackSizing::Relative(v.into())],
Value::Ratio(v) => vec![TrackSizing::Relative(v.into())],
Value::Relative(v) => vec![TrackSizing::Relative(v)],
Value::Fraction(v) => vec![TrackSizing::Fractional(v)],
Value::Int(v) => vec![TrackSizing::Auto; Value::Int(v).cast::<NonZeroUsize>()?.get()],
Value::Array(values) => values
Value::Auto => Self(vec![TrackSizing::Auto]),
Value::Length(v) => Self(vec![TrackSizing::Relative(v.into())]),
Value::Ratio(v) => Self(vec![TrackSizing::Relative(v.into())]),
Value::Relative(v) => Self(vec![TrackSizing::Relative(v)]),
Value::Fraction(v) => Self(vec![TrackSizing::Fractional(v)]),
Value::Int(v) => Self(vec![
TrackSizing::Auto;
Value::Int(v).cast::<NonZeroUsize>()?.get()
]),
Value::Array(values) => Self(values
.into_iter()
.filter_map(|v| v.cast().ok())
.collect(),
.collect()),
}
castable! {

View File

@ -28,19 +28,21 @@ use std::mem;
use comemo::Tracked;
use typed_arena::Arena;
use typst::diag::SourceResult;
use typst::frame::Frame;
use typst::geom::*;
use typst::model::{
capability, Barrier, Content, Node, SequenceNode, Show, StyleChain, StyleEntry,
StyleMap, StyleVec, StyleVecBuilder, StyledNode, Target,
};
use typst::World;
use crate::diag::SourceResult;
use crate::frame::Frame;
use crate::geom::*;
use crate::library::structure::{DocNode, ListItem, ListNode, DESC, ENUM, LIST};
use crate::library::text::{
use crate::structure::{
DescNode, DocNode, EnumNode, ListItem, ListNode, DESC, ENUM, LIST,
};
use crate::text::{
LinebreakNode, ParChild, ParNode, ParbreakNode, SmartQuoteNode, SpaceNode, TextNode,
};
use crate::model::{
capability, Barrier, Content, Interruption, Node, SequenceNode, Show, StyleChain,
StyleEntry, StyleMap, StyleVec, StyleVecBuilder, StyledNode, Target,
};
use crate::World;
/// The root-level layout.
#[capability]
@ -204,7 +206,7 @@ impl Regions {
///
/// If this is true, calling `next()` will have no effect.
pub fn in_last(&self) -> bool {
self.backlog.len() == 0 && self.last.map_or(true, |height| self.first.y == height)
self.backlog.is_empty() && self.last.map_or(true, |height| self.first.y == height)
}
/// Advance to the next region if there is any.
@ -255,6 +257,17 @@ struct Scratch<'a> {
templates: Arena<Content>,
}
/// Determines whether a style could interrupt some composable structure.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum Interruption {
/// The style forces a list break.
List,
/// The style forces a paragraph break.
Par,
/// The style forces a page break.
Page,
}
impl<'a> Builder<'a> {
pub fn new(
world: Tracked<'a, dyn World>,
@ -303,10 +316,8 @@ impl<'a> Builder<'a> {
return self.styled(styled, styles);
} else if let Some(seq) = content.downcast::<SequenceNode>() {
return self.sequence(seq, styles);
} else if content.has::<dyn Show>() {
if self.show(&content, styles)? {
return Ok(());
}
} else if content.has::<dyn Show>() && self.show(content, styles)? {
return Ok(());
}
if self.list.accept(content, styles) {
@ -371,7 +382,19 @@ impl<'a> Builder<'a> {
) -> SourceResult<()> {
let stored = self.scratch.styles.alloc(styles);
let styles = styled.map.chain(stored);
let intr = styled.map.interruption();
let intr = if styled.map.interrupts::<PageNode>() {
Some(Interruption::Page)
} else if styled.map.interrupts::<ParNode>() {
Some(Interruption::Par)
} else if styled.map.interrupts::<ListNode>()
|| styled.map.interrupts::<EnumNode>()
|| styled.map.interrupts::<DescNode>()
{
Some(Interruption::List)
} else {
None
};
if let Some(intr) = intr {
self.interrupt(intr, styles, false)?;
@ -396,10 +419,8 @@ impl<'a> Builder<'a> {
mem::take(&mut self.list).finish(self)?;
}
if intr >= Interruption::Par {
if !self.par.is_empty() {
mem::take(&mut self.par).finish(self);
}
if intr >= Interruption::Par && !self.par.is_empty() {
mem::take(&mut self.par).finish(self);
}
if intr >= Interruption::Page {
@ -471,7 +492,7 @@ impl<'a> FlowBuilder<'a> {
// 4 | generated weak fractional spacing
// 5 | par spacing
if let Some(_) = content.downcast::<ParbreakNode>() {
if content.is::<ParbreakNode>() {
/* Nothing to do */
} else if let Some(colbreak) = content.downcast::<ColbreakNode>() {
if colbreak.weak {

View File

@ -1,4 +1,4 @@
use crate::library::prelude::*;
use crate::prelude::*;
/// Pad content at the sides.
#[derive(Debug, Hash)]

View File

@ -1,7 +1,7 @@
use std::str::FromStr;
use super::ColumnsNode;
use crate::library::prelude::*;
use crate::prelude::*;
/// Layouts its child onto one or multiple pages.
#[derive(PartialEq, Clone, Hash)]

View File

@ -1,5 +1,5 @@
use super::AlignNode;
use crate::library::prelude::*;
use crate::prelude::*;
/// Place content at an absolute position.
#[derive(Debug, Hash)]
@ -8,7 +8,7 @@ pub struct PlaceNode(pub Content);
#[node(LayoutBlock)]
impl PlaceNode {
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
let aligns = args.find()?.unwrap_or(Axes::with_x(Some(RawAlign::Start)));
let aligns = args.find()?.unwrap_or(Axes::with_x(Some(GenAlign::Start)));
let dx = args.named("dx")?.unwrap_or_default();
let dy = args.named("dy")?.unwrap_or_default();
let body = args.expect::<Content>("body")?;

View File

@ -1,7 +1,7 @@
use std::cmp::Ordering;
use crate::library::prelude::*;
use crate::library::text::ParNode;
use crate::prelude::*;
use crate::text::ParNode;
/// Horizontal spacing.
#[derive(Debug, Clone, Hash)]

View File

@ -1,7 +1,8 @@
use typst::model::StyledNode;
use super::{AlignNode, Spacing};
use crate::library::prelude::*;
use crate::library::text::ParNode;
use crate::model::StyledNode;
use crate::prelude::*;
use crate::text::ParNode;
/// Arrange content and spacing along an axis.
#[derive(Debug, Hash)]

View File

@ -1,5 +1,6 @@
use crate::geom::Transform;
use crate::library::prelude::*;
use typst::geom::Transform;
use crate::prelude::*;
/// Move content without affecting layout.
#[derive(Debug, Hash)]
@ -61,7 +62,7 @@ pub type ScaleNode = TransformNode<SCALE>;
impl<const T: TransformKind> TransformNode<T> {
/// The origin of the transformation.
#[property(resolve)]
pub const ORIGIN: Axes<Option<RawAlign>> = Axes::default();
pub const ORIGIN: Axes<Option<GenAlign>> = Axes::default();
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
let transform = match T {

View File

@ -1,7 +1,4 @@
//! The standard library.
//!
//! Call [`scope`] to obtain a [`Scope`] containing all standard library
//! definitions.
//! Typst's standard library.
pub mod graphics;
pub mod layout;
@ -12,15 +9,13 @@ pub mod text;
pub mod utility;
mod ext;
mod raw;
pub use raw::*;
use typst::geom::{Align, Color, Dir, GenAlign};
use typst::model::{LangItems, Node, Scope, StyleMap};
use crate::geom::{Align, Color, Dir};
use crate::model::{Node, Scope};
use crate::LangItems;
use self::layout::Layout;
/// Construct a scope containing all standard library definitions.
/// Construct the standard library scope.
pub fn scope() -> Scope {
let mut std = Scope::new();
@ -140,21 +135,29 @@ pub fn scope() -> Scope {
std.define("rtl", Dir::RTL);
std.define("ttb", Dir::TTB);
std.define("btt", Dir::BTT);
std.define("start", RawAlign::Start);
std.define("end", RawAlign::End);
std.define("left", RawAlign::Specific(Align::Left));
std.define("center", RawAlign::Specific(Align::Center));
std.define("right", RawAlign::Specific(Align::Right));
std.define("top", RawAlign::Specific(Align::Top));
std.define("horizon", RawAlign::Specific(Align::Horizon));
std.define("bottom", RawAlign::Specific(Align::Bottom));
std.define("start", GenAlign::Start);
std.define("end", GenAlign::End);
std.define("left", GenAlign::Specific(Align::Left));
std.define("center", GenAlign::Specific(Align::Center));
std.define("right", GenAlign::Specific(Align::Right));
std.define("top", GenAlign::Specific(Align::Top));
std.define("horizon", GenAlign::Specific(Align::Horizon));
std.define("bottom", GenAlign::Specific(Align::Bottom));
std
}
/// Construct the language map.
/// Construct the standard style map.
pub fn styles() -> StyleMap {
StyleMap::new()
}
/// Construct the standard lang item mapping.
pub fn items() -> LangItems {
LangItems {
root: |world, content| content.layout(world),
em: |styles| styles.get(text::TextNode::SIZE),
dir: |styles| styles.get(text::TextNode::DIR),
space: || text::SpaceNode.pack(),
linebreak: |justify| text::LinebreakNode { justify }.pack(),
text: |text| text::TextNode(text).pack(),
@ -179,5 +182,10 @@ pub fn items() -> LangItems {
desc_item: |term, body| {
structure::ListItem::Desc(Box::new(structure::DescItem { term, body })).pack()
},
math: |children, display| math::MathNode { children, display }.pack(),
math_atom: |atom| math::AtomNode(atom).pack(),
math_script: |base, sub, sup| math::ScriptNode { base, sub, sup }.pack(),
math_frac: |num, denom| math::FracNode { num, denom }.pack(),
math_align: |count| math::AlignNode(count).pack(),
}
}

188
library/src/math/mod.rs Normal file
View 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
View 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
View 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};

View File

@ -1,5 +1,5 @@
use crate::library::layout::PageNode;
use crate::library::prelude::*;
use crate::layout::PageNode;
use crate::prelude::*;
/// A sequence of page runs.
#[derive(Hash)]

View File

@ -1,6 +1,6 @@
use crate::library::layout::{BlockNode, BlockSpacing};
use crate::library::prelude::*;
use crate::library::text::{FontFamily, TextNode, TextSize};
use crate::layout::{BlockNode, BlockSpacing};
use crate::prelude::*;
use crate::text::{FontFamily, TextNode, TextSize};
/// A section heading.
#[derive(Debug, Hash)]

View File

@ -1,9 +1,9 @@
use unscanny::Scanner;
use crate::library::layout::{BlockSpacing, GridNode, HNode, TrackSizing};
use crate::library::prelude::*;
use crate::library::text::{ParNode, SpaceNode};
use crate::library::utility::Numbering;
use crate::layout::{BlockSpacing, GridNode, HNode, TrackSizing};
use crate::prelude::*;
use crate::text::{ParNode, SpaceNode};
use crate::utility::Numbering;
/// An unordered (bulleted) or ordered (numbered) list.
#[derive(Debug, Hash)]

View File

@ -1,4 +1,4 @@
use crate::library::prelude::*;
use crate::prelude::*;
/// A reference to a label.
#[derive(Debug, Hash)]

View File

@ -1,5 +1,5 @@
use crate::library::layout::{BlockSpacing, GridNode, TrackSizing};
use crate::library::prelude::*;
use crate::layout::{BlockSpacing, GridNode, TrackSizing, TrackSizings};
use crate::prelude::*;
/// A table of items.
#[derive(Debug, Hash)]
@ -19,7 +19,7 @@ impl TableNode {
pub const FILL: Celled<Option<Paint>> = Celled::Value(None);
/// How to stroke the cells.
#[property(resolve, fold)]
pub const STROKE: Option<RawStroke> = Some(RawStroke::default());
pub const STROKE: Option<PartialStroke> = Some(PartialStroke::default());
/// How much to pad the cells's content.
pub const PADDING: Rel<Length> = Abs::pt(5.0).into();
@ -31,11 +31,11 @@ impl TableNode {
pub const BELOW: Option<BlockSpacing> = Some(Ratio::one().into());
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
let columns = args.named("columns")?.unwrap_or_default();
let rows = args.named("rows")?.unwrap_or_default();
let base_gutter: Vec<TrackSizing> = args.named("gutter")?.unwrap_or_default();
let column_gutter = args.named("column-gutter")?;
let row_gutter = args.named("row-gutter")?;
let TrackSizings(columns) = args.named("columns")?.unwrap_or_default();
let TrackSizings(rows) = args.named("rows")?.unwrap_or_default();
let TrackSizings(base_gutter) = args.named("gutter")?.unwrap_or_default();
let column_gutter = args.named("column-gutter")?.map(|TrackSizings(v)| v);
let row_gutter = args.named("row-gutter")?.map(|TrackSizings(v)| v);
Ok(Self {
tracks: Axes::new(columns, rows),
gutter: Axes::new(
@ -73,7 +73,7 @@ impl Show for TableNode {
styles: StyleChain,
) -> SourceResult<Content> {
let fill = styles.get(Self::FILL);
let stroke = styles.get(Self::STROKE).map(RawStroke::unwrap_or_default);
let stroke = styles.get(Self::STROKE).map(PartialStroke::unwrap_or_default);
let padding = styles.get(Self::PADDING);
let cols = self.tracks.x.len().max(1);

View File

@ -2,7 +2,7 @@ use kurbo::{BezPath, Line, ParamCurve};
use ttf_parser::{GlyphId, OutlineBuilder};
use super::TextNode;
use crate::library::prelude::*;
use crate::prelude::*;
/// Typeset underline, stricken-through or overlined text.
#[derive(Debug, Hash)]
@ -22,7 +22,7 @@ impl<const L: DecoLine> DecoNode<L> {
/// How to stroke the line. The text color and thickness are read from the
/// font tables if `auto`.
#[property(shorthand, resolve, fold)]
pub const STROKE: Smart<RawStroke> = Smart::Auto;
pub const STROKE: Smart<PartialStroke> = Smart::Auto;
/// Position of the line relative to the baseline, read from the font tables
/// if `auto`.
#[property(resolve)]
@ -72,7 +72,7 @@ impl<const L: DecoLine> Show for DecoNode<L> {
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Decoration {
pub line: DecoLine,
pub stroke: RawStroke<Abs>,
pub stroke: PartialStroke<Abs>,
pub offset: Smart<Abs>,
pub extent: Abs,
pub evade: bool,
@ -157,7 +157,6 @@ pub fn decorate(
if bbox.map_or(false, |bbox| {
let y_min = -text.font.to_em(bbox.y_max).at(text.size);
let y_max = -text.font.to_em(bbox.y_min).at(text.size);
offset >= y_min && offset <= y_max
}) {
// Find all intersections of segments with the line.

View File

@ -1,5 +1,5 @@
use super::TextNode;
use crate::library::prelude::*;
use crate::prelude::*;
/// Link text and other elements to a destination.
#[derive(Debug, Hash)]
@ -35,18 +35,6 @@ impl LinkNode {
}
}
castable! {
Destination,
Expected: "string or dictionary with `page`, `x`, and `y` keys",
Value::Str(string) => Self::Url(string.into()),
Value::Dict(dict) => {
let page = dict.get("page")?.clone().cast()?;
let x: Length = dict.get("x")?.clone().cast()?;
let y: Length = dict.get("y")?.clone().cast()?;
Self::Internal(Location { page, pos: Point::new(x.abs, y.abs) })
},
}
impl Show for LinkNode {
fn unguard_parts(&self, sel: Selector) -> Content {
Self {

View File

@ -19,10 +19,10 @@ pub use shift::*;
use std::borrow::Cow;
use rustybuzz::Tag;
use typst::font::{FontMetrics, FontStretch, FontStyle, FontWeight, VerticalFontMetric};
use typst::util::EcoString;
use crate::font::{FontMetrics, FontStretch, FontStyle, FontWeight, VerticalFontMetric};
use crate::library::prelude::*;
use crate::util::EcoString;
use crate::prelude::*;
/// A single run of text with the same style.
#[derive(Debug, Clone, Hash)]
@ -32,7 +32,7 @@ pub struct TextNode(pub EcoString);
impl TextNode {
/// A prioritized sequence of font families.
#[property(skip, referenced)]
pub const FAMILY: Vec<FontFamily> = vec![FontFamily::new("IBM Plex Sans")];
pub const FAMILY: FallbackList = FallbackList(vec![FontFamily::new("IBM Plex Sans")]);
/// Whether to allow font fallback when the primary font list contains no
/// match.
pub const FALLBACK: bool = true;
@ -73,11 +73,11 @@ impl TextNode {
/// The direction for text and inline objects. When `auto`, the direction is
/// automatically inferred from the language.
#[property(resolve)]
pub const DIR: Smart<HorizontalDir> = Smart::Auto;
pub const DIR: HorizontalDir = HorizontalDir(Smart::Auto);
/// Whether to hyphenate text to improve line breaking. When `auto`, words
/// will will be hyphenated if and only if justification is enabled.
#[property(resolve)]
pub const HYPHENATE: Smart<Hyphenate> = Smart::Auto;
pub const HYPHENATE: Hyphenate = Hyphenate(Smart::Auto);
/// Whether to apply smart quotes.
pub const SMART_QUOTES: bool = true;
@ -103,7 +103,7 @@ impl TextNode {
pub const FRACTIONS: bool = false;
/// Raw OpenType features to apply.
#[property(fold)]
pub const FEATURES: Vec<(Tag, u32)> = vec![];
pub const FEATURES: FontFeatures = FontFeatures(vec![]);
/// Whether the font weight should be increased by 300.
#[property(skip, fold)]
@ -156,7 +156,7 @@ impl TextNode {
list.push(args.find()?.unwrap());
}
styles.set(Self::FAMILY, list);
styles.set(Self::FAMILY, FallbackList(list));
}
}
}
@ -190,53 +190,19 @@ castable! {
Value::Str(string) => Self::new(&string),
}
/// Font family fallback list.
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
pub struct FallbackList(pub Vec<FontFamily>);
castable! {
Vec<FontFamily>,
FallbackList,
Expected: "string or array of strings",
Value::Str(string) => vec![FontFamily::new(&string)],
Value::Array(values) => values
Value::Str(string) => Self(vec![FontFamily::new(&string)]),
Value::Array(values) => Self(values
.into_iter()
.filter_map(|v| v.cast().ok())
.map(|string: EcoString| FontFamily::new(&string))
.collect(),
}
castable! {
FontStyle,
Expected: "string",
Value::Str(string) => match string.as_str() {
"normal" => Self::Normal,
"italic" => Self::Italic,
"oblique" => Self::Oblique,
_ => Err(r#"expected "normal", "italic" or "oblique""#)?,
},
}
castable! {
FontWeight,
Expected: "integer or string",
Value::Int(v) => Value::Int(v)
.cast::<usize>()?
.try_into()
.map_or(Self::BLACK, Self::from_number),
Value::Str(string) => match string.as_str() {
"thin" => Self::THIN,
"extralight" => Self::EXTRALIGHT,
"light" => Self::LIGHT,
"regular" => Self::REGULAR,
"medium" => Self::MEDIUM,
"semibold" => Self::SEMIBOLD,
"bold" => Self::BOLD,
"extrabold" => Self::EXTRABOLD,
"black" => Self::BLACK,
_ => Err("unknown font weight")?,
},
}
castable! {
FontStretch,
Expected: "ratio",
Value::Ratio(v) => Self::from_ratio(v.get() as f32),
.collect()),
}
/// The size of text.
@ -286,57 +252,49 @@ castable! {
}),
}
castable! {
Lang,
Expected: "string",
Value::Str(string) => Self::from_str(&string)
.ok_or("expected two or three letter language code (ISO 639-1/2/3)")?,
}
castable! {
Region,
Expected: "string",
Value::Str(string) => Self::from_str(&string)
.ok_or("expected two letter region code (ISO 3166-1 alpha-2)")?,
}
/// The direction of text and inline objects in their line.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct HorizontalDir(pub Dir);
pub struct HorizontalDir(pub Smart<Dir>);
castable! {
HorizontalDir,
Expected: "direction",
Expected: "direction or auto",
Value::Auto => Self(Smart::Auto),
@dir: Dir => match dir.axis() {
Axis::X => Self(*dir),
Axis::X => Self(Smart::Custom(*dir)),
Axis::Y => Err("must be horizontal")?,
},
}
impl Resolve for Smart<HorizontalDir> {
impl Resolve for HorizontalDir {
type Output = Dir;
fn resolve(self, styles: StyleChain) -> Self::Output {
match self {
match self.0 {
Smart::Auto => styles.get(TextNode::LANG).dir(),
Smart::Custom(dir) => dir.0,
Smart::Custom(dir) => dir,
}
}
}
/// Whether to hyphenate text.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Hyphenate(pub bool);
pub struct Hyphenate(pub Smart<bool>);
castable!(Hyphenate: bool);
castable! {
Hyphenate,
Expected: "boolean or auto",
Value::Auto => Self(Smart::Auto),
Value::Bool(v) => Self(Smart::Custom(v)),
}
impl Resolve for Smart<Hyphenate> {
impl Resolve for Hyphenate {
type Output = bool;
fn resolve(self, styles: StyleChain) -> Self::Output {
match self {
match self.0 {
Smart::Auto => styles.get(ParNode::JUSTIFY),
Smart::Custom(v) => v.0,
Smart::Custom(v) => v,
}
}
}
@ -404,29 +362,33 @@ castable! {
},
}
/// OpenType font features settings.
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
pub struct FontFeatures(pub Vec<(Tag, u32)>);
castable! {
Vec<(Tag, u32)>,
FontFeatures,
Expected: "array of strings or dictionary mapping tags to integers",
Value::Array(values) => values
Value::Array(values) => Self(values
.into_iter()
.filter_map(|v| v.cast().ok())
.map(|string: EcoString| (Tag::from_bytes_lossy(string.as_bytes()), 1))
.collect(),
Value::Dict(values) => values
.collect()),
Value::Dict(values) => Self(values
.into_iter()
.filter_map(|(k, v)| {
let tag = Tag::from_bytes_lossy(k.as_bytes());
let num = v.cast::<i64>().ok()?.try_into().ok()?;
Some((tag, num))
})
.collect(),
.collect()),
}
impl Fold for Vec<(Tag, u32)> {
impl Fold for FontFeatures {
type Output = Self;
fn fold(mut self, outer: Self::Output) -> Self::Output {
self.extend(outer);
self.0.extend(outer.0);
self
}
}
@ -515,7 +477,7 @@ pub fn smallcaps(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
Ok(Value::Content(body.styled(TextNode::SMALLCAPS, true)))
}
/// Strong text, rendered in boldface by default.
/// Strong content, rendered in boldface by default.
#[derive(Debug, Hash)]
pub struct StrongNode(pub Content);
@ -543,7 +505,7 @@ impl Show for StrongNode {
}
}
/// Emphasized text, rendered with an italic font by default.
/// Emphasized content, rendered with an italic font by default.
#[derive(Debug, Hash)]
pub struct EmphNode(pub Content);

View File

@ -1,13 +1,13 @@
use std::cmp::Ordering;
use typst::util::EcoString;
use unicode_bidi::{BidiInfo, Level as BidiLevel};
use unicode_script::{Script, UnicodeScript};
use xi_unicode::LineBreakIterator;
use super::{shape, Lang, Quoter, Quotes, ShapedText, TextNode};
use crate::library::layout::Spacing;
use crate::library::prelude::*;
use crate::util::EcoString;
use crate::layout::Spacing;
use crate::prelude::*;
/// Arrange text, spacing and inline-level nodes into a paragraph.
#[derive(Hash)]
@ -42,11 +42,10 @@ impl ParNode {
/// How to align text and inline objects in their line.
#[property(resolve)]
pub const ALIGN: HorizontalAlign = HorizontalAlign(RawAlign::Start);
pub const ALIGN: HorizontalAlign = HorizontalAlign(GenAlign::Start);
/// Whether to justify text in its line.
pub const JUSTIFY: bool = false;
/// How to determine line breaks.
#[property(resolve)]
pub const LINEBREAKS: Smart<Linebreaks> = Smart::Auto;
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
@ -113,12 +112,12 @@ impl PartialOrd for ParChild {
/// A horizontal alignment.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct HorizontalAlign(pub RawAlign);
pub struct HorizontalAlign(pub GenAlign);
castable! {
HorizontalAlign,
Expected: "alignment",
@align: RawAlign => match align.axis() {
@align: GenAlign => match align.axis() {
Axis::X => Self(*align),
Axis::Y => Err("must be horizontal")?,
},
@ -151,20 +150,6 @@ castable! {
},
}
impl Resolve for Smart<Linebreaks> {
type Output = Linebreaks;
fn resolve(self, styles: StyleChain) -> Self::Output {
self.unwrap_or_else(|| {
if styles.get(ParNode::JUSTIFY) {
Linebreaks::Optimized
} else {
Linebreaks::Simple
}
})
}
}
/// A paragraph break.
#[derive(Debug, Clone, Hash)]
pub struct ParbreakNode;
@ -446,7 +431,7 @@ fn collect<'a>(
let mut iter = par.0.iter().peekable();
while let Some((child, map)) = iter.next() {
let styles = map.chain(&styles);
let styles = map.chain(styles);
let segment = match child {
ParChild::Text(text) => {
let prev = full.len();
@ -515,7 +500,7 @@ fn prepare<'a>(
regions: &Regions,
styles: StyleChain<'a>,
) -> SourceResult<Preparation<'a>> {
let bidi = BidiInfo::new(&text, match styles.get(TextNode::DIR) {
let bidi = BidiInfo::new(text, match styles.get(TextNode::DIR) {
Dir::LTR => Some(BidiLevel::ltr()),
Dir::RTL => Some(BidiLevel::rtl()),
_ => None,
@ -642,7 +627,15 @@ fn linebreak<'a>(
world: Tracked<dyn World>,
width: Abs,
) -> Vec<Line<'a>> {
match p.styles.get(ParNode::LINEBREAKS) {
let linebreaks = p.styles.get(ParNode::LINEBREAKS).unwrap_or_else(|| {
if p.styles.get(ParNode::JUSTIFY) {
Linebreaks::Optimized
} else {
Linebreaks::Simple
}
});
match linebreaks {
Linebreaks::Simple => linebreak_simple(p, world, width),
Linebreaks::Optimized => linebreak_optimized(p, world, width),
}

View File

@ -1,5 +1,6 @@
use typst::syntax::is_newline;
use super::{Lang, Region};
use crate::syntax::is_newline;
/// State machine for smart quote subtitution.
#[derive(Debug, Clone)]

View File

@ -4,10 +4,11 @@ use syntect::highlighting::{
Color, FontStyle, Style, StyleModifier, Theme, ThemeItem, ThemeSettings,
};
use syntect::parsing::SyntaxSet;
use typst::syntax;
use super::{FontFamily, Hyphenate, LinebreakNode, TextNode};
use crate::library::layout::{BlockNode, BlockSpacing};
use crate::library::prelude::*;
use crate::layout::{BlockNode, BlockSpacing};
use crate::prelude::*;
/// Monospaced text with optional syntax highlighting.
#[derive(Debug, Hash)]
@ -70,12 +71,12 @@ impl Show for RawNode {
let mut realized = if matches!(lang.as_deref(), Some("typ" | "typst" | "typc")) {
let root = match lang.as_deref() {
Some("typc") => crate::syntax::parse_code(&self.text),
_ => crate::syntax::parse(&self.text),
Some("typc") => syntax::parse_code(&self.text),
_ => syntax::parse(&self.text),
};
let mut seq = vec![];
crate::syntax::highlight::highlight_themed(&root, &THEME, |range, style| {
syntax::highlight::highlight_themed(&root, &THEME, |range, style| {
seq.push(styled(&self.text[range], foreground, style));
});
@ -108,7 +109,7 @@ impl Show for RawNode {
let mut map = StyleMap::new();
map.set(TextNode::OVERHANG, false);
map.set(TextNode::HYPHENATE, Smart::Custom(Hyphenate(false)));
map.set(TextNode::HYPHENATE, Hyphenate(Smart::Custom(false)));
map.set(TextNode::SMART_QUOTES, false);
Ok(realized.styled_with_map(map))

View File

@ -2,11 +2,11 @@ use std::ops::Range;
use std::str::FromStr;
use rustybuzz::{Feature, Tag, UnicodeBuffer};
use typst::font::{Font, FontVariant};
use typst::util::SliceExt;
use super::*;
use crate::font::{Font, FontVariant};
use crate::library::prelude::*;
use crate::util::SliceExt;
use crate::prelude::*;
/// The result of shaping text.
///
@ -128,7 +128,7 @@ impl<'a> ShapedText<'a> {
// Apply line decorations.
for deco in &decos {
decorate(&mut frame, &deco, &text, shift, pos, width);
decorate(&mut frame, deco, &text, shift, pos, width);
}
frame.insert(text_layer, pos, Element::Text(text));
@ -339,7 +339,7 @@ pub fn shape<'a>(
};
if !text.is_empty() {
shape_segment(&mut ctx, 0, &text, families(styles));
shape_segment(&mut ctx, 0, text, families(styles));
}
track_and_space(&mut ctx);
@ -570,6 +570,7 @@ fn families(styles: StyleChain) -> impl Iterator<Item = &str> + Clone {
let tail = if styles.get(TextNode::FALLBACK) { FALLBACKS } else { &[] };
styles
.get(TextNode::FAMILY)
.0
.iter()
.map(|family| family.as_str())
.chain(tail.iter().copied())
@ -635,7 +636,7 @@ fn tags(styles: StyleChain) -> Vec<Feature> {
feat(b"frac", 1);
}
for (tag, value) in styles.get(TextNode::FEATURES) {
for (tag, value) in styles.get(TextNode::FEATURES).0 {
tags.push(Feature::new(tag, value, ..))
}

View File

@ -1,7 +1,8 @@
use typst::model::SequenceNode;
use typst::util::EcoString;
use super::{variant, SpaceNode, TextNode, TextSize};
use crate::library::prelude::*;
use crate::model::SequenceNode;
use crate::util::EcoString;
use crate::prelude::*;
/// Sub or superscript text.
///
@ -98,7 +99,7 @@ fn search_text(content: &Content, mode: ScriptKind) -> Option<EcoString> {
/// Checks whether the first retrievable family contains all code points of the
/// given string.
fn is_shapable(world: Tracked<dyn World>, text: &str, styles: StyleChain) -> bool {
for family in styles.get(TextNode::FAMILY).iter() {
for family in styles.get(TextNode::FAMILY).0.iter() {
if let Some(font) = world
.book()
.select(family.as_str(), variant(styles))

View File

@ -1,6 +1,6 @@
use std::str::FromStr;
use crate::library::prelude::*;
use crate::prelude::*;
/// Create a grayscale color.
pub fn luma(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {

View File

@ -1,7 +1,8 @@
use std::fmt::Write;
use crate::diag::format_xml_like_error;
use crate::library::prelude::*;
use typst::diag::format_xml_like_error;
use crate::prelude::*;
/// Read structured data from a CSV file.
pub fn csv(vm: &mut Vm, args: &mut Args) -> SourceResult<Value> {

View File

@ -1,6 +1,6 @@
use std::cmp::Ordering;
use crate::library::prelude::*;
use crate::prelude::*;
/// Convert a value to an integer.
pub fn int(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {

View File

@ -11,10 +11,10 @@ pub use math::*;
pub use string::*;
use comemo::Track;
use typst::model::{Eval, Route, Scopes, Vm};
use typst::syntax::Source;
use crate::library::prelude::*;
use crate::model::{Eval, Route, Scopes, Vm};
use crate::syntax::Source;
use crate::prelude::*;
/// The name of a value's type.
pub fn type_(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
@ -39,7 +39,7 @@ pub fn eval(vm: &mut Vm, args: &mut Args) -> SourceResult<Value> {
let ast = source.ast()?;
// Evaluate the source.
let std = &vm.world.config().std;
let std = &vm.world.config().scope;
let scopes = Scopes::new(Some(std));
let route = Route::default();
let mut sub = Vm::new(vm.world, route.track(), None, scopes);

View File

@ -1,5 +1,6 @@
use crate::library::prelude::*;
use crate::model::Regex;
use typst::model::Regex;
use crate::prelude::*;
/// The string representation of a value.
pub fn repr(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {

View File

@ -15,7 +15,7 @@ pub fn capability(_: TokenStream, item: TokenStream) -> TokenStream {
let name = &item_trait.ident;
quote! {
#item_trait
impl crate::model::Capability for dyn #name {}
impl ::typst::model::Capability for dyn #name {}
}.into()
}
@ -70,7 +70,7 @@ fn expand_node(
fn construct(
_: &mut model::Vm,
_: &mut model::Args,
) -> crate::diag::SourceResult<model::Content> {
) -> typst::diag::SourceResult<model::Content> {
unimplemented!()
}
}
@ -84,7 +84,7 @@ fn expand_node(
let checks = items.iter().map(|cap| {
quote! {
if id == TypeId::of::<dyn #cap>() {
return Some(unsafe { crate::util::fat::vtable(self as &dyn #cap) });
return Some(unsafe { typst::util::fat::vtable(self as &dyn #cap) });
}
}
});
@ -101,10 +101,10 @@ fn expand_node(
Ok(quote! {
#[allow(non_snake_case)]
mod #module {
use std::any::TypeId;
use std::marker::PhantomData;
use once_cell::sync::Lazy;
use crate::model;
use ::std::any::TypeId;
use ::std::marker::PhantomData;
use ::once_cell::sync::Lazy;
use ::typst::model;
use super::*;
#impl_block
@ -370,7 +370,7 @@ fn generate_set(
) -> syn::ImplItemMethod {
let user = user.map(|method| {
let block = &method.block;
quote! { (|| -> crate::diag::SourceResult<()> { #block; Ok(()) } )()?; }
quote! { (|| -> typst::diag::SourceResult<()> { #block; Ok(()) } )()?; }
});
let mut shorthands = vec![];
@ -379,7 +379,7 @@ fn generate_set(
.filter(|p| !p.skip)
.map(|property| {
let name = &property.name;
let string = name.to_string().replace("_", "-").to_lowercase();
let string = name.to_string().replace('_', "-").to_lowercase();
let value = if let Some(short) = &property.shorthand {
match short {
@ -409,7 +409,7 @@ fn generate_set(
fn set(
args: &mut model::Args,
constructor: bool,
) -> crate::diag::SourceResult<model::StyleMap> {
) -> typst::diag::SourceResult<model::StyleMap> {
let mut styles = model::StyleMap::new();
#user
#(#bindings)*

View File

@ -10,33 +10,41 @@ use std::string::FromUtf8Error;
use comemo::Tracked;
use crate::syntax::{ErrorPos, Span, Spanned};
use crate::util::EcoString;
use crate::util::{format_eco, EcoString};
use crate::World;
/// Early-return with a [`SourceError`].
#[macro_export]
macro_rules! bail {
#[doc(hidden)]
macro_rules! __bail {
($error:expr) => {
return Err(Box::new(vec![$error]))
};
($($tts:tt)*) => {
$crate::bail!($crate::error!($($tts)*))
$crate::diag::bail!($crate::diag::error!($($tts)*))
};
}
#[doc(inline)]
pub use crate::__bail as bail;
/// Construct a [`SourceError`].
#[macro_export]
macro_rules! error {
#[doc(hidden)]
macro_rules! __error {
($span:expr, $message:expr $(,)?) => {
$crate::diag::SourceError::new($span, $message)
};
($span:expr, $fmt:expr, $($arg:expr),+ $(,)?) => {
$crate::error!($span, format!($fmt, $($arg),+))
$crate::diag::error!($span, format!($fmt, $($arg),+))
};
}
#[doc(inline)]
pub use crate::__error as error;
/// A result that can carry multiple source errors.
pub type SourceResult<T> = Result<T, Box<Vec<SourceError>>>;

View File

@ -5,7 +5,7 @@ use pdf_writer::{Filter, Finish, Name, Rect, Str};
use ttf_parser::{name_id, GlyphId, Tag};
use super::{deflate, EmExt, PdfContext, RefExt};
use crate::util::SliceExt;
use crate::util::{format_eco, SliceExt};
/// Embed all used fonts into the PDF.
pub fn write_fonts(ctx: &mut PdfContext) {

View File

@ -71,7 +71,7 @@ pub fn write_outline_item(
let current_child = Ref::new(id.get() + 1);
outline.first(current_child);
outline.last(Ref::new(next_ref.get() - 1));
outline.count(-1 * node.children.len() as i32);
outline.count(-(node.children.len() as i32));
}
outline.title(TextStr(&node.heading.content));

View File

@ -12,6 +12,7 @@ use crate::geom::{
Transform,
};
use crate::image::Image;
use crate::util::format_eco;
/// Construct page objects.
pub fn construct_pages(ctx: &mut PdfContext, frames: &[Frame]) {

View File

@ -312,8 +312,10 @@ fn render_shape(
if let Some(Stroke { paint, thickness }) = shape.stroke {
let paint = paint.into();
let mut stroke = sk::Stroke::default();
stroke.width = thickness.to_f32();
let stroke = sk::Stroke {
width: thickness.to_f32(),
..Default::default()
};
canvas.stroke_path(&path, &paint, &stroke, ts, mask);
}
@ -364,14 +366,16 @@ fn render_image(
let scale_x = view_width / pixmap.width() as f32;
let scale_y = view_height / pixmap.height() as f32;
let mut paint = sk::Paint::default();
paint.shader = sk::Pattern::new(
pixmap.as_ref(),
sk::SpreadMode::Pad,
sk::FilterQuality::Nearest,
1.0,
sk::Transform::from_scale(scale_x, scale_y),
);
let paint = sk::Paint {
shader: sk::Pattern::new(
pixmap.as_ref(),
sk::SpreadMode::Pad,
sk::FilterQuality::Nearest,
1.0,
sk::Transform::from_scale(scale_x, scale_y),
),
..Default::default()
};
let rect = sk::Rect::from_xywh(0.0, 0.0, view_width, view_height)?;
canvas.fill_rect(rect, &paint, ts, mask);

View File

@ -170,7 +170,7 @@ bitflags::bitflags! {
impl FontInfo {
/// Compute metadata for all fonts in the given data.
pub fn from_data<'a>(data: &'a [u8]) -> impl Iterator<Item = FontInfo> + 'a {
pub fn from_data(data: &[u8]) -> impl Iterator<Item = FontInfo> + '_ {
let count = ttf_parser::fonts_in_collection(data).unwrap_or(1);
(0 .. count).filter_map(move |index| {
let ttf = ttf_parser::Face::parse(data, index).ok()?;

View File

@ -111,7 +111,7 @@ impl FontWeight {
/// The absolute number distance between this and another font weight.
pub fn distance(self, other: Self) -> u16 {
(self.0 as i16 - other.0 as i16).abs() as u16
(self.0 as i16 - other.0 as i16).unsigned_abs()
}
}

View File

@ -2,6 +2,7 @@
use std::fmt::{self, Debug, Formatter, Write};
use std::num::NonZeroUsize;
use std::str::FromStr;
use std::sync::Arc;
use crate::font::Font;
@ -9,7 +10,7 @@ use crate::geom::{
Abs, Align, Axes, Dir, Em, Numeric, Paint, Point, Shape, Size, Transform,
};
use crate::image::Image;
use crate::model::{Dict, Value};
use crate::model::{dict, Dict, Value};
use crate::util::EcoString;
/// A finished layout with elements at fixed positions.
@ -396,7 +397,7 @@ pub struct Glyph {
pub c: char,
}
/// A code for a natural language.
/// An identifier for a natural language.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Lang([u8; 3], u8);
@ -404,19 +405,6 @@ impl Lang {
/// The code for the english language.
pub const ENGLISH: Self = Self(*b"en ", 2);
/// Construct a language from a two- or three-byte ISO 639-1/2/3 code.
pub fn from_str(iso: &str) -> Option<Self> {
let len = iso.len();
if matches!(len, 2 ..= 3) && iso.is_ascii() {
let mut bytes = [b' '; 3];
bytes[.. len].copy_from_slice(iso.as_bytes());
bytes.make_ascii_lowercase();
Some(Self(bytes, len as u8))
} else {
None
}
}
/// Return the language code as an all lowercase string slice.
pub fn as_str(&self) -> &str {
std::str::from_utf8(&self.0[.. usize::from(self.1)]).unwrap_or_default()
@ -432,28 +420,49 @@ impl Lang {
}
}
/// A code for a region somewhere in the world.
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)]
pub struct Region([u8; 2]);
impl Region {
/// Construct a region from its two-byte ISO 3166-1 alpha-2 code.
pub fn from_str(iso: &str) -> Option<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.
pub fn as_str(&self) -> &str {
std::str::from_utf8(&self.0).unwrap_or_default()
}
}
impl FromStr for Region {
type Err = &'static str;
/// Construct a region from its two-byte ISO 3166-1 alpha-2 code.
fn from_str(iso: &str) -> Result<Self, Self::Err> {
if iso.len() == 2 && iso.is_ascii() {
let mut bytes: [u8; 2] = iso.as_bytes().try_into().unwrap();
bytes.make_ascii_uppercase();
Ok(Self(bytes))
} else {
Err("expected two letter region code (ISO 3166-1 alpha-2)")
}
}
}
/// A link destination.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum Destination {
@ -529,9 +538,9 @@ impl Role {
pub fn is_weak(self) -> bool {
// In Typst, all text is in a paragraph, so paragraph isn't very
// descriptive.
match self {
Self::Paragraph | Self::GenericBlock | Self::GenericInline => true,
_ => false,
}
matches!(
self,
Self::Paragraph | Self::GenericBlock | Self::GenericInline
)
}
}

View File

@ -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),
}
}
}

View File

@ -178,6 +178,6 @@ mod tests {
#[test]
fn test_angle_unit_conversion() {
assert!((Angle::rad(2.0 * PI).to_deg() - 360.0) < 1e-4);
assert!((Angle::deg(45.0).to_rad() - 0.7854) < 1e-4);
assert!((Angle::deg(45.0).to_rad() - std::f64::consts::FRAC_PI_4) < 1e-4);
}
}

View File

@ -21,6 +21,7 @@ mod rounded;
mod scalar;
mod sides;
mod size;
mod stroke;
mod transform;
pub use abs::*;
@ -42,6 +43,7 @@ pub use rounded::*;
pub use scalar::*;
pub use sides::*;
pub use size::*;
pub use stroke::*;
pub use transform::*;
use std::cmp::Ordering;

View File

@ -388,24 +388,6 @@ impl From<CmykColor> for Color {
}
}
/// A stroke of a geometric shape.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Stroke {
/// The stroke's paint.
pub paint: Paint,
/// The stroke's thickness.
pub thickness: Abs,
}
impl Default for Stroke {
fn default() -> Self {
Self {
paint: Paint::Solid(Color::BLACK.into()),
thickness: Abs::pt(1.0),
}
}
}
/// Convert to the closest u8.
fn round_u8(value: f64) -> u8 {
value.round() as u8

61
src/geom/stroke.rs Normal file
View 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>"),
}
}
}

View File

@ -26,9 +26,7 @@
//! [content]: model::Content
//! [PDF]: export::pdf
#![allow(clippy::len_without_is_empty)]
#![allow(clippy::or_fun_call)]
#![allow(clippy::try_err)]
extern crate self as typst;
#[macro_use]
pub mod util;
@ -42,10 +40,8 @@ pub mod export;
pub mod font;
pub mod frame;
pub mod image;
pub mod library;
pub mod syntax;
use std::num::NonZeroUsize;
use std::path::{Path, PathBuf};
use comemo::{Prehashed, Track};
@ -53,9 +49,9 @@ use comemo::{Prehashed, Track};
use crate::diag::{FileResult, SourceResult};
use crate::font::{Font, FontBook};
use crate::frame::Frame;
use crate::model::{Content, Route, Scope, StyleMap};
use crate::model::{LangItems, Route, Scope, StyleMap};
use crate::syntax::{Source, SourceId};
use crate::util::{Buffer, EcoString};
use crate::util::Buffer;
/// Typeset a source file into a collection of layouted frames.
///
@ -66,9 +62,10 @@ pub fn typeset(
world: &(dyn World + 'static),
main: SourceId,
) -> SourceResult<Vec<Frame>> {
crate::model::set_lang_items(world.config().items);
let route = Route::default();
let module = model::eval(world.track(), route.track(), main)?;
library::layout::Layout::layout(&module.content, world.track())
item!(root)(world.track(), &module.content)
}
/// The environment in which typesetting occurs.
@ -97,49 +94,11 @@ pub trait World {
#[derive(Debug, Clone, Hash)]
pub struct Config {
/// The compilation root, relative to which absolute paths are.
///
/// Default: Empty path.
pub root: PathBuf,
/// The scope containing definitions that are available everywhere.
///
/// Default: Typst's standard library.
pub std: Scope,
/// Defines which standard library items fulfill which syntactical roles.
///
/// Default: Typst's standard library's language map.
pub items: LangItems,
pub scope: Scope,
/// The default properties for page size, font selection and so on.
///
/// Default: Empty style map.
pub styles: StyleMap,
}
impl Default for Config {
fn default() -> Self {
Self {
root: PathBuf::new(),
std: library::scope(),
items: library::items(),
styles: StyleMap::new(),
}
}
}
/// Definition of certain standard library items the language is aware of.
#[derive(Debug, Clone, Hash)]
pub struct LangItems {
pub space: fn() -> Content,
pub linebreak: fn(justify: bool) -> Content,
pub text: fn(text: EcoString) -> Content,
pub smart_quote: fn(double: bool) -> Content,
pub parbreak: fn() -> Content,
pub strong: fn(body: Content) -> Content,
pub emph: fn(body: Content) -> Content,
pub raw: fn(text: EcoString, tag: Option<EcoString>, block: bool) -> Content,
pub link: fn(label: EcoString) -> Content,
pub ref_: fn(target: EcoString) -> Content,
pub heading: fn(level: NonZeroUsize, body: Content) -> Content,
pub list_item: fn(body: Content) -> Content,
pub enum_item: fn(number: Option<usize>, body: Content) -> Content,
pub desc_item: fn(term: Content, body: Content) -> Content,
/// Defines which standard library items fulfill which syntactical roles.
pub items: LangItems,
}

View File

@ -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())
}
}

View File

@ -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(),
}
}

View File

@ -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
}
}

View File

@ -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};

View File

@ -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,
},
}

View File

@ -1,7 +1,7 @@
use std::fmt::{self, Debug, Formatter, Write};
use super::{Array, Cast, Dict, Str, Value};
use crate::diag::{At, SourceResult};
use crate::diag::{bail, At, SourceResult};
use crate::syntax::{Span, Spanned};
/// Evaluated arguments to a function.

View File

@ -9,8 +9,9 @@ use crate::syntax::Spanned;
use crate::util::ArcExt;
/// Create a new [`Array`] from values.
#[allow(unused_macros)]
macro_rules! array {
#[macro_export]
#[doc(hidden)]
macro_rules! __array {
($value:expr; $count:expr) => {
$crate::model::Array::from_vec(vec![$value.into(); $count])
};
@ -20,6 +21,9 @@ macro_rules! array {
};
}
#[doc(inline)]
pub use crate::__array as array;
/// A reference counted array with value semantics.
#[derive(Default, Clone, PartialEq, Hash)]
pub struct Array(Arc<Vec<Value>>);
@ -97,7 +101,7 @@ impl Array {
.ok_or_else(|| out_of_bounds(index, len))?;
Arc::make_mut(&mut self.0).remove(i);
return Ok(());
Ok(())
}
/// Extract a contigous subregion of the array.

View File

@ -1,8 +1,13 @@
use std::num::NonZeroUsize;
use std::str::FromStr;
use super::{Pattern, Regex, Value};
use crate::diag::{with_alternative, StrResult};
use crate::geom::{Corners, Dir, Paint, Sides};
use crate::font::{FontStretch, FontStyle, FontWeight};
use crate::frame::{Destination, Lang, Location, Region};
use crate::geom::{
Axes, Corners, Dir, GenAlign, Get, Length, Paint, PartialStroke, Point, Rel, Sides,
};
use crate::syntax::Spanned;
use crate::util::EcoString;
@ -16,7 +21,9 @@ pub trait Cast<V = Value>: Sized {
}
/// Implement traits for dynamic types.
macro_rules! dynamic {
#[macro_export]
#[doc(hidden)]
macro_rules! __dynamic {
($type:ty: $name:literal, $($tts:tt)*) => {
impl $crate::model::Type for $type {
const TYPE_NAME: &'static str = $name;
@ -37,8 +44,13 @@ macro_rules! dynamic {
};
}
#[doc(inline)]
pub use crate::__dynamic as dynamic;
/// Make a type castable from a value.
macro_rules! castable {
#[macro_export]
#[doc(hidden)]
macro_rules! __castable {
($type:ty: $inner:ty) => {
impl $crate::model::Cast<$crate::model::Value> for $type {
fn is(value: &$crate::model::Value) -> bool {
@ -88,6 +100,9 @@ macro_rules! castable {
};
}
#[doc(inline)]
pub use crate::__castable as castable;
impl Cast for Value {
fn is(_: &Value) -> bool {
true
@ -119,14 +134,6 @@ impl<T: Cast> Cast<Spanned<Value>> for Spanned<T> {
}
}
dynamic! {
Dir: "direction",
}
dynamic! {
Regex: "regular expression",
}
castable! {
usize,
Expected: "non-negative integer",
@ -170,6 +177,10 @@ castable! {
Value::Str(string) => string.into(),
}
dynamic! {
Regex: "regular expression",
}
castable! {
Pattern,
Expected: "function, string or regular expression",
@ -178,6 +189,115 @@ castable! {
@regex: Regex => Self::Regex(regex.clone()),
}
dynamic! {
Dir: "direction",
}
dynamic! {
GenAlign: "alignment",
}
dynamic! {
Axes<GenAlign>: "2d alignment",
}
castable! {
Axes<Option<GenAlign>>,
Expected: "1d or 2d alignment",
@align: GenAlign => {
let mut aligns = Axes::default();
aligns.set(align.axis(), Some(*align));
aligns
},
@aligns: Axes<GenAlign> => aligns.map(Some),
}
dynamic! {
PartialStroke: "stroke",
Value::Length(thickness) => Self {
paint: Smart::Auto,
thickness: Smart::Custom(thickness),
},
Value::Color(color) => Self {
paint: Smart::Custom(color.into()),
thickness: Smart::Auto,
},
}
castable! {
Axes<Rel<Length>>,
Expected: "array of two relative lengths",
Value::Array(array) => {
let mut iter = array.into_iter();
match (iter.next(), iter.next(), iter.next()) {
(Some(a), Some(b), None) => Axes::new(a.cast()?, b.cast()?),
_ => Err("point array must contain exactly two entries")?,
}
},
}
castable! {
Destination,
Expected: "string or dictionary with `page`, `x`, and `y` keys",
Value::Str(string) => Self::Url(string.into()),
Value::Dict(dict) => {
let page = dict.get("page")?.clone().cast()?;
let x: Length = dict.get("x")?.clone().cast()?;
let y: Length = dict.get("y")?.clone().cast()?;
Self::Internal(Location { page, pos: Point::new(x.abs, y.abs) })
},
}
castable! {
FontStyle,
Expected: "string",
Value::Str(string) => match string.as_str() {
"normal" => Self::Normal,
"italic" => Self::Italic,
"oblique" => Self::Oblique,
_ => Err(r#"expected "normal", "italic" or "oblique""#)?,
},
}
castable! {
FontWeight,
Expected: "integer or string",
Value::Int(v) => Value::Int(v)
.cast::<usize>()?
.try_into()
.map_or(Self::BLACK, Self::from_number),
Value::Str(string) => match string.as_str() {
"thin" => Self::THIN,
"extralight" => Self::EXTRALIGHT,
"light" => Self::LIGHT,
"regular" => Self::REGULAR,
"medium" => Self::MEDIUM,
"semibold" => Self::SEMIBOLD,
"bold" => Self::BOLD,
"extrabold" => Self::EXTRABOLD,
"black" => Self::BLACK,
_ => Err("unknown font weight")?,
},
}
castable! {
FontStretch,
Expected: "ratio",
Value::Ratio(v) => Self::from_ratio(v.get() as f32),
}
castable! {
Lang,
Expected: "string",
Value::Str(string) => Self::from_str(&string)?,
}
castable! {
Region,
Expected: "string",
Value::Str(string) => Self::from_str(&string)?,
}
impl<T: Cast> Cast for Option<T> {
fn is(value: &Value) -> bool {
matches!(value, Value::None) || T::is(value)

View File

@ -9,8 +9,9 @@ use siphasher::sip128::{Hasher128, SipHasher};
use typst_macros::node;
use super::{Args, Key, Property, Selector, StyleEntry, StyleMap, Vm};
use crate as typst;
use crate::diag::{SourceResult, StrResult};
use crate::util::ReadableTypeId;
use crate::util::{EcoString, ReadableTypeId};
/// Composable representation of styled content.
///
@ -26,6 +27,11 @@ impl Content {
SequenceNode(vec![]).pack()
}
/// Create content from a string of text.
pub fn text(text: impl Into<EcoString>) -> Self {
item!(text)(text.into())
}
/// Create a new sequence node from multiples nodes.
pub fn sequence(seq: Vec<Self>) -> Self {
match seq.as_slice() {
@ -146,7 +152,7 @@ impl Add for Content {
let mut lhs = self;
if let Some(lhs_mut) = lhs.try_downcast_mut::<SequenceNode>() {
if let Some(rhs_mut) = rhs.try_downcast_mut::<SequenceNode>() {
lhs_mut.0.extend(rhs_mut.0.drain(..));
lhs_mut.0.append(&mut rhs_mut.0);
} else if let Some(rhs) = rhs.downcast::<SequenceNode>() {
lhs_mut.0.extend(rhs.0.iter().cloned());
} else {

View File

@ -10,8 +10,9 @@ use crate::syntax::Spanned;
use crate::util::ArcExt;
/// Create a new [`Dict`] from key-value pairs.
#[allow(unused_macros)]
macro_rules! dict {
#[macro_export]
#[doc(hidden)]
macro_rules! __dict {
($($key:expr => $value:expr),* $(,)?) => {{
#[allow(unused_mut)]
let mut map = std::collections::BTreeMap::new();
@ -20,6 +21,9 @@ macro_rules! dict {
}};
}
#[doc(inline)]
pub use crate::__dict as dict;
/// A reference-counted dictionary with value semantics.
#[derive(Default, Clone, PartialEq, Hash)]
pub struct Dict(Arc<BTreeMap<Str, Value>>);

View File

@ -1,22 +1,19 @@
//! Evaluation of markup into modules.
use std::collections::BTreeMap;
use std::sync::Arc;
use comemo::{Track, Tracked};
use unicode_segmentation::UnicodeSegmentation;
use super::{
methods, ops, Arg, Args, Array, CapturesVisitor, Closure, Content, Dict, Flow, Func,
Node, Pattern, Recipe, Scope, Scopes, Show, StyleEntry, StyleMap, Value, Vm,
Pattern, Recipe, Scope, Scopes, Show, StyleEntry, StyleMap, Value, Vm,
};
use crate::diag::{At, SourceResult, StrResult, Trace, Tracepoint};
use crate::diag::{bail, error, At, SourceResult, StrResult, Trace, Tracepoint};
use crate::geom::{Abs, Angle, Em, Fr, Ratio};
use crate::library::math;
use crate::library::text::TextNode;
use crate::syntax::ast::TypedNode;
use crate::syntax::{ast, SourceId, Span, Spanned, Unit};
use crate::util::EcoString;
use crate::util::{format_eco, EcoString};
use crate::World;
/// Evaluate a source file and return the resulting module.
@ -39,7 +36,7 @@ pub fn eval(
// Evaluate the module.
let route = unsafe { Route::insert(route, id) };
let ast = world.source(id).ast()?;
let std = &world.config().std;
let std = &world.config().scope;
let scopes = Scopes::new(Some(std));
let mut vm = Vm::new(world, route.track(), Some(id), scopes);
let result = ast.eval(&mut vm);
@ -136,8 +133,7 @@ fn eval_markup(
break;
}
eval_markup(vm, nodes)?
.styled_with_entry(StyleEntry::Recipe(recipe).into())
eval_markup(vm, nodes)?.styled_with_entry(StyleEntry::Recipe(recipe))
}
ast::MarkupNode::Expr(ast::Expr::Wrap(wrap)) => {
let tail = eval_markup(vm, nodes)?;
@ -165,10 +161,13 @@ impl Eval for ast::MarkupNode {
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
match self {
Self::Space(v) => v.eval(vm),
Self::Space(v) => Ok(match v.newlines() {
0 ..= 1 => (vm.items.space)(),
_ => (vm.items.parbreak)(),
}),
Self::Linebreak(v) => v.eval(vm),
Self::Text(v) => v.eval(vm),
Self::Escape(v) => v.eval(vm),
Self::Escape(v) => Ok((vm.items.text)(v.get().into())),
Self::Shorthand(v) => v.eval(vm),
Self::SmartQuote(v) => v.eval(vm),
Self::Strong(v) => v.eval(vm),
@ -187,23 +186,11 @@ impl Eval for ast::MarkupNode {
}
}
impl Eval for ast::Space {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
Ok(if self.newlines() < 2 {
(vm.items().space)()
} else {
(vm.items().parbreak)()
})
}
}
impl Eval for ast::Linebreak {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
Ok((vm.items().linebreak)(false))
Ok((vm.items.linebreak)(false))
}
}
@ -211,15 +198,7 @@ impl Eval for ast::Text {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
Ok(vm.text(self.get().clone()))
}
}
impl Eval for ast::Escape {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
Ok(vm.text(self.get()))
Ok((vm.items.text)(self.get().clone()))
}
}
@ -227,7 +206,7 @@ impl Eval for ast::Shorthand {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
Ok(vm.text(self.get()))
Ok((vm.items.text)(self.get().into()))
}
}
@ -235,7 +214,7 @@ impl Eval for ast::SmartQuote {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
Ok((vm.items().smart_quote)(self.double()))
Ok((vm.items.smart_quote)(self.double()))
}
}
@ -243,7 +222,7 @@ impl Eval for ast::Strong {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
Ok((vm.items().strong)(self.body().eval(vm)?))
Ok((vm.items.strong)(self.body().eval(vm)?))
}
}
@ -251,7 +230,7 @@ impl Eval for ast::Emph {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
Ok((vm.items().emph)(self.body().eval(vm)?))
Ok((vm.items.emph)(self.body().eval(vm)?))
}
}
@ -262,7 +241,7 @@ impl Eval for ast::Raw {
let text = self.text().clone();
let lang = self.lang().cloned();
let block = self.block();
Ok((vm.items().raw)(text, lang, block))
Ok((vm.items.raw)(text, lang, block))
}
}
@ -270,7 +249,7 @@ impl Eval for ast::Link {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
Ok((vm.items().link)(self.url().clone()))
Ok((vm.items.link)(self.url().clone()))
}
}
@ -286,7 +265,7 @@ impl Eval for ast::Ref {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
Ok((vm.items().ref_)(self.get().clone()))
Ok((vm.items.ref_)(self.get().clone()))
}
}
@ -296,7 +275,7 @@ impl Eval for ast::Heading {
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let level = self.level();
let body = self.body().eval(vm)?;
Ok((vm.items().heading)(level, body))
Ok((vm.items.heading)(level, body))
}
}
@ -304,7 +283,7 @@ impl Eval for ast::ListItem {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
Ok((vm.items().list_item)(self.body().eval(vm)?))
Ok((vm.items.list_item)(self.body().eval(vm)?))
}
}
@ -314,7 +293,7 @@ impl Eval for ast::EnumItem {
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let number = self.number();
let body = self.body().eval(vm)?;
Ok((vm.items().enum_item)(number, body))
Ok((vm.items.enum_item)(number, body))
}
}
@ -324,7 +303,7 @@ impl Eval for ast::DescItem {
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let term = self.term().eval(vm)?;
let body = self.body().eval(vm)?;
Ok((vm.items().desc_item)(term, body))
Ok((vm.items.desc_item)(term, body))
}
}
@ -332,82 +311,76 @@ impl Eval for ast::Math {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let nodes = self
.children()
.map(|node| node.eval(vm))
.collect::<SourceResult<_>>()?;
Ok(math::MathNode::Row(Arc::new(nodes), self.span()).pack())
Ok((vm.items.math)(
self.children()
.map(|node| node.eval(vm))
.collect::<SourceResult<_>>()?,
self.display(),
))
}
}
impl Eval for ast::MathNode {
type Output = math::MathNode;
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
Ok(match self {
Self::Space(_) => math::MathNode::Space,
Self::Linebreak(_) => math::MathNode::Linebreak,
Self::Escape(c) => math::MathNode::Atom(c.get().into()),
Self::Atom(atom) => math::MathNode::Atom(atom.get().clone()),
Self::Script(node) => node.eval(vm)?,
Self::Frac(node) => node.eval(vm)?,
Self::Align(node) => node.eval(vm)?,
Self::Group(node) => math::MathNode::Row(
Arc::new(
node.children()
.map(|node| node.eval(vm))
.collect::<SourceResult<_>>()?,
),
node.span(),
),
Self::Expr(expr) => {
let content = expr.eval(vm)?.display(vm.world);
if let Some(node) = content.downcast::<TextNode>() {
math::MathNode::Atom(node.0.clone())
} else {
bail!(expr.span(), "expected text")
}
}
Self::Space(_) => (vm.items.space)(),
Self::Linebreak(v) => v.eval(vm)?,
Self::Escape(v) => (vm.items.math_atom)(v.get().into()),
Self::Atom(v) => v.eval(vm)?,
Self::Script(v) => v.eval(vm)?,
Self::Frac(v) => v.eval(vm)?,
Self::Align(v) => v.eval(vm)?,
Self::Group(v) => v.eval(vm)?,
Self::Expr(v) => match v.eval(vm)? {
Value::None => Content::empty(),
Value::Int(v) => (vm.items.math_atom)(format_eco!("{}", v)),
Value::Float(v) => (vm.items.math_atom)(format_eco!("{}", v)),
Value::Str(v) => (vm.items.math_atom)(v.into()),
Value::Content(v) => v,
_ => bail!(v.span(), "unexpected garbage"),
},
})
}
}
impl Eval for ast::Script {
type Output = math::MathNode;
impl Eval for ast::Atom {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
Ok(math::MathNode::Script(Arc::new(math::ScriptNode {
base: self.base().eval(vm)?,
sub: self
.sub()
.map(|node| node.eval(vm))
.transpose()?
.map(|node| node.unparen()),
sup: self
.sup()
.map(|node| node.eval(vm))
.transpose()?
.map(|node| node.unparen()),
})))
Ok((vm.items.math_atom)(self.get().clone()))
}
}
impl Eval for ast::Script {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
Ok((vm.items.math_script)(
self.base().eval(vm)?,
self.sub().map(|node| node.eval(vm)).transpose()?,
self.sup().map(|node| node.eval(vm)).transpose()?,
))
}
}
impl Eval for ast::Frac {
type Output = math::MathNode;
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
Ok(math::MathNode::Frac(Arc::new(math::FracNode {
num: self.num().eval(vm)?.unparen(),
denom: self.denom().eval(vm)?.unparen(),
})))
Ok((vm.items.math_frac)(
self.num().eval(vm)?,
self.denom().eval(vm)?,
))
}
}
impl Eval for ast::Align {
type Output = math::MathNode;
type Output = Content;
fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
Ok(math::MathNode::Align(self.count()))
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
Ok((vm.items.math_align)(self.count()))
}
}
@ -515,7 +488,7 @@ fn eval_code(
}
ast::Expr::Show(show) => {
let recipe = show.eval(vm)?;
let entry = StyleEntry::Recipe(recipe).into();
let entry = StyleEntry::Recipe(recipe);
if vm.flow.is_some() {
break;
}
@ -627,7 +600,7 @@ impl Eval for ast::Unary {
ast::UnOp::Neg => ops::neg(value),
ast::UnOp::Not => ops::not(value),
};
Ok(result.at(self.span())?)
result.at(self.span())
}
}
@ -676,7 +649,7 @@ impl ast::Binary {
}
let rhs = self.rhs().eval(vm)?;
Ok(op(lhs, rhs).at(self.span())?)
op(lhs, rhs).at(self.span())
}
/// Apply an assignment operation.
@ -708,8 +681,7 @@ impl Eval for ast::FieldAccess {
.to::<dyn Show>()
.and_then(|node| node.field(&field))
.ok_or_else(|| format!("unknown field {field:?}"))
.at(span)?
.clone(),
.at(span)?,
v => bail!(
self.target().span(),
@ -754,9 +726,8 @@ impl Eval for ast::MethodCall {
Ok(if methods::is_mutating(&method) {
let args = self.args().eval(vm)?;
let mut value = self.target().access(vm)?;
methods::call_mut(&mut value, &method, args, span)
.trace(vm.world, point, span)?;
let value = self.target().access(vm)?;
methods::call_mut(value, &method, args, span).trace(vm.world, point, span)?;
Value::None
} else {
let value = self.target().eval(vm)?;
@ -882,7 +853,7 @@ impl Eval for ast::SetRule {
let target = self.target();
let target = target.eval(vm)?.cast::<Func>().at(target.span())?;
let args = self.args().eval(vm)?;
Ok(target.set(args)?)
target.set(args)
}
}
@ -1085,14 +1056,14 @@ impl Eval for ast::ModuleInclude {
let span = self.path().span();
let path = self.path().eval(vm)?.cast::<EcoString>().at(span)?;
let module = import(vm, &path, span)?;
Ok(module.content.clone())
Ok(module.content)
}
}
/// Process an import of a module relative to the current location.
fn import(vm: &mut Vm, path: &str, span: Span) -> SourceResult<Module> {
// Load the source file.
let full = vm.locate(&path).at(span)?;
let full = vm.locate(path).at(span)?;
let id = vm.world.resolve(&full).at(span)?;
// Prevent cyclic importing.

View File

@ -5,7 +5,7 @@ use std::sync::Arc;
use comemo::{Track, Tracked};
use super::{Args, Eval, Flow, Node, NodeId, Route, Scope, Scopes, StyleMap, Value, Vm};
use crate::diag::{SourceResult, StrResult};
use crate::diag::{bail, SourceResult, StrResult};
use crate::syntax::ast::{self, Expr, TypedNode};
use crate::syntax::{SourceId, SyntaxNode};
use crate::util::EcoString;
@ -229,7 +229,7 @@ impl Closure {
}
/// A visitor that determines which variables to capture for a closure.
pub struct CapturesVisitor<'a> {
pub(super) struct CapturesVisitor<'a> {
external: &'a Scopes<'a>,
internal: Scopes<'a>,
captures: Scope,

123
src/model/items.rs Normal file
View 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);
}
}

View File

@ -97,7 +97,7 @@ pub fn call(
},
Value::Func(func) => match method {
"with" => Value::Func(func.clone().with(args.take())),
"with" => Value::Func(func.with(args.take())),
_ => return missing(),
},

View File

@ -1,5 +1,7 @@
//! Layout and computation model.
#[macro_use]
mod items;
#[macro_use]
mod cast;
#[macro_use]
@ -16,12 +18,11 @@ mod args;
mod content;
mod eval;
mod func;
mod methods;
mod ops;
mod scope;
mod vm;
pub mod methods;
pub mod ops;
pub use self::str::*;
pub use args::*;
pub use array::*;
@ -30,6 +31,7 @@ pub use content::*;
pub use dict::*;
pub use eval::*;
pub use func::*;
pub use items::*;
pub use scope::*;
pub use styles::*;
pub use value::*;

View File

@ -1,12 +1,9 @@
//! Operations on values.
use std::cmp::Ordering;
use super::{Node, Regex, Smart, Value};
use super::{Regex, Smart, Value};
use crate::diag::StrResult;
use crate::geom::{Axes, Axis, Length, Numeric, Rel};
use crate::library::text::TextNode;
use crate::library::{RawAlign, RawStroke};
use crate::geom::{Axes, Axis, GenAlign, Length, Numeric, PartialStroke, Rel};
use std::cmp::Ordering;
use Value::*;
/// Bail with a type mismatch error.
@ -22,8 +19,8 @@ pub fn join(lhs: Value, rhs: Value) -> StrResult<Value> {
(a, None) => a,
(None, b) => b,
(Str(a), Str(b)) => Str(a + b),
(Str(a), Content(b)) => Content(TextNode(a.into()).pack() + b),
(Content(a), Str(b)) => Content(a + TextNode(b.into()).pack()),
(Str(a), Content(b)) => Content(super::Content::text(a) + b),
(Content(a), Str(b)) => Content(a + super::Content::text(b)),
(Content(a), Content(b)) => Content(a + b),
(Array(a), Array(b)) => Array(a + b),
(Dict(a), Dict(b)) => Dict(a + b),
@ -88,14 +85,14 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> {
(Str(a), Str(b)) => Str(a + b),
(Content(a), Content(b)) => Content(a + b),
(Content(a), Str(b)) => Content(a + TextNode(b.into()).pack()),
(Str(a), Content(b)) => Content(TextNode(a.into()).pack() + b),
(Content(a), Str(b)) => Content(a + super::Content::text(b)),
(Str(a), Content(b)) => Content(super::Content::text(a) + b),
(Array(a), Array(b)) => Array(a + b),
(Dict(a), Dict(b)) => Dict(a + b),
(Color(color), Length(thickness)) | (Length(thickness), Color(color)) => {
Value::dynamic(RawStroke {
Value::dynamic(PartialStroke {
paint: Smart::Custom(color.into()),
thickness: Smart::Custom(thickness),
})
@ -104,7 +101,7 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> {
(Dyn(a), Dyn(b)) => {
// 1D alignments can be summed into 2D alignments.
if let (Some(&a), Some(&b)) =
(a.downcast::<RawAlign>(), b.downcast::<RawAlign>())
(a.downcast::<GenAlign>(), b.downcast::<GenAlign>())
{
if a.axis() != b.axis() {
Value::dynamic(match a.axis() {

View File

@ -5,19 +5,23 @@ use std::ops::{Add, AddAssign, Deref};
use unicode_segmentation::UnicodeSegmentation;
use super::{Array, Dict, Value};
use super::{castable, dict, Array, Dict, Value};
use crate::diag::StrResult;
use crate::library::RawAlign;
use crate::geom::GenAlign;
use crate::util::EcoString;
/// Create a new [`Str`] from a format string.
#[allow(unused_macros)]
macro_rules! format_str {
#[macro_export]
#[doc(hidden)]
macro_rules! __format_str {
($($tts:tt)*) => {{
$crate::model::Str::from(format_eco!($($tts)*))
}};
}
#[doc(inline)]
pub use crate::__format_str as format_str;
/// An immutable reference counted string.
#[derive(Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Str(EcoString);
@ -463,9 +467,9 @@ pub enum StrSide {
castable! {
StrSide,
Expected: "start or end",
@align: RawAlign => match align {
RawAlign::Start => Self::Start,
RawAlign::End => Self::End,
@align: GenAlign => match align {
GenAlign::Start => Self::Start,
GenAlign::End => Self::End,
_ => Err("expected either `start` or `end`")?,
},
}

View File

@ -7,12 +7,11 @@ use std::sync::Arc;
use comemo::{Prehashed, Tracked};
use super::{capability, Args, Content, Func, Node, NodeId, Regex, Smart, Value};
use super::{capability, Args, Content, Func, NodeId, Regex, Smart, Value};
use crate::diag::SourceResult;
use crate::geom::{Abs, Axes, Corners, Em, Length, Numeric, Rel, Sides};
use crate::library::layout::PageNode;
use crate::library::structure::{DescNode, EnumNode, ListNode};
use crate::library::text::{ParNode, TextNode};
use crate::geom::{
Abs, Align, Axes, Corners, Em, GenAlign, Length, Numeric, PartialStroke, Rel, Sides,
};
use crate::syntax::Spanned;
use crate::util::ReadableTypeId;
use crate::World;
@ -111,9 +110,9 @@ impl StyleMap {
self
}
/// The highest-level kind of of structure the map interrupts.
pub fn interruption(&self) -> Option<Interruption> {
self.0.iter().filter_map(|entry| entry.interruption()).max()
/// Whether this map contains styles for the given `node.`
pub fn interrupts<T: 'static>(&self) -> bool {
self.0.iter().any(|entry| entry.is_of(NodeId::of::<T>()))
}
}
@ -132,17 +131,6 @@ impl Debug for StyleMap {
}
}
/// Determines whether a style could interrupt some composable structure.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum Interruption {
/// The style forces a list break.
List,
/// The style forces a paragraph break.
Par,
/// The style forces a page break.
Page,
}
/// An entry for a single style property, recipe or barrier.
#[derive(Clone, PartialEq, Hash)]
pub enum StyleEntry {
@ -193,12 +181,12 @@ impl StyleEntry {
}
}
/// The highest-level kind of structure the entry interrupts.
pub fn interruption(&self) -> Option<Interruption> {
/// Whether this entry contains styles for the given `node.`
pub fn is_of(&self, node: NodeId) -> bool {
match self {
Self::Property(property) => property.interruption(),
Self::Recipe(recipe) => recipe.interruption(),
_ => None,
Self::Property(property) => property.is_of(node),
Self::Recipe(recipe) => recipe.is_of(node),
_ => false,
}
}
}
@ -397,7 +385,7 @@ impl<'a, K: Key<'a>> Iterator for Values<'a, K> {
type Item = &'a K::Value;
fn next(&mut self) -> Option<Self::Item> {
while let Some(entry) = self.entries.next() {
for entry in &mut self.entries {
match entry {
StyleEntry::Property(property) => {
if let Some(value) = property.downcast::<K>() {
@ -662,9 +650,9 @@ impl Property {
self.key == KeyId::of::<K>()
}
/// Whether this property belongs to the node `T`.
pub fn is_of<T: 'static>(&self) -> bool {
self.node == NodeId::of::<T>()
/// Whether this property belongs to the node with the given id.
pub fn is_of(&self, node: NodeId) -> bool {
self.node == node
}
/// Access the property's value if it is of the given key.
@ -690,22 +678,6 @@ impl Property {
pub fn make_scoped(&mut self) {
self.scoped = true;
}
/// What kind of structure the property interrupts.
pub fn interruption(&self) -> Option<Interruption> {
if self.is_of::<PageNode>() {
Some(Interruption::Page)
} else if self.is_of::<ParNode>() {
Some(Interruption::Par)
} else if self.is_of::<ListNode>()
|| self.is_of::<EnumNode>()
|| self.is_of::<DescNode>()
{
Some(Interruption::List)
} else {
None
}
}
}
impl Debug for Property {
@ -826,7 +798,7 @@ impl Resolve for Em {
if self.is_zero() {
Abs::zero()
} else {
self.at(styles.get(TextNode::SIZE))
self.at(item!(em)(styles))
}
}
}
@ -891,6 +863,30 @@ where
}
}
impl Resolve for GenAlign {
type Output = Align;
fn resolve(self, styles: StyleChain) -> Self::Output {
let dir = item!(dir)(styles);
match self {
Self::Start => dir.start().into(),
Self::End => dir.end().into(),
Self::Specific(align) => align,
}
}
}
impl Resolve for PartialStroke {
type Output = PartialStroke<Abs>;
fn resolve(self, styles: StyleChain) -> Self::Output {
PartialStroke {
paint: self.paint,
thickness: self.thickness.resolve(styles),
}
}
}
/// A property that is folded to determine its final value.
pub trait Fold {
/// The type of the folded output.
@ -970,6 +966,17 @@ impl Fold for Corners<Option<Rel<Abs>>> {
}
}
impl Fold for PartialStroke<Abs> {
type Output = Self;
fn fold(self, outer: Self::Output) -> Self::Output {
Self {
paint: self.paint.or(outer.paint),
thickness: self.thickness.or(outer.thickness),
}
}
}
/// A show rule recipe.
#[derive(Clone, PartialEq, Hash)]
pub struct Recipe {
@ -1003,13 +1010,14 @@ impl Recipe {
}
(Target::Text(text), Pattern::Regex(regex)) => {
let make = world.config().items.text;
let mut result = vec![];
let mut cursor = 0;
for mat in regex.find_iter(text) {
let start = mat.start();
if cursor < start {
result.push(TextNode(text[cursor .. start].into()).pack());
result.push(make(text[cursor .. start].into()));
}
result.push(self.call(world, || Value::Str(mat.as_str().into()))?);
@ -1021,7 +1029,7 @@ impl Recipe {
}
if cursor < text.len() {
result.push(TextNode(text[cursor ..].into()).pack());
result.push(make(text[cursor ..].into()));
}
Content::sequence(result)
@ -1047,18 +1055,12 @@ impl Recipe {
Ok(self.func.v.call_detached(world, args)?.display(world))
}
/// What kind of structure the property interrupts.
pub fn interruption(&self) -> Option<Interruption> {
if let Pattern::Node(id) = self.pattern {
if id == NodeId::of::<ListNode>()
|| id == NodeId::of::<EnumNode>()
|| id == NodeId::of::<DescNode>()
{
return Some(Interruption::List);
}
/// Whether this recipe is for the given node.
pub fn is_of(&self, node: NodeId) -> bool {
match self.pattern {
Pattern::Node(id) => id == node,
_ => false,
}
None
}
}

View File

@ -7,11 +7,10 @@ use std::sync::Arc;
use comemo::Tracked;
use siphasher::sip128::{Hasher128, SipHasher};
use super::{ops, Args, Array, Cast, Content, Dict, Func, Node, Str};
use super::{format_str, ops, Args, Array, Cast, Content, Dict, Func, Str};
use crate::diag::StrResult;
use crate::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel, RgbaColor};
use crate::library::text::TextNode;
use crate::util::EcoString;
use crate::util::{format_eco, EcoString};
use crate::World;
/// A computational value.
@ -385,7 +384,7 @@ primitive! { Str: "string", Str }
primitive! { Content: "content",
Content,
None => Content::empty(),
Str(text) => TextNode(text.into()).pack()
Str(text) => Content::text(text)
}
primitive! { Array: "array", Array }
primitive! { Dict: "dictionary", Dict }
@ -395,6 +394,7 @@ primitive! { Args: "arguments", Args }
#[cfg(test)]
mod tests {
use super::*;
use crate::model::{array, dict};
#[track_caller]
fn test(value: impl Into<Value>, exp: &str) {

View File

@ -2,11 +2,11 @@ use std::path::PathBuf;
use comemo::Tracked;
use super::{Content, Route, Scopes, Value};
use crate::diag::{SourceError, StrResult};
use super::{LangItems, Route, Scopes, Value};
use crate::diag::{error, SourceError, StrResult};
use crate::syntax::{SourceId, Span};
use crate::util::{EcoString, PathExt};
use crate::{LangItems, World};
use crate::util::PathExt;
use crate::World;
/// A virtual machine.
pub struct Vm<'a> {
@ -20,6 +20,8 @@ pub struct Vm<'a> {
pub scopes: Scopes<'a>,
/// A control flow event that is currently happening.
pub flow: Option<Flow>,
/// The language items.
pub items: LangItems,
}
impl<'a> Vm<'a> {
@ -36,6 +38,7 @@ impl<'a> Vm<'a> {
location,
scopes,
flow: None,
items: world.config().items,
}
}
@ -54,18 +57,6 @@ impl<'a> Vm<'a> {
Err("cannot access file system from here".into())
}
/// The language items.
pub fn items(&self) -> &LangItems {
&self.world.config().items
}
/// Create text content.
///
/// This is a shorthand for `(vm.items().text)(..)`.
pub fn text(&self, text: impl Into<EcoString>) -> Content {
(self.items().text)(text.into())
}
}
/// A control flow event that occurred during evaluation.

View File

@ -55,7 +55,7 @@ node! {
impl Markup {
/// The children.
pub fn children(&self) -> impl Iterator<Item = MarkupNode> + '_ {
pub fn children(&self) -> impl DoubleEndedIterator<Item = MarkupNode> + '_ {
self.0.children().filter_map(SyntaxNode::cast)
}
}
@ -166,7 +166,7 @@ impl Space {
}
node! {
/// A forced line break.
/// A forced line break: `\`.
Linebreak
}
@ -414,9 +414,15 @@ node! {
impl Math {
/// The children.
pub fn children(&self) -> impl Iterator<Item = MathNode> + '_ {
pub fn children(&self) -> impl DoubleEndedIterator<Item = MathNode> + '_ {
self.0.children().filter_map(SyntaxNode::cast)
}
/// Whether this is a display-level math formula.
pub fn display(&self) -> bool {
matches!(self.children().next(), Some(MathNode::Space(_)))
&& matches!(self.children().last(), Some(MathNode::Space(_)))
}
}
/// A single piece of a math formula.
@ -424,7 +430,7 @@ impl Math {
pub enum MathNode {
/// Whitespace.
Space(Space),
/// A forced line break.
/// A forced line break: `\`.
Linebreak(Linebreak),
/// An escape sequence: `\#`, `\u{1F5FA}`.
Escape(Escape),
@ -535,7 +541,7 @@ impl Frac {
}
node! {
/// A math alignment indicator: `&`, `&&`.
/// An alignment indicator in a formula: `&`, `&&`.
Align
}
@ -736,7 +742,7 @@ node! {
impl CodeBlock {
/// The list of expressions contained in the block.
pub fn exprs(&self) -> impl Iterator<Item = Expr> + '_ {
pub fn exprs(&self) -> impl DoubleEndedIterator<Item = Expr> + '_ {
self.0.children().filter_map(SyntaxNode::cast)
}
}
@ -774,7 +780,7 @@ node! {
impl Array {
/// The array's items.
pub fn items(&self) -> impl Iterator<Item = ArrayItem> + '_ {
pub fn items(&self) -> impl DoubleEndedIterator<Item = ArrayItem> + '_ {
self.0.children().filter_map(SyntaxNode::cast)
}
}
@ -811,7 +817,7 @@ node! {
impl Dict {
/// The dictionary's items.
pub fn items(&self) -> impl Iterator<Item = DictItem> + '_ {
pub fn items(&self) -> impl DoubleEndedIterator<Item = DictItem> + '_ {
self.0.children().filter_map(SyntaxNode::cast)
}
}
@ -1204,7 +1210,7 @@ node! {
impl Args {
/// The positional and named arguments.
pub fn items(&self) -> impl Iterator<Item = Arg> + '_ {
pub fn items(&self) -> impl DoubleEndedIterator<Item = Arg> + '_ {
self.0.children().filter_map(SyntaxNode::cast)
}
}
@ -1252,7 +1258,7 @@ impl Closure {
}
/// The parameter bindings.
pub fn params(&self) -> impl Iterator<Item = Param> + '_ {
pub fn params(&self) -> impl DoubleEndedIterator<Item = Param> + '_ {
self.0
.children()
.find(|x| x.kind() == &NodeKind::Params)

View File

@ -97,7 +97,7 @@ where
}
}
let highlighter = Highlighter::new(&theme);
let highlighter = Highlighter::new(theme);
process(0, root, vec![], &highlighter, &mut f);
}

View File

@ -235,17 +235,17 @@ fn replace(
let (newborns, terminated, amount) = match mode {
ReparseMode::Code => reparse_code_block(
&prefix,
prefix,
&change.text[newborn_span.start ..],
newborn_span.len(),
),
ReparseMode::Content => reparse_content_block(
&prefix,
prefix,
&change.text[newborn_span.start ..],
newborn_span.len(),
),
ReparseMode::MarkupElements { at_start, min_indent } => reparse_markup_elements(
&prefix,
prefix,
&change.text[newborn_span.start ..],
newborn_span.len(),
differential,
@ -385,17 +385,17 @@ enum ReparseMode {
/// Whether changes _inside_ this node are safely encapsulated, so that only
/// this node must be reparsed.
fn is_bounded(kind: &NodeKind) -> bool {
match kind {
matches!(
kind,
NodeKind::CodeBlock
| NodeKind::ContentBlock
| NodeKind::Linebreak
| NodeKind::SmartQuote { .. }
| NodeKind::BlockComment
| NodeKind::Space { .. }
| NodeKind::Escape(_)
| NodeKind::Shorthand(_) => true,
_ => false,
}
| NodeKind::ContentBlock
| NodeKind::Linebreak
| NodeKind::SmartQuote { .. }
| NodeKind::BlockComment
| NodeKind::Space { .. }
| NodeKind::Escape(_)
| NodeKind::Shorthand(_)
)
}
/// Whether `at_start` would still be true after this node given the

View File

@ -99,50 +99,6 @@ impl SyntaxNode {
self.children().rev().find_map(Self::cast)
}
/// Change the type of the node.
pub fn convert(&mut self, kind: NodeKind) {
match self {
Self::Inner(inner) => {
let node = Arc::make_mut(inner);
node.erroneous |= kind.is_error();
node.data.kind = kind;
}
Self::Leaf(leaf) => leaf.kind = kind,
}
}
/// Set a synthetic span for the node and all its descendants.
pub fn synthesize(&mut self, span: Span) {
match self {
Self::Inner(inner) => Arc::make_mut(inner).synthesize(span),
Self::Leaf(leaf) => leaf.synthesize(span),
}
}
/// Assign spans to each node.
pub fn numberize(&mut self, id: SourceId, within: Range<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).
///
/// This method is slow and only intended for testing.
@ -156,6 +112,54 @@ impl SyntaxNode {
self.children().flat_map(Self::leafs).collect()
}
}
/// Change the type of the node.
pub(super) fn convert(&mut self, kind: NodeKind) {
match self {
Self::Inner(inner) => {
let node = Arc::make_mut(inner);
node.erroneous |= kind.is_error();
node.data.kind = kind;
}
Self::Leaf(leaf) => leaf.kind = kind,
}
}
/// Set a synthetic span for the node and all its descendants.
pub(super) fn synthesize(&mut self, span: Span) {
match self {
Self::Inner(inner) => Arc::make_mut(inner).synthesize(span),
Self::Leaf(leaf) => leaf.synthesize(span),
}
}
/// Assign spans to each node.
pub(super) fn numberize(
&mut self,
id: SourceId,
within: Range<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 {
@ -246,7 +250,7 @@ impl InnerNode {
}
/// Set a synthetic span for the node and all its descendants.
pub fn synthesize(&mut self, span: Span) {
fn synthesize(&mut self, span: Span) {
self.data.synthesize(span);
for child in &mut self.children {
child.synthesize(span);
@ -255,7 +259,7 @@ impl InnerNode {
/// Assign span numbers `within` an interval to this node's subtree or just
/// a `range` of its children.
pub fn numberize(
fn numberize(
&mut self,
id: SourceId,
range: Option<Range<usize>>,
@ -304,12 +308,12 @@ impl InnerNode {
}
/// The upper bound of assigned numbers in this subtree.
pub fn upper(&self) -> u64 {
fn upper(&self) -> u64 {
self.upper
}
/// If the span points into this node, convert it to a byte range.
pub fn range(&self, span: Span, mut offset: usize) -> Option<Range<usize>> {
fn range(&self, span: Span, mut offset: usize) -> Option<Range<usize>> {
// Check whether we found it.
if let Some(range) = self.data.range(span, offset) {
return Some(range);
@ -343,14 +347,14 @@ impl InnerNode {
}
/// The node's children, mutably.
pub(crate) fn children_mut(&mut self) -> &mut [SyntaxNode] {
pub(super) fn children_mut(&mut self) -> &mut [SyntaxNode] {
&mut self.children
}
/// Replaces a range of children with a replacement.
///
/// May have mutated the children if it returns `Err(_)`.
pub(crate) fn replace_children(
pub(super) fn replace_children(
&mut self,
mut range: Range<usize>,
replacement: Vec<SyntaxNode>,
@ -430,7 +434,7 @@ impl InnerNode {
}
/// Update this node after changes were made to one of its children.
pub(crate) fn update_parent(
pub(super) fn update_parent(
&mut self,
prev_len: usize,
new_len: usize,
@ -509,12 +513,12 @@ impl NodeData {
}
/// Set a synthetic span for the node.
pub fn synthesize(&mut self, span: Span) {
fn synthesize(&mut self, span: Span) {
self.span = span;
}
/// Assign a span to the node.
pub fn numberize(&mut self, id: SourceId, within: Range<u64>) -> NumberingResult {
fn numberize(&mut self, id: SourceId, within: Range<u64>) -> NumberingResult {
if within.start < within.end {
self.span = Span::new(id, (within.start + within.end) / 2);
Ok(())
@ -524,7 +528,7 @@ impl NodeData {
}
/// If the span points into this node, convert it to a byte range.
pub fn range(&self, span: Span, offset: usize) -> Option<Range<usize>> {
fn range(&self, span: Span, offset: usize) -> Option<Range<usize>> {
(self.span == span).then(|| offset .. offset + self.len())
}
}

View File

@ -3,7 +3,7 @@ use std::mem;
use std::ops::Range;
use super::{ErrorPos, InnerNode, NodeData, NodeKind, SyntaxNode, TokenMode, Tokens};
use crate::util::EcoString;
use crate::util::{format_eco, EcoString};
/// A convenient token-based parser.
pub struct Parser<'s> {

View File

@ -110,11 +110,11 @@ const fn to_non_zero(v: u64) -> NonZeroU64 {
}
/// Result of numbering a node within an interval.
pub type NumberingResult = Result<(), Unnumberable>;
pub(super) type NumberingResult = Result<(), Unnumberable>;
/// Indicates that a node cannot be numbered within a given interval.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct Unnumberable;
pub(super) struct Unnumberable;
impl Display for Unnumberable {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {

View File

@ -6,7 +6,7 @@ use unscanny::Scanner;
use super::resolve::{resolve_hex, resolve_raw, resolve_string};
use super::{ErrorPos, NodeKind, RawKind, Unit};
use crate::geom::{AbsUnit, AngleUnit};
use crate::util::EcoString;
use crate::util::{format_eco, EcoString};
/// An iterator over the tokens of a string of source code.
#[derive(Clone)]

View File

@ -8,7 +8,9 @@ use std::sync::Arc;
use super::ArcExt;
/// Create a new [`EcoString`] from a format string.
macro_rules! format_eco {
#[macro_export]
#[doc(hidden)]
macro_rules! __format_eco {
($($tts:tt)*) => {{
use std::fmt::Write;
let mut s = $crate::util::EcoString::new();
@ -17,6 +19,9 @@ macro_rules! format_eco {
}};
}
#[doc(inline)]
pub use crate::__format_eco as format_eco;
/// An economical string with inline storage and clone-on-write semantics.
#[derive(Clone)]
pub struct EcoString(Repr);
@ -55,7 +60,7 @@ impl EcoString {
}
/// Create an instance from an existing string-like type.
pub fn from_str<S>(s: S) -> Self
pub fn from_str_like<S>(s: S) -> Self
where
S: AsRef<str> + Into<String>,
{
@ -324,13 +329,13 @@ impl From<char> for EcoString {
impl From<&str> for EcoString {
fn from(s: &str) -> Self {
Self::from_str(s)
Self::from_str_like(s)
}
}
impl From<String> for EcoString {
fn from(s: String) -> Self {
Self::from_str(s)
Self::from_str_like(s)
}
}

View File

@ -3,7 +3,7 @@
pub mod fat;
pub use buffer::Buffer;
pub use eco::EcoString;
pub use eco::{format_eco, EcoString};
#[macro_use]
mod eco;
@ -11,9 +11,12 @@ mod buffer;
use std::any::TypeId;
use std::fmt::{self, Debug, Formatter};
use std::hash::Hash;
use std::path::{Component, Path, PathBuf};
use std::sync::Arc;
use siphasher::sip128::{Hasher128, SipHasher};
/// Turn a closure into a struct implementing [`Debug`].
pub fn debug<F>(f: F) -> impl Debug
where
@ -33,6 +36,13 @@ where
Wrapper(f)
}
/// Calculate a 128-bit siphash of a value.
pub fn hash128<T: Hash>(value: &T) -> u128 {
let mut state = SipHasher::new();
value.hash(&mut state);
state.finish128().as_u128()
}
/// Extra methods for [`str`].
pub trait StrExt {
/// The number of code units this string would use if it was encoded in

View File

@ -15,12 +15,6 @@ $ sum_(k=0)^n k = (n(n+1))/2 $
// Test that blackboard style looks nice.
$ f: NN arrow RR $
---
#set math(family: "IBM Plex Sans")
// Error: 1-4 font is not suitable for math
$a$
---
// Error: 1:3 expected dollar sign
$a

View File

@ -14,16 +14,16 @@ use tiny_skia as sk;
use unscanny::Scanner;
use walkdir::WalkDir;
use typst::diag::{FileError, FileResult};
use typst::diag::{bail, FileError, FileResult};
use typst::font::{Font, FontBook};
use typst::frame::{Element, Frame};
use typst::geom::{Abs, RgbaColor, Sides};
use typst::library::layout::PageNode;
use typst::library::text::{TextNode, TextSize};
use typst::model::{Smart, StyleMap, Value};
use typst::model::{Smart, Value};
use typst::syntax::{Source, SourceId, SyntaxNode};
use typst::util::{Buffer, PathExt};
use typst::{bail, Config, World};
use typst::{Config, World};
use typst_library::layout::PageNode;
use typst_library::text::{TextNode, TextSize};
const TYP_DIR: &str = "./typ";
const REF_DIR: &str = "./ref";
@ -149,7 +149,7 @@ fn config() -> Config {
// Set page width to 120pt with 10pt margins, so that the inner page is
// exactly 100pt wide. Page height is unbounded and font size is 10pt so
// that it multiplies to nice round numbers.
let mut styles = StyleMap::new();
let mut styles = typst_library::styles();
styles.set(PageNode::WIDTH, Smart::Custom(Abs::pt(120.0).into()));
styles.set(PageNode::HEIGHT, Smart::Auto);
styles.set(
@ -159,10 +159,10 @@ fn config() -> Config {
styles.set(TextNode::SIZE, TextSize(Abs::pt(10.0).into()));
// Hook up helpers into the global scope.
let mut std = typst::library::scope();
std.define("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF));
std.define("forest", RgbaColor::new(0x43, 0xA1, 0x27, 0xFF));
std.def_fn("test", move |_, args| {
let mut scope = typst_library::scope();
scope.define("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF));
scope.define("forest", RgbaColor::new(0x43, 0xA1, 0x27, 0xFF));
scope.def_fn("test", move |_, args| {
let lhs = args.expect::<Value>("left-hand side")?;
let rhs = args.expect::<Value>("right-hand side")?;
if lhs != rhs {
@ -170,7 +170,7 @@ fn config() -> Config {
}
Ok(Value::None)
});
std.def_fn("print", move |_, args| {
scope.def_fn("print", move |_, args| {
print!("> ");
for (i, value) in args.all::<Value>()?.into_iter().enumerate() {
if i > 0 {
@ -184,9 +184,9 @@ fn config() -> Config {
Config {
root: PathBuf::new(),
items: typst::library::items(),
std,
scope,
styles,
items: typst_library::items(),
}
}