diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-12-21 23:51:15 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-12-21 23:51:15 +0100 |
| commit | 4c92ab4ace41a20ccea59bbdf6917b5dfa29121f (patch) | |
| tree | 5d444b04657dfa0521884953520ed40af37efd4e /library/src/compute/construct.rs | |
| parent | 1eda162867afab656ec9b95c85f725cd676d4f04 (diff) | |
Rename the `create` category to `construct`
Diffstat (limited to 'library/src/compute/construct.rs')
| -rw-r--r-- | library/src/compute/construct.rs | 392 |
1 files changed, 392 insertions, 0 deletions
diff --git a/library/src/compute/construct.rs b/library/src/compute/construct.rs new file mode 100644 index 00000000..bfe42cbe --- /dev/null +++ b/library/src/compute/construct.rs @@ -0,0 +1,392 @@ +use std::str::FromStr; + +use typst::model::Regex; + +use crate::prelude::*; + +/// # Integer +/// 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 +/// ``` +/// #int(false) \ +/// #int(true) \ +/// #int(2.7) \ +/// { int("27") + int("4") } +/// ``` +/// +/// ## Parameters +/// - value: ToInt (positional, required) +/// The value that should be converted to an integer. +/// +/// ## Category +/// construct +#[func] +pub fn int(args: &mut Args) -> SourceResult<Value> { + Ok(Value::Int(args.expect::<ToInt>("value")?.0)) +} + +/// A value that can be cast to an integer. +struct ToInt(i64); + +castable! { + 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")?), +} + +/// # Float +/// Convert a value to a float. +/// +/// - Booleans are converted to `0.0` or `1.0`. +/// - Integers are converted to the closest 64-bit float. +/// - Strings are parsed in base 10 to the closest 64-bit float. +/// Exponential notation is supported. +/// +/// ## Example +/// ``` +/// #float(false) \ +/// #float(true) \ +/// #float(4) \ +/// #float("2.7") \ +/// #float("1e5") +/// ``` +/// +/// ## Parameters +/// - value: ToFloat (positional, required) +/// The value that should be converted to a float. +/// +/// ## Category +/// construct +#[func] +pub fn float(args: &mut Args) -> SourceResult<Value> { + Ok(Value::Float(args.expect::<ToFloat>("value")?.0)) +} + +/// A value that can be cast to a float. +struct ToFloat(f64); + +castable! { + ToFloat, + v: bool => Self(v as i64 as f64), + v: i64 => Self(v as f64), + v: f64 => Self(v), + v: EcoString => Self(v.parse().map_err(|_| "not a valid float")?), +} + +/// # Luma +/// Create a grayscale color. +/// +/// ## Example +/// ``` +/// #for x in range(250, step: 50) { +/// square(fill: luma(x)) +/// } +/// ``` +/// +/// ## Parameters +/// - gray: Component (positional, required) +/// The gray component. +/// +/// ## Category +/// construct +#[func] +pub fn luma(args: &mut Args) -> SourceResult<Value> { + let Component(luma) = args.expect("gray component")?; + Ok(Value::Color(LumaColor::new(luma).into())) +} + +/// # RGBA +/// 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 +/// ``` +/// #square(fill: rgb("#b1f2eb")) +/// #square(fill: rgb(87, 127, 230)) +/// #square(fill: rgb(25%, 13%, 65%)) +/// ``` +/// +/// ## Parameters +/// - hex: EcoString (positional) +/// The color in hexademical 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* +/// ] +/// ``` +/// +/// - red: Component (positional) +/// The red component. +/// +/// - green: Component (positional) +/// The green component. +/// +/// - blue: Component (positional) +/// The blue component. +/// +/// - alpha: Component (positional) +/// The alpha component. +/// +/// ## Category +/// construct +#[func] +pub fn rgb(args: &mut Args) -> SourceResult<Value> { + Ok(Value::Color(if let Some(string) = args.find::<Spanned<EcoString>>()? { + 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); + +castable! { + Component, + v: i64 => match v { + 0 ..= 255 => Self(v as u8), + _ => Err("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("must be between 0% and 100%")? + }, +} + +/// # CMYK +/// 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 +/// ``` +/// #square( +/// fill: cmyk(27%, 0%, 3%, 5%) +/// ) +/// ```` +/// +/// ## Parameters +/// - cyan: RatioComponent (positional, required) +/// The cyan component. +/// +/// - magenta: RatioComponent (positional, required) +/// The magenta component. +/// +/// - yellow: RatioComponent (positional, required) +/// The yellow component. +/// +/// - key: RatioComponent (positional, required) +/// The key component. +/// +/// ## Category +/// construct +#[func] +pub fn cmyk(args: &mut Args) -> SourceResult<Value> { + 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())) +} + +/// A component that must be a ratio. +struct RatioComponent(u8); + +castable! { + RatioComponent, + v: Ratio => if (0.0 ..= 1.0).contains(&v.get()) { + Self((v.get() * 255.0).round() as u8) + } else { + Err("must be between 0% and 100%")? + }, +} + +/// # String +/// 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 +/// ``` +/// #str(10) \ +/// #str(2.7) \ +/// #str(1e8) \ +/// #str(<intro>) +/// ``` +/// +/// ## Parameters +/// - value: ToStr (positional, required) +/// The value that should be converted to a string. +/// +/// ## Category +/// construct +#[func] +pub fn str(args: &mut Args) -> SourceResult<Value> { + Ok(Value::Str(args.expect::<ToStr>("value")?.0)) +} + +/// A value that can be cast to a string. +struct ToStr(Str); + +castable! { + ToStr, + v: i64 => Self(format_str!("{}", v)), + v: f64 => Self(format_str!("{}", v)), + v: Label => Self(v.0.into()), + v: Str => Self(v), +} + +/// # Label +/// 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](@ref) and styled +/// through the label. +/// +/// ## Example +/// ``` +/// #show <a>: set text(blue) +/// #show label("b"): set text(red) +/// +/// = Heading <a> +/// *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. +/// +/// ## Parameters +/// - name: EcoString (positional, required) +/// The name of the label. +/// +/// ## Category +/// construct +#[func] +pub fn label(args: &mut Args) -> SourceResult<Value> { + Ok(Value::Label(Label(args.expect("string")?))) +} + +/// # Regex +/// Create a regular expression from a string. +/// +/// The result can be used as a show rule +/// [selector](/docs/reference/concepts/#selector) and with +/// [string methods](/docs/reference/concepts/#methods) like `find`, `split`, +/// and `replace`. +/// +/// [See here](https://docs.rs/regex/latest/regex/#syntax) for a specification +/// of the supported syntax. +/// +/// ## 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("[,;]")) } +/// ``` +/// +/// ## Parameters +/// - regex: EcoString (positional, required) +/// 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("\\\\")}`. +/// +/// ## Category +/// construct +#[func] +pub fn regex(args: &mut Args) -> SourceResult<Value> { + let Spanned { v, span } = args.expect::<Spanned<EcoString>>("regular expression")?; + Ok(Regex::new(&v).at(span)?.into()) +} + +/// # Range +/// 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 +/// ``` +/// #range(5) \ +/// #range(2, 5) \ +/// #range(20, step: 4) \ +/// #range(21, step: 4) \ +/// #range(5, 2, step: -1) +/// ``` +/// +/// ## Parameters +/// - start: i64 (positional) +/// The start of the range (inclusive). +/// +/// - end: i64 (positional, required) +/// The end of the range (exclusive). +/// +/// - step: i64 (named) +/// The distance between the generated numbers. +/// +/// ## Category +/// construct +#[func] +pub fn range(args: &mut Args) -> SourceResult<Value> { + let first = args.expect::<i64>("end")?; + let (start, end) = match args.eat::<i64>()? { + 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))) +} |
