Split crates
This commit is contained in:
parent
56342bd972
commit
37a7afddfa
61
Cargo.lock
generated
61
Cargo.lock
generated
@ -1110,49 +1110,78 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"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",
|
||||
]
|
||||
|
||||
|
90
Cargo.toml
90
Cargo.toml
@ -5,97 +5,47 @@ authors = ["The Typst Project Developers"]
|
||||
edition = "2021"
|
||||
|
||||
[workspace]
|
||||
members = ["macros"]
|
||||
members = ["cli", "library", "macros"]
|
||||
|
||||
[dependencies]
|
||||
# Workspace
|
||||
typst-macros = { path = "./macros" }
|
||||
|
||||
# Utilities
|
||||
typst-macros = { path = "macros" }
|
||||
bitflags = "1"
|
||||
bytemuck = "1"
|
||||
comemo = "0.1"
|
||||
flate2 = "1"
|
||||
image = { version = "0.24", default-features = false, features = ["png", "jpeg", "gif"] }
|
||||
miniz_oxide = "0.5"
|
||||
once_cell = "1"
|
||||
pdf-writer = "0.6"
|
||||
pixglyph = { git = "https://github.com/typst/pixglyph" }
|
||||
regex = "1"
|
||||
resvg = { version = "0.22", default-features = false }
|
||||
rex = { git = "https://github.com/laurmaedje/ReX" }
|
||||
roxmltree = "0.14"
|
||||
rustybuzz = "0.5"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
siphasher = "0.3"
|
||||
typed-arena = "2"
|
||||
unscanny = "0.1"
|
||||
|
||||
# Text and font handling
|
||||
hypher = "0.1"
|
||||
kurbo = "0.8"
|
||||
rustybuzz = "0.5"
|
||||
ttf-parser = "0.17"
|
||||
unicode-bidi = "0.3.5"
|
||||
unicode-script = "0.5"
|
||||
unicode-segmentation = "1"
|
||||
unicode-xid = "0.2"
|
||||
xi-unicode = "0.3"
|
||||
|
||||
# Raster and vector graphics handling
|
||||
image = { version = "0.24", default-features = false, features = ["png", "jpeg", "gif"] }
|
||||
usvg = { version = "0.22", default-features = false }
|
||||
|
||||
# External implementation of user-facing features
|
||||
csv = "1"
|
||||
lipsum = { git = "https://github.com/reknih/lipsum" }
|
||||
rex = { git = "https://github.com/laurmaedje/ReX" }
|
||||
serde_json = "1"
|
||||
syntect = { version = "5", default-features = false, features = ["default-syntaxes", "regex-fancy"] }
|
||||
unicode-math = { git = "https://github.com/s3bk/unicode-math/" }
|
||||
|
||||
# PDF export
|
||||
miniz_oxide = "0.5"
|
||||
pdf-writer = "0.6"
|
||||
subsetter = "0.1"
|
||||
svg2pdf = "0.4"
|
||||
|
||||
# Rendering
|
||||
flate2 = "1"
|
||||
pixglyph = { git = "https://github.com/typst/pixglyph" }
|
||||
resvg = { version = "0.22", default-features = false }
|
||||
roxmltree = "0.14"
|
||||
syntect = { version = "5", default-features = false, features = ["default-syntaxes", "regex-fancy"] }
|
||||
tiny-skia = "0.6.2"
|
||||
|
||||
# Command line interface
|
||||
chrono = { version = "0.4", default-features = false, features = ["clock", "std"], optional = true }
|
||||
codespan-reporting = { version = "0.11", optional = true }
|
||||
dirs = { version = "4", optional = true }
|
||||
elsa = { version = "1.7", optional = true }
|
||||
memmap2 = { version = "0.5", optional = true }
|
||||
notify = { version = "5", optional = true }
|
||||
pico-args = { version = "0.4", optional = true }
|
||||
same-file = { version = "1", optional = true }
|
||||
walkdir = { version = "2", optional = true }
|
||||
ttf-parser = "0.17"
|
||||
unicode-segmentation = "1"
|
||||
unicode-xid = "0.2"
|
||||
unscanny = "0.1"
|
||||
usvg = { version = "0.22", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
typst-library = { path = "library" }
|
||||
iai = { git = "https://github.com/reknih/iai" }
|
||||
elsa = "1.7"
|
||||
walkdir = "2"
|
||||
|
||||
[features]
|
||||
cli = [
|
||||
"chrono",
|
||||
"codespan-reporting",
|
||||
"dirs",
|
||||
"elsa",
|
||||
"memmap2",
|
||||
"notify",
|
||||
"pico-args",
|
||||
"same-file",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[profile.dev]
|
||||
debug = 0 # Faster compilation
|
||||
debug = 0
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 2 # Faster test execution
|
||||
|
||||
[[bin]]
|
||||
name = "typst"
|
||||
required-features = ["cli"]
|
||||
opt-level = 2
|
||||
|
||||
[[test]]
|
||||
name = "typeset"
|
||||
|
@ -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
26
cli/Cargo.toml
Normal file
@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "typst-cli"
|
||||
version = "0.1.0"
|
||||
authors = ["The Typst Project Developers"]
|
||||
edition = "2021"
|
||||
|
||||
[[bin]]
|
||||
name = "typst"
|
||||
path = "src/main.rs"
|
||||
doc = false
|
||||
|
||||
[dependencies]
|
||||
typst = { path = ".." }
|
||||
typst-library = { path = "../library" }
|
||||
chrono = { version = "0.4", default-features = false, features = ["clock", "std"] }
|
||||
codespan-reporting = "0.11"
|
||||
comemo = "0.1"
|
||||
dirs = "4"
|
||||
elsa = "1.7"
|
||||
memmap2 = "0.5"
|
||||
notify = "5"
|
||||
once_cell = "1"
|
||||
pico-args = "0.4"
|
||||
same-file = "1"
|
||||
siphasher = "0.3"
|
||||
walkdir = "2"
|
@ -174,13 +174,20 @@ fn dispatch(command: Command) -> StrResult<()> {
|
||||
|
||||
/// Execute a typesetting command.
|
||||
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
26
library/Cargo.toml
Normal file
@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "typst-library"
|
||||
version = "0.1.0"
|
||||
authors = ["The Typst Project Developers"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
typst = { path = ".." }
|
||||
comemo = "0.1"
|
||||
csv = "1"
|
||||
hypher = "0.1"
|
||||
kurbo = "0.8"
|
||||
lipsum = { git = "https://github.com/reknih/lipsum" }
|
||||
once_cell = "1"
|
||||
rex = { git = "https://github.com/laurmaedje/ReX" }
|
||||
roxmltree = "0.14"
|
||||
rustybuzz = "0.5"
|
||||
serde_json = "1"
|
||||
syntect = { version = "5", default-features = false, features = ["default-syntaxes", "regex-fancy"] }
|
||||
ttf-parser = "0.17"
|
||||
typed-arena = "2"
|
||||
unicode-bidi = "0.3.5"
|
||||
unicode-math = { git = "https://github.com/s3bk/unicode-math/" }
|
||||
unicode-script = "0.5"
|
||||
unscanny = "0.1"
|
||||
xi-unicode = "0.3"
|
@ -1,5 +1,5 @@
|
||||
use super::*;
|
||||
use 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(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
use crate::library::prelude::*;
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Hide content without affecting layout.
|
||||
#[derive(Debug, Hash)]
|
@ -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)]
|
@ -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")?,
|
||||
}
|
||||
},
|
||||
}
|
@ -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))
|
||||
}
|
||||
};
|
||||
|
@ -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 {
|
@ -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)]
|
@ -1,4 +1,4 @@
|
||||
use crate::library::prelude::*;
|
||||
use crate::prelude::*;
|
||||
|
||||
/// An inline-level container that sizes content.
|
||||
#[derive(Debug, Clone, Hash)]
|
@ -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();
|
||||
}
|
||||
}
|
@ -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! {
|
@ -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 {
|
@ -1,4 +1,4 @@
|
||||
use crate::library::prelude::*;
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Pad content at the sides.
|
||||
#[derive(Debug, Hash)]
|
@ -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)]
|
@ -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")?;
|
@ -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)]
|
@ -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)]
|
@ -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 {
|
@ -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
188
library/src/math/mod.rs
Normal file
@ -0,0 +1,188 @@
|
||||
//! Mathematical formulas.
|
||||
|
||||
mod tex;
|
||||
|
||||
use std::fmt::Write;
|
||||
|
||||
use self::tex::{layout_tex, Texify};
|
||||
use crate::layout::BlockSpacing;
|
||||
use crate::prelude::*;
|
||||
use crate::text::FontFamily;
|
||||
|
||||
/// A piece of a mathematical formula.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub struct MathNode {
|
||||
/// The pieces of the formula.
|
||||
pub children: Vec<Content>,
|
||||
/// Whether the formula is display-level.
|
||||
pub display: bool,
|
||||
}
|
||||
|
||||
#[node(Show, LayoutInline, Texify)]
|
||||
impl MathNode {
|
||||
/// The math font family.
|
||||
#[property(referenced)]
|
||||
pub const FAMILY: FontFamily = FontFamily::new("NewComputerModernMath");
|
||||
/// The spacing above display math.
|
||||
#[property(resolve, shorthand(around))]
|
||||
pub const ABOVE: Option<BlockSpacing> = Some(Ratio::one().into());
|
||||
/// The spacing below display math.
|
||||
#[property(resolve, shorthand(around))]
|
||||
pub const BELOW: Option<BlockSpacing> = Some(Ratio::one().into());
|
||||
}
|
||||
|
||||
impl Show for MathNode {
|
||||
fn unguard_parts(&self, _: Selector) -> Content {
|
||||
self.clone().pack()
|
||||
}
|
||||
|
||||
fn field(&self, _: &str) -> Option<Value> {
|
||||
None
|
||||
}
|
||||
|
||||
fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
|
||||
Ok(if self.display {
|
||||
self.clone().pack().aligned(Axes::with_x(Some(Align::Center.into())))
|
||||
} else {
|
||||
self.clone().pack()
|
||||
})
|
||||
}
|
||||
|
||||
fn finalize(
|
||||
&self,
|
||||
_: Tracked<dyn World>,
|
||||
styles: StyleChain,
|
||||
realized: Content,
|
||||
) -> SourceResult<Content> {
|
||||
Ok(if self.display {
|
||||
realized.spaced(styles.get(Self::ABOVE), styles.get(Self::BELOW))
|
||||
} else {
|
||||
realized
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl LayoutInline for MathNode {
|
||||
fn layout_inline(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
_: &Regions,
|
||||
styles: StyleChain,
|
||||
) -> SourceResult<Vec<Frame>> {
|
||||
Ok(vec![layout_tex(
|
||||
&self.texify(),
|
||||
self.display,
|
||||
world,
|
||||
styles,
|
||||
)?])
|
||||
}
|
||||
}
|
||||
|
||||
impl Texify for MathNode {
|
||||
fn texify(&self) -> EcoString {
|
||||
self.children.iter().map(Texify::texify).collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// An atom in a math formula: `x`, `+`, `12`.
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct AtomNode(pub EcoString);
|
||||
|
||||
#[node(Texify)]
|
||||
impl AtomNode {}
|
||||
|
||||
impl Texify for AtomNode {
|
||||
fn texify(&self) -> EcoString {
|
||||
self.0.chars().map(escape_char).collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// A fraction in a mathematical formula.
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct FracNode {
|
||||
/// The numerator.
|
||||
pub num: Content,
|
||||
/// The denominator.
|
||||
pub denom: Content,
|
||||
}
|
||||
|
||||
#[node(Texify)]
|
||||
impl FracNode {}
|
||||
|
||||
impl Texify for FracNode {
|
||||
fn texify(&self) -> EcoString {
|
||||
format_eco!(
|
||||
"\\frac{{{}}}{{{}}}",
|
||||
unparen(self.num.texify()),
|
||||
unparen(self.denom.texify())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// A sub- and/or superscript in a mathematical formula.
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct ScriptNode {
|
||||
/// The base.
|
||||
pub base: Content,
|
||||
/// The subscript.
|
||||
pub sub: Option<Content>,
|
||||
/// The superscript.
|
||||
pub sup: Option<Content>,
|
||||
}
|
||||
|
||||
#[node(Texify)]
|
||||
impl ScriptNode {}
|
||||
|
||||
impl Texify for ScriptNode {
|
||||
fn texify(&self) -> EcoString {
|
||||
let mut tex = self.base.texify();
|
||||
|
||||
if let Some(sub) = &self.sub {
|
||||
write!(tex, "_{{{}}}", unparen(sub.texify())).unwrap();
|
||||
}
|
||||
|
||||
if let Some(sup) = &self.sup {
|
||||
write!(tex, "^{{{}}}", unparen(sup.texify())).unwrap();
|
||||
}
|
||||
|
||||
tex
|
||||
}
|
||||
}
|
||||
|
||||
/// A math alignment indicator: `&`, `&&`.
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct AlignNode(pub usize);
|
||||
|
||||
#[node(Texify)]
|
||||
impl AlignNode {}
|
||||
|
||||
impl Texify for AlignNode {
|
||||
fn texify(&self) -> EcoString {
|
||||
EcoString::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Escape a char for TeX usage.
|
||||
#[rustfmt::skip]
|
||||
fn escape_char(c: char) -> EcoString {
|
||||
match c {
|
||||
'{' | '}' | '%' | '&' | '$' | '#' => format_eco!(" \\{c} "),
|
||||
'a' ..= 'z' | 'A' ..= 'Z' | '0' ..= '9' | 'Α' ..= 'Ω' | 'α' ..= 'ω' |
|
||||
'*' | '+' | '-' | '[' | '(' | ']' | ')' | '?' | '!' | '=' | '<' | '>' |
|
||||
':' | ',' | ';' | '|' | '/' | '@' | '.' | '"' => c.into(),
|
||||
c => unicode_math::SYMBOLS
|
||||
.iter()
|
||||
.find(|sym| sym.codepoint == c)
|
||||
.map(|sym| format_eco!("\\{} ", sym.name))
|
||||
.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Trim grouping parenthesis≤.
|
||||
fn unparen(s: EcoString) -> EcoString {
|
||||
if s.starts_with('(') && s.ends_with(')') {
|
||||
s[1 .. s.len() - 1].into()
|
||||
} else {
|
||||
s
|
||||
}
|
||||
}
|
164
library/src/math/tex.rs
Normal file
164
library/src/math/tex.rs
Normal file
@ -0,0 +1,164 @@
|
||||
use rex::error::{Error, LayoutError};
|
||||
use rex::font::FontContext;
|
||||
use rex::layout::{LayoutSettings, Style};
|
||||
use rex::parser::color::RGBA;
|
||||
use rex::render::{Backend, Cursor, Renderer};
|
||||
use typst::font::Font;
|
||||
|
||||
use super::*;
|
||||
use crate::prelude::*;
|
||||
use crate::text::{variant, LinebreakNode, SpaceNode, TextNode};
|
||||
|
||||
/// Turn a math node into TeX math code.
|
||||
#[capability]
|
||||
pub trait Texify: 'static + Sync + Send {
|
||||
/// Perform the conversion.
|
||||
fn texify(&self) -> EcoString;
|
||||
}
|
||||
|
||||
impl Texify for Content {
|
||||
fn texify(&self) -> EcoString {
|
||||
if self.is::<SpaceNode>() {
|
||||
return EcoString::new();
|
||||
}
|
||||
|
||||
if self.is::<LinebreakNode>() {
|
||||
return r"\\".into();
|
||||
}
|
||||
|
||||
if let Some(node) = self.to::<dyn Texify>() {
|
||||
return node.texify();
|
||||
}
|
||||
|
||||
panic!("{self:?} is not math");
|
||||
}
|
||||
}
|
||||
|
||||
/// Layout a TeX formula into a frame.
|
||||
pub fn layout_tex(
|
||||
tex: &str,
|
||||
display: bool,
|
||||
world: Tracked<dyn World>,
|
||||
styles: StyleChain,
|
||||
) -> SourceResult<Frame> {
|
||||
// Load the font.
|
||||
let font = world
|
||||
.book()
|
||||
.select(styles.get(MathNode::FAMILY).as_str(), variant(styles))
|
||||
.and_then(|id| world.font(id))
|
||||
.expect("failed to find math font");
|
||||
|
||||
// Prepare the font context.
|
||||
let ctx = font
|
||||
.math()
|
||||
.map(|math| FontContext::new(font.ttf(), math))
|
||||
.expect("font is not suitable for math");
|
||||
|
||||
// Layout the formula.
|
||||
let em = styles.get(TextNode::SIZE);
|
||||
let style = if display { Style::Display } else { Style::Text };
|
||||
let settings = LayoutSettings::new(&ctx, em.to_pt(), style);
|
||||
let renderer = Renderer::new();
|
||||
let layout = renderer
|
||||
.layout(&tex, settings)
|
||||
.map_err(|err| match err {
|
||||
Error::Parse(err) => err.to_string(),
|
||||
Error::Layout(LayoutError::Font(err)) => err.to_string(),
|
||||
})
|
||||
.expect("failed to layout with rex");
|
||||
|
||||
// Determine the metrics.
|
||||
let (x0, y0, x1, y1) = renderer.size(&layout);
|
||||
let width = Abs::pt(x1 - x0);
|
||||
let mut top = Abs::pt(y1);
|
||||
let mut bottom = Abs::pt(-y0);
|
||||
if style != Style::Display {
|
||||
let metrics = font.metrics();
|
||||
top = styles.get(TextNode::TOP_EDGE).resolve(styles, metrics);
|
||||
bottom = -styles.get(TextNode::BOTTOM_EDGE).resolve(styles, metrics);
|
||||
};
|
||||
|
||||
// Prepare a frame rendering backend.
|
||||
let size = Size::new(width, top + bottom);
|
||||
let mut backend = FrameBackend {
|
||||
frame: {
|
||||
let mut frame = Frame::new(size);
|
||||
frame.set_baseline(top);
|
||||
frame.apply_role(Role::Formula);
|
||||
frame
|
||||
},
|
||||
baseline: top,
|
||||
font: font.clone(),
|
||||
fill: styles.get(TextNode::FILL),
|
||||
lang: styles.get(TextNode::LANG),
|
||||
colors: vec![],
|
||||
};
|
||||
|
||||
// Render into the frame.
|
||||
renderer.render(&layout, &mut backend);
|
||||
Ok(backend.frame)
|
||||
}
|
||||
|
||||
/// A ReX rendering backend that renders into a frame.
|
||||
struct FrameBackend {
|
||||
frame: Frame,
|
||||
baseline: Abs,
|
||||
font: Font,
|
||||
fill: Paint,
|
||||
lang: Lang,
|
||||
colors: Vec<RGBA>,
|
||||
}
|
||||
|
||||
impl FrameBackend {
|
||||
/// The currently active fill paint.
|
||||
fn fill(&self) -> Paint {
|
||||
self.colors
|
||||
.last()
|
||||
.map(|&RGBA(r, g, b, a)| RgbaColor::new(r, g, b, a).into())
|
||||
.unwrap_or(self.fill)
|
||||
}
|
||||
|
||||
/// Convert a cursor to a point.
|
||||
fn transform(&self, cursor: Cursor) -> Point {
|
||||
Point::new(Abs::pt(cursor.x), self.baseline + Abs::pt(cursor.y))
|
||||
}
|
||||
}
|
||||
|
||||
impl Backend for FrameBackend {
|
||||
fn symbol(&mut self, pos: Cursor, gid: u16, scale: f64) {
|
||||
self.frame.push(
|
||||
self.transform(pos),
|
||||
Element::Text(Text {
|
||||
font: self.font.clone(),
|
||||
size: Abs::pt(scale),
|
||||
fill: self.fill(),
|
||||
lang: self.lang,
|
||||
glyphs: vec![Glyph {
|
||||
id: gid,
|
||||
x_advance: Em::new(0.0),
|
||||
x_offset: Em::new(0.0),
|
||||
c: ' ',
|
||||
}],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
fn rule(&mut self, pos: Cursor, width: f64, height: f64) {
|
||||
self.frame.push(
|
||||
self.transform(pos),
|
||||
Element::Shape(Shape {
|
||||
geometry: Geometry::Rect(Size::new(Abs::pt(width), Abs::pt(height))),
|
||||
fill: Some(self.fill()),
|
||||
stroke: None,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
fn begin_color(&mut self, color: RGBA) {
|
||||
self.colors.push(color);
|
||||
}
|
||||
|
||||
fn end_color(&mut self) {
|
||||
self.colors.pop();
|
||||
}
|
||||
}
|
27
library/src/prelude.rs
Normal file
27
library/src/prelude.rs
Normal file
@ -0,0 +1,27 @@
|
||||
//! Helpful imports for creating library functionality.
|
||||
|
||||
pub use std::fmt::{self, Debug, Formatter};
|
||||
pub use std::hash::Hash;
|
||||
pub use std::io;
|
||||
pub use std::num::NonZeroUsize;
|
||||
pub use std::sync::Arc;
|
||||
|
||||
pub use comemo::Tracked;
|
||||
pub use typst::diag::{
|
||||
bail, error, with_alternative, At, FileError, FileResult, SourceError, SourceResult,
|
||||
StrResult,
|
||||
};
|
||||
pub use typst::frame::*;
|
||||
pub use typst::geom::*;
|
||||
pub use typst::model::{
|
||||
array, capability, castable, dict, dynamic, format_str, node, Args, Array,
|
||||
Capability, Cast, Content, Dict, Dynamic, Fold, Func, Key, LangItems, Node, Resolve,
|
||||
Scope, Selector, Show, Smart, Str, StyleChain, StyleMap, StyleVec, Value, Vm,
|
||||
};
|
||||
pub use typst::syntax::{Span, Spanned};
|
||||
pub use typst::util::{format_eco, EcoString};
|
||||
pub use typst::World;
|
||||
|
||||
pub use super::ext::{ContentExt, StyleMapExt};
|
||||
pub use super::layout::{Layout, LayoutBlock, LayoutInline, Regions};
|
||||
pub use super::text::{FallbackList, TextNode};
|
@ -1,5 +1,5 @@
|
||||
use crate::library::layout::PageNode;
|
||||
use crate::library::prelude::*;
|
||||
use crate::layout::PageNode;
|
||||
use crate::prelude::*;
|
||||
|
||||
/// A sequence of page runs.
|
||||
#[derive(Hash)]
|
@ -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)]
|
@ -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)]
|
@ -1,4 +1,4 @@
|
||||
use crate::library::prelude::*;
|
||||
use crate::prelude::*;
|
||||
|
||||
/// A reference to a label.
|
||||
#[derive(Debug, Hash)]
|
@ -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);
|
@ -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.
|
@ -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 {
|
@ -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);
|
||||
|
@ -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),
|
||||
}
|
@ -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)]
|
@ -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))
|
@ -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, ..))
|
||||
}
|
||||
|
@ -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))
|
@ -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> {
|
@ -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> {
|
@ -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> {
|
@ -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);
|
@ -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> {
|
@ -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)*
|
||||
|
18
src/diag.rs
18
src/diag.rs
@ -10,33 +10,41 @@ use std::string::FromUtf8Error;
|
||||
use comemo::Tracked;
|
||||
|
||||
use crate::syntax::{ErrorPos, Span, Spanned};
|
||||
use crate::util::EcoString;
|
||||
use crate::util::{format_eco, EcoString};
|
||||
use crate::World;
|
||||
|
||||
/// Early-return with a [`SourceError`].
|
||||
#[macro_export]
|
||||
macro_rules! bail {
|
||||
#[doc(hidden)]
|
||||
macro_rules! __bail {
|
||||
($error:expr) => {
|
||||
return Err(Box::new(vec![$error]))
|
||||
};
|
||||
|
||||
($($tts:tt)*) => {
|
||||
$crate::bail!($crate::error!($($tts)*))
|
||||
$crate::diag::bail!($crate::diag::error!($($tts)*))
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(inline)]
|
||||
pub use crate::__bail as bail;
|
||||
|
||||
/// Construct a [`SourceError`].
|
||||
#[macro_export]
|
||||
macro_rules! error {
|
||||
#[doc(hidden)]
|
||||
macro_rules! __error {
|
||||
($span:expr, $message:expr $(,)?) => {
|
||||
$crate::diag::SourceError::new($span, $message)
|
||||
};
|
||||
|
||||
($span:expr, $fmt:expr, $($arg:expr),+ $(,)?) => {
|
||||
$crate::error!($span, format!($fmt, $($arg),+))
|
||||
$crate::diag::error!($span, format!($fmt, $($arg),+))
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(inline)]
|
||||
pub use crate::__error as error;
|
||||
|
||||
/// A result that can carry multiple source errors.
|
||||
pub type SourceResult<T> = Result<T, Box<Vec<SourceError>>>;
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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));
|
||||
|
@ -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]) {
|
||||
|
@ -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);
|
||||
|
@ -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()?;
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
71
src/frame.rs
71
src/frame.rs
@ -2,6 +2,7 @@
|
||||
|
||||
use std::fmt::{self, Debug, Formatter, Write};
|
||||
use std::num::NonZeroUsize;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::font::Font;
|
||||
@ -9,7 +10,7 @@ use crate::geom::{
|
||||
Abs, Align, Axes, Dir, Em, Numeric, Paint, Point, Shape, Size, Transform,
|
||||
};
|
||||
use crate::image::Image;
|
||||
use crate::model::{Dict, Value};
|
||||
use crate::model::{dict, Dict, Value};
|
||||
use crate::util::EcoString;
|
||||
|
||||
/// A finished layout with elements at fixed positions.
|
||||
@ -396,7 +397,7 @@ pub struct Glyph {
|
||||
pub c: char,
|
||||
}
|
||||
|
||||
/// A code for a natural language.
|
||||
/// An identifier for a natural language.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct Lang([u8; 3], u8);
|
||||
|
||||
@ -404,19 +405,6 @@ impl Lang {
|
||||
/// The code for the english language.
|
||||
pub const ENGLISH: Self = Self(*b"en ", 2);
|
||||
|
||||
/// Construct a language from a two- or three-byte ISO 639-1/2/3 code.
|
||||
pub fn from_str(iso: &str) -> Option<Self> {
|
||||
let len = iso.len();
|
||||
if matches!(len, 2 ..= 3) && iso.is_ascii() {
|
||||
let mut bytes = [b' '; 3];
|
||||
bytes[.. len].copy_from_slice(iso.as_bytes());
|
||||
bytes.make_ascii_lowercase();
|
||||
Some(Self(bytes, len as u8))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the language code as an all lowercase string slice.
|
||||
pub fn as_str(&self) -> &str {
|
||||
std::str::from_utf8(&self.0[.. usize::from(self.1)]).unwrap_or_default()
|
||||
@ -432,28 +420,49 @@ impl Lang {
|
||||
}
|
||||
}
|
||||
|
||||
/// A code for a region somewhere in the world.
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -78,3 +78,40 @@ impl Debug for Align {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The generic alignment representation.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub enum GenAlign {
|
||||
/// Align at the start side of the text direction.
|
||||
Start,
|
||||
/// Align at the end side of the text direction.
|
||||
End,
|
||||
/// Align at a specific alignment.
|
||||
Specific(Align),
|
||||
}
|
||||
|
||||
impl GenAlign {
|
||||
/// The axis this alignment belongs to.
|
||||
pub const fn axis(self) -> Axis {
|
||||
match self {
|
||||
Self::Start | Self::End => Axis::X,
|
||||
Self::Specific(align) => align.axis(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Align> for GenAlign {
|
||||
fn from(align: Align) -> Self {
|
||||
Self::Specific(align)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for GenAlign {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Start => f.pad("start"),
|
||||
Self::End => f.pad("end"),
|
||||
Self::Specific(align) => align.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -178,6 +178,6 @@ mod tests {
|
||||
#[test]
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
61
src/geom/stroke.rs
Normal file
@ -0,0 +1,61 @@
|
||||
use super::*;
|
||||
use crate::model::Smart;
|
||||
|
||||
/// A stroke of a geometric shape.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Stroke {
|
||||
/// The stroke's paint.
|
||||
pub paint: Paint,
|
||||
/// The stroke's thickness.
|
||||
pub thickness: Abs,
|
||||
}
|
||||
|
||||
impl Default for Stroke {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
paint: Paint::Solid(Color::BLACK),
|
||||
thickness: Abs::pt(1.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A partial stroke representation.
|
||||
///
|
||||
/// In this representation, both fields are optional so that you can pass either
|
||||
/// just a paint (`red`), just a thickness (`0.1em`) or both (`2pt + red`) where
|
||||
/// this is expected.
|
||||
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct PartialStroke<T = Length> {
|
||||
/// The stroke's paint.
|
||||
pub paint: Smart<Paint>,
|
||||
/// The stroke's thickness.
|
||||
pub thickness: Smart<T>,
|
||||
}
|
||||
|
||||
impl PartialStroke<Abs> {
|
||||
/// Unpack the stroke, filling missing fields from the `default`.
|
||||
pub fn unwrap_or(self, default: Stroke) -> Stroke {
|
||||
Stroke {
|
||||
paint: self.paint.unwrap_or(default.paint),
|
||||
thickness: self.thickness.unwrap_or(default.thickness),
|
||||
}
|
||||
}
|
||||
|
||||
/// Unpack the stroke, filling missing fields with the default values.
|
||||
pub fn unwrap_or_default(self) -> Stroke {
|
||||
self.unwrap_or(Stroke::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug> Debug for PartialStroke<T> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match (self.paint, &self.thickness) {
|
||||
(Smart::Custom(paint), Smart::Custom(thickness)) => {
|
||||
write!(f, "{thickness:?} + {paint:?}")
|
||||
}
|
||||
(Smart::Custom(paint), Smart::Auto) => paint.fmt(f),
|
||||
(Smart::Auto, Smart::Custom(thickness)) => thickness.fmt(f),
|
||||
(Smart::Auto, Smart::Auto) => f.pad("<stroke>"),
|
||||
}
|
||||
}
|
||||
}
|
57
src/lib.rs
57
src/lib.rs
@ -26,9 +26,7 @@
|
||||
//! [content]: model::Content
|
||||
//! [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,
|
||||
}
|
||||
|
@ -1,17 +0,0 @@
|
||||
use super::*;
|
||||
use crate::library::prelude::*;
|
||||
|
||||
/// A fraction in a mathematical formula.
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct FracNode {
|
||||
/// The numerator.
|
||||
pub num: MathNode,
|
||||
/// The denominator.
|
||||
pub denom: MathNode,
|
||||
}
|
||||
|
||||
impl Texify for FracNode {
|
||||
fn texify(&self) -> EcoString {
|
||||
format_eco!("\\frac{{{}}}{{{}}}", self.num.texify(), self.denom.texify())
|
||||
}
|
||||
}
|
@ -1,295 +0,0 @@
|
||||
//! Mathematical formulas.
|
||||
|
||||
mod frac;
|
||||
mod script;
|
||||
|
||||
pub use frac::*;
|
||||
pub use script::*;
|
||||
|
||||
use rex::error::{Error, LayoutError};
|
||||
use rex::font::FontContext;
|
||||
use rex::layout::{LayoutSettings, Style};
|
||||
use rex::parser::color::RGBA;
|
||||
use rex::render::{Backend, Cursor, Renderer};
|
||||
|
||||
use crate::font::Font;
|
||||
use crate::library::layout::BlockSpacing;
|
||||
use crate::library::prelude::*;
|
||||
use crate::library::text::{variant, FontFamily, TextNode};
|
||||
|
||||
/// A piece of a mathematical formula.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub enum MathNode {
|
||||
/// Whitespace.
|
||||
Space,
|
||||
/// A forced line break.
|
||||
Linebreak,
|
||||
/// An atom in a math formula: `x`, `+`, `12`.
|
||||
Atom(EcoString),
|
||||
/// A base with optional sub and superscripts: `a_1^2`.
|
||||
Script(Arc<ScriptNode>),
|
||||
/// A fraction: `x/2`.
|
||||
Frac(Arc<FracNode>),
|
||||
/// A numbered math alignment indicator: `&`, `&&`.
|
||||
Align(usize),
|
||||
/// A row of mathematical material.
|
||||
Row(Arc<Vec<MathNode>>, Span),
|
||||
}
|
||||
|
||||
#[node(Show, LayoutInline)]
|
||||
impl MathNode {
|
||||
/// The math font family.
|
||||
#[property(referenced)]
|
||||
pub const FAMILY: FontFamily = FontFamily::new("NewComputerModernMath");
|
||||
/// The spacing above display math.
|
||||
#[property(resolve, shorthand(around))]
|
||||
pub const ABOVE: Option<BlockSpacing> = Some(Ratio::one().into());
|
||||
/// The spacing below display math.
|
||||
#[property(resolve, shorthand(around))]
|
||||
pub const BELOW: Option<BlockSpacing> = Some(Ratio::one().into());
|
||||
|
||||
fn construct(_: &mut Vm, _: &mut Args) -> SourceResult<Content> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl MathNode {
|
||||
/// Strip parentheses from the node.
|
||||
pub fn unparen(self) -> Self {
|
||||
if let Self::Row(row, span) = &self {
|
||||
if let [MathNode::Atom(l), .., MathNode::Atom(r)] = row.as_slice() {
|
||||
if l == "(" && r == ")" {
|
||||
let inner = row[1 .. row.len() - 1].to_vec();
|
||||
return Self::Row(Arc::new(inner), *span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Whether the formula is display level.
|
||||
pub fn display(&self) -> bool {
|
||||
if let Self::Row(row, _) = self {
|
||||
matches!(row.as_slice(), [MathNode::Space, .., MathNode::Space])
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Show for MathNode {
|
||||
fn unguard_parts(&self, _: Selector) -> Content {
|
||||
self.clone().pack()
|
||||
}
|
||||
|
||||
fn field(&self, _: &str) -> Option<Value> {
|
||||
None
|
||||
}
|
||||
|
||||
fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
|
||||
Ok(if self.display() {
|
||||
self.clone().pack().aligned(Axes::with_x(Some(Align::Center.into())))
|
||||
} else {
|
||||
self.clone().pack()
|
||||
})
|
||||
}
|
||||
|
||||
fn finalize(
|
||||
&self,
|
||||
_: Tracked<dyn World>,
|
||||
styles: StyleChain,
|
||||
realized: Content,
|
||||
) -> SourceResult<Content> {
|
||||
Ok(if self.display() {
|
||||
realized.spaced(styles.get(Self::ABOVE), styles.get(Self::BELOW))
|
||||
} else {
|
||||
realized
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl LayoutInline for MathNode {
|
||||
fn layout_inline(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
_: &Regions,
|
||||
styles: StyleChain,
|
||||
) -> SourceResult<Vec<Frame>> {
|
||||
let style = if self.display() { Style::Display } else { Style::Text };
|
||||
let span = match self {
|
||||
&Self::Row(_, span) => span,
|
||||
_ => Span::detached(),
|
||||
};
|
||||
|
||||
Ok(vec![layout_tex(world, self, span, style, styles)?])
|
||||
}
|
||||
}
|
||||
|
||||
/// Layout a TeX formula into a frame.
|
||||
fn layout_tex(
|
||||
world: Tracked<dyn World>,
|
||||
node: &dyn Texify,
|
||||
span: Span,
|
||||
style: Style,
|
||||
styles: StyleChain,
|
||||
) -> SourceResult<Frame> {
|
||||
let tex = node.texify();
|
||||
|
||||
// Load the font.
|
||||
let font = world
|
||||
.book()
|
||||
.select(styles.get(MathNode::FAMILY).as_str(), variant(styles))
|
||||
.and_then(|id| world.font(id))
|
||||
.ok_or("failed to find math font")
|
||||
.at(span)?;
|
||||
|
||||
// Prepare the font context.
|
||||
let ctx = font
|
||||
.math()
|
||||
.map(|math| FontContext::new(font.ttf(), math))
|
||||
.ok_or("font is not suitable for math")
|
||||
.at(span)?;
|
||||
|
||||
// Layout the formula.
|
||||
let em = styles.get(TextNode::SIZE);
|
||||
let settings = LayoutSettings::new(&ctx, em.to_pt(), style);
|
||||
let renderer = Renderer::new();
|
||||
let layout = renderer
|
||||
.layout(&tex, settings)
|
||||
.map_err(|err| match err {
|
||||
Error::Parse(err) => err.to_string(),
|
||||
Error::Layout(LayoutError::Font(err)) => err.to_string(),
|
||||
})
|
||||
.at(span)?;
|
||||
|
||||
// Determine the metrics.
|
||||
let (x0, y0, x1, y1) = renderer.size(&layout);
|
||||
let width = Abs::pt(x1 - x0);
|
||||
let mut top = Abs::pt(y1);
|
||||
let mut bottom = Abs::pt(-y0);
|
||||
if style != Style::Display {
|
||||
let metrics = font.metrics();
|
||||
top = styles.get(TextNode::TOP_EDGE).resolve(styles, metrics);
|
||||
bottom = -styles.get(TextNode::BOTTOM_EDGE).resolve(styles, metrics);
|
||||
};
|
||||
|
||||
// Prepare a frame rendering backend.
|
||||
let size = Size::new(width, top + bottom);
|
||||
let mut backend = FrameBackend {
|
||||
frame: {
|
||||
let mut frame = Frame::new(size);
|
||||
frame.set_baseline(top);
|
||||
frame.apply_role(Role::Formula);
|
||||
frame
|
||||
},
|
||||
baseline: top,
|
||||
font: font.clone(),
|
||||
fill: styles.get(TextNode::FILL),
|
||||
lang: styles.get(TextNode::LANG),
|
||||
colors: vec![],
|
||||
};
|
||||
|
||||
// Render into the frame.
|
||||
renderer.render(&layout, &mut backend);
|
||||
Ok(backend.frame)
|
||||
}
|
||||
|
||||
/// A ReX rendering backend that renders into a frame.
|
||||
struct FrameBackend {
|
||||
frame: Frame,
|
||||
baseline: Abs,
|
||||
font: Font,
|
||||
fill: Paint,
|
||||
lang: Lang,
|
||||
colors: Vec<RGBA>,
|
||||
}
|
||||
|
||||
impl FrameBackend {
|
||||
/// The currently active fill paint.
|
||||
fn fill(&self) -> Paint {
|
||||
self.colors
|
||||
.last()
|
||||
.map(|&RGBA(r, g, b, a)| RgbaColor::new(r, g, b, a).into())
|
||||
.unwrap_or(self.fill)
|
||||
}
|
||||
|
||||
/// Convert a cursor to a point.
|
||||
fn transform(&self, cursor: Cursor) -> Point {
|
||||
Point::new(Abs::pt(cursor.x), self.baseline + Abs::pt(cursor.y))
|
||||
}
|
||||
}
|
||||
|
||||
impl Backend for FrameBackend {
|
||||
fn symbol(&mut self, pos: Cursor, gid: u16, scale: f64) {
|
||||
self.frame.push(
|
||||
self.transform(pos),
|
||||
Element::Text(Text {
|
||||
font: self.font.clone(),
|
||||
size: Abs::pt(scale),
|
||||
fill: self.fill(),
|
||||
lang: self.lang,
|
||||
glyphs: vec![Glyph {
|
||||
id: gid,
|
||||
x_advance: Em::new(0.0),
|
||||
x_offset: Em::new(0.0),
|
||||
c: ' ',
|
||||
}],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
fn rule(&mut self, pos: Cursor, width: f64, height: f64) {
|
||||
self.frame.push(
|
||||
self.transform(pos),
|
||||
Element::Shape(Shape {
|
||||
geometry: Geometry::Rect(Size::new(Abs::pt(width), Abs::pt(height))),
|
||||
fill: Some(self.fill()),
|
||||
stroke: None,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
fn begin_color(&mut self, color: RGBA) {
|
||||
self.colors.push(color);
|
||||
}
|
||||
|
||||
fn end_color(&mut self) {
|
||||
self.colors.pop();
|
||||
}
|
||||
}
|
||||
|
||||
/// Turn a math node into TeX math code.
|
||||
trait Texify {
|
||||
/// Perform the conversion.
|
||||
fn texify(&self) -> EcoString;
|
||||
}
|
||||
|
||||
impl Texify for MathNode {
|
||||
fn texify(&self) -> EcoString {
|
||||
match self {
|
||||
Self::Space => "".into(),
|
||||
Self::Linebreak => r"\\".into(),
|
||||
Self::Atom(atom) => atom.chars().map(escape_char).collect(),
|
||||
Self::Script(script) => script.texify(),
|
||||
Self::Frac(frac) => frac.texify(),
|
||||
Self::Align(_) => "".into(),
|
||||
Self::Row(row, _) => row.iter().map(Texify::texify).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
fn escape_char(c: char) -> EcoString {
|
||||
match c {
|
||||
'{' | '}' | '%' | '&' | '$' | '#' => format_eco!(" \\{c} "),
|
||||
'a' ..= 'z' | 'A' ..= 'Z' | '0' ..= '9' | 'Α' ..= 'Ω' | 'α' ..= 'ω' |
|
||||
'*' | '+' | '-' | '[' | '(' | ']' | ')' | '?' | '!' | '=' | '<' | '>' |
|
||||
':' | ',' | ';' | '|' | '/' | '@' | '.' | '"' => c.into(),
|
||||
c => unicode_math::SYMBOLS
|
||||
.iter()
|
||||
.find(|sym| sym.codepoint == c)
|
||||
.map(|sym| format_eco!("\\{} ", sym.name))
|
||||
.unwrap_or_default(),
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
use std::fmt::Write;
|
||||
|
||||
use super::*;
|
||||
use crate::library::prelude::*;
|
||||
|
||||
/// A sub- and/or superscript in a mathematical formula.
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct ScriptNode {
|
||||
/// The base.
|
||||
pub base: MathNode,
|
||||
/// The subscript.
|
||||
pub sub: Option<MathNode>,
|
||||
/// The superscript.
|
||||
pub sup: Option<MathNode>,
|
||||
}
|
||||
|
||||
impl Texify for ScriptNode {
|
||||
fn texify(&self) -> EcoString {
|
||||
let mut tex = self.base.texify();
|
||||
|
||||
if let Some(sub) = &self.sub {
|
||||
write!(tex, "_{{{}}}", sub.texify()).unwrap();
|
||||
}
|
||||
|
||||
if let Some(sup) = &self.sup {
|
||||
write!(tex, "^{{{}}}", sup.texify()).unwrap();
|
||||
}
|
||||
|
||||
tex
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
//! Helpful imports for creating library functionality.
|
||||
|
||||
pub use std::fmt::{self, Debug, Formatter};
|
||||
pub use std::hash::Hash;
|
||||
pub use std::io;
|
||||
pub use std::num::NonZeroUsize;
|
||||
pub use std::sync::Arc;
|
||||
|
||||
pub use comemo::Tracked;
|
||||
|
||||
pub use super::ext::{ContentExt, StyleMapExt};
|
||||
pub use super::layout::{Layout, LayoutBlock, LayoutInline, Regions};
|
||||
pub use super::text::TextNode;
|
||||
pub use super::{RawAlign, RawStroke};
|
||||
pub use crate::diag::{
|
||||
with_alternative, At, FileError, FileResult, SourceError, SourceResult, StrResult,
|
||||
};
|
||||
pub use crate::frame::*;
|
||||
pub use crate::geom::*;
|
||||
pub use crate::model::{
|
||||
capability, node, Arg, Args, Array, Capability, Cast, Content, Dict, Dynamic, Fold,
|
||||
Func, Key, Node, Resolve, Scope, Selector, Show, Smart, Str, StyleChain, StyleMap,
|
||||
StyleVec, Value, Vm,
|
||||
};
|
||||
pub use crate::syntax::{Span, Spanned};
|
||||
pub use crate::util::EcoString;
|
||||
pub use crate::{LangItems, World};
|
@ -1,149 +0,0 @@
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
|
||||
use crate::geom::{Abs, Align, Axes, Axis, Get, Length, Paint, Stroke};
|
||||
use crate::library::text::TextNode;
|
||||
use crate::model::{Fold, Resolve, Smart, StyleChain, Value};
|
||||
|
||||
/// The unresolved alignment representation.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub enum RawAlign {
|
||||
/// Align at the start side of the text direction.
|
||||
Start,
|
||||
/// Align at the end side of the text direction.
|
||||
End,
|
||||
/// Align at a specific alignment.
|
||||
Specific(Align),
|
||||
}
|
||||
|
||||
impl Resolve for RawAlign {
|
||||
type Output = Align;
|
||||
|
||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||
let dir = styles.get(TextNode::DIR);
|
||||
match self {
|
||||
Self::Start => dir.start().into(),
|
||||
Self::End => dir.end().into(),
|
||||
Self::Specific(align) => align,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RawAlign {
|
||||
/// The axis this alignment belongs to.
|
||||
pub const fn axis(self) -> Axis {
|
||||
match self {
|
||||
Self::Start | Self::End => Axis::X,
|
||||
Self::Specific(align) => align.axis(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Align> for RawAlign {
|
||||
fn from(align: Align) -> Self {
|
||||
Self::Specific(align)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for RawAlign {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Start => f.pad("start"),
|
||||
Self::End => f.pad("end"),
|
||||
Self::Specific(align) => align.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dynamic! {
|
||||
RawAlign: "alignment",
|
||||
}
|
||||
|
||||
dynamic! {
|
||||
Axes<RawAlign>: "2d alignment",
|
||||
}
|
||||
|
||||
castable! {
|
||||
Axes<Option<RawAlign>>,
|
||||
Expected: "1d or 2d alignment",
|
||||
@align: RawAlign => {
|
||||
let mut aligns = Axes::default();
|
||||
aligns.set(align.axis(), Some(*align));
|
||||
aligns
|
||||
},
|
||||
@aligns: Axes<RawAlign> => aligns.map(Some),
|
||||
}
|
||||
|
||||
/// The unresolved stroke representation.
|
||||
///
|
||||
/// In this representation, both fields are optional so that you can pass either
|
||||
/// just a paint (`red`), just a thickness (`0.1em`) or both (`2pt + red`) where
|
||||
/// this is expected.
|
||||
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct RawStroke<T = Length> {
|
||||
/// The stroke's paint.
|
||||
pub paint: Smart<Paint>,
|
||||
/// The stroke's thickness.
|
||||
pub thickness: Smart<T>,
|
||||
}
|
||||
|
||||
impl RawStroke<Abs> {
|
||||
/// Unpack the stroke, filling missing fields from the `default`.
|
||||
pub fn unwrap_or(self, default: Stroke) -> Stroke {
|
||||
Stroke {
|
||||
paint: self.paint.unwrap_or(default.paint),
|
||||
thickness: self.thickness.unwrap_or(default.thickness),
|
||||
}
|
||||
}
|
||||
|
||||
/// Unpack the stroke, filling missing fields with the default values.
|
||||
pub fn unwrap_or_default(self) -> Stroke {
|
||||
self.unwrap_or(Stroke::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve for RawStroke {
|
||||
type Output = RawStroke<Abs>;
|
||||
|
||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||
RawStroke {
|
||||
paint: self.paint,
|
||||
thickness: self.thickness.resolve(styles),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Fold for RawStroke<Abs> {
|
||||
type Output = Self;
|
||||
|
||||
fn fold(self, outer: Self::Output) -> Self::Output {
|
||||
Self {
|
||||
paint: self.paint.or(outer.paint),
|
||||
thickness: self.thickness.or(outer.thickness),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug> Debug for RawStroke<T> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match (self.paint, &self.thickness) {
|
||||
(Smart::Custom(paint), Smart::Custom(thickness)) => {
|
||||
write!(f, "{thickness:?} + {paint:?}")
|
||||
}
|
||||
(Smart::Custom(paint), Smart::Auto) => paint.fmt(f),
|
||||
(Smart::Auto, Smart::Custom(thickness)) => thickness.fmt(f),
|
||||
(Smart::Auto, Smart::Auto) => f.pad("<stroke>"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dynamic! {
|
||||
RawStroke: "stroke",
|
||||
Value::Length(thickness) => Self {
|
||||
paint: Smart::Auto,
|
||||
thickness: Smart::Custom(thickness),
|
||||
},
|
||||
Value::Color(color) => Self {
|
||||
paint: Smart::Custom(color.into()),
|
||||
thickness: Smart::Auto,
|
||||
},
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
use std::fmt::{self, Debug, Formatter, Write};
|
||||
|
||||
use 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.
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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>>);
|
||||
|
@ -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.
|
||||
|
@ -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
123
src/model/items.rs
Normal file
@ -0,0 +1,123 @@
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use comemo::Tracked;
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
use super::{Content, StyleChain};
|
||||
use crate::diag::SourceResult;
|
||||
use crate::frame::Frame;
|
||||
use crate::geom::{Abs, Dir};
|
||||
use crate::util::{hash128, EcoString};
|
||||
use crate::World;
|
||||
|
||||
/// Global storage for lang items.
|
||||
#[doc(hidden)]
|
||||
pub static LANG_ITEMS: OnceCell<LangItems> = OnceCell::new();
|
||||
|
||||
/// Set the lang items. This is a hack :(
|
||||
///
|
||||
/// Passing the lang items everywhere they are needed (especially the text node
|
||||
/// related things) is very painful. By storing them globally, in theory, we
|
||||
/// break incremental, but only when different sets of lang items are used in
|
||||
/// the same program. For this reason, if this function is called multiple
|
||||
/// times, the items must be the same.
|
||||
pub fn set_lang_items(items: LangItems) {
|
||||
if LANG_ITEMS.set(items).is_err() {
|
||||
let first = hash128(LANG_ITEMS.get().unwrap());
|
||||
let second = hash128(&items);
|
||||
assert_eq!(first, second, "set differing lang items");
|
||||
}
|
||||
}
|
||||
|
||||
/// Access a lang item.
|
||||
macro_rules! item {
|
||||
($name:ident) => {
|
||||
$crate::model::LANG_ITEMS.get().unwrap().$name
|
||||
};
|
||||
}
|
||||
|
||||
/// Definition of certain standard library items the language is aware of.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct LangItems {
|
||||
/// The root layout function.
|
||||
pub root:
|
||||
fn(world: Tracked<dyn World>, document: &Content) -> SourceResult<Vec<Frame>>,
|
||||
/// Access the em size.
|
||||
pub em: fn(StyleChain) -> Abs,
|
||||
/// Access the text direction.
|
||||
pub dir: fn(StyleChain) -> Dir,
|
||||
/// A space.
|
||||
pub space: fn() -> Content,
|
||||
/// A forced line break.
|
||||
pub linebreak: fn(justify: bool) -> Content,
|
||||
/// Plain text.
|
||||
pub text: fn(text: EcoString) -> Content,
|
||||
/// A smart quote: `'` or `"`.
|
||||
pub smart_quote: fn(double: bool) -> Content,
|
||||
/// A paragraph break.
|
||||
pub parbreak: fn() -> Content,
|
||||
/// Strong content: `*Strong*`.
|
||||
pub strong: fn(body: Content) -> Content,
|
||||
/// Emphasized content: `_Emphasized_`.
|
||||
pub emph: fn(body: Content) -> Content,
|
||||
/// A raw block with optional syntax highlighting: `` `...` ``.
|
||||
pub raw: fn(text: EcoString, tag: Option<EcoString>, block: bool) -> Content,
|
||||
/// A hyperlink: `https://typst.org`.
|
||||
pub link: fn(url: EcoString) -> Content,
|
||||
/// A reference: `@target`.
|
||||
pub ref_: fn(target: EcoString) -> Content,
|
||||
/// A section heading: `= Introduction`.
|
||||
pub heading: fn(level: NonZeroUsize, body: Content) -> Content,
|
||||
/// An item in an unordered list: `- ...`.
|
||||
pub list_item: fn(body: Content) -> Content,
|
||||
/// An item in an enumeration (ordered list): `1. ...`.
|
||||
pub enum_item: fn(number: Option<usize>, body: Content) -> Content,
|
||||
/// An item in a description list: `/ Term: Details`.
|
||||
pub desc_item: fn(term: Content, body: Content) -> Content,
|
||||
/// A math formula: `$x$`, `$ x^2 $`.
|
||||
pub math: fn(children: Vec<Content>, display: bool) -> Content,
|
||||
/// A atom in a formula: `x`, `+`, `12`.
|
||||
pub math_atom: fn(atom: EcoString) -> Content,
|
||||
/// A base with an optional sub- and superscript in a formula: `a_1^2`.
|
||||
pub math_script:
|
||||
fn(base: Content, sub: Option<Content>, sup: Option<Content>) -> Content,
|
||||
/// A fraction in a formula: `x/2`
|
||||
pub math_frac: fn(num: Content, denom: Content) -> Content,
|
||||
/// An alignment indicator in a formula: `&`, `&&`.
|
||||
pub math_align: fn(count: usize) -> Content,
|
||||
}
|
||||
|
||||
impl Debug for LangItems {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad("LangItems { .. }")
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for LangItems {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
(self.root as usize).hash(state);
|
||||
(self.em as usize).hash(state);
|
||||
(self.dir as usize).hash(state);
|
||||
self.space.hash(state);
|
||||
self.linebreak.hash(state);
|
||||
self.text.hash(state);
|
||||
self.smart_quote.hash(state);
|
||||
self.parbreak.hash(state);
|
||||
self.strong.hash(state);
|
||||
self.emph.hash(state);
|
||||
self.raw.hash(state);
|
||||
self.link.hash(state);
|
||||
self.ref_.hash(state);
|
||||
self.heading.hash(state);
|
||||
self.list_item.hash(state);
|
||||
self.enum_item.hash(state);
|
||||
self.desc_item.hash(state);
|
||||
self.math.hash(state);
|
||||
self.math_atom.hash(state);
|
||||
self.math_script.hash(state);
|
||||
self.math_frac.hash(state);
|
||||
self.math_align.hash(state);
|
||||
}
|
||||
}
|
@ -97,7 +97,7 @@ pub fn call(
|
||||
},
|
||||
|
||||
Value::Func(func) => match method {
|
||||
"with" => Value::Func(func.clone().with(args.take())),
|
||||
"with" => Value::Func(func.with(args.take())),
|
||||
_ => return missing(),
|
||||
},
|
||||
|
||||
|
@ -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::*;
|
||||
|
@ -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() {
|
||||
|
@ -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`")?,
|
||||
},
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -97,7 +97,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
let highlighter = Highlighter::new(&theme);
|
||||
let highlighter = Highlighter::new(theme);
|
||||
process(0, root, vec![], &highlighter, &mut f);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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> {
|
||||
|
@ -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 {
|
||||
|
@ -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)]
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user