summaryrefslogtreecommitdiff
path: root/library/src/compute/construct.rs
diff options
context:
space:
mode:
Diffstat (limited to 'library/src/compute/construct.rs')
-rw-r--r--library/src/compute/construct.rs743
1 files changed, 0 insertions, 743 deletions
diff --git a/library/src/compute/construct.rs b/library/src/compute/construct.rs
deleted file mode 100644
index 956212ee..00000000
--- a/library/src/compute/construct.rs
+++ /dev/null
@@ -1,743 +0,0 @@
-use std::num::NonZeroI64;
-use std::str::FromStr;
-
-use time::{Month, PrimitiveDateTime};
-
-use typst::eval::{Datetime, Regex};
-
-use crate::prelude::*;
-
-/// Converts 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 }
-/// ```example
-/// #int(false) \
-/// #int(true) \
-/// #int(2.7) \
-/// #{ int("27") + int("4") }
-/// ```
-///
-/// Display: Integer
-/// Category: construct
-#[func]
-pub fn int(
- /// The value that should be converted to an integer.
- value: ToInt,
-) -> i64 {
- value.0
-}
-
-/// A value that can be cast to an integer.
-pub struct ToInt(i64);
-
-cast! {
- ToInt,
- v: bool => Self(v as i64),
- v: i64 => Self(v),
- v: f64 => Self(v as i64),
- v: EcoString => Self(v.parse().map_err(|_| eco_format!("invalid integer: {}", v))?),
-}
-
-/// Converts 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 }
-/// ```example
-/// #float(false) \
-/// #float(true) \
-/// #float(4) \
-/// #float(40%) \
-/// #float("2.7") \
-/// #float("1e5")
-/// ```
-///
-/// Display: Float
-/// Category: construct
-#[func]
-pub fn float(
- /// The value that should be converted to a float.
- value: ToFloat,
-) -> f64 {
- value.0
-}
-
-/// A value that can be cast to a float.
-pub struct ToFloat(f64);
-
-cast! {
- 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(|_| eco_format!("invalid float: {}", v))?),
-}
-
-/// Creates a grayscale color.
-///
-/// ## Example { #example }
-/// ```example
-/// #for x in range(250, step: 50) {
-/// box(square(fill: luma(x)))
-/// }
-/// ```
-///
-/// Display: Luma
-/// Category: construct
-#[func]
-pub fn luma(
- /// The gray component.
- gray: Component,
-) -> Color {
- LumaColor::new(gray.0).into()
-}
-
-/// Creates 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 }
-/// ```example
-/// #square(fill: rgb("#b1f2eb"))
-/// #square(fill: rgb(87, 127, 230))
-/// #square(fill: rgb(25%, 13%, 65%))
-/// ```
-///
-/// Display: RGB
-/// Category: construct
-#[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]
- hex: EcoString,
- /// The red component.
- #[external]
- red: Component,
- /// The green component.
- #[external]
- green: Component,
- /// The blue component.
- #[external]
- blue: Component,
- /// The alpha component.
- #[external]
- alpha: Component,
- /// The arguments.
- args: Args,
-) -> SourceResult<Color> {
- let mut args = args;
- Ok(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.
-pub struct Component(u8);
-
-cast! {
- Component,
- v: i64 => match v {
- 0 ..= 255 => Self(v as u8),
- _ => bail!("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 {
- bail!("ratio must be between 0% and 100%");
- },
-}
-
-/// Creates a new datetime.
-///
-/// You can specify the [datetime]($type/datetime) using a year, month, day,
-/// hour, minute, and second. You can also get the current date with
-/// [`datetime.today`]($func/datetime.today).
-///
-/// ## Example
-/// ```example
-/// #let date = datetime(
-/// year: 2012,
-/// month: 8,
-/// day: 3,
-/// )
-///
-/// #date.display() \
-/// #date.display(
-/// "[day].[month].[year]"
-/// )
-/// ```
-///
-/// ## Format
-/// _Note_: Depending on which components of the datetime you specify, Typst
-/// will store it in one of the following three ways:
-/// * If you specify year, month and day, Typst will store just a date.
-/// * If you specify hour, minute and second, Typst will store just a time.
-/// * If you specify all of year, month, day, hour, minute and second, Typst
-/// will store a full datetime.
-///
-/// Depending on how it is stored, the [`display`]($type/datetime.display)
-/// method will choose a different formatting by default.
-///
-/// Display: Datetime
-/// Category: construct
-#[func]
-#[scope(
- scope.define("today", datetime_today_func());
- scope
-)]
-pub fn datetime(
- /// The year of the datetime.
- #[named]
- year: Option<YearComponent>,
- /// The month of the datetime.
- #[named]
- month: Option<MonthComponent>,
- /// The day of the datetime.
- #[named]
- day: Option<DayComponent>,
- /// The hour of the datetime.
- #[named]
- hour: Option<HourComponent>,
- /// The minute of the datetime.
- #[named]
- minute: Option<MinuteComponent>,
- /// The second of the datetime.
- #[named]
- second: Option<SecondComponent>,
-) -> StrResult<Datetime> {
- let time = match (hour, minute, second) {
- (Some(hour), Some(minute), Some(second)) => {
- match time::Time::from_hms(hour.0, minute.0, second.0) {
- Ok(time) => Some(time),
- Err(_) => bail!("time is invalid"),
- }
- }
- (None, None, None) => None,
- _ => bail!("time is incomplete"),
- };
-
- let date = match (year, month, day) {
- (Some(year), Some(month), Some(day)) => {
- match time::Date::from_calendar_date(year.0, month.0, day.0) {
- Ok(date) => Some(date),
- Err(_) => bail!("date is invalid"),
- }
- }
- (None, None, None) => None,
- _ => bail!("date is incomplete"),
- };
-
- Ok(match (date, time) {
- (Some(date), Some(time)) => {
- Datetime::Datetime(PrimitiveDateTime::new(date, time))
- }
- (Some(date), None) => Datetime::Date(date),
- (None, Some(time)) => Datetime::Time(time),
- (None, None) => {
- bail!("at least one of date or time must be fully specified")
- }
- })
-}
-
-pub struct YearComponent(i32);
-pub struct MonthComponent(Month);
-pub struct DayComponent(u8);
-pub struct HourComponent(u8);
-pub struct MinuteComponent(u8);
-pub struct SecondComponent(u8);
-
-cast! {
- YearComponent,
- v: i32 => Self(v),
-}
-
-cast! {
- MonthComponent,
- v: u8 => Self(Month::try_from(v).map_err(|_| "month is invalid")?)
-}
-
-cast! {
- DayComponent,
- v: u8 => Self(v),
-}
-
-cast! {
- HourComponent,
- v: u8 => Self(v),
-}
-
-cast! {
- MinuteComponent,
- v: u8 => Self(v),
-}
-
-cast! {
- SecondComponent,
- v: u8 => Self(v),
-}
-
-/// Returns the current date.
-///
-/// Refer to the documentation of the [`display`]($type/datetime.display) method
-/// for details on how to affect the formatting of the date.
-///
-/// ## Example
-/// ```example
-/// Today's date is
-/// #datetime.today().display().
-/// ```
-///
-/// Display: Today
-/// Category: construct
-#[func]
-pub fn datetime_today(
- /// An offset to apply to the current UTC date. If set to `{auto}`, the
- /// offset will be the local offset.
- #[named]
- #[default]
- offset: Smart<i64>,
- /// The virtual machine.
- vt: &mut Vt,
-) -> StrResult<Datetime> {
- Ok(vt
- .world
- .today(offset.as_custom())
- .ok_or("unable to get the current date")?)
-}
-
-/// Creates 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 }
-/// ```example
-/// #square(
-/// fill: cmyk(27%, 0%, 3%, 5%)
-/// )
-/// ````
-///
-/// Display: CMYK
-/// Category: construct
-#[func]
-pub fn cmyk(
- /// The cyan component.
- cyan: RatioComponent,
- /// The magenta component.
- magenta: RatioComponent,
- /// The yellow component.
- yellow: RatioComponent,
- /// The key component.
- key: RatioComponent,
-) -> Color {
- CmykColor::new(cyan.0, magenta.0, yellow.0, key.0).into()
-}
-
-/// A component that must be a ratio.
-pub struct RatioComponent(u8);
-
-cast! {
- RatioComponent,
- v: Ratio => if (0.0 ..= 1.0).contains(&v.get()) {
- Self((v.get() * 255.0).round() as u8)
- } else {
- bail!("ratio must be between 0% and 100%");
- },
-}
-
-/// Creates a custom symbol with modifiers.
-///
-/// ## Example { #example }
-/// ```example
-/// #let envelope = symbol(
-/// "🖂",
-/// ("stamped", "🖃"),
-/// ("stamped.pen", "🖆"),
-/// ("lightning", "🖄"),
-/// ("fly", "🖅"),
-/// )
-///
-/// #envelope
-/// #envelope.stamped
-/// #envelope.stamped.pen
-/// #envelope.lightning
-/// #envelope.fly
-/// ```
-///
-/// Display: Symbol
-/// Category: construct
-#[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<Spanned<Variant>>,
- /// The callsite span.
- span: Span,
-) -> SourceResult<Symbol> {
- let mut list = Vec::new();
- if variants.is_empty() {
- bail!(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));
- }
- Ok(Symbol::runtime(list.into_boxed_slice()))
-}
-
-/// A value that can be cast to a symbol.
-pub struct Variant(EcoString, char);
-
-cast! {
- 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()?),
- _ => bail!("point array must contain exactly two entries"),
- }
- },
-}
-
-/// Converts a value to a string.
-///
-/// - Integers are formatted in base 10. This can be overridden with the
-/// optional `base` parameter.
-/// - Floats are formatted in base 10 and never in exponential notation.
-/// - From labels the name is extracted.
-///
-/// If you wish to convert from and to Unicode code points, see
-/// [`str.to-unicode`]($func/str.to-unicode) and
-/// [`str.from-unicode`]($func/str.from-unicode).
-///
-/// ## Example { #example }
-/// ```example
-/// #str(10) \
-/// #str(4000, base: 16) \
-/// #str(2.7) \
-/// #str(1e8) \
-/// #str(<intro>)
-/// ```
-///
-/// Display: String
-/// Category: construct
-#[func]
-#[scope(
- scope.define("to-unicode", str_to_unicode_func());
- scope.define("from-unicode", str_from_unicode_func());
- scope
-)]
-pub fn str(
- /// The value that should be converted to a string.
- value: ToStr,
- /// The base (radix) to display integers in, between 2 and 36.
- #[named]
- #[default(Spanned::new(10, Span::detached()))]
- base: Spanned<i64>,
-) -> SourceResult<Str> {
- Ok(match value {
- ToStr::Str(s) => {
- if base.v != 10 {
- bail!(base.span, "base is only supported for integers");
- }
- s
- }
- ToStr::Int(n) => {
- if base.v < 2 || base.v > 36 {
- bail!(base.span, "base must be between 2 and 36");
- }
- int_to_base(n, base.v).into()
- }
- })
-}
-
-/// A value that can be cast to a string.
-pub enum ToStr {
- /// A string value ready to be used as-is.
- Str(Str),
- /// An integer about to be formatted in a given base.
- Int(i64),
-}
-
-cast! {
- ToStr,
- v: i64 => Self::Int(v),
- v: f64 => Self::Str(format_str!("{}", v)),
- v: Label => Self::Str(v.0.into()),
- v: Str => Self::Str(v),
-}
-
-/// Format an integer in a base.
-fn int_to_base(mut n: i64, base: i64) -> EcoString {
- if n == 0 {
- return "0".into();
- }
-
- // In Rust, `format!("{:x}", -14i64)` is not `-e` but `fffffffffffffff2`.
- // So we can only use the built-in for decimal, not bin/oct/hex.
- if base == 10 {
- return eco_format!("{n}");
- }
-
- // The largest output is `to_base(i64::MIN, 2)`, which is 65 chars long.
- const SIZE: usize = 65;
- let mut digits = [b'\0'; SIZE];
- let mut i = SIZE;
-
- // It's tempting to take the absolute value, but this will fail for i64::MIN.
- // Instead, we turn n negative, as -i64::MAX is perfectly representable.
- let negative = n < 0;
- if n > 0 {
- n = -n;
- }
-
- while n != 0 {
- let digit = char::from_digit(-(n % base) as u32, base as u32);
- i -= 1;
- digits[i] = digit.unwrap_or('?') as u8;
- n /= base;
- }
-
- if negative {
- i -= 1;
- digits[i] = b'-';
- }
-
- std::str::from_utf8(&digits[i..]).unwrap_or_default().into()
-}
-
-/// Converts a character into its corresponding code point.
-///
-/// ## Example
-/// ```example
-/// #str.to-unicode("a") \
-/// #"a\u{0300}".codepoints().map(str.to-unicode)
-/// ```
-///
-/// Display: String To Unicode
-/// Category: construct
-#[func]
-pub fn str_to_unicode(
- /// The character that should be converted.
- value: char,
-) -> u32 {
- value.into()
-}
-
-/// Converts a Unicode code point into its corresponding string.
-///
-/// ```example
-/// #str.from-unicode(97)
-/// ```
-///
-/// Display: String From Unicode
-/// Category: construct
-#[func]
-pub fn str_from_unicode(
- /// The code point that should be converted.
- value: CodePoint,
-) -> Str {
- format_str!("{}", value.0)
-}
-
-/// The numeric representation of a single unicode code point.
-pub struct CodePoint(char);
-
-cast! {
- CodePoint,
- v: i64 => {
- Self(v.try_into().ok().and_then(|v: u32| v.try_into().ok()).ok_or_else(
- || eco_format!("{:#x} is not a valid codepoint", v),
- )?)
- },
-}
-
-/// Creates 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 }
-/// ```example
-/// #show <a>: set text(blue)
-/// #show label("b"): set text(red)
-///
-/// = Heading <a>
-/// *Strong* #label("b")
-/// ```
-///
-/// ## Syntax { #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
-#[func]
-pub fn label(
- /// The name of the label.
- name: EcoString,
-) -> Label {
- Label(name)
-}
-
-/// Creates 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 [the specification of the supported syntax](https://docs.rs/regex/latest/regex/#syntax).
-///
-/// ## Example { #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
-#[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<EcoString>,
-) -> SourceResult<Regex> {
- Regex::new(&regex.v).at(regex.span)
-}
-
-/// Creates an array consisting of consecutive integers.
-///
-/// 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 }
-/// ```example
-/// #range(5) \
-/// #range(2, 5) \
-/// #range(20, step: 4) \
-/// #range(21, step: 4) \
-/// #range(5, 2, step: -1)
-/// ```
-///
-/// Display: Range
-/// Category: construct
-#[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,
- /// The arguments.
- args: Args,
-) -> SourceResult<Array> {
- let mut args = args;
- let first = args.expect::<i64>("end")?;
- let (start, end) = match args.eat::<i64>()? {
- 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;
- }
-
- Ok(array)
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_to_base() {
- assert_eq!(&int_to_base(0, 10), "0");
- assert_eq!(&int_to_base(0, 16), "0");
- assert_eq!(&int_to_base(0, 36), "0");
- assert_eq!(
- &int_to_base(i64::MAX, 2),
- "111111111111111111111111111111111111111111111111111111111111111"
- );
- assert_eq!(
- &int_to_base(i64::MIN, 2),
- "-1000000000000000000000000000000000000000000000000000000000000000"
- );
- assert_eq!(&int_to_base(i64::MAX, 10), "9223372036854775807");
- assert_eq!(&int_to_base(i64::MIN, 10), "-9223372036854775808");
- assert_eq!(&int_to_base(i64::MAX, 16), "7fffffffffffffff");
- assert_eq!(&int_to_base(i64::MIN, 16), "-8000000000000000");
- assert_eq!(&int_to_base(i64::MAX, 36), "1y2p0ij32e8e7");
- assert_eq!(&int_to_base(i64::MIN, 36), "-1y2p0ij32e8e8");
- }
-}