use std::num::NonZeroI64; use std::str::FromStr; use typst::eval::Regex; use crate::prelude::*; /// Convert a value to an integer. /// /// - Booleans are converted to `0` or `1`. /// - Floats are floored to the next 64-bit integer. /// - Strings are parsed in base 10. /// /// ## Example /// ```example /// #int(false) \ /// #int(true) \ /// #int(2.7) \ /// #{ int("27") + int("4") } /// ``` /// /// Display: Integer /// Category: construct /// Returns: integer #[func] pub fn int( /// The value that should be converted to an integer. value: ToInt, ) -> Value { Value::Int(value.0) } /// A value that can be cast to an integer. struct ToInt(i64); cast_from_value! { ToInt, v: bool => Self(v as i64), v: i64 => Self(v), v: f64 => Self(v as i64), v: EcoString => Self(v.parse().map_err(|_| "not a valid integer")?), } /// Convert a value to a float. /// /// - Booleans are converted to `0.0` or `1.0`. /// - Integers are converted to the closest 64-bit float. /// - Ratios are divided by 100%. /// - Strings are parsed in base 10 to the closest 64-bit float. /// Exponential notation is supported. /// /// ## Example /// ```example /// #float(false) \ /// #float(true) \ /// #float(4) \ /// #float(40%) \ /// #float("2.7") \ /// #float("1e5") /// ``` /// /// Display: Float /// Category: construct /// Returns: float #[func] pub fn float( /// The value that should be converted to a float. value: ToFloat, ) -> Value { Value::Float(value.0) } /// A value that can be cast to a float. struct ToFloat(f64); cast_from_value! { ToFloat, v: bool => Self(v as i64 as f64), v: i64 => Self(v as f64), v: f64 => Self(v), v: Ratio => Self(v.get()), v: EcoString => Self(v.parse().map_err(|_| "not a valid float")?), } /// Create a grayscale color. /// /// ## Example /// ```example /// #for x in range(250, step: 50) { /// box(square(fill: luma(x))) /// } /// ``` /// /// Display: Luma /// Category: construct /// Returns: color #[func] pub fn luma( /// The gray component. gray: Component, ) -> Value { Value::Color(LumaColor::new(gray.0).into()) } /// Create an RGB(A) color. /// /// The color is specified in the sRGB color space. /// /// _Note:_ While you can specify transparent colors and Typst's preview will /// render them correctly, the PDF export does not handle them properly at the /// moment. This will be fixed in the future. /// /// ## Example /// ```example /// #square(fill: rgb("#b1f2eb")) /// #square(fill: rgb(87, 127, 230)) /// #square(fill: rgb(25%, 13%, 65%)) /// ``` /// /// Display: RGB /// Category: construct /// Returns: color #[func] pub fn rgb( /// The color in hexadecimal notation. /// /// Accepts three, four, six or eight hexadecimal digits and optionally /// a leading hashtag. /// /// If this string is given, the individual components should not be given. /// /// ```example /// #text(16pt, rgb("#239dad"))[ /// *Typst* /// ] /// ``` #[external] #[default] hex: EcoString, /// The red component. #[external] #[default] red: Component, /// The green component. #[external] #[default] green: Component, /// The blue component. #[external] #[default] blue: Component, /// The alpha component. #[external] #[default] alpha: Component, ) -> Value { 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() }) } /// An integer or ratio component. struct Component(u8); cast_from_value! { Component, v: i64 => match v { 0 ..= 255 => Self(v as u8), _ => Err("number must be between 0 and 255")?, }, v: Ratio => if (0.0 ..= 1.0).contains(&v.get()) { Self((v.get() * 255.0).round() as u8) } else { Err("ratio must be between 0% and 100%")? }, } /// Create a CMYK color. /// /// This is useful if you want to target a specific printer. The conversion /// to RGB for display preview might differ from how your printer reproduces /// the color. /// /// ## Example /// ```example /// #square( /// fill: cmyk(27%, 0%, 3%, 5%) /// ) /// ```` /// /// Display: CMYK /// Category: construct /// Returns: color #[func] pub fn cmyk( /// The cyan component. cyan: RatioComponent, /// The magenta component. magenta: RatioComponent, /// The yellow component. yellow: RatioComponent, /// The key component. key: RatioComponent, ) -> Value { Value::Color(CmykColor::new(cyan.0, magenta.0, yellow.0, key.0).into()) } /// A component that must be a ratio. struct RatioComponent(u8); cast_from_value! { RatioComponent, v: Ratio => if (0.0 ..= 1.0).contains(&v.get()) { Self((v.get() * 255.0).round() as u8) } else { Err("ratio must be between 0% and 100%")? }, } /// Create a custom symbol with modifiers. /// /// ## Example /// ```example /// #let envelope = symbol( /// "🖂", /// ("stamped", "🖃"), /// ("stamped.pen", "🖆"), /// ("lightning", "🖄"), /// ("fly", "🖅"), /// ) /// /// #envelope /// #envelope.stamped /// #envelope.stamped.pen /// #envelope.lightning /// #envelope.fly /// ``` /// /// Display: Symbol /// Category: construct /// Returns: symbol #[func] pub fn symbol( /// The variants of the symbol. /// /// Can be a just a string consisting of a single character for the /// modifierless variant or an array with two strings specifying the modifiers /// and the symbol. Individual modifiers should be separated by dots. When /// displaying a symbol, Typst selects the first from the variants that have /// all attached modifiers and the minimum number of other modifiers. #[variadic] variants: Vec>, ) -> Value { let mut list = Vec::new(); if variants.is_empty() { bail!(args.span, "expected at least one variant"); } for Spanned { v, span } in variants { if list.iter().any(|(prev, _)| &v.0 == prev) { bail!(span, "duplicate variant"); } list.push((v.0, v.1)); } Value::Symbol(Symbol::runtime(list.into_boxed_slice())) } /// A value that can be cast to a symbol. struct Variant(EcoString, char); cast_from_value! { Variant, c: char => Self(EcoString::new(), c), array: Array => { let mut iter = array.into_iter(); match (iter.next(), iter.next(), iter.next()) { (Some(a), Some(b), None) => Self(a.cast()?, b.cast()?), _ => Err("point array must contain exactly two entries")?, } }, } /// Convert a value to a string. /// /// - Integers are formatted in base 10. /// - Floats are formatted in base 10 and never in exponential notation. /// - From labels the name is extracted. /// /// ## Example /// ```example /// #str(10) \ /// #str(2.7) \ /// #str(1e8) \ /// #str() /// ``` /// /// Display: String /// Category: construct /// Returns: string #[func] pub fn str( /// The value that should be converted to a string. value: ToStr, ) -> Value { Value::Str(value.0) } /// A value that can be cast to a string. struct ToStr(Str); cast_from_value! { ToStr, v: i64 => Self(format_str!("{}", v)), v: f64 => Self(format_str!("{}", v)), v: Label => Self(v.0.into()), v: Str => Self(v), } /// Create a label from a string. /// /// Inserting a label into content attaches it to the closest previous element /// that is not a space. Then, the element can be [referenced]($func/ref) and /// styled through the label. /// /// ## Example /// ```example /// #show : set text(blue) /// #show label("b"): set text(red) /// /// = Heading /// *Strong* #label("b") /// ``` /// /// ## Syntax /// This function also has dedicated syntax: You can create a label by enclosing /// its name in angle brackets. This works both in markup and code. /// /// Display: Label /// Category: construct /// Returns: label #[func] pub fn label( /// The name of the label. name: EcoString, ) -> Value { Value::Label(Label(name)) } /// Create a regular expression from a string. /// /// The result can be used as a /// [show rule selector]($styling/#show-rules) and with /// [string methods]($type/string) like `find`, `split`, and `replace`. /// /// [See here](https://docs.rs/regex/latest/regex/#syntax) for a specification /// of the supported syntax. /// /// ## Example /// ```example /// // Works with show rules. /// #show regex("\d+"): set text(red) /// /// The numbers 1 to 10. /// /// // Works with string methods. /// #("a,b;c" /// .split(regex("[,;]"))) /// ``` /// /// Display: Regex /// Category: construct /// Returns: regex #[func] pub fn regex( /// The regular expression as a string. /// /// Most regex escape sequences just work because they are not valid Typst /// escape sequences. To produce regex escape sequences that are also valid in /// Typst (e.g. `[\\]`), you need to escape twice. Thus, to match a verbatim /// backslash, you would need to write `{regex("\\\\")}`. /// /// If you need many escape sequences, you can also create a raw element /// and extract its text to use it for your regular expressions: /// ```{regex(`\d+\.\d+\.\d+`.text)}```. regex: Spanned, ) -> Value { Regex::new(®ex.v).at(regex.span)?.into() } /// Create an array consisting of a sequence of numbers. /// /// If you pass just one positional parameter, it is interpreted as the `end` of /// the range. If you pass two, they describe the `start` and `end` of the /// range. /// /// ## Example /// ```example /// #range(5) \ /// #range(2, 5) \ /// #range(20, step: 4) \ /// #range(21, step: 4) \ /// #range(5, 2, step: -1) /// ``` /// /// Display: Range /// Category: construct /// Returns: array #[func] pub fn range( /// The start of the range (inclusive). #[external] #[default] start: i64, /// The end of the range (exclusive). #[external] end: i64, /// The distance between the generated numbers. #[named] #[default(NonZeroI64::new(1).unwrap())] step: NonZeroI64, ) -> Value { let first = args.expect::("end")?; let (start, end) = match args.eat::()? { Some(second) => (first, second), None => (0, first), }; let step = step.get(); let mut x = start; let mut array = Array::new(); while x.cmp(&end) == 0.cmp(&step) { array.push(Value::Int(x)); x += step; } Value::Array(array) }