typst_library/foundations/
fields.rs

1//! Fields on values.
2
3use ecow::{eco_format, EcoString};
4
5use crate::diag::StrResult;
6use crate::foundations::{IntoValue, Type, Value, Version};
7use crate::layout::{Alignment, Length, Rel};
8use crate::visualize::Stroke;
9
10/// Try to access a field on a value.
11///
12/// This function is exclusively for types which have predefined fields, such as
13/// stroke and length.
14pub(crate) fn field(value: &Value, field: &str) -> StrResult<Value> {
15    let ty = value.ty();
16    let nope = || Err(no_fields(ty));
17    let missing = || Err(missing_field(ty, field));
18
19    // Special cases, such as module and dict, are handled by Value itself
20    let result = match value {
21        Value::Version(version) => match version.component(field) {
22            Ok(i) => i.into_value(),
23            Err(_) => return missing(),
24        },
25        Value::Length(length) => match field {
26            "em" => length.em.get().into_value(),
27            "abs" => length.abs.into_value(),
28            _ => return missing(),
29        },
30        Value::Relative(rel) => match field {
31            "ratio" => rel.rel.into_value(),
32            "length" => rel.abs.into_value(),
33            _ => return missing(),
34        },
35        Value::Dyn(dynamic) => {
36            if let Some(stroke) = dynamic.downcast::<Stroke>() {
37                match field {
38                    "paint" => stroke.paint.clone().into_value(),
39                    "thickness" => stroke.thickness.into_value(),
40                    "cap" => stroke.cap.into_value(),
41                    "join" => stroke.join.into_value(),
42                    "dash" => stroke.dash.clone().into_value(),
43                    "miter-limit" => {
44                        stroke.miter_limit.map(|limit| limit.get()).into_value()
45                    }
46                    _ => return missing(),
47                }
48            } else if let Some(align) = dynamic.downcast::<Alignment>() {
49                match field {
50                    "x" => align.x().into_value(),
51                    "y" => align.y().into_value(),
52                    _ => return missing(),
53                }
54            } else {
55                return nope();
56            }
57        }
58        _ => return nope(),
59    };
60
61    Ok(result)
62}
63
64/// The error message for a type not supporting field access.
65#[cold]
66fn no_fields(ty: Type) -> EcoString {
67    eco_format!("cannot access fields on type {ty}")
68}
69
70/// The missing field error message.
71#[cold]
72fn missing_field(ty: Type, field: &str) -> EcoString {
73    eco_format!("{ty} does not contain field \"{field}\"")
74}
75
76/// List the available fields for a type.
77pub fn fields_on(ty: Type) -> &'static [&'static str] {
78    if ty == Type::of::<Version>() {
79        &Version::COMPONENTS
80    } else if ty == Type::of::<Length>() {
81        &["em", "abs"]
82    } else if ty == Type::of::<Rel>() {
83        &["ratio", "length"]
84    } else if ty == Type::of::<Stroke>() {
85        &["paint", "thickness", "cap", "join", "dash", "miter-limit"]
86    } else if ty == Type::of::<Alignment>() {
87        &["x", "y"]
88    } else {
89        &[]
90    }
91}