From 33928a00dc58250e24da1dae4e5db17e7b598d70 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 3 Nov 2022 16:50:26 +0100 Subject: Tidy up library --- library/src/base/calc.rs | 144 +++++++++++++++++++++++++++++++++++++ library/src/base/color.rs | 66 +++++++++++++++++ library/src/base/data.rs | 132 ++++++++++++++++++++++++++++++++++ library/src/base/mod.rs | 54 ++++++++++++++ library/src/base/string.rs | 141 ++++++++++++++++++++++++++++++++++++ library/src/ext.rs | 2 +- library/src/layout/flow.rs | 12 ++-- library/src/layout/grid.rs | 6 +- library/src/layout/mod.rs | 40 +++++------ library/src/layout/page.rs | 1 + library/src/layout/stack.rs | 22 +++--- library/src/lib.rs | 58 +++++++-------- library/src/math/tex.rs | 2 +- library/src/prelude.rs | 17 ++--- library/src/structure/list.rs | 4 +- library/src/structure/reference.rs | 1 + library/src/text/deco.rs | 4 +- library/src/text/mod.rs | 14 ++-- library/src/text/quotes.rs | 4 +- library/src/text/shaping.rs | 8 +-- library/src/text/shift.rs | 16 ++--- library/src/utility/color.rs | 66 ----------------- library/src/utility/data.rs | 132 ---------------------------------- library/src/utility/math.rs | 144 ------------------------------------- library/src/utility/mod.rs | 54 -------------- library/src/utility/string.rs | 141 ------------------------------------ 26 files changed, 638 insertions(+), 647 deletions(-) create mode 100644 library/src/base/calc.rs create mode 100644 library/src/base/color.rs create mode 100644 library/src/base/data.rs create mode 100644 library/src/base/mod.rs create mode 100644 library/src/base/string.rs delete mode 100644 library/src/utility/color.rs delete mode 100644 library/src/utility/data.rs delete mode 100644 library/src/utility/math.rs delete mode 100644 library/src/utility/mod.rs delete mode 100644 library/src/utility/string.rs (limited to 'library/src') diff --git a/library/src/base/calc.rs b/library/src/base/calc.rs new file mode 100644 index 00000000..dd37e8e7 --- /dev/null +++ b/library/src/base/calc.rs @@ -0,0 +1,144 @@ +use std::cmp::Ordering; + +use crate::prelude::*; + +/// Convert a value to an integer. +pub fn int(_: &mut Vm, args: &mut Args) -> SourceResult { + let Spanned { v, span } = args.expect("value")?; + Ok(Value::Int(match v { + Value::Bool(v) => v as i64, + Value::Int(v) => v, + Value::Float(v) => v as i64, + Value::Str(v) => match v.parse() { + Ok(v) => v, + Err(_) => bail!(span, "invalid integer"), + }, + v => bail!(span, "cannot convert {} to integer", v.type_name()), + })) +} + +/// Convert a value to a float. +pub fn float(_: &mut Vm, args: &mut Args) -> SourceResult { + let Spanned { v, span } = args.expect("value")?; + Ok(Value::Float(match v { + Value::Int(v) => v as f64, + Value::Float(v) => v, + Value::Str(v) => match v.parse() { + Ok(v) => v, + Err(_) => bail!(span, "invalid float"), + }, + v => bail!(span, "cannot convert {} to float", v.type_name()), + })) +} + +/// The absolute value of a numeric value. +pub fn abs(_: &mut Vm, args: &mut Args) -> SourceResult { + let Spanned { v, span } = args.expect("numeric value")?; + Ok(match v { + Value::Int(v) => Value::Int(v.abs()), + Value::Float(v) => Value::Float(v.abs()), + Value::Angle(v) => Value::Angle(v.abs()), + Value::Ratio(v) => Value::Ratio(v.abs()), + Value::Fraction(v) => Value::Fraction(v.abs()), + Value::Length(_) | Value::Relative(_) => { + bail!(span, "cannot take absolute value of a length") + } + v => bail!(span, "expected numeric value, found {}", v.type_name()), + }) +} + +/// The minimum of a sequence of values. +pub fn min(_: &mut Vm, args: &mut Args) -> SourceResult { + minmax(args, Ordering::Less) +} + +/// The maximum of a sequence of values. +pub fn max(_: &mut Vm, args: &mut Args) -> SourceResult { + minmax(args, Ordering::Greater) +} + +/// Find the minimum or maximum of a sequence of values. +fn minmax(args: &mut Args, goal: Ordering) -> SourceResult { + let mut extremum = args.expect::("value")?; + for Spanned { v, span } in args.all::>()? { + match v.partial_cmp(&extremum) { + Some(ordering) => { + if ordering == goal { + extremum = v; + } + } + None => bail!( + span, + "cannot compare {} and {}", + extremum.type_name(), + v.type_name(), + ), + } + } + Ok(extremum) +} + +/// Whether an integer is even. +pub fn even(_: &mut Vm, args: &mut Args) -> SourceResult { + Ok(Value::Bool(args.expect::("integer")? % 2 == 0)) +} + +/// Whether an integer is odd. +pub fn odd(_: &mut Vm, args: &mut Args) -> SourceResult { + Ok(Value::Bool(args.expect::("integer")? % 2 != 0)) +} + +/// The modulo of two numbers. +pub fn mod_(_: &mut Vm, args: &mut Args) -> SourceResult { + let Spanned { v: v1, span: span1 } = args.expect("integer or float")?; + let Spanned { v: v2, span: span2 } = args.expect("integer or float")?; + + let (a, b) = match (v1, v2) { + (Value::Int(a), Value::Int(b)) => match a.checked_rem(b) { + Some(res) => return Ok(Value::Int(res)), + None => bail!(span2, "divisor must not be zero"), + }, + (Value::Int(a), Value::Float(b)) => (a as f64, b), + (Value::Float(a), Value::Int(b)) => (a, b as f64), + (Value::Float(a), Value::Float(b)) => (a, b), + (Value::Int(_), b) | (Value::Float(_), b) => bail!( + span2, + format!("expected integer or float, found {}", b.type_name()) + ), + (a, _) => bail!( + span1, + format!("expected integer or float, found {}", a.type_name()) + ), + }; + + if b == 0.0 { + bail!(span2, "divisor must not be zero"); + } + + Ok(Value::Float(a % b)) +} + +/// Create a sequence of numbers. +pub fn range(_: &mut Vm, args: &mut Args) -> SourceResult { + let first = args.expect::("end")?; + let (start, end) = match args.eat::()? { + Some(second) => (first, second), + None => (0, first), + }; + + let step: i64 = match args.named("step")? { + Some(Spanned { v: 0, span }) => bail!(span, "step must not be zero"), + Some(Spanned { v, .. }) => v, + None => 1, + }; + + let mut x = start; + let mut seq = vec![]; + + while x.cmp(&end) == 0.cmp(&step) { + seq.push(Value::Int(x)); + x += step; + } + + Ok(Value::Array(Array::from_vec(seq))) +} diff --git a/library/src/base/color.rs b/library/src/base/color.rs new file mode 100644 index 00000000..8bb12334 --- /dev/null +++ b/library/src/base/color.rs @@ -0,0 +1,66 @@ +use std::str::FromStr; + +use crate::prelude::*; + +/// Create a grayscale color. +pub fn luma(_: &mut Vm, args: &mut Args) -> SourceResult { + let Component(luma) = args.expect("gray component")?; + Ok(Value::Color(LumaColor::new(luma).into())) +} + +/// Create an RGB(A) color. +pub fn rgb(_: &mut Vm, args: &mut Args) -> SourceResult { + Ok(Value::Color( + if let Some(string) = args.find::>()? { + match RgbaColor::from_str(&string.v) { + Ok(color) => color.into(), + Err(msg) => bail!(string.span, msg), + } + } else { + let Component(r) = args.expect("red component")?; + let Component(g) = args.expect("green component")?; + let Component(b) = args.expect("blue component")?; + let Component(a) = args.eat()?.unwrap_or(Component(255)); + RgbaColor::new(r, g, b, a).into() + }, + )) +} + +/// Create a CMYK color. +pub fn cmyk(_: &mut Vm, args: &mut Args) -> SourceResult { + let RatioComponent(c) = args.expect("cyan component")?; + let RatioComponent(m) = args.expect("magenta component")?; + let RatioComponent(y) = args.expect("yellow component")?; + let RatioComponent(k) = args.expect("key component")?; + Ok(Value::Color(CmykColor::new(c, m, y, k).into())) +} + +/// An integer or ratio component. +struct Component(u8); + +castable! { + Component, + Expected: "integer or ratio", + Value::Int(v) => match v { + 0 ..= 255 => Self(v as u8), + _ => Err("must be between 0 and 255")?, + }, + Value::Ratio(v) => if (0.0 ..= 1.0).contains(&v.get()) { + Self((v.get() * 255.0).round() as u8) + } else { + Err("must be between 0% and 100%")? + }, +} + +/// A component that must be a ratio. +struct RatioComponent(u8); + +castable! { + RatioComponent, + Expected: "ratio", + Value::Ratio(v) => if (0.0 ..= 1.0).contains(&v.get()) { + Self((v.get() * 255.0).round() as u8) + } else { + Err("must be between 0% and 100%")? + }, +} diff --git a/library/src/base/data.rs b/library/src/base/data.rs new file mode 100644 index 00000000..1199056f --- /dev/null +++ b/library/src/base/data.rs @@ -0,0 +1,132 @@ +use std::fmt::Write; + +use typst::diag::{format_xml_like_error, FileError}; + +use crate::prelude::*; + +/// Read structured data from a CSV file. +pub fn csv(vm: &mut Vm, args: &mut Args) -> SourceResult { + let Spanned { v: path, span } = + args.expect::>("path to csv file")?; + + let path = vm.locate(&path).at(span)?; + let data = vm.world.file(&path).at(span)?; + + let mut builder = csv::ReaderBuilder::new(); + builder.has_headers(false); + + let mut reader = builder.from_reader(data.as_slice()); + let mut vec = vec![]; + + for result in reader.records() { + let row = result.map_err(format_csv_error).at(span)?; + let array = row.iter().map(|field| Value::Str(field.into())).collect(); + vec.push(Value::Array(array)) + } + + Ok(Value::Array(Array::from_vec(vec))) +} + +/// Format the user-facing CSV error message. +fn format_csv_error(error: csv::Error) -> String { + match error.kind() { + csv::ErrorKind::Utf8 { .. } => "file is not valid utf-8".into(), + csv::ErrorKind::UnequalLengths { pos, expected_len, len } => { + let mut msg = format!( + "failed to parse csv file: found {len} instead of {expected_len} fields" + ); + if let Some(pos) = pos { + write!(msg, " in line {}", pos.line()).unwrap(); + } + msg + } + _ => "failed to parse csv file".into(), + } +} + +/// Read structured data from a JSON file. +pub fn json(vm: &mut Vm, args: &mut Args) -> SourceResult { + let Spanned { v: path, span } = + args.expect::>("path to json file")?; + + let path = vm.locate(&path).at(span)?; + let data = vm.world.file(&path).at(span)?; + let value: serde_json::Value = + serde_json::from_slice(&data).map_err(format_json_error).at(span)?; + + Ok(convert_json(value)) +} + +/// Convert a JSON value to a Typst value. +fn convert_json(value: serde_json::Value) -> Value { + match value { + serde_json::Value::Null => Value::None, + serde_json::Value::Bool(v) => Value::Bool(v), + serde_json::Value::Number(v) => match v.as_i64() { + Some(int) => Value::Int(int), + None => Value::Float(v.as_f64().unwrap_or(f64::NAN)), + }, + serde_json::Value::String(v) => Value::Str(v.into()), + serde_json::Value::Array(v) => { + Value::Array(v.into_iter().map(convert_json).collect()) + } + serde_json::Value::Object(v) => Value::Dict( + v.into_iter() + .map(|(key, value)| (key.into(), convert_json(value))) + .collect(), + ), + } +} + +/// Format the user-facing JSON error message. +fn format_json_error(error: serde_json::Error) -> String { + assert!(error.is_syntax() || error.is_eof()); + format!( + "failed to parse json file: syntax error in line {}", + error.line() + ) +} + +/// Read structured data from an XML file. +pub fn xml(vm: &mut Vm, args: &mut Args) -> SourceResult { + let Spanned { v: path, span } = + args.expect::>("path to xml file")?; + + let path = vm.locate(&path).at(span)?; + let data = vm.world.file(&path).at(span)?; + let text = std::str::from_utf8(&data).map_err(FileError::from).at(span)?; + + let document = roxmltree::Document::parse(text).map_err(format_xml_error).at(span)?; + + Ok(convert_xml(document.root())) +} + +/// Convert an XML node to a Typst value. +fn convert_xml(node: roxmltree::Node) -> Value { + if node.is_text() { + return Value::Str(node.text().unwrap_or_default().into()); + } + + let children: Array = node.children().map(convert_xml).collect(); + if node.is_root() { + return Value::Array(children); + } + + let tag: Str = node.tag_name().name().into(); + let attrs: Dict = node + .attributes() + .iter() + .map(|attr| (attr.name().into(), attr.value().into())) + .collect(); + + Value::Dict(dict! { + "tag" => tag, + "attrs" => attrs, + "children" => children, + }) +} + +/// Format the user-facing XML error message. +fn format_xml_error(error: roxmltree::Error) -> String { + format_xml_like_error("xml file", error) +} diff --git a/library/src/base/mod.rs b/library/src/base/mod.rs new file mode 100644 index 00000000..bb1c8c51 --- /dev/null +++ b/library/src/base/mod.rs @@ -0,0 +1,54 @@ +//! Foundational functions. + +mod calc; +mod color; +mod data; +mod string; + +pub use calc::*; +pub use color::*; +pub use data::*; +pub use string::*; + +use comemo::Track; +use typst::model::{Eval, Route, Scopes, Vm}; +use typst::syntax::Source; + +use crate::prelude::*; + +/// The name of a value's type. +pub fn type_(_: &mut Vm, args: &mut Args) -> SourceResult { + Ok(args.expect::("value")?.type_name().into()) +} + +/// Ensure that a condition is fulfilled. +pub fn assert(_: &mut Vm, args: &mut Args) -> SourceResult { + let Spanned { v, span } = args.expect::>("condition")?; + if !v { + bail!(span, "assertion failed"); + } + Ok(Value::None) +} + +/// Evaluate a string as Typst markup. +pub fn eval(vm: &mut Vm, args: &mut Args) -> SourceResult { + let Spanned { v: text, span } = args.expect::>("source")?; + + // Parse the source and set a synthetic span for all nodes. + let source = Source::synthesized(text, span); + let ast = source.ast()?; + + // Evaluate the source. + let std = &vm.world.config().scope; + let scopes = Scopes::new(Some(std)); + let route = Route::default(); + let mut sub = Vm::new(vm.world, route.track(), None, scopes); + let result = ast.eval(&mut sub); + + // Handle control flow. + if let Some(flow) = sub.flow { + bail!(flow.forbidden()); + } + + Ok(Value::Content(result?)) +} diff --git a/library/src/base/string.rs b/library/src/base/string.rs new file mode 100644 index 00000000..ed444d35 --- /dev/null +++ b/library/src/base/string.rs @@ -0,0 +1,141 @@ +use typst::model::Regex; + +use crate::prelude::*; + +/// The string representation of a value. +pub fn repr(_: &mut Vm, args: &mut Args) -> SourceResult { + Ok(args.expect::("value")?.repr().into()) +} + +/// Convert a value to a string. +pub fn str(_: &mut Vm, args: &mut Args) -> SourceResult { + let Spanned { v, span } = args.expect("value")?; + Ok(Value::Str(match v { + Value::Int(v) => format_str!("{}", v), + Value::Float(v) => format_str!("{}", v), + Value::Str(v) => v, + v => bail!(span, "cannot convert {} to string", v.type_name()), + })) +} + +/// Create blind text. +pub fn lorem(_: &mut Vm, args: &mut Args) -> SourceResult { + let words: usize = args.expect("number of words")?; + Ok(Value::Str(lipsum::lipsum(words).into())) +} + +/// Create a regular expression. +pub fn regex(_: &mut Vm, args: &mut Args) -> SourceResult { + let Spanned { v, span } = args.expect::>("regular expression")?; + Ok(Regex::new(&v).at(span)?.into()) +} + +/// Converts an integer into one or multiple letters. +pub fn letter(_: &mut Vm, args: &mut Args) -> SourceResult { + numbered(Numbering::Letter, args) +} + +/// Converts an integer into a roman numeral. +pub fn roman(_: &mut Vm, args: &mut Args) -> SourceResult { + numbered(Numbering::Roman, args) +} + +/// Convert a number into a symbol. +pub fn symbol(_: &mut Vm, args: &mut Args) -> SourceResult { + numbered(Numbering::Symbol, args) +} + +fn numbered(numbering: Numbering, args: &mut Args) -> SourceResult { + let n = args.expect::("non-negative integer")?; + Ok(Value::Str(numbering.apply(n).into())) +} + +/// Allows to convert a number into letters, roman numerals and symbols. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum Numbering { + Arabic, + Letter, + Roman, + Symbol, +} + +impl Numbering { + /// Apply the numbering to the given number. + pub fn apply(self, mut n: usize) -> EcoString { + match self { + Self::Arabic => { + format_eco!("{}", n) + } + Self::Letter => { + if n == 0 { + return '-'.into(); + } + + n -= 1; + + let mut letters = vec![]; + loop { + letters.push(b'a' + (n % 26) as u8); + n /= 26; + if n == 0 { + break; + } + } + + letters.reverse(); + String::from_utf8(letters).unwrap().into() + } + Self::Roman => { + if n == 0 { + return 'N'.into(); + } + + // Adapted from Yann Villessuzanne's roman.rs under the Unlicense, at + // https://github.com/linfir/roman.rs/ + let mut fmt = EcoString::new(); + for &(name, value) in ROMANS { + while n >= value { + n -= value; + fmt.push_str(name); + } + } + + fmt + } + Self::Symbol => { + if n == 0 { + return '-'.into(); + } + + let symbol = SYMBOLS[(n - 1) % SYMBOLS.len()]; + let amount = ((n - 1) / SYMBOLS.len()) + 1; + std::iter::repeat(symbol).take(amount).collect() + } + } + } +} + +const ROMANS: &[(&str, usize)] = &[ + ("M̅", 1000000), + ("D̅", 500000), + ("C̅", 100000), + ("L̅", 50000), + ("X̅", 10000), + ("V̅", 5000), + ("I̅V̅", 4000), + ("M", 1000), + ("CM", 900), + ("D", 500), + ("CD", 400), + ("C", 100), + ("XC", 90), + ("L", 50), + ("XL", 40), + ("X", 10), + ("IX", 9), + ("V", 5), + ("IV", 4), + ("I", 1), +]; + +const SYMBOLS: &[char] = &['*', '†', '‡', '§', '‖', '¶']; diff --git a/library/src/ext.rs b/library/src/ext.rs index 72ef484b..6f5b1b67 100644 --- a/library/src/ext.rs +++ b/library/src/ext.rs @@ -115,7 +115,7 @@ impl StyleMapExt for StyleMap { fn set_family(&mut self, preferred: text::FontFamily, existing: StyleChain) { self.set( text::TextNode::FAMILY, - FallbackList( + text::FallbackList( std::iter::once(preferred) .chain(existing.get(text::TextNode::FAMILY).0.iter().cloned()) .collect(), diff --git a/library/src/layout/flow.rs b/library/src/layout/flow.rs index a5992796..347c1dd8 100644 --- a/library/src/layout/flow.rs +++ b/library/src/layout/flow.rs @@ -80,7 +80,7 @@ impl PartialOrd for FlowChild { } /// Performs flow layout. -pub struct FlowLayouter { +struct FlowLayouter { /// The regions to layout children into. regions: Regions, /// Whether the flow should expand to fill the region. @@ -112,7 +112,7 @@ enum FlowItem { impl FlowLayouter { /// Create a new flow layouter. - pub fn new(regions: &Regions) -> Self { + fn new(regions: &Regions) -> Self { let expand = regions.expand; let full = regions.first; @@ -132,7 +132,7 @@ impl FlowLayouter { } /// Layout spacing. - pub fn layout_spacing(&mut self, spacing: Spacing, styles: StyleChain) { + fn layout_spacing(&mut self, spacing: Spacing, styles: StyleChain) { match spacing { Spacing::Relative(v) => { // Resolve the spacing and limit it to the remaining space. @@ -150,7 +150,7 @@ impl FlowLayouter { } /// Layout a block. - pub fn layout_block( + fn layout_block( &mut self, world: Tracked, block: &Content, @@ -206,7 +206,7 @@ impl FlowLayouter { } /// Finish the frame for one region. - pub fn finish_region(&mut self) { + fn finish_region(&mut self) { // Determine the size of the flow in this region dependening on whether // the region expands. let mut size = self.expand.select(self.full, self.used); @@ -254,7 +254,7 @@ impl FlowLayouter { } /// Finish layouting and return the resulting frames. - pub fn finish(mut self) -> Vec { + fn finish(mut self) -> Vec { if self.expand.y { while !self.regions.backlog.is_empty() { self.finish_region(); diff --git a/library/src/layout/grid.rs b/library/src/layout/grid.rs index f6610d78..a6f93ab6 100644 --- a/library/src/layout/grid.rs +++ b/library/src/layout/grid.rs @@ -99,7 +99,7 @@ castable! { } /// Performs grid layout. -pub struct GridLayouter<'a> { +struct GridLayouter<'a> { /// The core context. world: Tracked<'a, dyn World>, /// The grid cells. @@ -140,7 +140,7 @@ impl<'a> GridLayouter<'a> { /// Create a new grid layouter. /// /// This prepares grid layout by unifying content and gutter tracks. - pub fn new( + fn new( world: Tracked<'a, dyn World>, tracks: Axes<&[TrackSizing]>, gutter: Axes<&[TrackSizing]>, @@ -211,7 +211,7 @@ impl<'a> GridLayouter<'a> { } /// Determines the columns sizes and then layouts the grid row-by-row. - pub fn layout(mut self) -> SourceResult> { + fn layout(mut self) -> SourceResult> { self.measure_columns()?; for y in 0 .. self.rows.len() { diff --git a/library/src/layout/mod.rs b/library/src/layout/mod.rs index ddfaa351..5ab5f42e 100644 --- a/library/src/layout/mod.rs +++ b/library/src/layout/mod.rs @@ -44,16 +44,16 @@ use crate::text::{ LinebreakNode, ParChild, ParNode, ParbreakNode, SmartQuoteNode, SpaceNode, TextNode, }; -/// The root-level layout. +/// Root-level layout. #[capability] -pub trait Layout: 'static + Sync + Send { +pub trait LayoutRoot: 'static + Sync + Send { /// Layout into one frame per page. - fn layout(&self, world: Tracked) -> SourceResult>; + fn layout_root(&self, world: Tracked) -> SourceResult>; } -impl Layout for Content { +impl LayoutRoot for Content { #[comemo::memoize] - fn layout(&self, world: Tracked) -> SourceResult> { + fn layout_root(&self, world: Tracked) -> SourceResult> { let styles = StyleChain::with_root(&world.config().styles); let scratch = Scratch::default(); @@ -259,7 +259,7 @@ struct Scratch<'a> { /// Determines whether a style could interrupt some composable structure. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] -pub enum Interruption { +enum Interruption { /// The style forces a list break. List, /// The style forces a paragraph break. @@ -269,11 +269,7 @@ pub enum Interruption { } impl<'a> Builder<'a> { - pub fn new( - world: Tracked<'a, dyn World>, - scratch: &'a Scratch<'a>, - top: bool, - ) -> Self { + fn new(world: Tracked<'a, dyn World>, scratch: &'a Scratch<'a>, top: bool) -> Self { Self { world, scratch, @@ -284,7 +280,7 @@ impl<'a> Builder<'a> { } } - pub fn into_doc( + fn into_doc( mut self, styles: StyleChain<'a>, ) -> SourceResult<(DocNode, StyleChain<'a>)> { @@ -293,7 +289,7 @@ impl<'a> Builder<'a> { Ok((DocNode(pages), shared)) } - pub fn into_flow( + fn into_flow( mut self, styles: StyleChain<'a>, ) -> SourceResult<(FlowNode, StyleChain<'a>)> { @@ -302,7 +298,7 @@ impl<'a> Builder<'a> { Ok((FlowNode(children), shared)) } - pub fn accept( + fn accept( &mut self, content: &'a Content, styles: StyleChain<'a>, @@ -740,7 +736,7 @@ enum Last { impl<'a, T> CollapsingBuilder<'a, T> { /// Create a new style-vec builder. - pub fn new() -> Self { + fn new() -> Self { Self { builder: StyleVecBuilder::new(), staged: vec![], @@ -749,7 +745,7 @@ impl<'a, T> CollapsingBuilder<'a, T> { } /// Whether the builder is empty. - pub fn is_empty(&self) -> bool { + fn is_empty(&self) -> bool { self.builder.is_empty() && self.staged.is_empty() } @@ -760,7 +756,7 @@ impl<'a, T> CollapsingBuilder<'a, T> { /// Between weak items, there may be at least one per layer and among the /// candidates the strongest one (smallest `weakness`) wins. When tied, /// the one that compares larger through `PartialOrd` wins. - pub fn weak(&mut self, item: T, styles: StyleChain<'a>, weakness: u8) + fn weak(&mut self, item: T, styles: StyleChain<'a>, weakness: u8) where T: PartialOrd, { @@ -788,31 +784,31 @@ impl<'a, T> CollapsingBuilder<'a, T> { } /// Forces nearby weak items to collapse. - pub fn destructive(&mut self, item: T, styles: StyleChain<'a>) { + fn destructive(&mut self, item: T, styles: StyleChain<'a>) { self.flush(false); self.builder.push(item, styles); self.last = Last::Destructive; } /// Allows nearby weak items to exist. - pub fn supportive(&mut self, item: T, styles: StyleChain<'a>) { + fn supportive(&mut self, item: T, styles: StyleChain<'a>) { self.flush(true); self.builder.push(item, styles); self.last = Last::Supportive; } /// Has no influence on other items. - pub fn ignorant(&mut self, item: T, styles: StyleChain<'a>) { + fn ignorant(&mut self, item: T, styles: StyleChain<'a>) { self.staged.push((item, styles, None)); } /// Iterate over the contained items. - pub fn items(&self) -> impl DoubleEndedIterator { + fn items(&self) -> impl DoubleEndedIterator { self.builder.items().chain(self.staged.iter().map(|(item, ..)| item)) } /// Return the finish style vec and the common prefix chain. - pub fn finish(mut self) -> (StyleVec, StyleChain<'a>) { + fn finish(mut self) -> (StyleVec, StyleChain<'a>) { self.flush(false); self.builder.finish() } diff --git a/library/src/layout/page.rs b/library/src/layout/page.rs index 53a8cbc7..7ce0d633 100644 --- a/library/src/layout/page.rs +++ b/library/src/layout/page.rs @@ -2,6 +2,7 @@ use std::str::FromStr; use super::ColumnsNode; use crate::prelude::*; +use crate::text::TextNode; /// Layouts its child onto one or multiple pages. #[derive(PartialEq, Clone, Hash)] diff --git a/library/src/layout/stack.rs b/library/src/layout/stack.rs index 44bcbf67..ec1063fd 100644 --- a/library/src/layout/stack.rs +++ b/library/src/layout/stack.rs @@ -89,7 +89,7 @@ castable! { } /// Performs stack layout. -pub struct StackLayouter<'a> { +struct StackLayouter<'a> { /// The stacking direction. dir: Dir, /// The axis of the stacking direction. @@ -125,7 +125,7 @@ enum StackItem { impl<'a> StackLayouter<'a> { /// Create a new stack layouter. - pub fn new(dir: Dir, regions: &Regions, styles: StyleChain<'a>) -> Self { + fn new(dir: Dir, regions: &Regions, styles: StyleChain<'a>) -> Self { let axis = dir.axis(); let expand = regions.expand; let full = regions.first; @@ -149,7 +149,7 @@ impl<'a> StackLayouter<'a> { } /// Add spacing along the spacing direction. - pub fn layout_spacing(&mut self, spacing: Spacing) { + fn layout_spacing(&mut self, spacing: Spacing) { match spacing { Spacing::Relative(v) => { // Resolve the spacing and limit it to the remaining space. @@ -169,7 +169,7 @@ impl<'a> StackLayouter<'a> { } /// Layout an arbitrary block. - pub fn layout_block( + fn layout_block( &mut self, world: Tracked, block: &Content, @@ -223,7 +223,7 @@ impl<'a> StackLayouter<'a> { } /// Advance to the next region. - pub fn finish_region(&mut self) { + fn finish_region(&mut self) { // Determine the size of the stack in this region dependening on whether // the region expands. let used = self.used.to_axes(self.axis); @@ -279,7 +279,7 @@ impl<'a> StackLayouter<'a> { } /// Finish layouting and return the resulting frames. - pub fn finish(mut self) -> Vec { + fn finish(mut self) -> Vec { self.finish_region(); self.finished } @@ -287,7 +287,7 @@ impl<'a> StackLayouter<'a> { /// A container with a main and cross component. #[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] -pub struct Gen { +struct Gen { /// The main component. pub cross: T, /// The cross component. @@ -296,12 +296,12 @@ pub struct Gen { impl Gen { /// Create a new instance from the two components. - pub const fn new(cross: T, main: T) -> Self { + const fn new(cross: T, main: T) -> Self { Self { cross, main } } /// Convert to the specific representation, given the current main axis. - pub fn to_axes(self, main: Axis) -> Axes { + fn to_axes(self, main: Axis) -> Axes { match main { Axis::X => Axes::new(self.main, self.cross), Axis::Y => Axes::new(self.cross, self.main), @@ -311,12 +311,12 @@ impl Gen { impl Gen { /// The zero value. - pub fn zero() -> Self { + fn zero() -> Self { Self { cross: Abs::zero(), main: Abs::zero() } } /// Convert to a point. - pub fn to_point(self, main: Axis) -> Point { + fn to_point(self, main: Axis) -> Point { self.to_axes(main).to_point() } } diff --git a/library/src/lib.rs b/library/src/lib.rs index ed332a06..7ffb490c 100644 --- a/library/src/lib.rs +++ b/library/src/lib.rs @@ -1,19 +1,19 @@ //! Typst's standard library. +pub mod base; pub mod graphics; pub mod layout; pub mod math; pub mod prelude; pub mod structure; pub mod text; -pub mod utility; mod ext; use typst::geom::{Align, Color, Dir, GenAlign}; use typst::model::{LangItems, Node, Scope, StyleMap}; -use self::layout::Layout; +use self::layout::LayoutRoot; /// Construct the standard library scope. pub fn scope() -> Scope { @@ -83,32 +83,32 @@ pub fn scope() -> Scope { std.define("NN", "ℕ"); std.define("RR", "ℝ"); - // Utility. - std.def_fn("type", utility::type_); - std.def_fn("assert", utility::assert); - std.def_fn("eval", utility::eval); - std.def_fn("int", utility::int); - std.def_fn("float", utility::float); - std.def_fn("abs", utility::abs); - std.def_fn("min", utility::min); - std.def_fn("max", utility::max); - std.def_fn("even", utility::even); - std.def_fn("odd", utility::odd); - std.def_fn("mod", utility::mod_); - std.def_fn("range", utility::range); - std.def_fn("luma", utility::luma); - std.def_fn("rgb", utility::rgb); - std.def_fn("cmyk", utility::cmyk); - std.def_fn("repr", utility::repr); - std.def_fn("str", utility::str); - std.def_fn("regex", utility::regex); - std.def_fn("letter", utility::letter); - std.def_fn("roman", utility::roman); - std.def_fn("symbol", utility::symbol); - std.def_fn("lorem", utility::lorem); - std.def_fn("csv", utility::csv); - std.def_fn("json", utility::json); - std.def_fn("xml", utility::xml); + // Base. + std.def_fn("type", base::type_); + std.def_fn("assert", base::assert); + std.def_fn("eval", base::eval); + std.def_fn("int", base::int); + std.def_fn("float", base::float); + std.def_fn("abs", base::abs); + std.def_fn("min", base::min); + std.def_fn("max", base::max); + std.def_fn("even", base::even); + std.def_fn("odd", base::odd); + std.def_fn("mod", base::mod_); + std.def_fn("range", base::range); + std.def_fn("luma", base::luma); + std.def_fn("rgb", base::rgb); + std.def_fn("cmyk", base::cmyk); + std.def_fn("repr", base::repr); + std.def_fn("str", base::str); + std.def_fn("regex", base::regex); + std.def_fn("letter", base::letter); + std.def_fn("roman", base::roman); + std.def_fn("symbol", base::symbol); + std.def_fn("lorem", base::lorem); + std.def_fn("csv", base::csv); + std.def_fn("json", base::json); + std.def_fn("xml", base::xml); // Predefined colors. std.define("black", Color::BLACK); @@ -155,7 +155,7 @@ pub fn styles() -> StyleMap { /// Construct the standard lang item mapping. pub fn items() -> LangItems { LangItems { - root: |world, content| content.layout(world), + root: |world, content| content.layout_root(world), em: |styles| styles.get(text::TextNode::SIZE), dir: |styles| styles.get(text::TextNode::DIR), space: || text::SpaceNode.pack(), diff --git a/library/src/math/tex.rs b/library/src/math/tex.rs index 7b40aa2b..7de68d7b 100644 --- a/library/src/math/tex.rs +++ b/library/src/math/tex.rs @@ -5,7 +5,7 @@ use rex::parser::color::RGBA; use rex::render::{Backend, Cursor, Renderer}; use typst::font::Font; -use super::*; +use super::MathNode; use crate::prelude::*; use crate::text::{variant, LinebreakNode, SpaceNode, TextNode}; diff --git a/library/src/prelude.rs b/library/src/prelude.rs index 0c3b0eb1..11095c67 100644 --- a/library/src/prelude.rs +++ b/library/src/prelude.rs @@ -1,27 +1,20 @@ //! Helpful imports for creating library functionality. pub use std::fmt::{self, Debug, Formatter}; -pub use std::hash::Hash; -pub use std::io; pub use std::num::NonZeroUsize; -pub use std::sync::Arc; pub use comemo::Tracked; -pub use typst::diag::{ - bail, error, with_alternative, At, FileError, FileResult, SourceError, SourceResult, - StrResult, -}; +pub use typst::diag::{bail, error, with_alternative, At, SourceResult, StrResult}; pub use typst::frame::*; pub use typst::geom::*; pub use typst::model::{ - array, capability, castable, dict, dynamic, format_str, node, Args, Array, - Capability, Cast, Content, Dict, Dynamic, Fold, Func, Key, LangItems, Node, Resolve, - Scope, Selector, Show, Smart, Str, StyleChain, StyleMap, StyleVec, Value, Vm, + array, capability, castable, dict, dynamic, format_str, node, Args, Array, Cast, + Content, Dict, Fold, Func, Key, Node, Resolve, Scope, Selector, Show, Smart, Str, + StyleChain, StyleMap, StyleVec, Value, Vm, }; pub use typst::syntax::{Span, Spanned}; pub use typst::util::{format_eco, EcoString}; pub use typst::World; pub use super::ext::{ContentExt, StyleMapExt}; -pub use super::layout::{Layout, LayoutBlock, LayoutInline, Regions}; -pub use super::text::{FallbackList, TextNode}; +pub use super::layout::{LayoutBlock, LayoutInline, Regions}; diff --git a/library/src/structure/list.rs b/library/src/structure/list.rs index 2015f19b..d461ef2d 100644 --- a/library/src/structure/list.rs +++ b/library/src/structure/list.rs @@ -1,9 +1,9 @@ use unscanny::Scanner; +use crate::base::Numbering; use crate::layout::{BlockSpacing, GridNode, HNode, TrackSizing}; use crate::prelude::*; -use crate::text::{ParNode, SpaceNode}; -use crate::utility::Numbering; +use crate::text::{ParNode, SpaceNode, TextNode}; /// An unordered (bulleted) or ordered (numbered) list. #[derive(Debug, Hash)] diff --git a/library/src/structure/reference.rs b/library/src/structure/reference.rs index 632ecba5..56f8b8e3 100644 --- a/library/src/structure/reference.rs +++ b/library/src/structure/reference.rs @@ -1,4 +1,5 @@ use crate::prelude::*; +use crate::text::TextNode; /// A reference to a label. #[derive(Debug, Hash)] diff --git a/library/src/text/deco.rs b/library/src/text/deco.rs index cd3acef5..aaf6cfa8 100644 --- a/library/src/text/deco.rs +++ b/library/src/text/deco.rs @@ -70,7 +70,7 @@ impl Show for DecoNode { /// /// For more details, see [`DecoNode`]. #[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct Decoration { +pub(super) struct Decoration { pub line: DecoLine, pub stroke: PartialStroke, pub offset: Smart, @@ -91,7 +91,7 @@ pub const STRIKETHROUGH: DecoLine = 1; pub const OVERLINE: DecoLine = 2; /// Add line decorations to a single run of shaped text. -pub fn decorate( +pub(super) fn decorate( frame: &mut Frame, deco: &Decoration, text: &Text, diff --git a/library/src/text/mod.rs b/library/src/text/mod.rs index d793f614..61edacbe 100644 --- a/library/src/text/mod.rs +++ b/library/src/text/mod.rs @@ -11,7 +11,6 @@ mod shift; pub use deco::*; pub use link::*; pub use par::*; -pub use quotes::*; pub use raw::*; pub use shaping::*; pub use shift::*; @@ -22,6 +21,7 @@ use rustybuzz::Tag; use typst::font::{FontMetrics, FontStretch, FontStyle, FontWeight, VerticalFontMetric}; use typst::util::EcoString; +use self::quotes::*; use crate::prelude::*; /// A single run of text with the same style. @@ -107,22 +107,22 @@ impl TextNode { /// Whether the font weight should be increased by 300. #[property(skip, fold)] - pub const BOLD: Toggle = false; + pub(super) const BOLD: Toggle = false; /// Whether the font style should be inverted. #[property(skip, fold)] - pub const ITALIC: Toggle = false; + pub(super) const ITALIC: Toggle = false; /// A case transformation that should be applied to the text. #[property(skip)] - pub const CASE: Option = None; + pub(super) const CASE: Option = None; /// Whether small capital glyphs should be used. ("smcp") #[property(skip)] - pub const SMALLCAPS: bool = false; + pub(super) const SMALLCAPS: bool = false; /// A destination the text should be linked to. #[property(skip, referenced)] - pub const LINK: Option = None; + pub(crate) const LINK: Option = None; /// Decorative lines. #[property(skip, fold)] - pub const DECO: Decoration = vec![]; + pub(super) const DECO: Decoration = vec![]; fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { // The text constructor is special: It doesn't create a text node. diff --git a/library/src/text/quotes.rs b/library/src/text/quotes.rs index ab4d3f9d..af10de46 100644 --- a/library/src/text/quotes.rs +++ b/library/src/text/quotes.rs @@ -4,7 +4,7 @@ use super::{Lang, Region}; /// State machine for smart quote subtitution. #[derive(Debug, Clone)] -pub struct Quoter { +pub(super) struct Quoter { /// How many quotes have been opened. quote_depth: usize, /// Whether an opening quote might follow. @@ -68,7 +68,7 @@ fn is_opening_bracket(c: char) -> bool { } /// Decides which quotes to subtitute smart quotes with. -pub struct Quotes<'s> { +pub(super) struct Quotes<'s> { /// The opening single quote. pub single_open: &'s str, /// The closing single quote. diff --git a/library/src/text/shaping.rs b/library/src/text/shaping.rs index 32143862..bab02eca 100644 --- a/library/src/text/shaping.rs +++ b/library/src/text/shaping.rs @@ -13,7 +13,7 @@ use crate::prelude::*; /// This type contains owned or borrowed shaped text runs, which can be /// measured, used to reshape substrings more quickly and converted into a /// frame. -pub struct ShapedText<'a> { +pub(super) struct ShapedText<'a> { /// The text that was shaped. pub text: &'a str, /// The text direction. @@ -32,7 +32,7 @@ pub struct ShapedText<'a> { /// A single glyph resulting from shaping. #[derive(Debug, Clone)] -pub struct ShapedGlyph { +pub(super) struct ShapedGlyph { /// The font the glyph is contained in. pub font: Font, /// The glyph's index in the font. @@ -318,7 +318,7 @@ struct ShapingContext<'a> { } /// Shape text into [`ShapedText`]. -pub fn shape<'a>( +pub(super) fn shape<'a>( world: Tracked, text: &'a str, styles: StyleChain<'a>, @@ -534,7 +534,7 @@ fn nbsp_delta(font: &Font) -> Option { Some(font.advance(nbsp)? - font.advance(space)?) } -/// Resolve the font variant with `BOLD` and `ITALIC` factored in. +/// Resolve the font variant. pub fn variant(styles: StyleChain) -> FontVariant { let mut variant = FontVariant::new( styles.get(TextNode::STYLE), diff --git a/library/src/text/shift.rs b/library/src/text/shift.rs index e5f142dd..856d0f96 100644 --- a/library/src/text/shift.rs +++ b/library/src/text/shift.rs @@ -11,7 +11,7 @@ use crate::prelude::*; /// codepoints. If that fails, we fall back to rendering shrunk normal letters /// in a raised way. #[derive(Debug, Hash)] -pub struct ShiftNode(pub Content); +pub struct ShiftNode(pub Content); /// Shift the text into superscript. pub type SuperNode = ShiftNode; @@ -20,7 +20,7 @@ pub type SuperNode = ShiftNode; pub type SubNode = ShiftNode; #[node(Show)] -impl ShiftNode { +impl ShiftNode { /// Whether to prefer the dedicated sub- and superscript characters of the /// font. pub const TYPOGRAPHIC: bool = true; @@ -35,7 +35,7 @@ impl ShiftNode { } } -impl Show for ShiftNode { +impl Show for ShiftNode { fn unguard_parts(&self, _: Selector) -> Content { Self(self.0.clone()).pack() } @@ -72,7 +72,7 @@ impl Show for ShiftNode { /// Find and transform the text contained in `content` to the given script kind /// if and only if it only consists of `Text`, `Space`, and `Empty` leaf nodes. -fn search_text(content: &Content, mode: ScriptKind) -> Option { +fn search_text(content: &Content, mode: ShiftKind) -> Option { if content.is_empty() { Some(EcoString::new()) } else if content.is::() { @@ -114,7 +114,7 @@ fn is_shapable(world: Tracked, text: &str, styles: StyleChain) -> boo /// Convert a string to sub- or superscript codepoints if all characters /// can be mapped to such a codepoint. -fn convert_script(text: &str, mode: ScriptKind) -> Option { +fn convert_script(text: &str, mode: ShiftKind) -> Option { let mut result = EcoString::with_capacity(text.len()); let converter = match mode { SUPERSCRIPT => to_superscript_codepoint, @@ -179,10 +179,10 @@ fn to_subscript_codepoint(c: char) -> Option { } /// A category of script. -pub type ScriptKind = usize; +pub type ShiftKind = usize; /// Text that is rendered smaller and raised, also known as superior. -const SUPERSCRIPT: ScriptKind = 0; +const SUPERSCRIPT: ShiftKind = 0; /// Text that is rendered smaller and lowered, also known as inferior. -const SUBSCRIPT: ScriptKind = 1; +const SUBSCRIPT: ShiftKind = 1; diff --git a/library/src/utility/color.rs b/library/src/utility/color.rs deleted file mode 100644 index 8bb12334..00000000 --- a/library/src/utility/color.rs +++ /dev/null @@ -1,66 +0,0 @@ -use std::str::FromStr; - -use crate::prelude::*; - -/// Create a grayscale color. -pub fn luma(_: &mut Vm, args: &mut Args) -> SourceResult { - let Component(luma) = args.expect("gray component")?; - Ok(Value::Color(LumaColor::new(luma).into())) -} - -/// Create an RGB(A) color. -pub fn rgb(_: &mut Vm, args: &mut Args) -> SourceResult { - Ok(Value::Color( - if let Some(string) = args.find::>()? { - match RgbaColor::from_str(&string.v) { - Ok(color) => color.into(), - Err(msg) => bail!(string.span, msg), - } - } else { - let Component(r) = args.expect("red component")?; - let Component(g) = args.expect("green component")?; - let Component(b) = args.expect("blue component")?; - let Component(a) = args.eat()?.unwrap_or(Component(255)); - RgbaColor::new(r, g, b, a).into() - }, - )) -} - -/// Create a CMYK color. -pub fn cmyk(_: &mut Vm, args: &mut Args) -> SourceResult { - let RatioComponent(c) = args.expect("cyan component")?; - let RatioComponent(m) = args.expect("magenta component")?; - let RatioComponent(y) = args.expect("yellow component")?; - let RatioComponent(k) = args.expect("key component")?; - Ok(Value::Color(CmykColor::new(c, m, y, k).into())) -} - -/// An integer or ratio component. -struct Component(u8); - -castable! { - Component, - Expected: "integer or ratio", - Value::Int(v) => match v { - 0 ..= 255 => Self(v as u8), - _ => Err("must be between 0 and 255")?, - }, - Value::Ratio(v) => if (0.0 ..= 1.0).contains(&v.get()) { - Self((v.get() * 255.0).round() as u8) - } else { - Err("must be between 0% and 100%")? - }, -} - -/// A component that must be a ratio. -struct RatioComponent(u8); - -castable! { - RatioComponent, - Expected: "ratio", - Value::Ratio(v) => if (0.0 ..= 1.0).contains(&v.get()) { - Self((v.get() * 255.0).round() as u8) - } else { - Err("must be between 0% and 100%")? - }, -} diff --git a/library/src/utility/data.rs b/library/src/utility/data.rs deleted file mode 100644 index 3edade55..00000000 --- a/library/src/utility/data.rs +++ /dev/null @@ -1,132 +0,0 @@ -use std::fmt::Write; - -use typst::diag::format_xml_like_error; - -use crate::prelude::*; - -/// Read structured data from a CSV file. -pub fn csv(vm: &mut Vm, args: &mut Args) -> SourceResult { - let Spanned { v: path, span } = - args.expect::>("path to csv file")?; - - let path = vm.locate(&path).at(span)?; - let data = vm.world.file(&path).at(span)?; - - let mut builder = csv::ReaderBuilder::new(); - builder.has_headers(false); - - let mut reader = builder.from_reader(data.as_slice()); - let mut vec = vec![]; - - for result in reader.records() { - let row = result.map_err(format_csv_error).at(span)?; - let array = row.iter().map(|field| Value::Str(field.into())).collect(); - vec.push(Value::Array(array)) - } - - Ok(Value::Array(Array::from_vec(vec))) -} - -/// Format the user-facing CSV error message. -fn format_csv_error(error: csv::Error) -> String { - match error.kind() { - csv::ErrorKind::Utf8 { .. } => "file is not valid utf-8".into(), - csv::ErrorKind::UnequalLengths { pos, expected_len, len } => { - let mut msg = format!( - "failed to parse csv file: found {len} instead of {expected_len} fields" - ); - if let Some(pos) = pos { - write!(msg, " in line {}", pos.line()).unwrap(); - } - msg - } - _ => "failed to parse csv file".into(), - } -} - -/// Read structured data from a JSON file. -pub fn json(vm: &mut Vm, args: &mut Args) -> SourceResult { - let Spanned { v: path, span } = - args.expect::>("path to json file")?; - - let path = vm.locate(&path).at(span)?; - let data = vm.world.file(&path).at(span)?; - let value: serde_json::Value = - serde_json::from_slice(&data).map_err(format_json_error).at(span)?; - - Ok(convert_json(value)) -} - -/// Convert a JSON value to a Typst value. -fn convert_json(value: serde_json::Value) -> Value { - match value { - serde_json::Value::Null => Value::None, - serde_json::Value::Bool(v) => Value::Bool(v), - serde_json::Value::Number(v) => match v.as_i64() { - Some(int) => Value::Int(int), - None => Value::Float(v.as_f64().unwrap_or(f64::NAN)), - }, - serde_json::Value::String(v) => Value::Str(v.into()), - serde_json::Value::Array(v) => { - Value::Array(v.into_iter().map(convert_json).collect()) - } - serde_json::Value::Object(v) => Value::Dict( - v.into_iter() - .map(|(key, value)| (key.into(), convert_json(value))) - .collect(), - ), - } -} - -/// Format the user-facing JSON error message. -fn format_json_error(error: serde_json::Error) -> String { - assert!(error.is_syntax() || error.is_eof()); - format!( - "failed to parse json file: syntax error in line {}", - error.line() - ) -} - -/// Read structured data from an XML file. -pub fn xml(vm: &mut Vm, args: &mut Args) -> SourceResult { - let Spanned { v: path, span } = - args.expect::>("path to xml file")?; - - let path = vm.locate(&path).at(span)?; - let data = vm.world.file(&path).at(span)?; - let text = std::str::from_utf8(&data).map_err(FileError::from).at(span)?; - - let document = roxmltree::Document::parse(text).map_err(format_xml_error).at(span)?; - - Ok(convert_xml(document.root())) -} - -/// Convert an XML node to a Typst value. -fn convert_xml(node: roxmltree::Node) -> Value { - if node.is_text() { - return Value::Str(node.text().unwrap_or_default().into()); - } - - let children: Array = node.children().map(convert_xml).collect(); - if node.is_root() { - return Value::Array(children); - } - - let tag: Str = node.tag_name().name().into(); - let attrs: Dict = node - .attributes() - .iter() - .map(|attr| (attr.name().into(), attr.value().into())) - .collect(); - - Value::Dict(dict! { - "tag" => tag, - "attrs" => attrs, - "children" => children, - }) -} - -/// Format the user-facing XML error message. -fn format_xml_error(error: roxmltree::Error) -> String { - format_xml_like_error("xml file", error) -} diff --git a/library/src/utility/math.rs b/library/src/utility/math.rs deleted file mode 100644 index dd37e8e7..00000000 --- a/library/src/utility/math.rs +++ /dev/null @@ -1,144 +0,0 @@ -use std::cmp::Ordering; - -use crate::prelude::*; - -/// Convert a value to an integer. -pub fn int(_: &mut Vm, args: &mut Args) -> SourceResult { - let Spanned { v, span } = args.expect("value")?; - Ok(Value::Int(match v { - Value::Bool(v) => v as i64, - Value::Int(v) => v, - Value::Float(v) => v as i64, - Value::Str(v) => match v.parse() { - Ok(v) => v, - Err(_) => bail!(span, "invalid integer"), - }, - v => bail!(span, "cannot convert {} to integer", v.type_name()), - })) -} - -/// Convert a value to a float. -pub fn float(_: &mut Vm, args: &mut Args) -> SourceResult { - let Spanned { v, span } = args.expect("value")?; - Ok(Value::Float(match v { - Value::Int(v) => v as f64, - Value::Float(v) => v, - Value::Str(v) => match v.parse() { - Ok(v) => v, - Err(_) => bail!(span, "invalid float"), - }, - v => bail!(span, "cannot convert {} to float", v.type_name()), - })) -} - -/// The absolute value of a numeric value. -pub fn abs(_: &mut Vm, args: &mut Args) -> SourceResult { - let Spanned { v, span } = args.expect("numeric value")?; - Ok(match v { - Value::Int(v) => Value::Int(v.abs()), - Value::Float(v) => Value::Float(v.abs()), - Value::Angle(v) => Value::Angle(v.abs()), - Value::Ratio(v) => Value::Ratio(v.abs()), - Value::Fraction(v) => Value::Fraction(v.abs()), - Value::Length(_) | Value::Relative(_) => { - bail!(span, "cannot take absolute value of a length") - } - v => bail!(span, "expected numeric value, found {}", v.type_name()), - }) -} - -/// The minimum of a sequence of values. -pub fn min(_: &mut Vm, args: &mut Args) -> SourceResult { - minmax(args, Ordering::Less) -} - -/// The maximum of a sequence of values. -pub fn max(_: &mut Vm, args: &mut Args) -> SourceResult { - minmax(args, Ordering::Greater) -} - -/// Find the minimum or maximum of a sequence of values. -fn minmax(args: &mut Args, goal: Ordering) -> SourceResult { - let mut extremum = args.expect::("value")?; - for Spanned { v, span } in args.all::>()? { - match v.partial_cmp(&extremum) { - Some(ordering) => { - if ordering == goal { - extremum = v; - } - } - None => bail!( - span, - "cannot compare {} and {}", - extremum.type_name(), - v.type_name(), - ), - } - } - Ok(extremum) -} - -/// Whether an integer is even. -pub fn even(_: &mut Vm, args: &mut Args) -> SourceResult { - Ok(Value::Bool(args.expect::("integer")? % 2 == 0)) -} - -/// Whether an integer is odd. -pub fn odd(_: &mut Vm, args: &mut Args) -> SourceResult { - Ok(Value::Bool(args.expect::("integer")? % 2 != 0)) -} - -/// The modulo of two numbers. -pub fn mod_(_: &mut Vm, args: &mut Args) -> SourceResult { - let Spanned { v: v1, span: span1 } = args.expect("integer or float")?; - let Spanned { v: v2, span: span2 } = args.expect("integer or float")?; - - let (a, b) = match (v1, v2) { - (Value::Int(a), Value::Int(b)) => match a.checked_rem(b) { - Some(res) => return Ok(Value::Int(res)), - None => bail!(span2, "divisor must not be zero"), - }, - (Value::Int(a), Value::Float(b)) => (a as f64, b), - (Value::Float(a), Value::Int(b)) => (a, b as f64), - (Value::Float(a), Value::Float(b)) => (a, b), - (Value::Int(_), b) | (Value::Float(_), b) => bail!( - span2, - format!("expected integer or float, found {}", b.type_name()) - ), - (a, _) => bail!( - span1, - format!("expected integer or float, found {}", a.type_name()) - ), - }; - - if b == 0.0 { - bail!(span2, "divisor must not be zero"); - } - - Ok(Value::Float(a % b)) -} - -/// Create a sequence of numbers. -pub fn range(_: &mut Vm, args: &mut Args) -> SourceResult { - let first = args.expect::("end")?; - let (start, end) = match args.eat::()? { - Some(second) => (first, second), - None => (0, first), - }; - - let step: i64 = match args.named("step")? { - Some(Spanned { v: 0, span }) => bail!(span, "step must not be zero"), - Some(Spanned { v, .. }) => v, - None => 1, - }; - - let mut x = start; - let mut seq = vec![]; - - while x.cmp(&end) == 0.cmp(&step) { - seq.push(Value::Int(x)); - x += step; - } - - Ok(Value::Array(Array::from_vec(seq))) -} diff --git a/library/src/utility/mod.rs b/library/src/utility/mod.rs deleted file mode 100644 index 402944cd..00000000 --- a/library/src/utility/mod.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! Computational utility functions. - -mod color; -mod data; -mod math; -mod string; - -pub use color::*; -pub use data::*; -pub use math::*; -pub use string::*; - -use comemo::Track; -use typst::model::{Eval, Route, Scopes, Vm}; -use typst::syntax::Source; - -use crate::prelude::*; - -/// The name of a value's type. -pub fn type_(_: &mut Vm, args: &mut Args) -> SourceResult { - Ok(args.expect::("value")?.type_name().into()) -} - -/// Ensure that a condition is fulfilled. -pub fn assert(_: &mut Vm, args: &mut Args) -> SourceResult { - let Spanned { v, span } = args.expect::>("condition")?; - if !v { - bail!(span, "assertion failed"); - } - Ok(Value::None) -} - -/// Evaluate a string as Typst markup. -pub fn eval(vm: &mut Vm, args: &mut Args) -> SourceResult { - let Spanned { v: text, span } = args.expect::>("source")?; - - // Parse the source and set a synthetic span for all nodes. - let source = Source::synthesized(text, span); - let ast = source.ast()?; - - // Evaluate the source. - let std = &vm.world.config().scope; - let scopes = Scopes::new(Some(std)); - let route = Route::default(); - let mut sub = Vm::new(vm.world, route.track(), None, scopes); - let result = ast.eval(&mut sub); - - // Handle control flow. - if let Some(flow) = sub.flow { - bail!(flow.forbidden()); - } - - Ok(Value::Content(result?)) -} diff --git a/library/src/utility/string.rs b/library/src/utility/string.rs deleted file mode 100644 index ed444d35..00000000 --- a/library/src/utility/string.rs +++ /dev/null @@ -1,141 +0,0 @@ -use typst::model::Regex; - -use crate::prelude::*; - -/// The string representation of a value. -pub fn repr(_: &mut Vm, args: &mut Args) -> SourceResult { - Ok(args.expect::("value")?.repr().into()) -} - -/// Convert a value to a string. -pub fn str(_: &mut Vm, args: &mut Args) -> SourceResult { - let Spanned { v, span } = args.expect("value")?; - Ok(Value::Str(match v { - Value::Int(v) => format_str!("{}", v), - Value::Float(v) => format_str!("{}", v), - Value::Str(v) => v, - v => bail!(span, "cannot convert {} to string", v.type_name()), - })) -} - -/// Create blind text. -pub fn lorem(_: &mut Vm, args: &mut Args) -> SourceResult { - let words: usize = args.expect("number of words")?; - Ok(Value::Str(lipsum::lipsum(words).into())) -} - -/// Create a regular expression. -pub fn regex(_: &mut Vm, args: &mut Args) -> SourceResult { - let Spanned { v, span } = args.expect::>("regular expression")?; - Ok(Regex::new(&v).at(span)?.into()) -} - -/// Converts an integer into one or multiple letters. -pub fn letter(_: &mut Vm, args: &mut Args) -> SourceResult { - numbered(Numbering::Letter, args) -} - -/// Converts an integer into a roman numeral. -pub fn roman(_: &mut Vm, args: &mut Args) -> SourceResult { - numbered(Numbering::Roman, args) -} - -/// Convert a number into a symbol. -pub fn symbol(_: &mut Vm, args: &mut Args) -> SourceResult { - numbered(Numbering::Symbol, args) -} - -fn numbered(numbering: Numbering, args: &mut Args) -> SourceResult { - let n = args.expect::("non-negative integer")?; - Ok(Value::Str(numbering.apply(n).into())) -} - -/// Allows to convert a number into letters, roman numerals and symbols. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum Numbering { - Arabic, - Letter, - Roman, - Symbol, -} - -impl Numbering { - /// Apply the numbering to the given number. - pub fn apply(self, mut n: usize) -> EcoString { - match self { - Self::Arabic => { - format_eco!("{}", n) - } - Self::Letter => { - if n == 0 { - return '-'.into(); - } - - n -= 1; - - let mut letters = vec![]; - loop { - letters.push(b'a' + (n % 26) as u8); - n /= 26; - if n == 0 { - break; - } - } - - letters.reverse(); - String::from_utf8(letters).unwrap().into() - } - Self::Roman => { - if n == 0 { - return 'N'.into(); - } - - // Adapted from Yann Villessuzanne's roman.rs under the Unlicense, at - // https://github.com/linfir/roman.rs/ - let mut fmt = EcoString::new(); - for &(name, value) in ROMANS { - while n >= value { - n -= value; - fmt.push_str(name); - } - } - - fmt - } - Self::Symbol => { - if n == 0 { - return '-'.into(); - } - - let symbol = SYMBOLS[(n - 1) % SYMBOLS.len()]; - let amount = ((n - 1) / SYMBOLS.len()) + 1; - std::iter::repeat(symbol).take(amount).collect() - } - } - } -} - -const ROMANS: &[(&str, usize)] = &[ - ("M̅", 1000000), - ("D̅", 500000), - ("C̅", 100000), - ("L̅", 50000), - ("X̅", 10000), - ("V̅", 5000), - ("I̅V̅", 4000), - ("M", 1000), - ("CM", 900), - ("D", 500), - ("CD", 400), - ("C", 100), - ("XC", 90), - ("L", 50), - ("XL", 40), - ("X", 10), - ("IX", 9), - ("V", 5), - ("IV", 4), - ("I", 1), -]; - -const SYMBOLS: &[char] = &['*', '†', '‡', '§', '‖', '¶']; -- cgit v1.2.3