diff options
56 files changed, 672 insertions, 445 deletions
diff --git a/benches/bench.typ b/benches/bench.typ index f290844b..75c97d02 100644 --- a/benches/bench.typ +++ b/benches/bench.typ @@ -1,5 +1,5 @@ // Configuration with `page` and `font` functions. -#page(width: 450pt, margins: 1cm) +#set page(width: 450pt, margins: 1cm) // There are variables and they can take normal values like strings, ... #let city = "Berlin" diff --git a/src/eval/class.rs b/src/eval/class.rs new file mode 100644 index 00000000..45674933 --- /dev/null +++ b/src/eval/class.rs @@ -0,0 +1,101 @@ +use std::fmt::{self, Debug, Formatter, Write}; +use std::marker::PhantomData; +use std::rc::Rc; + +use super::{Args, EvalContext, Node, Styles}; +use crate::diag::TypResult; +use crate::util::EcoString; + +/// A class of nodes. +#[derive(Clone)] +pub struct Class(Rc<Inner<dyn Bounds>>); + +/// The unsized structure behind the [`Rc`]. +struct Inner<T: ?Sized> { + name: EcoString, + dispatch: T, +} + +impl Class { + /// Create a new class. + pub fn new<T>(name: EcoString) -> Self + where + T: Construct + Set + 'static, + { + Self(Rc::new(Inner { + name, + dispatch: Dispatch::<T>(PhantomData), + })) + } + + /// The name of the class. + pub fn name(&self) -> &EcoString { + &self.0.name + } + + /// Construct an instance of the class. + pub fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult<Node> { + self.0.dispatch.construct(ctx, args) + } + + /// Execute the class's set rule. + pub fn set(&self, styles: &mut Styles, args: &mut Args) -> TypResult<()> { + self.0.dispatch.set(styles, args) + } +} + +impl Debug for Class { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.write_str("<class ")?; + f.write_str(&self.0.name)?; + f.write_char('>') + } +} + +impl PartialEq for Class { + fn eq(&self, other: &Self) -> bool { + // We cast to thin pointers for comparison. + std::ptr::eq( + Rc::as_ptr(&self.0) as *const (), + Rc::as_ptr(&other.0) as *const (), + ) + } +} + +/// Construct an instance of a class. +pub trait Construct { + /// Construct an instance of this class from the arguments. + /// + /// This is passed only the arguments that remain after execution of the + /// class's set rule. + fn construct(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Node>; +} + +/// Set style properties of a class. +pub trait Set { + /// Parse the arguments and insert style properties of this class into the + /// given style map. + fn set(styles: &mut Styles, args: &mut Args) -> TypResult<()>; +} + +/// Zero-sized struct whose vtable contains the constructor and set rule of a +/// class. +struct Dispatch<T>(PhantomData<T>); + +trait Bounds { + fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult<Node>; + fn set(&self, styles: &mut Styles, args: &mut Args) -> TypResult<()>; +} + +impl<T> Bounds for Dispatch<T> +where + T: Construct + Set, +{ + fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult<Node> { + T::construct(ctx, args) + } + + fn set(&self, styles: &mut Styles, args: &mut Args) -> TypResult<()> { + T::set(styles, args) + } +} diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 6dcff900..ae330134 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -9,6 +9,7 @@ mod value; #[macro_use] mod styles; mod capture; +mod class; mod function; mod node; mod ops; @@ -16,6 +17,7 @@ mod scope; pub use array::*; pub use capture::*; +pub use class::*; pub use dict::*; pub use function::*; pub use node::*; @@ -54,7 +56,7 @@ pub fn eval(ctx: &mut Context, source: SourceId, markup: &Markup) -> TypResult<M pub struct Module { /// The top-level definitions that were bound in this module. pub scope: Scope, - /// The node defined by this module. + /// The module's layoutable contents. pub node: Node, } @@ -288,6 +290,7 @@ impl Eval for Expr { Self::Unary(v) => v.eval(ctx), Self::Binary(v) => v.eval(ctx), Self::Let(v) => v.eval(ctx), + Self::Set(v) => v.eval(ctx), Self::If(v) => v.eval(ctx), Self::While(v) => v.eval(ctx), Self::For(v) => v.eval(ctx), @@ -474,9 +477,17 @@ impl Eval for CallExpr { Ok(value) } + Value::Class(class) => { + let mut styles = Styles::new(); + class.set(&mut styles, &mut args)?; + let node = class.construct(ctx, &mut args)?; + args.finish()?; + Ok(Value::Node(node.styled(styles))) + } + v => bail!( self.callee().span(), - "expected function or collection, found {}", + "expected callable or collection, found {}", v.type_name(), ), } @@ -643,6 +654,19 @@ impl Eval for LetExpr { } } +impl Eval for SetExpr { + type Output = Value; + + fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> { + let class = self.class(); + let class = class.eval(ctx)?.cast::<Class>().at(class.span())?; + let mut args = self.args().eval(ctx)?; + class.set(&mut ctx.styles, &mut args)?; + args.finish()?; + Ok(Value::None) + } +} + impl Eval for IfExpr { type Output = Value; diff --git a/src/eval/node.rs b/src/eval/node.rs index 5653beff..a04fe84b 100644 --- a/src/eval/node.rs +++ b/src/eval/node.rs @@ -36,6 +36,8 @@ pub enum Node { Inline(PackedNode), /// A block node. Block(PackedNode), + /// A page node. + Page(PackedNode), /// A sequence of nodes (which may themselves contain sequences). Sequence(Vec<(Self, Styles)>), } @@ -214,6 +216,14 @@ impl Packer { Node::Block(block) => { self.push_block(block.styled(styles)); } + Node::Page(flow) => { + if self.top { + self.pagebreak(); + self.pages.push(PageNode { child: flow, styles }); + } else { + self.push_block(flow.styled(styles)); + } + } Node::Sequence(list) => { // For a list of nodes, we apply the list's styles to each node // individually. diff --git a/src/eval/scope.rs b/src/eval/scope.rs index ffe2d63e..5178c819 100644 --- a/src/eval/scope.rs +++ b/src/eval/scope.rs @@ -4,7 +4,7 @@ use std::fmt::{self, Debug, Formatter}; use std::iter; use std::rc::Rc; -use super::{Args, EvalContext, Function, Value}; +use super::{Args, Class, Construct, EvalContext, Function, Set, Value}; use crate::diag::TypResult; use crate::util::EcoString; @@ -88,15 +88,6 @@ impl Scope { self.values.insert(var.into(), Rc::new(cell)); } - /// Define a constant function. - pub fn def_func<F>(&mut self, name: impl Into<EcoString>, f: F) - where - F: Fn(&mut EvalContext, &mut Args) -> TypResult<Value> + 'static, - { - let name = name.into(); - self.def_const(name.clone(), Function::new(Some(name), f)); - } - /// Define a mutable variable with a value. pub fn def_mut(&mut self, var: impl Into<EcoString>, value: impl Into<Value>) { self.values.insert(var.into(), Rc::new(RefCell::new(value.into()))); @@ -107,6 +98,24 @@ impl Scope { self.values.insert(var.into(), slot); } + /// Define a constant function. + pub fn def_func<F>(&mut self, name: &str, f: F) + where + F: Fn(&mut EvalContext, &mut Args) -> TypResult<Value> + 'static, + { + let name = EcoString::from(name); + self.def_const(name.clone(), Function::new(Some(name), f)); + } + + /// Define a constant class. + pub fn def_class<T>(&mut self, name: &str) + where + T: Construct + Set + 'static, + { + let name = EcoString::from(name); + self.def_const(name.clone(), Class::new::<T>(name)); + } + /// Look up the value of a variable. pub fn get(&self, var: &str) -> Option<&Slot> { self.values.get(var) diff --git a/src/eval/value.rs b/src/eval/value.rs index 2cf82a26..0995ab75 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -4,7 +4,7 @@ use std::fmt::{self, Debug, Formatter}; use std::hash::Hash; use std::rc::Rc; -use super::{ops, Array, Dict, Function, Node}; +use super::{ops, Array, Class, Dict, Function, Node}; use crate::diag::StrResult; use crate::geom::{Angle, Color, Fractional, Length, Linear, Relative, RgbaColor}; use crate::layout::Layout; @@ -46,6 +46,8 @@ pub enum Value { Node(Node), /// An executable function. Func(Function), + /// A class of nodes. + Class(Class), /// A dynamic value. Dyn(Dynamic), } @@ -86,6 +88,7 @@ impl Value { Self::Dict(_) => Dict::TYPE_NAME, Self::Node(_) => Node::TYPE_NAME, Self::Func(_) => Function::TYPE_NAME, + Self::Class(_) => Class::TYPE_NAME, Self::Dyn(v) => v.type_name(), } } @@ -148,6 +151,7 @@ impl Debug for Value { Self::Dict(v) => Debug::fmt(v, f), Self::Node(_) => f.pad("<template>"), Self::Func(v) => Debug::fmt(v, f), + Self::Class(v) => Debug::fmt(v, f), Self::Dyn(v) => Debug::fmt(v, f), } } @@ -394,6 +398,7 @@ primitive! { Array: "array", Array } primitive! { Dict: "dictionary", Dict } primitive! { Node: "template", Node } primitive! { Function: "function", Func } +primitive! { Class: "class", Class } impl Cast<Value> for Value { fn is(_: &Value) -> bool { diff --git a/src/library/mod.rs b/src/library/mod.rs index 9b6da6a9..1e2ee224 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -27,7 +27,9 @@ mod prelude { pub use std::rc::Rc; pub use crate::diag::{At, TypResult}; - pub use crate::eval::{Args, EvalContext, Node, Property, Smart, Styles, Value}; + pub use crate::eval::{ + Args, Construct, EvalContext, Node, Property, Set, Smart, Styles, Value, + }; pub use crate::frame::*; pub use crate::geom::*; pub use crate::layout::*; @@ -60,22 +62,24 @@ use crate::geom::*; pub fn new() -> Scope { let mut std = Scope::new(); - // Text. - std.def_func("font", font); - std.def_func("par", par); - std.def_func("parbreak", parbreak); + // Classes. + std.def_class::<PageNode>("page"); + std.def_class::<ParNode>("par"); + std.def_class::<TextNode>("text"); + + // Text functions. std.def_func("strike", strike); std.def_func("underline", underline); std.def_func("overline", overline); std.def_func("link", link); - // Layout. - std.def_func("page", page); - std.def_func("pagebreak", pagebreak); + // Layout functions. std.def_func("h", h); std.def_func("v", v); std.def_func("box", box_); std.def_func("block", block); + std.def_func("pagebreak", pagebreak); + std.def_func("parbreak", parbreak); std.def_func("stack", stack); std.def_func("grid", grid); std.def_func("pad", pad); @@ -85,14 +89,14 @@ pub fn new() -> Scope { std.def_func("scale", scale); std.def_func("rotate", rotate); - // Elements. + // Element functions. std.def_func("image", image); std.def_func("rect", rect); std.def_func("square", square); std.def_func("ellipse", ellipse); std.def_func("circle", circle); - // Utility. + // Utility functions. std.def_func("assert", assert); std.def_func("type", type_); std.def_func("repr", repr); @@ -110,14 +114,14 @@ pub fn new() -> Scope { std.def_func("len", len); std.def_func("sorted", sorted); - // Colors. + // Predefined colors. std.def_const("white", RgbaColor::WHITE); std.def_const("black", RgbaColor::BLACK); std.def_const("eastern", RgbaColor::new(0x23, 0x9D, 0xAD, 0xFF)); std.def_const("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF)); std.def_const("forest", RgbaColor::new(0x43, 0xA1, 0x27, 0xFF)); - // Arbitrary constants. + // Other constants. std.def_const("ltr", Dir::LTR); std.def_const("rtl", Dir::RTL); std.def_const("ttb", Dir::TTB); diff --git a/src/library/page.rs b/src/library/page.rs index 4a4fab03..0b0cc2d9 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -6,53 +6,6 @@ use std::str::FromStr; use super::prelude::*; use super::PadNode; -/// `page`: Configure pages. -pub fn page(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { - castable! { - Paper, - Expected: "string", - Value::Str(string) => Paper::from_str(&string).map_err(|e| e.to_string())?, - } - - let body: Option<Node> = args.find(); - - let mut map = Styles::new(); - let styles = match body { - Some(_) => &mut map, - None => &mut ctx.styles, - }; - - if let Some(paper) = args.named::<Paper>("paper")?.or_else(|| args.find()) { - styles.set(PageNode::CLASS, paper.class()); - styles.set(PageNode::WIDTH, Smart::Custom(paper.width())); - styles.set(PageNode::HEIGHT, Smart::Custom(paper.height())); - } - - if let Some(width) = args.named("width")? { - styles.set(PageNode::CLASS, PaperClass::Custom); - styles.set(PageNode::WIDTH, width); - } - - if let Some(height) = args.named("height")? { - styles.set(PageNode::CLASS, PaperClass::Custom); - styles.set(PageNode::HEIGHT, height); - } - - let margins = args.named("margins")?; - - set!(styles, PageNode::FLIPPED => args.named("flipped")?); - set!(styles, PageNode::LEFT => args.named("left")?.or(margins)); - set!(styles, PageNode::TOP => args.named("top")?.or(margins)); - set!(styles, PageNode::RIGHT => args.named("right")?.or(margins)); - set!(styles, PageNode::BOTTOM => args.named("bottom")?.or(margins)); - set!(styles, PageNode::FILL => args.named("fill")?); - - Ok(match body { - Some(body) => Value::block(body.into_block().styled(map)), - None => Value::None, - }) -} - /// `pagebreak`: Start a new page. pub fn pagebreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> { Ok(Value::Node(Node::Pagebreak)) @@ -90,6 +43,45 @@ properties! { FILL: Option<Paint> = None, } +impl Construct for PageNode { + fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> { + // TODO(set): Make sure it's really a page so that it doesn't merge + // with adjacent pages. + Ok(Node::Page(args.expect::<Node>("body")?.into_block())) + } +} + +impl Set for PageNode { + fn set(styles: &mut Styles, args: &mut Args) -> TypResult<()> { + if let Some(paper) = args.named::<Paper>("paper")?.or_else(|| args.find()) { + styles.set(PageNode::CLASS, paper.class()); + styles.set(PageNode::WIDTH, Smart::Custom(paper.width())); + styles.set(PageNode::HEIGHT, Smart::Custom(paper.height())); + } + + if let Some(width) = args.named("width")? { + styles.set(PageNode::CLASS, PaperClass::Custom); + styles.set(PageNode::WIDTH, width); + } + + if let Some(height) = args.named("height")? { + styles.set(PageNode::CLASS, PaperClass::Custom); + styles.set(PageNode::HEIGHT, height); + } + + let margins = args.named("margins")?; + + set!(styles, PageNode::FLIPPED => args.named("flipped")?); + set!(styles, PageNode::LEFT => args.named("left")?.or(margins)); + set!(styles, PageNode::TOP => args.named("top")?.or(margins)); + set!(styles, PageNode::RIGHT => args.named("right")?.or(margins)); + set!(styles, PageNode::BOTTOM => args.named("bottom")?.or(margins)); + set!(styles, PageNode::FILL => args.named("fill")?); + + Ok(()) + } +} + impl PageNode { /// Layout the page run into a sequence of frames, one per page. pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>> { @@ -182,6 +174,12 @@ impl Default for Paper { } } +castable! { + Paper, + Expected: "string", + Value::Str(string) => Paper::from_str(&string).map_err(|e| e.to_string())?, +} + /// Defines default margins for a class of related papers. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum PaperClass { diff --git a/src/library/par.rs b/src/library/par.rs index 6ea960bf..d63c2315 100644 --- a/src/library/par.rs +++ b/src/library/par.rs @@ -9,46 +9,6 @@ use super::prelude::*; use super::{shape, ShapedText, SpacingKind, SpacingNode, TextNode}; use crate::util::{EcoString, RangeExt, RcExt, SliceExt}; -/// `par`: Configure paragraphs. -pub fn par(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { - let spacing = args.named("spacing")?; - let leading = args.named("leading")?; - - let mut dir = - args.named("lang")? - .map(|iso: EcoString| match iso.to_lowercase().as_str() { - "ar" | "he" | "fa" | "ur" | "ps" | "yi" => Dir::RTL, - "en" | "fr" | "de" => Dir::LTR, - _ => Dir::LTR, - }); - - if let Some(Spanned { v, span }) = args.named::<Spanned<Dir>>("dir")? { - if v.axis() != SpecAxis::Horizontal { - bail!(span, "must be horizontal"); - } - dir = Some(v); - } - - let mut align = None; - if let Some(Spanned { v, span }) = args.named::<Spanned<Align>>("align")? { - if v.axis() != SpecAxis::Horizontal { - bail!(span, "must be horizontal"); - } - align = Some(v); - } - - if let (Some(dir), None) = (dir, align) { - align = Some(if dir == Dir::LTR { Align::Left } else { Align::Right }); - } - - set!(ctx.styles, ParNode::DIR => dir); - set!(ctx.styles, ParNode::ALIGN => align); - set!(ctx.styles, ParNode::LEADING => leading); - set!(ctx.styles, ParNode::SPACING => spacing); - - Ok(Value::None) -} - /// `parbreak`: Start a new paragraph. pub fn parbreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> { Ok(Value::Node(Node::Parbreak)) @@ -71,6 +31,54 @@ properties! { SPACING: Linear = Relative::new(1.2).into(), } +impl Construct for ParNode { + fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> { + // Lift to a block so that it doesn't merge with adjacent stuff. + Ok(Node::Block(args.expect::<Node>("body")?.into_block())) + } +} + +impl Set for ParNode { + fn set(styles: &mut Styles, args: &mut Args) -> TypResult<()> { + let spacing = args.named("spacing")?; + let leading = args.named("leading")?; + + let mut dir = + args.named("lang")? + .map(|iso: EcoString| match iso.to_lowercase().as_str() { + "ar" | "he" | "fa" | "ur" | "ps" | "yi" => Dir::RTL, + "en" | "fr" | "de" => Dir::LTR, + _ => Dir::LTR, + }); + + if let Some(Spanned { v, span }) = args.named::<Spanned<Dir>>("dir")? { + if v.axis() != SpecAxis::Horizontal { + bail!(span, "must be horizontal"); + } + dir = Some(v); + } + + let mut align = None; + if let Some(Spanned { v, span }) = args.named::<Spanned<Align>>("align")? { + if v.axis() != SpecAxis::Horizontal { + bail!(span, "must be horizontal"); + } + align = Some(v); + } + + if let (Some(dir), None) = (dir, align) { + align = Some(if dir == Dir::LTR { Align::Left } else { Align::Right }); + } + + set!(styles, ParNode::DIR => dir); + set!(styles, ParNode::ALIGN => align); + set!(styles, ParNode::LEADING => leading); + set!(styles, ParNode::SPACING => spacing); + + Ok(()) + } +} + impl Layout for ParNode { fn layout( &self, diff --git a/src/library/text.rs b/src/library/text.rs index 4ab378c2..4baf4bc5 100644 --- a/src/library/text.rs +++ b/src/library/text.rs @@ -15,54 +15,6 @@ use crate::font::{ use crate::geom::{Dir, Em, Length, Point, Size}; use crate::util::{EcoString, SliceExt}; -/// `font`: Configure the font. -pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { - let body = args.find::<Node>(); - - let mut map = Styles::new(); - let styles = match body { - Some(_) => &mut map, - None => &mut ctx.styles, - }; - - let list = args.named("family")?.or_else(|| { - let families: Vec<_> = args.all().collect(); - (!families.is_empty()).then(|| families) - }); - - set!(styles, TextNode::FAMILY_LIST => list); - set!(styles, TextNode::SERIF_LIST => args.named("serif")?); - set!(styles, TextNode::SANS_SERIF_LIST => args.named("sans-serif")?); - set!(styles, TextNode::MONOSPACE_LIST => args.named("monospace")?); - set!(styles, TextNode::FALLBACK => args.named("fallback")?); - set!(styles, TextNode::STYLE => args.named("style")?); - set!(styles, TextNode::WEIGHT => args.named("weight")?); - set!(styles, TextNode::STRETCH => args.named("stretch")?); - set!(styles, TextNode::FILL => args.named("fill")?.or_else(|| args.find())); - set!(styles, TextNode::SIZE => args.named("size")?.or_else(|| args.find())); - set!(styles, TextNode::TRACKING => args.named("tracking")?.map(Em::new)); - set!(styles, TextNode::TOP_EDGE => args.named("top-edge")?); - set!(styles, TextNode::BOTTOM_EDGE => args.named("bottom-edge")?); - set!(styles, TextNode::KERNING => args.named("kerning")?); - set!(styles, TextNode::SMALLCAPS => args.named("smallcaps")?); - set!(styles, TextNode::ALTERNATES => args.named("alternates")?); - set!(styles, TextNode::STYLISTIC_SET => args.named("stylistic-set")?); - set!(styles, TextNode::LIGATURES => args.named("ligatures")?); - set!(styles, TextNode::DISCRETIONARY_LIGATURES => args.named("discretionary-ligatures")?); - set!(styles, TextNode::HISTORICAL_LIGATURES => args.named("historical-ligatures")?); - set!(styles, TextNode::NUMBER_TYPE => args.named("number-type")?); - set!(styles, TextNode::NUMBER_WIDTH => args.named("number-width")?); - set!(styles, TextNode::NUMBER_POSITION => args.named("number-position")?); - set!(styles, TextNode::SLASHED_ZERO => args.named("slashed-zero")?); - set!(styles, TextNode::FRACTIONS => args.named("fractions")?); - set!(styles, TextNode::FEATURES => args.named("features")?); - - Ok(match body { - Some(body) => Value::Node(body.styled(map)), - None => Value::None, - }) -} - /// `strike`: Typeset striken-through text. pub fn strike(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { line_impl(args, LineKind::Strikethrough) @@ -172,6 +124,53 @@ properties! { FEATURES: Vec<(Tag, u32)> = vec![], } +impl Construct for TextNode { + fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> { + // We don't need to do anything more here because the whole point of the + // text constructor is to apply the styles and that happens + // automatically during class construction. + args.expect::<Node>("body") + } +} + +impl Set for TextNode { + fn set(styles: &mut Styles, args: &mut Args) -> TypResult<()> { + let list = args.named("family")?.or_else(|| { + let families: Vec<_> = args.all().collect(); + (!families.is_empty()).then(|| families) + }); + + set!(styles, TextNode::FAMILY_LIST => list); + set!(styles, TextNode::SERIF_LIST => args.named("serif")?); + set!(styles, TextNode::SANS_SERIF_LIST => args.named("sans-serif")?); + set!(styles, TextNode::MONOSPACE_LIST => args.named("monospace")?); + set!(styles, TextNode::FALLBACK => args.named("fallback")?); + set!(styles, TextNode::STYLE => args.named("style")?); + set!(styles, TextNode::WEIGHT => args.named("weight")?); + set!(styles, TextNode::STRETCH => args.named("stretch")?); + set!(styles, TextNode::FILL => args.named("fill")?.or_else(|| args.find())); + set!(styles, TextNode::SIZE => args.named("size")?.or_else(|| args.find())); + set!(styles, TextNode::TRACKING => args.named("tracking")?.map(Em::new)); + set!(styles, TextNode::TOP_EDGE => args.named("top-edge")?); + set!(styles, TextNode::BOTTOM_EDGE => args.named("bottom-edge")?); + set!(styles, TextNode::KERNING => args.named("kerning")?); + set!(styles, TextNode::SMALLCAPS => args.named("smallcaps")?); + set!(styles, TextNode::ALTERNATES => args.named("alternates")?); + set!(styles, TextNode::STYLISTIC_SET => args.named("stylistic-set")?); + set!(styles, TextNode::LIGATURES => args.named("ligatures")?); + set!(styles, TextNode::DISCRETIONARY_LIGATURES => args.named("discretionary-ligatures")?); + set!(styles, TextNode::HISTORICAL_LIGATURES => args.named("historical-ligatures")?); + set!(styles, TextNode::NUMBER_TYPE => args.named("number-type")?); + set!(styles, TextNode::NUMBER_WIDTH => args.named("number-width")?); + set!(styles, TextNode::NUMBER_POSITION => args.named("number-position")?); + set!(styles, TextNode::SLASHED_ZERO => args.named("slashed-zero")?); + set!(styles, TextNode::FRACTIONS => args.named("fractions")?); + set!(styles, TextNode::FEATURES => args.named("features")?); + + Ok(()) + } +} + impl Debug for TextNode { fn fmt(&self, f: &mut Formatter) -> fmt::Result { if f.alternate() { diff --git a/src/parse/mod.rs b/src/parse/mod.rs index dbec0a5e..0a2f73f5 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -110,12 +110,13 @@ fn markup_node(p: &mut Parser, at_start: &mut bool) { // Hashtag + keyword / identifier. NodeKind::Ident(_) | NodeKind::Let + | NodeKind::Set | NodeKind::If | NodeKind::While | NodeKind::For | NodeKind::Import | NodeKind::Include => { - let stmt = matches!(token, NodeKind::Let | NodeKind::Import); + let stmt = matches!(token, NodeKind::Let | NodeKind::Set | NodeKind::Import); let group = if stmt { Group::Stmt } else { Group::Expr }; p.start_group(group); @@ -265,6 +266,7 @@ fn primary(p: &mut Parser, atomic: bool) -> ParseResult { // Keywords. Some(NodeKind::Let) => let_expr(p), + Some(NodeKind::Set) => set_expr(p), Some(NodeKind::If) => if_expr(p), Some(NodeKind::While) => while_expr(p), Some(NodeKind::For) => for_expr(p), @@ -507,45 +509,40 @@ fn block(p: &mut Parser) { /// Parse a function call. fn call(p: &mut Parser, callee: Marker) -> ParseResult { - callee.perform(p, NodeKind::Call, |p| match p.peek_direct() { - Some(NodeKind::LeftParen | NodeKind::LeftBracket) => { - args(p, true); - Ok(()) - } - _ => { - p.expected_at("argument list"); - Err(()) - } - }) + callee.perform(p, NodeKind::Call, |p| args(p, true, true)) } /// Parse the arguments to a function call. -fn args(p: &mut Parser, allow_template: bool) { +fn args(p: &mut Parser, direct: bool, brackets: bool) -> ParseResult { + match if direct { p.peek_direct() } else { p.peek() } { + Some(NodeKind::LeftParen) => {} + Some(NodeKind::LeftBracket) if brackets => {} + _ => { + p.expected("argument list"); + return Err(()); + } + } + p.perform(NodeKind::CallArgs, |p| { - if !allow_template || p.peek_direct() == Some(&NodeKind::LeftParen) { + if p.at(&NodeKind::LeftParen) { p.start_group(Group::Paren); collection(p); p.end_group(); } - while allow_template && p.peek_direct() == Some(&NodeKind::LeftBracket) { + while brackets && p.peek_direct() == Some(&NodeKind::LeftBracket) { template(p); } - }) + }); + + Ok(()) } /// Parse a with expression. fn with_expr(p: &mut Parser, marker: Marker) -> ParseResult { marker.perform(p, NodeKind::WithExpr, |p| { p.eat_assert(&NodeKind::With); - - if p.at(&NodeKind::LeftParen) { - args(p, false); - Ok(()) - } else { - p.expected("argument list"); - Err(()) - } + args(p, false, false) }) } @@ -587,6 +584,15 @@ fn let_expr(p: &mut Parser) -> ParseResult { }) } +/// Parse a set expression. +fn set_expr(p: &mut Parser) -> ParseResult { + p.perform(NodeKind::SetExpr, |p| { + p.eat_assert(&NodeKind::Set); + ident(p)?; + args(p, true, false) + }) +} + /// Parse an if expresion. fn if_expr(p: &mut Parser) -> ParseResult { p.perform(NodeKind::IfExpr, |p| { @@ -612,8 +618,7 @@ fn while_expr(p: &mut Parser) -> ParseResult { p.perform(NodeKind::WhileExpr, |p| { p.eat_assert(&NodeKind::While); expr(p)?; - body(p)?; - Ok(()) + body(p) }) } @@ -624,8 +629,7 @@ fn for_expr(p: &mut Parser) -> ParseResult { for_pattern(p)?; p.eat_expect(&NodeKind::In)?; expr(p)?; - body(p)?; - Ok(()) + body(p) }) } @@ -664,9 +668,7 @@ fn import_expr(p: &mut Parser) -> ParseResult { }; p.eat_expect(&NodeKind::From)?; - expr(p)?; - - Ok(()) + expr(p) }) } @@ -674,8 +676,7 @@ fn import_expr(p: &mut Parser) -> ParseResult { fn include_expr(p: &mut Parser) -> ParseResult { p.perform(NodeKind::IncludeExpr, |p| { p.eat_assert(&NodeKind::Include); - expr(p)?; - Ok(()) + expr(p) }) } diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 6598b1f2..503158a9 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -125,6 +125,7 @@ impl<'s> Parser<'s> { } /// Eat, debug-asserting that the token is the given one. + #[track_caller] pub fn eat_assert(&mut self, t: &NodeKind) { debug_assert_eq!(self.peek(), Some(t)); self.eat(); @@ -199,6 +200,7 @@ impl<'s> Parser<'s> { /// to `end_group`. /// /// This panics if the current token does not start the given group. + #[track_caller] pub fn start_group(&mut self, kind: Group) { self.groups.push(GroupEntry { kind, prev_mode: self.tokens.mode() }); self.tokens.set_mode(match kind { @@ -220,6 +222,7 @@ impl<'s> Parser<'s> { /// End the parsing of a group. /// /// This panics if no group was started. + #[track_caller] pub fn end_group(&mut self) { let group_mode = self.tokens.mode(); let group = self.groups.pop().expect("no started group"); diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs index 07a6fe12..27ec046d 100644 --- a/src/parse/tokens.rs +++ b/src/parse/tokens.rs @@ -527,6 +527,7 @@ fn keyword(ident: &str) -> Option<NodeKind> { "or" => NodeKind::Or, "with" => NodeKind::With, "let" => NodeKind::Let, + "set" => NodeKind::Set, "if" => NodeKind::If, "else" => NodeKind::Else, "for" => NodeKind::For, diff --git a/src/source.rs b/src/source.rs index 509b0a76..5fd85ed9 100644 --- a/src/source.rs +++ b/src/source.rs @@ -149,6 +149,11 @@ impl SourceFile { Self::new(SourceId(0), Path::new(""), src.into()) } + /// The root node of the untyped green tree. + pub fn root(&self) -> &Rc<GreenNode> { + &self.root + } + /// The file's abstract syntax tree. pub fn ast(&self) -> TypResult<Markup> { let red = RedNode::from_root(self.root.clone(), self.id); diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index 8df25f59..9190953f 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -211,6 +211,8 @@ pub enum Expr { With(WithExpr), /// A let expression: `let x = 1`. Let(LetExpr), + /// A set expression: `set text(...)`. + Set(SetExpr), /// An if-else expression: `if x { y } else { z }`. If(IfExpr), /// A while loop expression: `while x { y }`. @@ -238,6 +240,7 @@ impl TypedNode for Expr { NodeKind::Closure => node.cast().map(Self::Closure), NodeKind::WithExpr => node.cast().map(Self::With), NodeKind::LetExpr => node.cast().map(Self::Let), + NodeKind::SetExpr => node.cast().map(Self::Set), NodeKind::IfExpr => node.cast().map(Self::If), NodeKind::WhileExpr => node.cast().map(Self::While), NodeKind::ForExpr => node.cast().map(Self::For), @@ -262,6 +265,7 @@ impl TypedNode for Expr { Self::Closure(v) => v.as_red(), Self::With(v) => v.as_red(), Self::Let(v) => v.as_red(), + Self::Set(v) => v.as_red(), Self::If(v) => v.as_red(), Self::While(v) => v.as_red(), Self::For(v) => v.as_red(), @@ -838,6 +842,25 @@ impl LetExpr { } node! { + /// A set expression: `set text(...)`. + SetExpr +} + +impl SetExpr { + /// The class to set style properties for. + pub fn class(&self) -> Ident { + self.0.cast_first_child().expect("set expression is missing class") + } + + /// The style properties to set. + pub fn args(&self) -> CallArgs { + self.0 + .cast_first_child() + .expect("set expression is missing argument list") + } +} + +node! { /// An import expression: `import a, b, c from "utils.typ"`. ImportExpr } diff --git a/src/syntax/highlight.rs b/src/syntax/highlight.rs index 22e6cf50..85fbef12 100644 --- a/src/syntax/highlight.rs +++ b/src/syntax/highlight.rs @@ -96,22 +96,23 @@ impl Category { NodeKind::EnDash => Some(Category::Shortcut), NodeKind::EmDash => Some(Category::Shortcut), NodeKind::Escape(_) => Some(Category::Escape), + NodeKind::Not => Some(Category::Keyword), + NodeKind::And => Some(Category::Keyword), + NodeKind::Or => Some(Category::Keyword), + NodeKind::With => Some(Category::Keyword), NodeKind::Let => Some(Category::Keyword), + NodeKind::Set => Some(Category::Keyword), NodeKind::If => Some(Category::Keyword), NodeKind::Else => Some(Category::Keyword), + NodeKind::While => Some(Category::Keyword), NodeKind::For => Some(Category::Keyword), NodeKind::In => Some(Category::Keyword), - NodeKind::While => Some(Category::Keyword), NodeKind::Break => Some(Category::Keyword), NodeKind::Continue => Some(Category::Keyword), NodeKind::Return => Some(Category::Keyword), NodeKind::Import => Some(Category::Keyword), - NodeKind::Include => Some(Category::Keyword), NodeKind::From => Some(Category::Keyword), - NodeKind::Not => Some(Category::Keyword), - NodeKind::And => Some(Category::Keyword), - NodeKind::Or => Some(Category::Keyword), - NodeKind::With => Some(Category::Keyword), + NodeKind::Include => Some(Category::Keyword), NodeKind::Plus => Some(Category::Operator), NodeKind::Star => Some(Category::Operator), NodeKind::Slash => Some(Category::Operator), @@ -139,6 +140,7 @@ impl Category { Some(Category::Function) } NodeKind::WithExpr => Some(Category::Function), + NodeKind::SetExpr => Some(Category::Function), NodeKind::Call => Some(Category::Function), _ => Some(Category::Variable), }, @@ -161,21 +163,22 @@ impl Category { NodeKind::Array => None, NodeKind::Dict => None, NodeKind::Named => None, + NodeKind::Template => None, NodeKind::Group => None, + NodeKind::Block => None, NodeKind::Unary => None, NodeKind::Binary => None, NodeKind::Call => None, NodeKind::CallArgs => None, + NodeKind::Spread => None, NodeKind::Closure => None, NodeKind::ClosureParams => None, - NodeKind::Spread => None, - NodeKind::Template => None, - NodeKind::Block => None, - NodeKind::ForExpr => None, - NodeKind::WhileExpr => None, - NodeKind::IfExpr => None, - NodeKind::LetExpr => None, NodeKind::WithExpr => None, + NodeKind::LetExpr => None, + NodeKind::SetExpr => None, + NodeKind::IfExpr => None, + NodeKind::WhileExpr => None, + NodeKind::ForExpr => None, NodeKind::ForPattern => None, NodeKind::ImportExpr => None, NodeKind::ImportItems => None, diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index e9011a4d..b9b00487 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -83,19 +83,15 @@ impl Default for Green { impl Debug for Green { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{:?}: {}", self.kind(), self.len())?; - if let Self::Node(n) = self { - if !n.children.is_empty() { - f.write_str(" ")?; - f.debug_list().entries(&n.children).finish()?; - } + match self { + Self::Node(node) => node.fmt(f), + Self::Token(token) => token.fmt(f), } - Ok(()) } } /// An inner node in the untyped green tree. -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, PartialEq)] pub struct GreenNode { /// Node metadata. data: GreenData, @@ -145,8 +141,19 @@ impl From<Rc<GreenNode>> for Green { } } +impl Debug for GreenNode { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.data.fmt(f)?; + if !self.children.is_empty() { + f.write_str(" ")?; + f.debug_list().entries(&self.children).finish()?; + } + Ok(()) + } +} + /// Data shared between inner and leaf nodes. -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, PartialEq)] pub struct GreenData { /// What kind of node this is (each kind would have its own struct in a /// strongly typed AST). @@ -178,6 +185,12 @@ impl From<GreenData> for Green { } } +impl Debug for GreenData { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{:?}: {}", self.kind, self.len()) + } +} + /// A owned wrapper for a green node with span information. /// /// Owned variant of [`RedRef`]. Can be [cast](Self::cast) to an AST node. @@ -465,6 +478,8 @@ pub enum NodeKind { Auto, /// The `let` keyword. Let, + /// The `set` keyword. + Set, /// The `if` keyword. If, /// The `else` keyword. @@ -552,8 +567,12 @@ pub enum NodeKind { Dict, /// A named pair: `thickness: 3pt`. Named, + /// A template expression: `[*Hi* there!]`. + Template, /// A grouped expression: `(1 + 2)`. Group, + /// A block expression: `{ let x = 1; x + 2 }`. + Block, /// A unary operation: `-x`. Unary, /// A binary operation: `a + b`. @@ -562,39 +581,37 @@ pub enum NodeKind { Call, /// A function call's argument list: `(x, y)`. CallArgs, + /// Spreaded arguments or a parameter sink: `..x`. + Spread, /// A closure expression: `(x, y) => z`. Closure, /// A closure's parameters: `(x, y)`. ClosureParams, - /// A parameter sink: `..x`. - Spread, - /// A template expression: `[*Hi* there!]`. - Template, - /// A block expression: `{ let x = 1; x + 2 }`. - Block, - /// A for loop expression: `for x in y { ... }`. - ForExpr, - /// A while loop expression: `while x { ... }`. - WhileExpr, - /// An if expression: `if x { ... }`. - IfExpr, + /// A with expression: `f with (x, y: 1)`. + WithExpr, /// A let expression: `let x = 1`. LetExpr, - /// The `with` expression: `with (1)`. - WithExpr, + /// A set expression: `set text(...)`. + SetExpr, + /// An if-else expression: `if x { y } else { z }`. + IfExpr, + /// A while loop expression: `while x { ... }`. + WhileExpr, + /// A for loop expression: `for x in y { ... }`. + ForExpr, /// A for loop's destructuring pattern: `x` or `x, y`. ForPattern, - /// The import expression: `import x from "foo.typ"`. + /// An import expression: `import a, b, c from "utils.typ"`. ImportExpr, /// Items to import: `a, b, c`. ImportItems, - /// The include expression: `include "foo.typ"`. + /// An include expression: `include "chapter1.typ"`. IncludeExpr, - /// Two slashes followed by inner contents, terminated with a newline: - /// `//<str>\n`. + /// A line comment, two slashes followed by inner contents, terminated with + /// a newline: `//<str>\n`. LineComment, - /// A slash and a star followed by inner contents, terminated with a star - /// and a slash: `/*<str>*/`. + /// A block comment, a slash and a star followed by inner contents, + /// terminated with a star and a slash: `/*<str>*/`. /// /// The comment can contain nested block comments. BlockComment, @@ -616,11 +633,6 @@ pub enum ErrorPos { } impl NodeKind { - /// Whether this is some kind of parenthesis. - pub fn is_paren(&self) -> bool { - matches!(self, Self::LeftParen | Self::RightParen) - } - /// Whether this is some kind of bracket. pub fn is_bracket(&self) -> bool { matches!(self, Self::LeftBracket | Self::RightBracket) @@ -631,6 +643,11 @@ impl NodeKind { matches!(self, Self::LeftBrace | Self::RightBrace) } + /// Whether this is some kind of parenthesis. + pub fn is_paren(&self) -> bool { + matches!(self, Self::LeftParen | Self::RightParen) + } + /// Whether this is some kind of error. pub fn is_error(&self) -> bool { matches!(self, NodeKind::Error(_, _) | NodeKind::Unknown(_)) @@ -672,6 +689,7 @@ impl NodeKind { Self::None => "`none`", Self::Auto => "`auto`", Self::Let => "keyword `let`", + Self::Set => "keyword `set`", Self::If => "keyword `if`", Self::Else => "keyword `else`", Self::For => "keyword `for`", @@ -712,21 +730,22 @@ impl NodeKind { Self::Array => "array", Self::Dict => "dictionary", Self::Named => "named argument", + Self::Template => "template", Self::Group => "group", + Self::Block => "block", Self::Unary => "unary expression", Self::Binary => "binary expression", Self::Call => "call", Self::CallArgs => "call arguments", + Self::Spread => "parameter sink", Self::Closure => "closure", Self::ClosureParams => "closure parameters", - Self::Spread => "parameter sink", - Self::Template => "template", - Self::Block => "block", - Self::ForExpr => "for-loop expression", - Self::WhileExpr => "while-loop expression", - Self::IfExpr => "`if` expression", - Self::LetExpr => "`let` expression", Self::WithExpr => "`with` expression", + Self::LetExpr => "`let` expression", + Self::SetExpr => "`set` expression", + Self::IfExpr => "`if` expression", + Self::WhileExpr => "while-loop expression", + Self::ForExpr => "for-loop expression", Self::ForPattern => "for-loop destructuring pattern", Self::ImportExpr => "`import` expression", Self::ImportItems => "import items", diff --git a/src/syntax/pretty.rs b/src/syntax/pretty.rs index c453fb56..62ecb8cd 100644 --- a/src/syntax/pretty.rs +++ b/src/syntax/pretty.rs @@ -225,6 +225,7 @@ impl Pretty for Expr { Self::Closure(v) => v.pretty(p), Self::With(v) => v.pretty(p), Self::Let(v) => v.pretty(p), + Self::Set(v) => v.pretty(p), Self::If(v) => v.pretty(p), Self::While(v) => v.pretty(p), Self::For(v) => v.pretty(p), @@ -444,6 +445,16 @@ impl Pretty for LetExpr { } } +impl Pretty for SetExpr { + fn pretty(&self, p: &mut Printer) { + p.push_str("set "); + self.class().pretty(p); + p.push_str("("); + self.args().pretty(p); + p.push(')'); + } +} + impl Pretty for IfExpr { fn pretty(&self, p: &mut Printer) { p.push_str("if "); @@ -639,6 +650,7 @@ mod tests { // Control flow. roundtrip("#let x = 1 + 2"); roundtrip("#let f(x) = y"); + roundtrip("#set text(size: 12pt)"); roundtrip("#if x [y] else [z]"); roundtrip("#if x {} else if y {} else {}"); roundtrip("#while x {y}"); diff --git a/tests/typ/code/call.typ b/tests/typ/code/call.typ index 2c16af1c..5736c63b 100644 --- a/tests/typ/code/call.typ +++ b/tests/typ/code/call.typ @@ -5,7 +5,8 @@ // Ref: true // Ommitted space. -[#font(weight:"bold")Bold] +#let f() = {} +[#f()*Bold*] // Call return value of function with body. #let f(x, body) = (y) => [#x] + body + [#y] @@ -44,25 +45,25 @@ } --- -// Error: 2-6 expected function or collection, found boolean +// Error: 2-6 expected callable or collection, found boolean {true()} --- #let x = "x" -// Error: 1-3 expected function or collection, found string +// Error: 1-3 expected callable or collection, found string #x() --- #let f(x) = x -// Error: 1-6 expected function or collection, found integer +// Error: 1-6 expected callable or collection, found integer #f(1)(2) --- #let f(x) = x -// Error: 1-6 expected function or collection, found template +// Error: 1-6 expected callable or collection, found template #f[1](2) --- diff --git a/tests/typ/code/include.typ b/tests/typ/code/include.typ index 83e00384..1e5d5827 100644 --- a/tests/typ/code/include.typ +++ b/tests/typ/code/include.typ @@ -1,7 +1,7 @@ // Test include statements. --- -#page(width: 200pt) +#set page(width: 200pt) = Document diff --git a/tests/typ/code/spread.typ b/tests/typ/code/spread.typ index 41e790a4..5f7d2061 100644 --- a/tests/typ/code/spread.typ +++ b/tests/typ/code/spread.typ @@ -4,14 +4,14 @@ --- // Test standard argument overriding. { - let font(style: "normal", weight: "regular") = { + let f(style: "normal", weight: "regular") = { "(style: " + style + ", weight: " + weight + ")" } - let myfont(..args) = font(weight: "bold", ..args) - test(myfont(), "(style: normal, weight: bold)") - test(myfont(weight: "black"), "(style: normal, weight: black)") - test(myfont(style: "italic"), "(style: italic, weight: bold)") + let myf(..args) = f(weight: "bold", ..args) + test(myf(), "(style: normal, weight: bold)") + test(myf(weight: "black"), "(style: normal, weight: black)") + test(myf(style: "italic"), "(style: italic, weight: bold)") } --- diff --git a/tests/typ/coma.typ b/tests/typ/coma.typ index 77baec45..ef9e9f86 100644 --- a/tests/typ/coma.typ +++ b/tests/typ/coma.typ @@ -1,4 +1,4 @@ -#page(width: 450pt, margins: 1cm) +#set page(width: 450pt, margins: 1cm) *Technische Universität Berlin* #h(1fr) *WiSe 2019/2020* \ *Fakultät II, Institut for Mathematik* #h(1fr) Woche 3 \ diff --git a/tests/typ/elements/circle.typ b/tests/typ/elements/circle.typ index ad9d3a4e..8b795830 100644 --- a/tests/typ/elements/circle.typ +++ b/tests/typ/elements/circle.typ @@ -23,7 +23,7 @@ Center-aligned rect in auto-sized circle. Rect in auto-sized circle. \ #circle(fill: forest, rect(fill: conifer, stroke: white, padding: 4pt)[ - #font(8pt) + #set text(8pt) But, soft! what light through yonder window breaks? ] ) @@ -38,7 +38,7 @@ Expanded by height. --- // Test relative sizing. #let centered(body) = align(center + horizon, body) -#font(fill: white) +#set text(fill: white) #rect(width: 100pt, height: 50pt, fill: rgb("aaa"), centered[ #circle(radius: 10pt, fill: eastern, centered[A]) // D=20pt #circle(height: 60%, fill: eastern, centered[B]) // D=30pt diff --git a/tests/typ/elements/ellipse.typ b/tests/typ/elements/ellipse.typ index 39c73a6f..154144c4 100644 --- a/tests/typ/elements/ellipse.typ +++ b/tests/typ/elements/ellipse.typ @@ -18,6 +18,6 @@ Rect in ellipse in fixed rect. \ Auto-sized ellipse. \ #ellipse(fill: conifer, stroke: forest, thickness: 3pt, padding: 3pt)[ - #font(8pt) + #set text(8pt) But, soft! what light through yonder window breaks? ] diff --git a/tests/typ/elements/image.typ b/tests/typ/elements/image.typ index 7fddb12d..8817713f 100644 --- a/tests/typ/elements/image.typ +++ b/tests/typ/elements/image.typ @@ -7,7 +7,7 @@ #image("../../res/rhino.png") // Load an RGB JPEG image. -#page(height: 60pt) +#set page(height: 60pt) #image("../../res/tiger.jpg") --- @@ -25,7 +25,7 @@ --- // Test all three fit modes. -#page(height: 50pt, margins: 0pt) +#set page(height: 50pt, margins: 0pt) #grid( columns: (1fr, 1fr, 1fr), rows: 100%, @@ -37,7 +37,7 @@ --- // Does not fit to remaining height of page. -#page(height: 60pt) +#set page(height: 60pt) Stuff \ Stuff #image("../../res/rhino.png") diff --git a/tests/typ/elements/rect.typ b/tests/typ/elements/rect.typ index b3d4d286..add39b80 100644 --- a/tests/typ/elements/rect.typ +++ b/tests/typ/elements/rect.typ @@ -5,7 +5,7 @@ #rect() --- -#page(width: 150pt) +#set page(width: 150pt) // Fit to text. #rect(fill: conifer, padding: 3pt)[Textbox] diff --git a/tests/typ/elements/square.typ b/tests/typ/elements/square.typ index f09fc3e0..c4ece778 100644 --- a/tests/typ/elements/square.typ +++ b/tests/typ/elements/square.typ @@ -8,7 +8,7 @@ --- // Test auto-sized square. #square(fill: eastern, padding: 5pt)[ - #font(fill: white, weight: "bold") + #set text(fill: white, weight: "bold") Typst ] @@ -21,14 +21,14 @@ --- // Test text overflowing height. -#page(width: 75pt, height: 100pt) +#set page(width: 75pt, height: 100pt) #square(fill: conifer)[ But, soft! what light through yonder window breaks? ] --- // Test that square does not overflow page. -#page(width: 100pt, height: 75pt) +#set page(width: 100pt, height: 75pt) #square(fill: conifer)[ But, soft! what light through yonder window breaks? ] diff --git a/tests/typ/layout/align.typ b/tests/typ/layout/align.typ index 13b88ac1..09c4dee5 100644 --- a/tests/typ/layout/align.typ +++ b/tests/typ/layout/align.typ @@ -1,7 +1,7 @@ // Test alignment. --- -#page(height: 100pt) +#set page(height: 100pt) #stack(dir: ltr, align(left, square(size: 15pt, fill: eastern)), align(center, square(size: 20pt, fill: eastern)), diff --git a/tests/typ/layout/aspect.typ b/tests/typ/layout/aspect.typ index c9b8ee41..2c3e9b0c 100644 --- a/tests/typ/layout/aspect.typ +++ b/tests/typ/layout/aspect.typ @@ -3,14 +3,14 @@ --- // Test relative width and height and size that is smaller // than default size. -#page(width: 120pt, height: 70pt) +#set page(width: 120pt, height: 70pt) #square(width: 50%, align(bottom)[A]) #square(height: 50%) #box(stack(square(size: 10pt), 5pt, square(size: 10pt, [B]))) --- // Test alignment in automatically sized square and circle. -#font(8pt) +#set text(8pt) #square(padding: 4pt)[ Hey there, #align(center + bottom, rotate(180deg, [you!])) ] @@ -23,19 +23,19 @@ --- // Test square that is limited by region size. -#page(width: 20pt, height: 10pt, margins: 0pt) +#set page(width: 20pt, height: 10pt, margins: 0pt) #stack(dir: ltr, square(fill: forest), square(fill: conifer)) --- // Test different ways of sizing. -#page(width: 120pt, height: 40pt) +#set page(width: 120pt, height: 40pt) #circle(radius: 5pt) #circle(width: 10%) #circle(height: 50%) --- // Test square that is overflowing due to its aspect ratio. -#page(width: 40pt, height: 20pt, margins: 5pt) +#set page(width: 40pt, height: 20pt, margins: 5pt) #square(width: 100%) #square(width: 100%)[Hello] diff --git a/tests/typ/layout/background.typ b/tests/typ/layout/background.typ index f55262b2..f64bf0ee 100644 --- a/tests/typ/layout/background.typ +++ b/tests/typ/layout/background.typ @@ -1,8 +1,8 @@ // Test placing a background image on a page. --- -#page(paper: "a10", flipped: true) -#font(fill: white) +#set page(paper: "a10", flipped: true) +#set text(fill: white) #place( dx: -10pt, dy: -10pt, diff --git a/tests/typ/layout/containers.typ b/tests/typ/layout/containers.typ index d4556a0f..14258c1e 100644 --- a/tests/typ/layout/containers.typ +++ b/tests/typ/layout/containers.typ @@ -12,7 +12,7 @@ Apart --- // Test block over multiple pages. -#page(height: 60pt) +#set page(height: 60pt) First! #block[ diff --git a/tests/typ/layout/grid-1.typ b/tests/typ/layout/grid-1.typ index 647e366f..a6596c98 100644 --- a/tests/typ/layout/grid-1.typ +++ b/tests/typ/layout/grid-1.typ @@ -3,7 +3,7 @@ --- #let cell(width, color) = rect(width: width, height: 2cm, fill: color) -#page(width: 100pt, height: 140pt) +#set page(width: 100pt, height: 140pt) #grid( columns: (auto, 1fr, 3fr, 0.25cm, 3%, 2mm + 10%), cell(0.5cm, rgb("2a631a")), @@ -31,7 +31,7 @@ ) --- -#page(height: 3cm, margins: 0pt) +#set page(height: 3cm, margins: 0pt) #grid( columns: (1fr,), rows: (1fr, auto, 2fr), diff --git a/tests/typ/layout/grid-2.typ b/tests/typ/layout/grid-2.typ index c522a402..7df64a56 100644 --- a/tests/typ/layout/grid-2.typ +++ b/tests/typ/layout/grid-2.typ @@ -1,7 +1,7 @@ // Test using the `grid` function to create a finance table. --- -#page(width: 12cm, height: 2.5cm) +#set page(width: 12cm, height: 2.5cm) #grid( columns: 5, column-gutter: (2fr, 1fr, 1fr), diff --git a/tests/typ/layout/grid-3.typ b/tests/typ/layout/grid-3.typ index 6f7f6977..8d04722e 100644 --- a/tests/typ/layout/grid-3.typ +++ b/tests/typ/layout/grid-3.typ @@ -1,7 +1,7 @@ // Test grid cells that overflow to the next region. --- -#page(width: 5cm, height: 3cm) +#set page(width: 5cm, height: 3cm) #grid( columns: 2, row-gutter: 8pt, @@ -18,7 +18,7 @@ --- // Test a column that starts overflowing right after another row/column did // that. -#page(width: 5cm, height: 2cm) +#set page(width: 5cm, height: 2cm) #grid( columns: 4 * (1fr,), row-gutter: 10pt, @@ -32,7 +32,7 @@ --- // Test two columns in the same row overflowing by a different amount. -#page(width: 5cm, height: 2cm) +#set page(width: 5cm, height: 2cm) #grid( columns: 3 * (1fr,), row-gutter: 8pt, @@ -48,7 +48,7 @@ --- // Test grid within a grid, overflowing. -#page(width: 5cm, height: 2.25cm) +#set page(width: 5cm, height: 2.25cm) #grid( columns: 4 * (1fr,), row-gutter: 10pt, @@ -62,7 +62,7 @@ --- // Test partition of `fr` units before and after multi-region layout. -#page(width: 5cm, height: 4cm) +#set page(width: 5cm, height: 4cm) #grid( columns: 2 * (1fr,), rows: (1fr, 2fr, auto, 1fr, 1cm), diff --git a/tests/typ/layout/grid-4.typ b/tests/typ/layout/grid-4.typ index d6aa6358..e02d98c5 100644 --- a/tests/typ/layout/grid-4.typ +++ b/tests/typ/layout/grid-4.typ @@ -23,7 +23,7 @@ --- // Test that all three kinds of rows use the correct bases. -#page(height: 4cm, margins: 0cm) +#set page(height: 4cm, margins: 0cm) #grid( rows: (1cm, 1fr, 1fr, auto), rect(height: 50%, width: 100%, fill: conifer), diff --git a/tests/typ/layout/grid-5.typ b/tests/typ/layout/grid-5.typ index 585fc6ce..db7c525a 100644 --- a/tests/typ/layout/grid-5.typ +++ b/tests/typ/layout/grid-5.typ @@ -1,7 +1,7 @@ --- // Test that trailing linebreak doesn't overflow the region. -#page(height: 2cm) +#set page(height: 2cm) #grid[ Hello \ Hello \ @@ -12,7 +12,7 @@ --- // Test that broken cell expands vertically. -#page(height: 2.25cm) +#set page(height: 2.25cm) #grid( columns: 2, gutter: 10pt, diff --git a/tests/typ/layout/pad.typ b/tests/typ/layout/pad.typ index 1fa0f79d..502137ec 100644 --- a/tests/typ/layout/pad.typ +++ b/tests/typ/layout/pad.typ @@ -19,7 +19,7 @@ Hi #box(pad(left: 10pt)[A]) there --- // Test that the pad node doesn't consume the whole region. -#page(height: 6cm) +#set page(height: 6cm) #align(left)[Before] #pad(10pt, image("../../res/tiger.jpg")) #align(right)[After] diff --git a/tests/typ/layout/page.typ b/tests/typ/layout/page.typ index 3dad26c8..35f338f4 100644 --- a/tests/typ/layout/page.typ +++ b/tests/typ/layout/page.typ @@ -2,33 +2,33 @@ --- // Set width and height. -#page(width: 80pt, height: 80pt) -[#page(width: 40pt) High] -[#page(height: 40pt) Wide] +#set page(width: 80pt, height: 80pt) +[#set page(width: 40pt);High] +[#set page(height: 40pt);Wide] // Set all margins at once. [ - #page(margins: 5pt) + #set page(margins: 5pt) #place(top + left)[TL] #place(bottom + right)[BR] ] // Set individual margins. -#page(height: 40pt) -[#page(left: 0pt) #align(left)[Left]] -[#page(right: 0pt) #align(right)[Right]] -[#page(top: 0pt) #align(top)[Top]] -[#page(bottom: 0pt) #align(bottom)[Bottom]] +#set page(height: 40pt) +[#set page(left: 0pt); #align(left)[Left]] +[#set page(right: 0pt); #align(right)[Right]] +[#set page(top: 0pt); #align(top)[Top]] +[#set page(bottom: 0pt); #align(bottom)[Bottom]] // Ensure that specific margins override general margins. -[#page(margins: 0pt, left: 20pt) Overriden] +[#set page(margins: 0pt, left: 20pt); Overriden] // Flipped predefined paper. -[#page(paper: "a11", flipped: true) Flipped A11] +[#set page(paper: "a11", flipped: true);Flipped A11] --- -#page(width: 80pt, height: 40pt, fill: eastern) -#font(15pt, "Roboto", fill: white, smallcaps: true)[Typst] +#set page(width: 80pt, height: 40pt, fill: eastern) +#text(15pt, "Roboto", fill: white, smallcaps: true)[Typst] -#page(width: 40pt, fill: none, margins: auto, top: 10pt) +#set page(width: 40pt, fill: none, margins: auto, top: 10pt) Hi diff --git a/tests/typ/layout/pagebreak.typ b/tests/typ/layout/pagebreak.typ index e0ffc92e..9a74d2bf 100644 --- a/tests/typ/layout/pagebreak.typ +++ b/tests/typ/layout/pagebreak.typ @@ -3,7 +3,7 @@ --- First of two #pagebreak() -#page(height: 40pt) +#set page(height: 40pt) Second of two --- @@ -12,7 +12,7 @@ A #box[ B #pagebreak() - #page("a4") + #set page("a4") ] C @@ -23,13 +23,13 @@ D --- // Test a combination of pages with bodies and normal content. -#page(width: 80pt, height: 30pt) +#set page(width: 80pt, height: 30pt) -Fi[#page(width: 80pt)rst] -[#page(width: 70pt) Second] +Fi[#set page(width: 80pt);rst] +[#set page(width: 70pt); Second] #pagebreak() #pagebreak() Fourth #page(height: 20pt)[] Sixth -[#page() Seventh] +[#set page(); Seventh] diff --git a/tests/typ/layout/placed.typ b/tests/typ/layout/placed.typ index 017cdc20..527e0559 100644 --- a/tests/typ/layout/placed.typ +++ b/tests/typ/layout/placed.typ @@ -1,7 +1,7 @@ // Test the `place` function. --- -#page("a8") +#set page("a8") #place(bottom + center)[© Typst] = Placement @@ -26,7 +26,7 @@ the line breaks still had to be inserted manually. --- // Test how the placed node interacts with paragraph spacing around it. -#page("a8", height: 60pt) +#set page("a8", height: 60pt) First diff --git a/tests/typ/layout/spacing.typ b/tests/typ/layout/spacing.typ index 78b778c5..98a6100c 100644 --- a/tests/typ/layout/spacing.typ +++ b/tests/typ/layout/spacing.typ @@ -20,8 +20,8 @@ Add #h(10pt) #h(10pt) up --- // Test that spacing has style properties. -A[#par(align: right)#h(1cm)]B -[#page(height: 20pt)#v(1cm)] +A[#set par(align: right);#h(1cm)]B +[#set page(height: 20pt);#v(1cm)] B --- diff --git a/tests/typ/layout/stack-1.typ b/tests/typ/layout/stack-1.typ index 02083d2b..a4a0d6b8 100644 --- a/tests/typ/layout/stack-1.typ +++ b/tests/typ/layout/stack-1.typ @@ -15,13 +15,13 @@ #let items = for w in widths { (align(right, shaded(w)),) } -#page(width: 50pt, margins: 0pt) +#set page(width: 50pt, margins: 0pt) #stack(dir: btt, ..items) --- // Test RTL alignment. -#page(width: 50pt, margins: 5pt) -#font(8pt) +#set page(width: 50pt, margins: 5pt) +#set text(8pt) #stack(dir: rtl, align(center, [A]), align(left, [B]), @@ -30,8 +30,8 @@ --- // Test spacing. -#page(width: 50pt, margins: 0pt) -#par(spacing: 5pt) +#set page(width: 50pt, margins: 0pt) +#set par(spacing: 5pt) #let x = square(size: 10pt, fill: eastern) #stack(dir: rtl, spacing: 5pt, x, x, x) @@ -40,7 +40,7 @@ --- // Test overflow. -#page(width: 50pt, height: 30pt, margins: 0pt) +#set page(width: 50pt, height: 30pt, margins: 0pt) #box(stack( rect(width: 40pt, height: 20pt, fill: conifer), rect(width: 30pt, height: 13pt, fill: forest), diff --git a/tests/typ/layout/stack-2.typ b/tests/typ/layout/stack-2.typ index 2167f48f..f88f7a58 100644 --- a/tests/typ/layout/stack-2.typ +++ b/tests/typ/layout/stack-2.typ @@ -1,7 +1,7 @@ // Test fr units in stacks. --- -#page(height: 3.5cm) +#set page(height: 3.5cm) #stack( dir: ltr, spacing: 1fr, @@ -15,8 +15,8 @@ from #h(1fr) the #h(1fr) wonderful World! 🌍 --- -#page(height: 2cm) -#font(white) +#set page(height: 2cm) +#set text(white) #rect(fill: forest)[ #v(1fr) #h(1fr) Hi you! #h(5pt) diff --git a/tests/typ/layout/transform.typ b/tests/typ/layout/transform.typ index 5b1fa2a1..82ee1390 100644 --- a/tests/typ/layout/transform.typ +++ b/tests/typ/layout/transform.typ @@ -23,13 +23,13 @@ [X] } -#font("Latin Modern Math", size) +#set text("Latin Modern Math", size) Neither #tex, \ nor #xetex! --- // Test combination of scaling and rotation. -#page(height: 80pt) +#set page(height: 80pt) #align(center + horizon, rotate(20deg, scale(70%, image("../../res/tiger.jpg"))) ) @@ -43,7 +43,7 @@ nor #xetex! --- // Test setting scaling origin. #let r = rect(width: 100pt, height: 10pt, fill: forest) -#page(height: 65pt) +#set page(height: 65pt) #scale(r, x: 50%, y: 200%, origin: left + top) #scale(r, x: 50%, origin: center) #scale(r, x: 50%, y: 200%, origin: right + bottom) diff --git a/tests/typ/text/baseline.typ b/tests/typ/text/baseline.typ index f22fce08..7100ab52 100644 --- a/tests/typ/text/baseline.typ +++ b/tests/typ/text/baseline.typ @@ -1,4 +1,4 @@ // Test text baseline. --- -Hi #font(150%)[You], #font(75%)[how are you?] +Hi #text(150%)[You], #text(75%)[how are you?] diff --git a/tests/typ/text/basic.typ b/tests/typ/text/basic.typ index 0c6c8896..a074a046 100644 --- a/tests/typ/text/basic.typ +++ b/tests/typ/text/basic.typ @@ -1,7 +1,7 @@ // Test simple text. --- -#page(width: 250pt, height: 120pt) +#set page(width: 250pt, height: 120pt) But, soft! what light through yonder window breaks? It is the east, and Juliet is the sun. Arise, fair sun, and kill the envious moon, Who is already sick and diff --git a/tests/typ/text/bidi.typ b/tests/typ/text/bidi.typ index 7d33aeea..658c7fa6 100644 --- a/tests/typ/text/bidi.typ +++ b/tests/typ/text/bidi.typ @@ -2,55 +2,55 @@ --- // Test reordering with different top-level paragraph directions. -#let text = [Text טֶקסט] -#font(serif, "Noto Serif Hebrew") -#par(lang: "he") {text} -#par(lang: "de") {text} +#let content = [Text טֶקסט] +#set text(serif, "Noto Serif Hebrew") +#par(lang: "he", content) +#par(lang: "de", content) --- // Test that consecutive, embedded LTR runs stay LTR. // Here, we have two runs: "A" and italic "B". -#let text = [أنت A_B_مطرC] -#font(serif, "Noto Sans Arabic") -#par(lang: "ar") {text} -#par(lang: "de") {text} +#let content = [أنت A_B_مطرC] +#set text(serif, "Noto Sans Arabic") +#par(lang: "ar", content) +#par(lang: "de", content) --- // Test that consecutive, embedded RTL runs stay RTL. // Here, we have three runs: "גֶ", bold "שֶׁ", and "ם". -#let text = [Aגֶ*שֶׁ*םB] -#font(serif, "Noto Serif Hebrew") -#par(lang: "he") {text} -#par(lang: "de") {text} +#let content = [Aגֶ*שֶׁ*םB] +#set text(serif, "Noto Serif Hebrew") +#par(lang: "he", content) +#par(lang: "de", content) --- // Test embedding up to level 4 with isolates. -#font(serif, "Noto Serif Hebrew", "Twitter Color Emoji") -#par(dir: rtl) +#set text(serif, "Noto Serif Hebrew", "Twitter Color Emoji") +#set par(dir: rtl) א\u{2066}A\u{2067}Bב\u{2069}? --- // Test hard line break (leads to two paragraphs in unicode-bidi). -#font("Noto Sans Arabic", serif) -#par(lang: "ar") +#set text("Noto Sans Arabic", serif) +#set par(lang: "ar") Life المطر هو الحياة \ الحياة تمطر is rain. --- // Test spacing. -#font(serif, "Noto Serif Hebrew") +#set text(serif, "Noto Serif Hebrew") L #h(1cm) ריווחR \ Lריווח #h(1cm) R --- // Test inline object. -#font("Noto Serif Hebrew", serif) -#par(lang: "he") +#set text("Noto Serif Hebrew", serif) +#set par(lang: "he") קרנפיםRh#image("../../res/rhino.png", height: 11pt)inoחיים --- // Test setting a vertical direction. // Ref: false -// Error: 11-14 must be horizontal -#par(dir: ttb) +// Error: 15-18 must be horizontal +#set par(dir: ttb) diff --git a/tests/typ/text/chinese.typ b/tests/typ/text/chinese.typ index 0800a220..04fa7027 100644 --- a/tests/typ/text/chinese.typ +++ b/tests/typ/text/chinese.typ @@ -1,7 +1,7 @@ // Test chinese text from Wikipedia. --- -#font("Noto Serif CJK SC") +#set text("Noto Serif CJK SC") 是美国广播公司电视剧《迷失》第3季的第22和23集,也是全剧的第71集和72集 由执行制作人戴蒙·林道夫和卡尔顿·库斯编剧,导演则是另一名执行制作人杰克·本德 diff --git a/tests/typ/text/decorations.typ b/tests/typ/text/decorations.typ index 9cd7b096..14dfe821 100644 --- a/tests/typ/text/decorations.typ +++ b/tests/typ/text/decorations.typ @@ -13,7 +13,7 @@ #underline(red)[Critical information is conveyed here.] // Inherits font color. -#font(fill: red, underline[Change with the wind.]) +#text(fill: red, underline[Change with the wind.]) // Both over- and underline. #overline(underline[Running amongst the wolves.]) diff --git a/tests/typ/text/features.typ b/tests/typ/text/features.typ index d60583d8..3f61873e 100644 --- a/tests/typ/text/features.typ +++ b/tests/typ/text/features.typ @@ -2,72 +2,73 @@ --- // Test turning kerning off. -#font(kerning: true)[Tq] \ -#font(kerning: false)[Tq] +#text(kerning: true)[Tq] \ +#text(kerning: false)[Tq] --- // Test smallcaps. -#font("Roboto") -#font(smallcaps: true)[Smallcaps] +#set text("Roboto") +#text(smallcaps: true)[Smallcaps] --- // Test alternates and stylistic sets. -#font("IBM Plex Serif") -a vs #font(alternates: true)[a] \ -ß vs #font(stylistic-set: 5)[ß] +#set text("IBM Plex Serif") +a vs #text(alternates: true)[a] \ +ß vs #text(stylistic-set: 5)[ß] --- // Test ligatures. -fi vs. #font(ligatures: false)[No fi] \ +fi vs. #text(ligatures: false)[No fi] \ --- // Test number type. -#font("Roboto") -#font(number-type: "old-style") 0123456789 \ -#font(number-type: auto)[0123456789] +#set text("Roboto") +#set text(number-type: "old-style") +0123456789 \ +#text(number-type: auto)[0123456789] --- // Test number width. -#font("Roboto") -#font(number-width: "proportional")[0123456789] \ -#font(number-width: "tabular")[3456789123] \ -#font(number-width: "tabular")[0123456789] +#set text("Roboto") +#text(number-width: "proportional")[0123456789] \ +#text(number-width: "tabular")[3456789123] \ +#text(number-width: "tabular")[0123456789] --- // Test number position. -#font("IBM Plex Sans") -#font(number-position: "normal")[C2H4] \ -#font(number-position: "subscript")[C2H4] \ -#font(number-position: "superscript")[C2H4] +#set text("IBM Plex Sans") +#text(number-position: "normal")[C2H4] \ +#text(number-position: "subscript")[C2H4] \ +#text(number-position: "superscript")[C2H4] --- // Test extra number stuff. -#font("IBM Plex Sans") -0 vs. #font(slashed-zero: true)[0] \ -1/2 vs. #font(fractions: true)[1/2] +#set text("IBM Plex Sans") +0 vs. #text(slashed-zero: true)[0] \ +1/2 vs. #text(fractions: true)[1/2] --- // Test raw features. -#font("Roboto") -#font(features: ("smcp",))[Smcp] \ -fi vs. #font(features: (liga: 0))[No fi] +#set text("Roboto") +#text(features: ("smcp",))[Smcp] \ +fi vs. #text(features: (liga: 0))[No fi] --- -// Error: 22-27 expected integer or none, found boolean -#font(stylistic-set: false) +// Error: 26-31 expected integer or none, found boolean +#set text(stylistic-set: false) --- -// Error: 22-24 must be between 1 and 20 -#font(stylistic-set: 25) +// Error: 26-28 must be between 1 and 20 +#set text(stylistic-set: 25) --- -// Error: 20-21 expected string or auto, found integer -#font(number-type: 2) +// Error: 24-25 expected string or auto, found integer +#set text(number-type: 2) --- -// Error: 20-31 expected "lining" or "old-style" -#font(number-type: "different") +// Error: 24-35 expected "lining" or "old-style" +#set text(number-type: "different") --- -// Error: 17-22 expected array of strings or dictionary mapping tags to integers, found boolean -#font(features: false) +// Error: 21-26 expected array of strings or dictionary mapping tags to integers, found boolean +#set text(features: false) diff --git a/tests/typ/text/font.typ b/tests/typ/text/font.typ index 5c97d367..71ac8d3d 100644 --- a/tests/typ/text/font.typ +++ b/tests/typ/text/font.typ @@ -2,57 +2,57 @@ --- // Set same font size in three different ways. -#font(20pt)[A] -#font(200%)[A] -#font(size: 15pt + 50%)[A] +#text(20pt)[A] +#text(200%)[A] +#text(size: 15pt + 50%)[A] // Do nothing. -#font()[Normal] +#text()[Normal] // Set style (is available). -#font(style: "italic")[Italic] +#text(style: "italic")[Italic] // Set weight (is available). -#font(weight: "bold")[Bold] +#text(weight: "bold")[Bold] // Set stretch (not available, matching closest). -#font(stretch: 50%)[Condensed] +#text(stretch: 50%)[Condensed] // Set family. -#font(family: serif)[Serif] +#text(family: serif)[Serif] // Emoji. Emoji: 🐪, 🌋, 🏞 // Math. -#font("Latin Modern Math")[∫ 𝛼 + 3𝛽 d𝑡] +#text("Latin Modern Math")[∫ 𝛼 + 3𝛽 d𝑡] // Colors. [ - #font(fill: eastern) - This is #font(rgb("FA644B"))[way more] colorful. + #set text(fill: eastern) + This is #text(rgb("FA644B"))[way more] colorful. ] // Disable font fallback beyond the user-specified list. // Without disabling, Latin Modern Math would come to the rescue. -#font("PT Sans", "Twitter Color Emoji", fallback: false) +#set text("PT Sans", "Twitter Color Emoji", fallback: false) 2π = 𝛼 + 𝛽. ✅ --- // Test class definitions. -#font(sans-serif: "PT Sans") -#font(family: sans-serif)[Sans-serif.] \ -#font(monospace)[Monospace.] \ -#font(monospace, monospace: ("Nope", "Latin Modern Math"))[Math.] +#set text(sans-serif: "PT Sans") +#text(family: sans-serif)[Sans-serif.] \ +#text(monospace)[Monospace.] \ +#text(monospace, monospace: ("Nope", "Latin Modern Math"))[Math.] --- // Test top and bottom edge. -#page(width: 150pt) -#font(size: 8pt) +#set page(width: 150pt) +#set text(size: 8pt) #let try(top, bottom) = rect(fill: conifer)[ - #font(monospace, top-edge: top, bottom-edge: bottom) + #set text(monospace, top-edge: top, bottom-edge: bottom) From #top to #bottom ] @@ -64,33 +64,33 @@ Emoji: 🐪, 🌋, 🏞 #try(1pt + 27%, -18%) --- -// Error: 7-12 unexpected argument -#font(false) +// Error: 11-16 unexpected argument +#set text(false) --- -// Error: 14-20 expected "normal", "italic" or "oblique" -#font(style: "bold", weight: "thin") +// Error: 18-24 expected "normal", "italic" or "oblique" +#set text(style: "bold", weight: "thin") --- -// Error: 17-19 expected linear or string, found array -#font(top-edge: ()) +// Error: 21-23 expected linear or string, found array +#set text(top-edge: ()) --- -// Error: 17-19 unknown font metric -#font(top-edge: "") +// Error: 21-23 unknown font metric +#set text(top-edge: "") --- -// Error: 14-15 expected string or array of strings, found integer -#font(serif: 0) +// Error: 18-19 expected string or array of strings, found integer +#set text(serif: 0) --- -// Error: 19-23 unexpected argument -#font(size: 10pt, 12pt) +// Error: 23-27 unexpected argument +#set text(size: 10pt, 12pt) --- -// Error: 28-35 unexpected argument -#font(family: "Helvetica", "Arial") +// Error: 32-39 unexpected argument +#set text(family: "Helvetica", "Arial") --- -// Error: 7-27 unexpected argument -#font(something: "invalid") +// Error: 11-31 unexpected argument +#set text(something: "invalid") diff --git a/tests/typ/text/links.typ b/tests/typ/text/links.typ index 1a77e34b..f2da8b9c 100644 --- a/tests/typ/text/links.typ +++ b/tests/typ/text/links.typ @@ -12,12 +12,12 @@ Contact #link("mailto:hi@typst.app") or call #link("tel:123") for more informati --- // Styled with underline and color. -#let link(url, body) = link(url, font(fill: rgb("283663"), underline(body))) +#let link(url, body) = link(url, text(fill: rgb("283663"), underline(body))) You could also make the #link("https://html5zombo.com/")[link look way more typical.] --- // Transformed link. -#page(height: 60pt) +#set page(height: 60pt) #let link = link("https://typst.app/")[LINK] My cool #move(x: 0.7cm, y: 0.7cm, rotate(10deg, scale(200%, link))) diff --git a/tests/typ/text/par.typ b/tests/typ/text/par.typ index 9c14279c..771a6218 100644 --- a/tests/typ/text/par.typ +++ b/tests/typ/text/par.typ @@ -2,37 +2,37 @@ --- // Test ragged-left. -#par(align: right) +#set par(align: right) To the right! Where the sunlight peeks behind the mountain. --- // Test that explicit paragraph break respects active styles. -#par(spacing: 7pt) -[#par(spacing: 100pt) First] +#set par(spacing: 7pt) +[#set par(spacing: 100pt);First] -[#par(spacing: 100pt) Second] -#par(spacing: 20pt) +[#set par(spacing: 100pt);Second] +#set par(spacing: 20pt) Third --- // Test that paragraph break due to incompatibility respects // spacing defined by the two adjacent paragraphs. -#let a = [#par(spacing: 40pt) Hello] -#let b = [#par(spacing: 60pt) World] +#let a = [#set par(spacing: 40pt);Hello] +#let b = [#set par(spacing: 60pt);World] {a}{b} --- // Test weird metrics. -#par(spacing: 100%, leading: 0pt) +#set par(spacing: 100%, leading: 0pt) But, soft! what light through yonder window breaks? It is the east, and Juliet is the sun. --- -// Error: 13-16 must be horizontal -#par(align: top) +// Error: 17-20 must be horizontal +#set par(align: top) --- -// Error: 13-29 expected alignment, found 2d alignment -#par(align: horizon + center) +// Error: 17-33 expected alignment, found 2d alignment +#set par(align: horizon + center) diff --git a/tests/typ/text/shaping.typ b/tests/typ/text/shaping.typ index 1a8a7933..bb8f4ce9 100644 --- a/tests/typ/text/shaping.typ +++ b/tests/typ/text/shaping.typ @@ -7,11 +7,11 @@ Le fira // This should just shape nicely. -#font("Noto Sans Arabic") +#set text("Noto Sans Arabic") دع النص يمطر عليك // This should form a three-member family. -#font("Twitter Color Emoji") +#set text("Twitter Color Emoji") 👩👩👦 🤚🏿 // These two shouldn't be affected by a zero-width joiner. @@ -20,7 +20,7 @@ Le fira --- // Test font fallback. -#font(sans-serif, "Noto Sans Arabic", "Twitter Color Emoji") +#set text(sans-serif, "Noto Sans Arabic", "Twitter Color Emoji") // Font fallback for emoji. A😀B @@ -40,6 +40,6 @@ A🐈中文B --- // Test reshaping. -#font("Noto Serif Hebrew") -#par(lang: "he") +#set text("Noto Serif Hebrew") +#set par(lang: "he") ס \ טֶ diff --git a/tests/typ/text/tracking.typ b/tests/typ/text/tracking.typ index 695e6734..e3ff70ff 100644 --- a/tests/typ/text/tracking.typ +++ b/tests/typ/text/tracking.typ @@ -1,12 +1,12 @@ // Test tracking characters apart or together. --- -#font(tracking: -0.01) +#set text(tracking: -0.01) I saw Zoe yӛsterday, on the tram. --- -I'm in#font(tracking: 0.3)[ spaace]! +I'm in#text(tracking: 0.3)[ spaace]! --- -#font("Noto Serif Hebrew", tracking: 0.3) +#set text("Noto Serif Hebrew", tracking: 0.3) טֶקסט diff --git a/tests/typ/text/whitespace.typ b/tests/typ/text/whitespace.typ index e01b047c..4c3d4db5 100644 --- a/tests/typ/text/whitespace.typ +++ b/tests/typ/text/whitespace.typ @@ -30,11 +30,11 @@ A #for _ in (none,) {"B"}C --- // Test that a run consisting only of whitespace isn't trimmed. -A[#font(serif) ]B +A[#set text(serif); ]B --- // Test font change after space. -Left [#font(serif)Right]. +Left [#set text(serif);Right]. --- // Test that space at start of line is not trimmed. |
