Math module

This commit is contained in:
Laurenz 2023-01-23 15:03:10 +01:00
parent 84c6c8b0e6
commit 4653ffebb4
21 changed files with 406 additions and 316 deletions

View File

@ -11,144 +11,146 @@ pub mod text;
pub mod visualize; pub mod visualize;
use typst::geom::{Align, Color, Dir, GenAlign}; use typst::geom::{Align, Color, Dir, GenAlign};
use typst::model::{LangItems, Library, Node, NodeId, Scope, StyleMap}; use typst::model::{LangItems, Library, Module, Node, NodeId, Scope, StyleMap};
use self::layout::LayoutRoot; use self::layout::LayoutRoot;
/// Construct the standard library. /// Construct the standard library.
pub fn build() -> Library { pub fn build() -> Library {
Library { scope: scope(), styles: styles(), items: items() } let math = math::module();
let global = global(math.clone());
Library { global, math, styles: styles(), items: items() }
} }
/// Construct the standard scope. /// Construct the module with global definitions.
fn scope() -> Scope { fn global(math: Module) -> Module {
let mut std = Scope::new(); let mut global = Scope::deduplicating();
// Basics. // Basics.
std.def_func::<basics::HeadingNode>("heading"); global.def_func::<basics::HeadingNode>("heading");
std.def_func::<basics::ListNode>("list"); global.def_func::<basics::ListNode>("list");
std.def_func::<basics::EnumNode>("enum"); global.def_func::<basics::EnumNode>("enum");
std.def_func::<basics::TermsNode>("terms"); global.def_func::<basics::TermsNode>("terms");
std.def_func::<basics::TableNode>("table"); global.def_func::<basics::TableNode>("table");
// Text. // Text.
std.def_func::<text::TextNode>("text"); global.def_func::<text::TextNode>("text");
std.def_func::<text::LinebreakNode>("linebreak"); global.def_func::<text::LinebreakNode>("linebreak");
std.def_func::<text::SymbolNode>("symbol"); global.def_func::<text::SymbolNode>("symbol");
std.def_func::<text::SmartQuoteNode>("smartquote"); global.def_func::<text::SmartQuoteNode>("smartquote");
std.def_func::<text::StrongNode>("strong"); global.def_func::<text::StrongNode>("strong");
std.def_func::<text::EmphNode>("emph"); global.def_func::<text::EmphNode>("emph");
std.def_func::<text::LowerFunc>("lower"); global.def_func::<text::LowerFunc>("lower");
std.def_func::<text::UpperFunc>("upper"); global.def_func::<text::UpperFunc>("upper");
std.def_func::<text::SmallcapsFunc>("smallcaps"); global.def_func::<text::SmallcapsFunc>("smallcaps");
std.def_func::<text::SubNode>("sub"); global.def_func::<text::SubNode>("sub");
std.def_func::<text::SuperNode>("super"); global.def_func::<text::SuperNode>("super");
std.def_func::<text::UnderlineNode>("underline"); global.def_func::<text::UnderlineNode>("underline");
std.def_func::<text::StrikeNode>("strike"); global.def_func::<text::StrikeNode>("strike");
std.def_func::<text::OverlineNode>("overline"); global.def_func::<text::OverlineNode>("overline");
std.def_func::<text::RawNode>("raw"); global.def_func::<text::RawNode>("raw");
// Math. // Math.
math::define(&mut std); global.define("math", math);
// Layout. // Layout.
std.def_func::<layout::PageNode>("page"); global.def_func::<layout::PageNode>("page");
std.def_func::<layout::PagebreakNode>("pagebreak"); global.def_func::<layout::PagebreakNode>("pagebreak");
std.def_func::<layout::VNode>("v"); global.def_func::<layout::VNode>("v");
std.def_func::<layout::ParNode>("par"); global.def_func::<layout::ParNode>("par");
std.def_func::<layout::ParbreakNode>("parbreak"); global.def_func::<layout::ParbreakNode>("parbreak");
std.def_func::<layout::HNode>("h"); global.def_func::<layout::HNode>("h");
std.def_func::<layout::BoxNode>("box"); global.def_func::<layout::BoxNode>("box");
std.def_func::<layout::BlockNode>("block"); global.def_func::<layout::BlockNode>("block");
std.def_func::<layout::StackNode>("stack"); global.def_func::<layout::StackNode>("stack");
std.def_func::<layout::GridNode>("grid"); global.def_func::<layout::GridNode>("grid");
std.def_func::<layout::ColumnsNode>("columns"); global.def_func::<layout::ColumnsNode>("columns");
std.def_func::<layout::ColbreakNode>("colbreak"); global.def_func::<layout::ColbreakNode>("colbreak");
std.def_func::<layout::PlaceNode>("place"); global.def_func::<layout::PlaceNode>("place");
std.def_func::<layout::AlignNode>("align"); global.def_func::<layout::AlignNode>("align");
std.def_func::<layout::PadNode>("pad"); global.def_func::<layout::PadNode>("pad");
std.def_func::<layout::RepeatNode>("repeat"); global.def_func::<layout::RepeatNode>("repeat");
std.def_func::<layout::MoveNode>("move"); global.def_func::<layout::MoveNode>("move");
std.def_func::<layout::ScaleNode>("scale"); global.def_func::<layout::ScaleNode>("scale");
std.def_func::<layout::RotateNode>("rotate"); global.def_func::<layout::RotateNode>("rotate");
std.def_func::<layout::HideNode>("hide"); global.def_func::<layout::HideNode>("hide");
// Visualize. // Visualize.
std.def_func::<visualize::ImageNode>("image"); global.def_func::<visualize::ImageNode>("image");
std.def_func::<visualize::LineNode>("line"); global.def_func::<visualize::LineNode>("line");
std.def_func::<visualize::RectNode>("rect"); global.def_func::<visualize::RectNode>("rect");
std.def_func::<visualize::SquareNode>("square"); global.def_func::<visualize::SquareNode>("square");
std.def_func::<visualize::EllipseNode>("ellipse"); global.def_func::<visualize::EllipseNode>("ellipse");
std.def_func::<visualize::CircleNode>("circle"); global.def_func::<visualize::CircleNode>("circle");
// Meta. // Meta.
std.def_func::<meta::DocumentNode>("document"); global.def_func::<meta::DocumentNode>("document");
std.def_func::<meta::RefNode>("ref"); global.def_func::<meta::RefNode>("ref");
std.def_func::<meta::LinkNode>("link"); global.def_func::<meta::LinkNode>("link");
std.def_func::<meta::OutlineNode>("outline"); global.def_func::<meta::OutlineNode>("outline");
// Compute. // Compute.
std.def_func::<compute::TypeFunc>("type"); global.def_func::<compute::TypeFunc>("type");
std.def_func::<compute::ReprFunc>("repr"); global.def_func::<compute::ReprFunc>("repr");
std.def_func::<compute::AssertFunc>("assert"); global.def_func::<compute::AssertFunc>("assert");
std.def_func::<compute::EvalFunc>("eval"); global.def_func::<compute::EvalFunc>("eval");
std.def_func::<compute::IntFunc>("int"); global.def_func::<compute::IntFunc>("int");
std.def_func::<compute::FloatFunc>("float"); global.def_func::<compute::FloatFunc>("float");
std.def_func::<compute::LumaFunc>("luma"); global.def_func::<compute::LumaFunc>("luma");
std.def_func::<compute::RgbFunc>("rgb"); global.def_func::<compute::RgbFunc>("rgb");
std.def_func::<compute::CmykFunc>("cmyk"); global.def_func::<compute::CmykFunc>("cmyk");
std.def_func::<compute::StrFunc>("str"); global.def_func::<compute::StrFunc>("str");
std.def_func::<compute::LabelFunc>("label"); global.def_func::<compute::LabelFunc>("label");
std.def_func::<compute::RegexFunc>("regex"); global.def_func::<compute::RegexFunc>("regex");
std.def_func::<compute::RangeFunc>("range"); global.def_func::<compute::RangeFunc>("range");
std.def_func::<compute::AbsFunc>("abs"); global.def_func::<compute::AbsFunc>("abs");
std.def_func::<compute::MinFunc>("min"); global.def_func::<compute::MinFunc>("min");
std.def_func::<compute::MaxFunc>("max"); global.def_func::<compute::MaxFunc>("max");
std.def_func::<compute::EvenFunc>("even"); global.def_func::<compute::EvenFunc>("even");
std.def_func::<compute::OddFunc>("odd"); global.def_func::<compute::OddFunc>("odd");
std.def_func::<compute::ModFunc>("mod"); global.def_func::<compute::ModFunc>("mod");
std.def_func::<compute::ReadFunc>("read"); global.def_func::<compute::ReadFunc>("read");
std.def_func::<compute::CsvFunc>("csv"); global.def_func::<compute::CsvFunc>("csv");
std.def_func::<compute::JsonFunc>("json"); global.def_func::<compute::JsonFunc>("json");
std.def_func::<compute::XmlFunc>("xml"); global.def_func::<compute::XmlFunc>("xml");
std.def_func::<compute::LoremFunc>("lorem"); global.def_func::<compute::LoremFunc>("lorem");
std.def_func::<compute::NumberingFunc>("numbering"); global.def_func::<compute::NumberingFunc>("numbering");
// Colors. // Colors.
std.define("black", Color::BLACK); global.define("black", Color::BLACK);
std.define("gray", Color::GRAY); global.define("gray", Color::GRAY);
std.define("silver", Color::SILVER); global.define("silver", Color::SILVER);
std.define("white", Color::WHITE); global.define("white", Color::WHITE);
std.define("navy", Color::NAVY); global.define("navy", Color::NAVY);
std.define("blue", Color::BLUE); global.define("blue", Color::BLUE);
std.define("aqua", Color::AQUA); global.define("aqua", Color::AQUA);
std.define("teal", Color::TEAL); global.define("teal", Color::TEAL);
std.define("eastern", Color::EASTERN); global.define("eastern", Color::EASTERN);
std.define("purple", Color::PURPLE); global.define("purple", Color::PURPLE);
std.define("fuchsia", Color::FUCHSIA); global.define("fuchsia", Color::FUCHSIA);
std.define("maroon", Color::MAROON); global.define("maroon", Color::MAROON);
std.define("red", Color::RED); global.define("red", Color::RED);
std.define("orange", Color::ORANGE); global.define("orange", Color::ORANGE);
std.define("yellow", Color::YELLOW); global.define("yellow", Color::YELLOW);
std.define("olive", Color::OLIVE); global.define("olive", Color::OLIVE);
std.define("green", Color::GREEN); global.define("green", Color::GREEN);
std.define("lime", Color::LIME); global.define("lime", Color::LIME);
// Other constants. // Other constants.
std.define("ltr", Dir::LTR); global.define("ltr", Dir::LTR);
std.define("rtl", Dir::RTL); global.define("rtl", Dir::RTL);
std.define("ttb", Dir::TTB); global.define("ttb", Dir::TTB);
std.define("btt", Dir::BTT); global.define("btt", Dir::BTT);
std.define("start", GenAlign::Start); global.define("start", GenAlign::Start);
std.define("end", GenAlign::End); global.define("end", GenAlign::End);
std.define("left", GenAlign::Specific(Align::Left)); global.define("left", GenAlign::Specific(Align::Left));
std.define("center", GenAlign::Specific(Align::Center)); global.define("center", GenAlign::Specific(Align::Center));
std.define("right", GenAlign::Specific(Align::Right)); global.define("right", GenAlign::Specific(Align::Right));
std.define("top", GenAlign::Specific(Align::Top)); global.define("top", GenAlign::Specific(Align::Top));
std.define("horizon", GenAlign::Specific(Align::Horizon)); global.define("horizon", GenAlign::Specific(Align::Horizon));
std.define("bottom", GenAlign::Specific(Align::Bottom)); global.define("bottom", GenAlign::Specific(Align::Bottom));
std Module::new("global").with_scope(global)
} }
/// Construct the standard style map. /// Construct the standard style map.
@ -187,7 +189,7 @@ fn items() -> LangItems {
term_item: |term, description| { term_item: |term, description| {
layout::ListItem::Term(basics::TermItem { term, description }).pack() layout::ListItem::Term(basics::TermItem { term, description }).pack()
}, },
math: |body, block| math::MathNode { body, block }.pack(), math_formula: |body, block| math::FormulaNode { body, block }.pack(),
math_atom: |atom| math::AtomNode(atom).pack(), math_atom: |atom| math::AtomNode(atom).pack(),
math_delimited: |body| math::LrNode(body).pack(), math_delimited: |body| math::LrNode(body).pack(),
math_script: |base, sub, sup| math::ScriptNode { base, sub, sup }.pack(), math_script: |base, sub, sup| math::ScriptNode { base, sub, sup }.pack(),

View File

@ -133,7 +133,7 @@ fn attachment(ctx: &MathContext, id: GlyphId, italics_correction: Abs) -> Abs {
/// Extract a single character from content. /// Extract a single character from content.
fn extract(accent: &Content) -> Option<char> { fn extract(accent: &Content) -> Option<char> {
let atom = accent.to::<MathNode>()?.body.to::<AtomNode>()?; let atom = accent.to::<FormulaNode>()?.body.to::<AtomNode>()?;
let mut chars = atom.0.chars(); let mut chars = atom.0.chars();
let c = chars.next().filter(|_| chars.next().is_none())?; let c = chars.next().filter(|_| chars.next().is_none())?;
Some(combining(c)) Some(combining(c))

View File

@ -98,6 +98,7 @@ fn layout(
denom: &Content, denom: &Content,
binom: bool, binom: bool,
) -> SourceResult<()> { ) -> SourceResult<()> {
let short_fall = DELIM_SHORT_FALL.scaled(ctx);
let axis = scaled!(ctx, axis_height); let axis = scaled!(ctx, axis_height);
let thickness = scaled!(ctx, fraction_rule_thickness); let thickness = scaled!(ctx, fraction_rule_thickness);
let shift_up = scaled!( let shift_up = scaled!(
@ -149,9 +150,9 @@ fn layout(
frame.push_frame(denom_pos, denom); frame.push_frame(denom_pos, denom);
if binom { if binom {
ctx.push(GlyphFragment::new(ctx, '(')); ctx.push(GlyphFragment::new(ctx, '(').stretch_vertical(ctx, height, short_fall));
ctx.push(frame); ctx.push(frame);
ctx.push(GlyphFragment::new(ctx, ')')); ctx.push(GlyphFragment::new(ctx, ')').stretch_vertical(ctx, height, short_fall));
} else { } else {
frame.push( frame.push(
line_pos, line_pos,

View File

@ -1,7 +1,7 @@
use super::*; use super::*;
/// How much less high scaled delimiters can be than what they wrap. /// How much less high scaled delimiters can be than what they wrap.
const DELIM_SHORT_FALL: Em = Em::new(0.1); pub(super) const DELIM_SHORT_FALL: Em = Em::new(0.1);
/// # Left-Right /// # Left-Right
/// Scales delimiters. /// Scales delimiters.
@ -62,7 +62,7 @@ impl LayoutMath for LrNode {
} }
let MathFragment::Glyph(glyph) = *fragment else { continue }; let MathFragment::Glyph(glyph) = *fragment else { continue };
let short_fall = DELIM_SHORT_FALL.at(glyph.font_size); let short_fall = DELIM_SHORT_FALL.scaled(ctx);
*fragment = MathFragment::Variant( *fragment = MathFragment::Variant(
glyph.stretch_vertical(ctx, height, short_fall), glyph.stretch_vertical(ctx, height, short_fall),
); );
@ -76,3 +76,90 @@ impl LayoutMath for LrNode {
Ok(()) Ok(())
} }
} }
/// # Floor
/// Floor an expression.
///
/// ## Example
/// ```
/// $ floor(x/2) $
/// ```
///
/// ## Parameters
/// - body: Content (positional, required)
/// The expression to floor.
///
/// ## Category
/// math
#[func]
pub fn floor(args: &mut Args) -> SourceResult<Value> {
delimited(args, '⌊', '⌋')
}
/// # Ceil
/// Ceil an expression.
///
/// ## Example
/// ```
/// $ ceil(x/2) $
/// ```
///
/// ## Parameters
/// - body: Content (positional, required)
/// The expression to ceil.
///
/// ## Category
/// math
#[func]
pub fn ceil(args: &mut Args) -> SourceResult<Value> {
delimited(args, '⌈', '⌉')
}
/// # Abs
/// Take the absolute value of an expression.
///
/// ## Example
/// ```
/// $ abs(x/2) $
/// ```
///
/// ## Parameters
/// - body: Content (positional, required)
/// The expression to take the absolute value of.
///
/// ## Category
/// math
#[func]
pub fn abs(args: &mut Args) -> SourceResult<Value> {
delimited(args, '|', '|')
}
/// # Norm
/// Take the norm of an expression.
///
/// ## Example
/// ```
/// $ norm(x/2) $
/// ```
///
/// ## Parameters
/// - body: Content (positional, required)
/// The expression to take the norm of.
///
/// ## Category
/// math
#[func]
pub fn norm(args: &mut Args) -> SourceResult<Value> {
delimited(args, '‖', '‖')
}
fn delimited(args: &mut Args, left: char, right: char) -> SourceResult<Value> {
Ok(Value::Content(
LrNode(Content::sequence(vec![
AtomNode(left.into()).pack(),
args.expect::<Content>("body")?,
AtomNode(right.into()).pack(),
]))
.pack(),
))
}

View File

@ -161,6 +161,7 @@ fn layout(
) -> SourceResult<()> { ) -> SourceResult<()> {
let axis = scaled!(ctx, axis_height); let axis = scaled!(ctx, axis_height);
let gap = ROW_GAP.scaled(ctx); let gap = ROW_GAP.scaled(ctx);
let short_fall = DELIM_SHORT_FALL.scaled(ctx);
ctx.style(ctx.style.for_denominator()); ctx.style(ctx.style.for_denominator());
let mut rows = vec![]; let mut rows = vec![];
@ -169,17 +170,20 @@ fn layout(
} }
ctx.unstyle(); ctx.unstyle();
if let Some(left) = left {
ctx.push(GlyphFragment::new(ctx, left));
}
let mut frame = stack(ctx, rows, align, gap, 0); let mut frame = stack(ctx, rows, align, gap, 0);
let height = frame.height();
frame.set_baseline(frame.height() / 2.0 + axis); frame.set_baseline(frame.height() / 2.0 + axis);
if let Some(left) = left {
ctx.push(GlyphFragment::new(ctx, left).stretch_vertical(ctx, height, short_fall));
}
ctx.push(frame); ctx.push(frame);
if let Some(right) = right { if let Some(right) = right {
ctx.push(GlyphFragment::new(ctx, right)); ctx.push(
GlyphFragment::new(ctx, right).stretch_vertical(ctx, height, short_fall),
);
} }
Ok(()) Ok(())

View File

@ -33,7 +33,7 @@ pub use self::style::*;
use ttf_parser::GlyphId; use ttf_parser::GlyphId;
use ttf_parser::Rect; use ttf_parser::Rect;
use typst::font::Font; use typst::font::Font;
use typst::model::{Guard, Scope, SequenceNode}; use typst::model::{Guard, Module, Scope, SequenceNode};
use unicode_math_class::MathClass; use unicode_math_class::MathClass;
use self::ctx::*; use self::ctx::*;
@ -48,38 +48,38 @@ use crate::text::TextNode;
use crate::text::TextSize; use crate::text::TextSize;
use crate::text::{families, variant, FallbackList, FontFamily, SpaceNode, SymbolNode}; use crate::text::{families, variant, FallbackList, FontFamily, SpaceNode, SymbolNode};
/// Hook up all math definitions. /// Create a module with all math definitions.
pub fn define(scope: &mut Scope) { pub fn module() -> Module {
scope.def_func::<MathNode>("math"); let mut math = Scope::deduplicating();
scope.def_func::<LrNode>("lr"); math.def_func::<FormulaNode>("formula");
scope.def_func::<AccentNode>("accent"); math.def_func::<LrNode>("lr");
scope.def_func::<FracNode>("frac"); math.def_func::<FloorFunc>("floor");
scope.def_func::<BinomNode>("binom"); math.def_func::<CeilFunc>("ceil");
scope.def_func::<ScriptNode>("script"); math.def_func::<AbsFunc>("abs");
scope.def_func::<SqrtNode>("sqrt"); math.def_func::<AccentNode>("accent");
scope.def_func::<RootNode>("root"); math.def_func::<FracNode>("frac");
scope.def_func::<FloorNode>("floor"); math.def_func::<BinomNode>("binom");
scope.def_func::<CeilNode>("ceil"); math.def_func::<ScriptNode>("script");
scope.def_func::<VecNode>("vec"); math.def_func::<SqrtNode>("sqrt");
scope.def_func::<CasesNode>("cases"); math.def_func::<RootNode>("root");
scope.def_func::<UnderbraceNode>("underbrace"); math.def_func::<VecNode>("vec");
scope.def_func::<OverbraceNode>("overbrace"); math.def_func::<CasesNode>("cases");
scope.def_func::<BoldNode>("bold"); math.def_func::<UnderbraceNode>("underbrace");
scope.def_func::<ItalicNode>("italic"); math.def_func::<OverbraceNode>("overbrace");
scope.def_func::<SerifNode>("serif"); math.def_func::<BoldNode>("bold");
scope.def_func::<SansNode>("sans"); math.def_func::<ItalicNode>("italic");
scope.def_func::<CalNode>("cal"); math.def_func::<SerifNode>("serif");
scope.def_func::<FrakNode>("frak"); math.def_func::<SansNode>("sans");
scope.def_func::<MonoNode>("mono"); math.def_func::<CalNode>("cal");
scope.def_func::<BbNode>("bb"); math.def_func::<FrakNode>("frak");
scope.define("thin", HNode::strong(THIN).pack()); math.def_func::<MonoNode>("mono");
scope.define("med", HNode::strong(MEDIUM).pack()); math.def_func::<BbNode>("bb");
scope.define("thick", HNode::strong(THICK).pack()); define_spacings(&mut math);
scope.define("quad", HNode::strong(QUAD).pack()); define_operators(&mut math);
define_operators(scope); Module::new("math").with_scope(math)
} }
/// # Math /// # Formula
/// A mathematical formula. /// A mathematical formula.
/// ///
/// ## Syntax /// ## Syntax
@ -131,7 +131,7 @@ pub fn define(scope: &mut Scope) {
#[func] #[func]
#[capable(Show, Finalize, Layout, Inline, LayoutMath)] #[capable(Show, Finalize, Layout, Inline, LayoutMath)]
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, Hash)]
pub struct MathNode { pub struct FormulaNode {
/// Whether the formula is displayed as a separate block. /// Whether the formula is displayed as a separate block.
pub block: bool, pub block: bool,
/// The content of the formula. /// The content of the formula.
@ -139,7 +139,7 @@ pub struct MathNode {
} }
#[node] #[node]
impl MathNode { impl FormulaNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
let body = args.expect("body")?; let body = args.expect("body")?;
let block = args.named("block")?.unwrap_or(false); let block = args.named("block")?.unwrap_or(false);
@ -148,13 +148,14 @@ impl MathNode {
fn field(&self, name: &str) -> Option<Value> { fn field(&self, name: &str) -> Option<Value> {
match name { match name {
"body" => Some(Value::Content(self.body.clone())),
"block" => Some(Value::Bool(self.block)), "block" => Some(Value::Bool(self.block)),
_ => None, _ => None,
} }
} }
} }
impl Show for MathNode { impl Show for FormulaNode {
fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> SourceResult<Content> { fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> SourceResult<Content> {
let mut realized = self.clone().pack().guarded(Guard::Base(NodeId::of::<Self>())); let mut realized = self.clone().pack().guarded(Guard::Base(NodeId::of::<Self>()));
if self.block { if self.block {
@ -164,7 +165,7 @@ impl Show for MathNode {
} }
} }
impl Finalize for MathNode { impl Finalize for FormulaNode {
fn finalize(&self, realized: Content) -> Content { fn finalize(&self, realized: Content) -> Content {
realized.styled( realized.styled(
TextNode::FAMILY, TextNode::FAMILY,
@ -173,7 +174,7 @@ impl Finalize for MathNode {
} }
} }
impl Layout for MathNode { impl Layout for FormulaNode {
fn layout( fn layout(
&self, &self,
vt: &mut Vt, vt: &mut Vt,
@ -200,14 +201,14 @@ impl Layout for MathNode {
} }
} }
impl Inline for MathNode {} impl Inline for FormulaNode {}
#[capability] #[capability]
trait LayoutMath { trait LayoutMath {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()>; fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()>;
} }
impl LayoutMath for MathNode { impl LayoutMath for FormulaNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
self.body.layout_math(ctx) self.body.layout_math(ctx)
} }

View File

@ -25,12 +25,6 @@ pub struct OpNode {
pub limits: bool, pub limits: bool,
} }
impl OpNode {
fn new(text: impl Into<EcoString>, limits: bool) -> Self {
Self { text: text.into(), limits }
}
}
#[node] #[node]
impl OpNode { impl OpNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
@ -55,96 +49,41 @@ impl LayoutMath for OpNode {
} }
/// Hook up all operators. /// Hook up all operators.
pub fn define_operators(scope: &mut Scope) { pub(super) fn define_operators(math: &mut Scope) {
let mut define = |name: &str, limits| { math.define("arccos", op("arccos", false));
scope.define(name, OpNode { text: name.into(), limits }.pack()); math.define("arcsin", op("arcsin", false));
}; math.define("arctan", op("arctan", false));
math.define("arg", op("arg", false));
// These have the same name in code and display. math.define("cos", op("cos", false));
define("arccos", false); math.define("cosh", op("cosh", false));
define("arcsin", false); math.define("cot", op("cot", false));
define("arctan", false); math.define("coth", op("coth", false));
define("arg", false); math.define("csc", op("csc", false));
define("cos", false); math.define("deg", op("deg", false));
define("cosh", false); math.define("det", op("det", true));
define("cot", false); math.define("dim", op("dim", false));
define("coth", false); math.define("exp", op("exp", false));
define("csc", false); math.define("gcd", op("gcd", true));
define("deg", false); math.define("hom", op("hom", false));
define("det", true); math.define("inf", op("inf", true));
define("dim", false); math.define("ker", op("ker", false));
define("exp", false); math.define("lg", op("lg", false));
define("gcd", true); math.define("lim", op("lim", true));
define("hom", false); math.define("ln", op("ln", false));
define("inf", true); math.define("log", op("log", false));
define("ker", false); math.define("max", op("max", true));
define("lg", false); math.define("min", op("min", true));
define("lim", true); math.define("Pr", op("Pr", true));
define("ln", false); math.define("sec", op("sec", false));
define("log", false); math.define("sin", op("sin", false));
define("max", true); math.define("sinh", op("sinh", false));
define("min", true); math.define("sup", op("sup", true));
define("Pr", true); math.define("tan", op("tan", false));
define("sec", false); math.define("tanh", op("tanh", false));
define("sin", false); math.define("liminf", op("liminf", true));
define("sinh", false); math.define("limsup", op("limsup", true));
define("sup", true);
define("tan", false);
define("tanh", false);
// These have an extra thin space.
scope.define("liminf", OpNode::new("liminf", true).pack());
scope.define("limsup", OpNode::new("limsup", true).pack());
} }
/// # Floor fn op(name: &str, limits: bool) -> Content {
/// A floored expression. OpNode { text: name.into(), limits }.pack()
///
/// ## Example
/// ```
/// $ floor(x/2) $
/// ```
///
/// ## Parameters
/// - body: Content (positional, required)
/// The expression to floor.
///
/// ## Category
/// math
#[func]
#[capable]
#[derive(Debug, Hash)]
pub struct FloorNode(pub Content);
#[node]
impl FloorNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack())
}
}
/// # Ceil
/// A ceiled expression.
///
/// ## Example
/// ```
/// $ ceil(x/2) $
/// ```
///
/// ## Parameters
/// - body: Content (positional, required)
/// The expression to ceil.
///
/// ## Category
/// math
#[func]
#[capable]
#[derive(Debug, Hash)]
pub struct CeilNode(pub Content);
#[node]
impl CeilNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack())
}
} }

View File

@ -155,7 +155,7 @@ fn layout(
/// Select a precomposed radical, if the font has it. /// Select a precomposed radical, if the font has it.
fn precomposed(ctx: &MathContext, index: Option<&Content>, target: Abs) -> Option<Frame> { fn precomposed(ctx: &MathContext, index: Option<&Content>, target: Abs) -> Option<Frame> {
let node = index?.to::<MathNode>()?.body.to::<AtomNode>()?; let node = index?.to::<FormulaNode>()?.body.to::<AtomNode>()?;
let c = match node.0.as_str() { let c = match node.0.as_str() {
"3" => '∛', "3" => '∛',
"4" => '∜', "4" => '∜',

View File

@ -1,10 +1,18 @@
use super::*; use super::*;
pub(super) const ZERO: Em = Em::zero(); const ZERO: Em = Em::zero();
pub(super) const THIN: Em = Em::new(1.0 / 6.0); const THIN: Em = Em::new(1.0 / 6.0);
pub(super) const MEDIUM: Em = Em::new(2.0 / 9.0); const MEDIUM: Em = Em::new(2.0 / 9.0);
pub(super) const THICK: Em = Em::new(5.0 / 18.0); const THICK: Em = Em::new(5.0 / 18.0);
pub(super) const QUAD: Em = Em::new(1.0); const QUAD: Em = Em::new(1.0);
/// Hook up all spacings.
pub(super) fn define_spacings(math: &mut Scope) {
math.define("thin", HNode::strong(THIN).pack());
math.define("med", HNode::strong(MEDIUM).pack());
math.define("thick", HNode::strong(THICK).pack());
math.define("quad", HNode::strong(QUAD).pack());
}
/// Determine the spacing between two fragments in a given style. /// Determine the spacing between two fragments in a given style.
pub(super) fn spacing(left: MathClass, right: MathClass, style: MathStyle) -> Em { pub(super) fn spacing(left: MathClass, right: MathClass, style: MathStyle) -> Em {

View File

@ -4,7 +4,6 @@ use if_chain::if_chain;
use super::{plain_docs_sentence, summarize_font_family}; use super::{plain_docs_sentence, summarize_font_family};
use crate::model::{CastInfo, Scope, Value}; use crate::model::{CastInfo, Scope, Value};
use crate::syntax::ast::AstNode;
use crate::syntax::{ast, LinkedNode, Source, SyntaxKind}; use crate::syntax::{ast, LinkedNode, Source, SyntaxKind};
use crate::util::{format_eco, EcoString}; use crate::util::{format_eco, EcoString};
use crate::World; use crate::World;
@ -118,8 +117,8 @@ fn complete_params(ctx: &mut CompletionContext) -> bool {
if let Some(grand) = parent.parent(); if let Some(grand) = parent.parent();
if let Some(expr) = grand.cast::<ast::Expr>(); if let Some(expr) = grand.cast::<ast::Expr>();
let set = matches!(expr, ast::Expr::Set(_)); let set = matches!(expr, ast::Expr::Set(_));
if let Some(callee) = match expr { if let Some(ast::Expr::Ident(callee)) = match expr {
ast::Expr::FuncCall(call) => call.callee().as_untyped().cast(), ast::Expr::FuncCall(call) => Some(call.callee()),
ast::Expr::Set(set) => Some(set.target()), ast::Expr::Set(set) => Some(set.target()),
_ => None, _ => None,
}; };
@ -377,7 +376,7 @@ impl<'a> CompletionContext<'a> {
let leaf = LinkedNode::new(source.root()).leaf_at(cursor)?; let leaf = LinkedNode::new(source.root()).leaf_at(cursor)?;
Some(Self { Some(Self {
world, world,
scope: &world.library().scope, scope: &world.library().global.scope(),
before: &text[..cursor], before: &text[..cursor],
after: &text[cursor..], after: &text[cursor..],
leaf, leaf,

View File

@ -2,7 +2,7 @@ use if_chain::if_chain;
use super::{plain_docs_sentence, summarize_font_family}; use super::{plain_docs_sentence, summarize_font_family};
use crate::model::{CastInfo, Value}; use crate::model::{CastInfo, Value};
use crate::syntax::ast::{self, AstNode}; use crate::syntax::ast;
use crate::syntax::{LinkedNode, Source, SyntaxKind}; use crate::syntax::{LinkedNode, Source, SyntaxKind};
use crate::World; use crate::World;
@ -23,7 +23,7 @@ fn function_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<String> {
leaf.parent_kind(), leaf.parent_kind(),
Some(SyntaxKind::FuncCall | SyntaxKind::SetRule), Some(SyntaxKind::FuncCall | SyntaxKind::SetRule),
); );
if let Some(Value::Func(func)) = world.library().scope.get(&ident); if let Some(Value::Func(func)) = world.library().global.scope().get(&ident);
if let Some(info) = func.info(); if let Some(info) = func.info();
then { then {
return Some(plain_docs_sentence(info.docs)); return Some(plain_docs_sentence(info.docs));
@ -44,14 +44,14 @@ fn named_param_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<String> {
if matches!(grand.kind(), SyntaxKind::Args); if matches!(grand.kind(), SyntaxKind::Args);
if let Some(grand_grand) = grand.parent(); if let Some(grand_grand) = grand.parent();
if let Some(expr) = grand_grand.cast::<ast::Expr>(); if let Some(expr) = grand_grand.cast::<ast::Expr>();
if let Some(callee) = match expr { if let Some(ast::Expr::Ident(callee)) = match expr {
ast::Expr::FuncCall(call) => call.callee().as_untyped().cast(), ast::Expr::FuncCall(call) => Some(call.callee()),
ast::Expr::Set(set) => Some(set.target()), ast::Expr::Set(set) => Some(set.target()),
_ => None, _ => None,
}; };
// Find metadata about the function. // Find metadata about the function.
if let Some(Value::Func(func)) = world.library().scope.get(&callee); if let Some(Value::Func(func)) = world.library().global.scope().get(&callee);
if let Some(info) = func.info(); if let Some(info) = func.info();
then { (info, named) } then { (info, named) }
else { return None; } else { return None; }
@ -103,8 +103,8 @@ fn font_family_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<String> {
if matches!(parent.kind(), SyntaxKind::Args); if matches!(parent.kind(), SyntaxKind::Args);
if let Some(grand) = parent.parent(); if let Some(grand) = parent.parent();
if let Some(expr) = grand.cast::<ast::Expr>(); if let Some(expr) = grand.cast::<ast::Expr>();
if let Some(callee) = match expr { if let Some(ast::Expr::Ident(callee)) = match expr {
ast::Expr::FuncCall(call) => call.callee().as_untyped().cast(), ast::Expr::FuncCall(call) => Some(call.callee()),
ast::Expr::Set(set) => Some(set.target()), ast::Expr::Set(set) => Some(set.target()),
_ => None, _ => None,
}; };

View File

@ -2,7 +2,7 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::mem; use std::mem;
use std::path::PathBuf; use std::path::{Path, PathBuf};
use comemo::{Track, Tracked}; use comemo::{Track, Tracked};
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
@ -32,9 +32,9 @@ pub fn eval(
) -> SourceResult<Module> { ) -> SourceResult<Module> {
// Prevent cyclic evaluation. // Prevent cyclic evaluation.
let id = source.id(); let id = source.id();
let path = if id.is_detached() { Path::new("") } else { world.source(id).path() };
if route.contains(id) { if route.contains(id) {
let path = world.source(id).path().display(); panic!("Tried to cyclicly evaluate {}", path.display());
panic!("Tried to cyclicly evaluate {}", path);
} }
// Hook up the lang items. // Hook up the lang items.
@ -43,7 +43,7 @@ pub fn eval(
// Evaluate the module. // Evaluate the module.
let route = unsafe { Route::insert(route, id) }; let route = unsafe { Route::insert(route, id) };
let scopes = Scopes::new(Some(&library.scope)); let scopes = Scopes::new(Some(library));
let mut vm = Vm::new(world, route.track(), id, scopes, 0); let mut vm = Vm::new(world, route.track(), id, scopes, 0);
let result = source.ast()?.eval(&mut vm); let result = source.ast()?.eval(&mut vm);
@ -53,7 +53,8 @@ pub fn eval(
} }
// Assemble the module. // Assemble the module.
Ok(Module::evaluated(source.path(), vm.scopes.top, result?)) let name = path.file_stem().unwrap_or_default().to_string_lossy();
Ok(Module::new(name).with_scope(vm.scopes.top).with_content(result?))
} }
/// A virtual machine. /// A virtual machine.
@ -521,7 +522,7 @@ impl Eval for ast::Math {
.map(|expr| expr.eval_in_math(vm)) .map(|expr| expr.eval_in_math(vm))
.collect::<SourceResult<_>>()?; .collect::<SourceResult<_>>()?;
let block = self.block(); let block = self.block();
Ok((vm.items.math)(Content::sequence(seq), block)) Ok((vm.items.math_formula)(Content::sequence(seq), block))
} }
} }
@ -608,11 +609,11 @@ impl Eval for ast::Ident {
impl ast::Ident { impl ast::Ident {
fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> { fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> {
if self.as_untyped().len() == self.len() if self.as_untyped().len() == self.len()
&& matches!(vm.scopes.get(self), Ok(Value::Func(_)) | Err(_)) && matches!(vm.scopes.get_in_math(self), Ok(Value::Func(_)) | Err(_))
{ {
Ok((vm.items.symbol)(EcoString::from(self.get()) + ":op".into())) Ok((vm.items.symbol)(EcoString::from(self.get()) + ":op".into()))
} else { } else {
Ok(self.eval(vm)?.display_in_math()) Ok(vm.scopes.get_in_math(self).at(self.span())?.clone().display_in_math())
} }
} }
} }
@ -933,7 +934,13 @@ impl Eval for ast::FuncCall {
impl ast::FuncCall { impl ast::FuncCall {
fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> { fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> {
let callee = self.callee().eval(vm)?; let callee = match self.callee() {
ast::Expr::Ident(ident) => {
vm.scopes.get_in_math(&ident).at(ident.span())?.clone()
}
expr => expr.eval(vm)?,
};
if let Value::Func(callee) = callee { if let Value::Func(callee) = callee {
let args = self.args().eval(vm)?; let args = self.args().eval(vm)?;
Ok(Self::eval_call(vm, &callee, args, self.span())?.display_in_math()) Ok(Self::eval_call(vm, &callee, args, self.span())?.display_in_math())

View File

@ -4,7 +4,7 @@ use std::num::NonZeroUsize;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use super::{Content, NodeId, Scope, StyleChain, StyleMap, Vt}; use super::{Content, Module, NodeId, StyleChain, StyleMap, Vt};
use crate::diag::SourceResult; use crate::diag::SourceResult;
use crate::doc::Document; use crate::doc::Document;
use crate::geom::{Abs, Dir}; use crate::geom::{Abs, Dir};
@ -14,7 +14,9 @@ use crate::util::{hash128, EcoString};
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, Hash)]
pub struct Library { pub struct Library {
/// The scope containing definitions that are available everywhere. /// The scope containing definitions that are available everywhere.
pub scope: Scope, pub global: Module,
/// The scope containing definitions available in math mode.
pub math: Module,
/// The default properties for page size, font selection and so on. /// The default properties for page size, font selection and so on.
pub styles: StyleMap, pub styles: StyleMap,
/// Defines which standard library items fulfill which syntactical roles. /// Defines which standard library items fulfill which syntactical roles.
@ -66,7 +68,7 @@ pub struct LangItems {
/// An item in a term list: `/ Term: Details`. /// An item in a term list: `/ Term: Details`.
pub term_item: fn(term: Content, description: Content) -> Content, pub term_item: fn(term: Content, description: Content) -> Content,
/// A mathematical formula: `$x$`, `$ x^2 $`. /// A mathematical formula: `$x$`, `$ x^2 $`.
pub math: fn(body: Content, block: bool) -> Content, pub math_formula: fn(body: Content, block: bool) -> Content,
/// A subsection in a math formula that is surrounded by matched delimiters: /// A subsection in a math formula that is surrounded by matched delimiters:
/// `[x + y]`. /// `[x + y]`.
pub math_delimited: fn(body: Content) -> Content, pub math_delimited: fn(body: Content) -> Content,
@ -106,7 +108,7 @@ impl Hash for LangItems {
self.list_item.hash(state); self.list_item.hash(state);
self.enum_item.hash(state); self.enum_item.hash(state);
self.term_item.hash(state); self.term_item.hash(state);
self.math.hash(state); self.math_formula.hash(state);
self.math_atom.hash(state); self.math_atom.hash(state);
self.math_script.hash(state); self.math_script.hash(state);
self.math_frac.hash(state); self.math_frac.hash(state);

View File

@ -1,5 +1,4 @@
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use super::{Content, Scope, Value}; use super::{Content, Scope, Value};
@ -22,7 +21,7 @@ struct Repr {
} }
impl Module { impl Module {
/// Create a new, empty module with the given `name`. /// Create a new module.
pub fn new(name: impl Into<EcoString>) -> Self { pub fn new(name: impl Into<EcoString>) -> Self {
Self(Arc::new(Repr { Self(Arc::new(Repr {
name: name.into(), name: name.into(),
@ -31,10 +30,16 @@ impl Module {
})) }))
} }
/// Create a new module from an evalauted file. /// Update the module's scope.
pub fn evaluated(path: &Path, scope: Scope, content: Content) -> Self { pub fn with_scope(mut self, scope: Scope) -> Self {
let name = path.file_stem().unwrap_or_default().to_string_lossy().into(); Arc::make_mut(&mut self.0).scope = scope;
Self(Arc::new(Repr { name, scope, content })) self
}
/// Update the module's content.
pub fn with_content(mut self, content: Content) -> Self {
Arc::make_mut(&mut self.0).content = content;
self
} }
/// Get the module's name. /// Get the module's name.
@ -47,6 +52,11 @@ impl Module {
&self.0.scope &self.0.scope
} }
/// Access the module's scope, mutably.
pub fn scope_mut(&mut self) -> &mut Scope {
&mut Arc::make_mut(&mut self.0).scope
}
/// Try to access a definition in the module. /// Try to access a definition in the module.
pub fn get(&self, name: &str) -> StrResult<&Value> { pub fn get(&self, name: &str) -> StrResult<&Value> {
self.scope().get(&name).ok_or_else(|| { self.scope().get(&name).ok_or_else(|| {

View File

@ -2,7 +2,7 @@ use std::collections::BTreeMap;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use std::hash::Hash; use std::hash::Hash;
use super::{Func, FuncType, Value}; use super::{Func, FuncType, Library, Value};
use crate::diag::StrResult; use crate::diag::StrResult;
use crate::util::EcoString; use crate::util::EcoString;
@ -13,13 +13,13 @@ pub struct Scopes<'a> {
pub top: Scope, pub top: Scope,
/// The stack of lower scopes. /// The stack of lower scopes.
pub scopes: Vec<Scope>, pub scopes: Vec<Scope>,
/// The base scope. /// The standard library.
pub base: Option<&'a Scope>, pub base: Option<&'a Library>,
} }
impl<'a> Scopes<'a> { impl<'a> Scopes<'a> {
/// Create a new, empty hierarchy of scopes. /// Create a new, empty hierarchy of scopes.
pub fn new(base: Option<&'a Scope>) -> Self { pub fn new(base: Option<&'a Library>) -> Self {
Self { top: Scope::new(), scopes: vec![], base } Self { top: Scope::new(), scopes: vec![], base }
} }
@ -39,7 +39,16 @@ impl<'a> Scopes<'a> {
pub fn get(&self, var: &str) -> StrResult<&Value> { pub fn get(&self, var: &str) -> StrResult<&Value> {
Ok(std::iter::once(&self.top) Ok(std::iter::once(&self.top)
.chain(self.scopes.iter().rev()) .chain(self.scopes.iter().rev())
.chain(self.base.into_iter()) .chain(self.base.map(|base| base.global.scope()))
.find_map(|scope| scope.get(var))
.ok_or("unknown variable")?)
}
/// Try to access a variable immutably from within a math formula.
pub fn get_in_math(&self, var: &str) -> StrResult<&Value> {
Ok(std::iter::once(&self.top)
.chain(self.scopes.iter().rev())
.chain(self.base.map(|base| base.math.scope()))
.find_map(|scope| scope.get(var)) .find_map(|scope| scope.get(var))
.ok_or("unknown variable")?) .ok_or("unknown variable")?)
} }
@ -50,10 +59,9 @@ impl<'a> Scopes<'a> {
.chain(&mut self.scopes.iter_mut().rev()) .chain(&mut self.scopes.iter_mut().rev())
.find_map(|scope| scope.get_mut(var)) .find_map(|scope| scope.get_mut(var))
.ok_or_else(|| { .ok_or_else(|| {
if self.base.map_or(false, |base| base.get(var).is_some()) { match self.base.and_then(|base| base.global.scope().get(var)) {
"cannot mutate a constant" Some(_) => "cannot mutate a constant",
} else { _ => "unknown variable",
"unknown variable"
} }
})? })?
} }
@ -61,17 +69,29 @@ impl<'a> Scopes<'a> {
/// A map from binding names to values. /// A map from binding names to values.
#[derive(Default, Clone, Hash)] #[derive(Default, Clone, Hash)]
pub struct Scope(BTreeMap<EcoString, Slot>); pub struct Scope(BTreeMap<EcoString, Slot>, bool);
impl Scope { impl Scope {
/// Create a new empty scope. /// Create a new empty scope.
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self(BTreeMap::new(), false)
}
/// Create a new scope with duplication prevention.
pub fn deduplicating() -> Self {
Self(BTreeMap::new(), true)
} }
/// Bind a value to a name. /// Bind a value to a name.
pub fn define(&mut self, name: impl Into<EcoString>, value: impl Into<Value>) { pub fn define(&mut self, name: impl Into<EcoString>, value: impl Into<Value>) {
self.0.insert(name.into(), Slot::new(value.into(), Kind::Normal)); let name = name.into();
#[cfg(debug_assertions)]
if self.1 && self.0.contains_key(&name) {
panic!("duplicate definition: {name}");
}
self.0.insert(name, Slot::new(value.into(), Kind::Normal));
} }
/// Define a function through a native rust function. /// Define a function through a native rust function.

View File

@ -1505,7 +1505,7 @@ node! {
impl SetRule { impl SetRule {
/// The function to set style properties for. /// The function to set style properties for.
pub fn target(&self) -> Ident { pub fn target(&self) -> Expr {
self.0.cast_first_match().expect("set rule is missing target") self.0.cast_first_match().expect("set rule is missing target")
} }

View File

@ -712,7 +712,14 @@ fn let_binding(p: &mut Parser) {
fn set_rule(p: &mut Parser) { fn set_rule(p: &mut Parser) {
let m = p.marker(); let m = p.marker();
p.assert(SyntaxKind::Set); p.assert(SyntaxKind::Set);
let m2 = p.marker();
p.expect(SyntaxKind::Ident); p.expect(SyntaxKind::Ident);
while p.eat_if(SyntaxKind::Dot) {
p.expect(SyntaxKind::Ident);
p.wrap(m2, SyntaxKind::FieldAccess);
}
args(p); args(p);
if p.eat_if(SyntaxKind::If) { if p.eat_if(SyntaxKind::If) {
code_expr(p); code_expr(p);

View File

@ -186,10 +186,14 @@ fn library() -> Library {
lib.styles.set(TextNode::SIZE, TextSize(Abs::pt(10.0).into())); lib.styles.set(TextNode::SIZE, TextSize(Abs::pt(10.0).into()));
// Hook up helpers into the global scope. // Hook up helpers into the global scope.
lib.scope.def_func::<TestFunc>("test"); lib.global.scope_mut().def_func::<TestFunc>("test");
lib.scope.def_func::<PrintFunc>("print"); lib.global.scope_mut().def_func::<PrintFunc>("print");
lib.scope.define("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF)); lib.global
lib.scope.define("forest", RgbaColor::new(0x43, 0xA1, 0x27, 0xFF)); .scope_mut()
.define("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF));
lib.global
.scope_mut()
.define("forest", RgbaColor::new(0x43, 0xA1, 0x27, 0xFF));
lib lib
} }

View File

@ -25,7 +25,6 @@ Hello
// While we're at it, test the larger block spacing wins. // While we're at it, test the larger block spacing wins.
#set block(spacing: 0pt) #set block(spacing: 0pt)
#show raw: set block(spacing: 15pt) #show raw: set block(spacing: 15pt)
#show math: set block(spacing: 7.5pt)
#show list: set block(spacing: 2.5pt) #show list: set block(spacing: 2.5pt)
```rust ```rust

View File

@ -7,7 +7,7 @@ $ v = vec(1, 2+3, 4) $
$ binom(n, 1) = 1/2 n (n-1) $ $ binom(n, 1) = 1/2 n (n-1) $
--- ---
#set vec(delim: "|") #set math.vec(delim: "|")
$ vec(1, 2) $ $ vec(1, 2) $
--- ---
@ -19,8 +19,8 @@ $ f(x, y) := cases(
) $ ) $
--- ---
// Error: 17-20 expected "(", "[", "{", or "|" // Error: 22-25 expected "(", "[", "{", "|", or "||"
#set vec(delim: "%") #set math.vec(delim: "%")
--- ---
// Error: 9-12 missing argument: lower index // Error: 9-12 missing argument: lower index

View File

@ -1,6 +1,6 @@
#let part = $ a b A B $ #let part = $ a b A B $
#let kinds = (serif, sans, cal, frak, mono, bb) #let kinds = (math.serif, math.sans, math.cal, math.frak, math.mono, math.bb)
#let modifiers = (v => v, italic, bold, v => italic(bold(v))) #let modifiers = (v => v, math.italic, math.bold, v => math.italic(math.bold(v)))
#let cells = ([:triangle:nested:], [--], [`italic`], [`bold`], [both]) #let cells = ([:triangle:nested:], [--], [`italic`], [`bold`], [both])
#for k in kinds { #for k in kinds {