mirror of
https://github.com/typst/typst
synced 2025-06-28 08:12:53 +08:00
Add fields and methods to several primitives (#790)
This commit is contained in:
parent
507efc3a1c
commit
9b1a2b41f0
@ -33,10 +33,23 @@ pub struct AlignElem {
|
||||
/// - `horizon`
|
||||
/// - `bottom`
|
||||
///
|
||||
/// You may use the `axis` method on a single-axis alignment to obtain
|
||||
/// whether it is `{"horizontal"}` or `{"vertical"}`. You may also use the
|
||||
/// `inv` method to obtain its inverse alignment. For example,
|
||||
/// `{top.axis()}` is `{"vertical"}`, while `{top.inv()}` is equal to
|
||||
/// `{bottom}`.
|
||||
///
|
||||
/// To align along both axes at the same time, add the two alignments using
|
||||
/// the `+` operator to get a `2d alignment`. For example, `top + right`
|
||||
/// aligns the content to the top right corner.
|
||||
///
|
||||
/// For 2d alignments, you may use the `x` and `y` fields to access their
|
||||
/// horizontal and vertical components, respectively. Additionally, you may
|
||||
/// use the `inv` method to obtain a 2d alignment with both components
|
||||
/// inverted. For instance, `{(top + right).x}` is `right`,
|
||||
/// `{(top + right).y}` is `top`, and `{(top + right).inv()}` is equal to
|
||||
/// `bottom + left`.
|
||||
///
|
||||
/// ```example
|
||||
/// #set page(height: 6cm)
|
||||
/// #set text(lang: "ar")
|
||||
|
@ -26,6 +26,15 @@ pub struct StackElem {
|
||||
/// - `{rtl}`: Right to left.
|
||||
/// - `{ttb}`: Top to bottom.
|
||||
/// - `{btt}`: Bottom to top.
|
||||
///
|
||||
/// You may use the `start` and `end` methods to obtain the initial and
|
||||
/// final points (respectively) of a direction, as `alignment`. You may
|
||||
/// also use the `axis` method to obtain whether a direction is
|
||||
/// `{"horizontal"}` or `{"vertical"}`. Finally, the `inv` method returns
|
||||
/// its inverse direction.
|
||||
///
|
||||
/// For example, `{ttb.start()}` is `top`, `{ttb.end()}` is `bottom`,
|
||||
/// `{ttb.axis()}` is `{"vertical"}` and `{ttb.inv()}` is equal to `btt`.
|
||||
#[default(Dir::TTB)]
|
||||
pub dir: Dir,
|
||||
|
||||
|
@ -95,6 +95,9 @@ fn items() -> LangItems {
|
||||
elem.pack()
|
||||
},
|
||||
term_item: |term, description| layout::TermItem::new(term, description).pack(),
|
||||
rgb_func: compute::rgb_func(),
|
||||
cmyk_func: compute::cmyk_func(),
|
||||
luma_func: compute::luma_func(),
|
||||
equation: |body, block| math::EquationElem::new(body).with_block(block).pack(),
|
||||
math_align_point: || math::AlignPointElem::new().pack(),
|
||||
math_delimited: |open, body, close| math::LrElem::new(open + body + close).pack(),
|
||||
|
@ -69,6 +69,11 @@ pub struct LineElem {
|
||||
/// the array above), and `phase` (of type [length]($type/length)),
|
||||
/// which defines where in the pattern to start drawing.
|
||||
///
|
||||
/// Note that, for any `stroke` object, you may access any of the fields
|
||||
/// mentioned in the dictionary format above. For example,
|
||||
/// `{(2pt + blue).thickness}` is `{2pt}`, `{(2pt + blue).miter-limit}` is
|
||||
/// `{4.0}` (the default), and so on.
|
||||
///
|
||||
/// ```example
|
||||
/// #set line(length: 100%)
|
||||
/// #stack(
|
||||
|
93
crates/typst/src/eval/fields.rs
Normal file
93
crates/typst/src/eval/fields.rs
Normal file
@ -0,0 +1,93 @@
|
||||
use ecow::{eco_format, EcoString};
|
||||
|
||||
use crate::diag::StrResult;
|
||||
use crate::geom::{Axes, GenAlign, PartialStroke, Stroke};
|
||||
|
||||
use super::{IntoValue, Value};
|
||||
|
||||
/// Try to access a field on a value.
|
||||
/// This function is exclusively for types which have
|
||||
/// predefined fields, such as stroke and length.
|
||||
pub(crate) fn field(value: &Value, field: &str) -> StrResult<Value> {
|
||||
let name = value.type_name();
|
||||
let not_supported = || Err(no_fields(name));
|
||||
let missing = || Err(missing_field(name, field));
|
||||
|
||||
// Special cases, such as module and dict, are handled by Value itself
|
||||
let result = match value {
|
||||
Value::Length(length) => match field {
|
||||
"em" => length.em.into_value(),
|
||||
"abs" => length.abs.into_value(),
|
||||
_ => return missing(),
|
||||
},
|
||||
Value::Relative(rel) => match field {
|
||||
"ratio" => rel.rel.into_value(),
|
||||
"length" => rel.abs.into_value(),
|
||||
_ => return missing(),
|
||||
},
|
||||
Value::Dyn(dynamic) => {
|
||||
if let Some(stroke) = dynamic.downcast::<PartialStroke>() {
|
||||
match field {
|
||||
"paint" => stroke
|
||||
.paint
|
||||
.clone()
|
||||
.unwrap_or_else(|| Stroke::default().paint)
|
||||
.into_value(),
|
||||
"thickness" => stroke
|
||||
.thickness
|
||||
.unwrap_or_else(|| Stroke::default().thickness.into())
|
||||
.into_value(),
|
||||
"cap" => stroke
|
||||
.line_cap
|
||||
.unwrap_or_else(|| Stroke::default().line_cap)
|
||||
.into_value(),
|
||||
"join" => stroke
|
||||
.line_join
|
||||
.unwrap_or_else(|| Stroke::default().line_join)
|
||||
.into_value(),
|
||||
"dash" => stroke.dash_pattern.clone().unwrap_or(None).into_value(),
|
||||
"miter-limit" => stroke
|
||||
.miter_limit
|
||||
.unwrap_or_else(|| Stroke::default().miter_limit)
|
||||
.0
|
||||
.into_value(),
|
||||
_ => return missing(),
|
||||
}
|
||||
} else if let Some(align2d) = dynamic.downcast::<Axes<GenAlign>>() {
|
||||
match field {
|
||||
"x" => align2d.x.into_value(),
|
||||
"y" => align2d.y.into_value(),
|
||||
_ => return missing(),
|
||||
}
|
||||
} else {
|
||||
return not_supported();
|
||||
}
|
||||
}
|
||||
_ => return not_supported(),
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// The error message for a type not supporting field access.
|
||||
#[cold]
|
||||
fn no_fields(type_name: &str) -> EcoString {
|
||||
eco_format!("cannot access fields on type {type_name}")
|
||||
}
|
||||
|
||||
/// The missing field error message.
|
||||
#[cold]
|
||||
fn missing_field(type_name: &str, field: &str) -> EcoString {
|
||||
eco_format!("{type_name} does not contain field \"{field}\"")
|
||||
}
|
||||
|
||||
/// List the available fields for a type.
|
||||
pub fn fields_on(type_name: &str) -> &[&'static str] {
|
||||
match type_name {
|
||||
"length" => &["em", "abs"],
|
||||
"relative length" => &["ratio", "length"],
|
||||
"stroke" => &["paint", "thickness", "cap", "join", "dash", "miter-limit"],
|
||||
"2d alignment" => &["x", "y"],
|
||||
_ => &[],
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ use comemo::Tracked;
|
||||
use ecow::EcoString;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use super::{Args, Dynamic, Module, Value, Vm};
|
||||
use super::{Args, Dynamic, Module, NativeFunc, Value, Vm};
|
||||
use crate::diag::SourceResult;
|
||||
use crate::doc::Document;
|
||||
use crate::geom::{Abs, Dir};
|
||||
@ -77,6 +77,12 @@ pub struct LangItems {
|
||||
pub enum_item: fn(number: Option<usize>, body: Content) -> Content,
|
||||
/// An item in a term list: `/ Term: Details`.
|
||||
pub term_item: fn(term: Content, description: Content) -> Content,
|
||||
/// The constructor for the 'rgba' color kind.
|
||||
pub rgb_func: &'static NativeFunc,
|
||||
/// The constructor for the 'cmyk' color kind.
|
||||
pub cmyk_func: &'static NativeFunc,
|
||||
/// The constructor for the 'luma' color kind.
|
||||
pub luma_func: &'static NativeFunc,
|
||||
/// A mathematical equation: `$x$`, `$ x^2 $`.
|
||||
pub equation: fn(body: Content, block: bool) -> Content,
|
||||
/// An alignment point in math: `&`.
|
||||
@ -144,6 +150,9 @@ impl Hash for LangItems {
|
||||
self.list_item.hash(state);
|
||||
self.enum_item.hash(state);
|
||||
self.term_item.hash(state);
|
||||
self.rgb_func.hash(state);
|
||||
self.cmyk_func.hash(state);
|
||||
self.luma_func.hash(state);
|
||||
self.equation.hash(state);
|
||||
self.math_align_point.hash(state);
|
||||
self.math_delimited.hash(state);
|
||||
|
@ -1,10 +1,11 @@
|
||||
//! Methods on values.
|
||||
|
||||
use ecow::EcoString;
|
||||
use ecow::{eco_format, EcoString};
|
||||
|
||||
use super::{Args, IntoValue, Str, Value, Vm};
|
||||
use crate::diag::{At, SourceResult};
|
||||
use crate::eval::Datetime;
|
||||
use crate::diag::{At, Hint, SourceResult};
|
||||
use crate::eval::{bail, Datetime};
|
||||
use crate::geom::{Align, Axes, Color, Dir, Em, GenAlign};
|
||||
use crate::model::{Location, Selector};
|
||||
use crate::syntax::Span;
|
||||
|
||||
@ -24,6 +25,29 @@ pub fn call(
|
||||
"lighten" => color.lighten(args.expect("amount")?).into_value(),
|
||||
"darken" => color.darken(args.expect("amount")?).into_value(),
|
||||
"negate" => color.negate().into_value(),
|
||||
"kind" => match color {
|
||||
Color::Luma(_) => vm.items.luma_func.into_value(),
|
||||
Color::Rgba(_) => vm.items.rgb_func.into_value(),
|
||||
Color::Cmyk(_) => vm.items.cmyk_func.into_value(),
|
||||
},
|
||||
"hex" => color.to_rgba().to_hex().into_value(),
|
||||
"rgba" => color.to_rgba().to_array().into_value(),
|
||||
"cmyk" => match color {
|
||||
Color::Luma(luma) => luma.to_cmyk().to_array().into_value(),
|
||||
Color::Rgba(_) => {
|
||||
bail!(span, "cannot obtain cmyk values from rgba color")
|
||||
}
|
||||
Color::Cmyk(cmyk) => cmyk.to_array().into_value(),
|
||||
},
|
||||
"luma" => match color {
|
||||
Color::Luma(luma) => luma.0.into_value(),
|
||||
Color::Rgba(_) => {
|
||||
bail!(span, "cannot obtain the luma value of rgba color")
|
||||
}
|
||||
Color::Cmyk(_) => {
|
||||
bail!(span, "cannot obtain the luma value of cmyk color")
|
||||
}
|
||||
},
|
||||
_ => return missing(),
|
||||
},
|
||||
|
||||
@ -152,6 +176,30 @@ pub fn call(
|
||||
_ => return missing(),
|
||||
},
|
||||
|
||||
Value::Length(length) => match method {
|
||||
unit @ ("pt" | "cm" | "mm" | "inches") => {
|
||||
if length.em != Em::zero() {
|
||||
return Err(eco_format!("cannot convert a length with non-zero em units ({length:?}) to {unit}"))
|
||||
.hint(eco_format!("use 'length.abs.{unit}()' instead to ignore its em component"))
|
||||
.at(span);
|
||||
}
|
||||
match unit {
|
||||
"pt" => length.abs.to_pt().into_value(),
|
||||
"cm" => length.abs.to_cm().into_value(),
|
||||
"mm" => length.abs.to_mm().into_value(),
|
||||
"inches" => length.abs.to_inches().into_value(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
_ => return missing(),
|
||||
},
|
||||
|
||||
Value::Angle(angle) => match method {
|
||||
"deg" => angle.to_deg().into_value(),
|
||||
"rad" => angle.to_rad().into_value(),
|
||||
_ => return missing(),
|
||||
},
|
||||
|
||||
Value::Args(args) => match method {
|
||||
"pos" => args.to_pos().into_value(),
|
||||
"named" => args.to_named().into_value(),
|
||||
@ -198,6 +246,27 @@ pub fn call(
|
||||
"second" => datetime.second().into_value(),
|
||||
_ => return missing(),
|
||||
}
|
||||
} else if let Some(direction) = dynamic.downcast::<Dir>() {
|
||||
match method {
|
||||
"axis" => direction.axis().description().into_value(),
|
||||
"start" => {
|
||||
GenAlign::from(Align::from(direction.start())).into_value()
|
||||
}
|
||||
"end" => GenAlign::from(Align::from(direction.end())).into_value(),
|
||||
"inv" => direction.inv().into_value(),
|
||||
_ => return missing(),
|
||||
}
|
||||
} else if let Some(align) = dynamic.downcast::<GenAlign>() {
|
||||
match method {
|
||||
"axis" => align.axis().description().into_value(),
|
||||
"inv" => align.inv().into_value(),
|
||||
_ => return missing(),
|
||||
}
|
||||
} else if let Some(align2d) = dynamic.downcast::<Axes<GenAlign>>() {
|
||||
match method {
|
||||
"inv" => align2d.map(GenAlign::inv).into_value(),
|
||||
_ => return missing(),
|
||||
}
|
||||
} else {
|
||||
return (vm.items.library_method)(vm, &dynamic, method, args, span);
|
||||
}
|
||||
@ -294,7 +363,16 @@ fn missing_method(type_name: &str, method: &str) -> String {
|
||||
/// List the available methods for a type and whether they take arguments.
|
||||
pub fn methods_on(type_name: &str) -> &[(&'static str, bool)] {
|
||||
match type_name {
|
||||
"color" => &[("lighten", true), ("darken", true), ("negate", false)],
|
||||
"color" => &[
|
||||
("lighten", true),
|
||||
("darken", true),
|
||||
("negate", false),
|
||||
("kind", false),
|
||||
("hex", false),
|
||||
("rgba", false),
|
||||
("cmyk", false),
|
||||
("luma", false),
|
||||
],
|
||||
"string" => &[
|
||||
("len", false),
|
||||
("at", true),
|
||||
@ -357,9 +435,16 @@ pub fn methods_on(type_name: &str) -> &[(&'static str, bool)] {
|
||||
("values", false),
|
||||
],
|
||||
"function" => &[("where", true), ("with", true)],
|
||||
"length" => &[("pt", false), ("cm", false), ("mm", false), ("inches", false)],
|
||||
"angle" => &[("deg", false), ("rad", false)],
|
||||
"arguments" => &[("named", false), ("pos", false)],
|
||||
"location" => &[("page", false), ("position", false), ("page-numbering", false)],
|
||||
"selector" => &[("or", true), ("and", true), ("before", true), ("after", true)],
|
||||
"direction" => {
|
||||
&[("axis", false), ("start", false), ("end", false), ("inv", false)]
|
||||
}
|
||||
"alignment" => &[("axis", false), ("inv", false)],
|
||||
"2d alignment" => &[("inv", false)],
|
||||
"counter" => &[
|
||||
("display", true),
|
||||
("at", true),
|
||||
|
@ -15,6 +15,7 @@ mod value;
|
||||
mod args;
|
||||
mod auto;
|
||||
mod datetime;
|
||||
mod fields;
|
||||
mod func;
|
||||
mod int;
|
||||
mod methods;
|
||||
@ -43,6 +44,7 @@ pub use self::cast::{
|
||||
};
|
||||
pub use self::datetime::Datetime;
|
||||
pub use self::dict::{dict, Dict};
|
||||
pub use self::fields::fields_on;
|
||||
pub use self::func::{Func, FuncInfo, NativeFunc, Param, ParamInfo};
|
||||
pub use self::library::{set_lang_items, LangItems, Library};
|
||||
pub use self::methods::methods_on;
|
||||
|
@ -8,7 +8,7 @@ use ecow::eco_format;
|
||||
use siphasher::sip128::{Hasher128, SipHasher13};
|
||||
|
||||
use super::{
|
||||
cast, format_str, ops, Args, Array, CastInfo, Content, Dict, FromValue, Func,
|
||||
cast, fields, format_str, ops, Args, Array, CastInfo, Content, Dict, FromValue, Func,
|
||||
IntoValue, Module, Reflect, Str, Symbol,
|
||||
};
|
||||
use crate::diag::StrResult;
|
||||
@ -132,7 +132,7 @@ impl Value {
|
||||
Self::Content(content) => content.at(field, None),
|
||||
Self::Module(module) => module.get(field).cloned(),
|
||||
Self::Func(func) => func.get(field).cloned(),
|
||||
v => Err(eco_format!("cannot access fields on type {}", v.type_name())),
|
||||
_ => fields::field(self, field),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,6 +98,15 @@ impl GenAlign {
|
||||
Self::Specific(align) => align.axis(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The inverse alignment.
|
||||
pub const fn inv(self) -> Self {
|
||||
match self {
|
||||
Self::Start => Self::End,
|
||||
Self::End => Self::Start,
|
||||
Self::Specific(align) => Self::Specific(align.inv()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Align> for GenAlign {
|
||||
|
@ -178,14 +178,19 @@ impl Axis {
|
||||
Self::Y => Self::X,
|
||||
}
|
||||
}
|
||||
|
||||
/// A description of this axis' direction.
|
||||
pub fn description(self) -> &'static str {
|
||||
match self {
|
||||
Self::X => "horizontal",
|
||||
Self::Y => "vertical",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Axis {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad(match self {
|
||||
Self::X => "horizontal",
|
||||
Self::Y => "vertical",
|
||||
})
|
||||
f.pad(self.description())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
use ecow::{eco_format, EcoString};
|
||||
use std::str::FromStr;
|
||||
|
||||
use super::*;
|
||||
@ -287,6 +288,20 @@ impl RgbaColor {
|
||||
a: self.a,
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts this color to a RGB Hex Code.
|
||||
pub fn to_hex(self) -> EcoString {
|
||||
if self.a != 255 {
|
||||
eco_format!("#{:02x}{:02x}{:02x}{:02x}", self.r, self.g, self.b, self.a)
|
||||
} else {
|
||||
eco_format!("#{:02x}{:02x}{:02x}", self.r, self.g, self.b)
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts this color to an array of R, G, B, A components.
|
||||
pub fn to_array(self) -> Array {
|
||||
array![self.r, self.g, self.b, self.a]
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for RgbaColor {
|
||||
@ -335,11 +350,7 @@ impl Debug for RgbaColor {
|
||||
if f.alternate() {
|
||||
write!(f, "rgba({}, {}, {}, {})", self.r, self.g, self.b, self.a,)?;
|
||||
} else {
|
||||
write!(f, "rgb(\"#{:02x}{:02x}{:02x}", self.r, self.g, self.b)?;
|
||||
if self.a != 255 {
|
||||
write!(f, "{:02x}", self.a)?;
|
||||
}
|
||||
write!(f, "\")")?;
|
||||
write!(f, "rgb(\"{}\")", self.to_hex())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -420,6 +431,13 @@ impl CmykColor {
|
||||
k: self.k,
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts this color to an array of C, M, Y, K components.
|
||||
pub fn to_array(self) -> Array {
|
||||
// convert to ratio
|
||||
let g = |c| Ratio::new(c as f64 / 255.0);
|
||||
array![g(self.c), g(self.m), g(self.y), g(self.k)]
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for CmykColor {
|
||||
@ -442,6 +460,11 @@ impl From<CmykColor> for Color {
|
||||
}
|
||||
}
|
||||
|
||||
cast! {
|
||||
CmykColor,
|
||||
self => Value::Color(self.into()),
|
||||
}
|
||||
|
||||
/// Convert to the closest u8.
|
||||
fn round_u8(value: f64) -> u8 {
|
||||
value.round() as u8
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::eval::{Cast, FromValue};
|
||||
use crate::eval::{dict, Cast, FromValue, NoneValue};
|
||||
|
||||
use super::*;
|
||||
|
||||
@ -159,7 +159,12 @@ impl<T: Debug> Debug for PartialStroke<T> {
|
||||
sep = ", ";
|
||||
}
|
||||
if let Smart::Custom(dash) = &dash_pattern {
|
||||
write!(f, "{}dash: {:?}", sep, dash)?;
|
||||
write!(f, "{}dash: ", sep)?;
|
||||
if let Some(dash) = dash {
|
||||
Debug::fmt(dash, f)?;
|
||||
} else {
|
||||
Debug::fmt(&NoneValue, f)?;
|
||||
}
|
||||
sep = ", ";
|
||||
}
|
||||
if let Smart::Custom(miter_limit) = &miter_limit {
|
||||
@ -322,6 +327,7 @@ impl Resolve for DashPattern {
|
||||
// https://tex.stackexchange.com/questions/45275/tikz-get-values-for-predefined-dash-patterns
|
||||
cast! {
|
||||
DashPattern,
|
||||
self => dict! { "array" => self.array, "phase" => self.phase }.into_value(),
|
||||
|
||||
"solid" => Vec::new().into(),
|
||||
"dotted" => vec![DashLength::LineWidth, Abs::pt(2.0).into()].into(),
|
||||
@ -348,7 +354,7 @@ cast! {
|
||||
}
|
||||
|
||||
/// The length of a dash in a line dash pattern
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
#[derive(Clone, Eq, PartialEq, Hash)]
|
||||
pub enum DashLength<T = Length> {
|
||||
LineWidth,
|
||||
Length(T),
|
||||
@ -369,6 +375,15 @@ impl<T> DashLength<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug> Debug for DashLength<T> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::LineWidth => write!(f, "\"dot\""),
|
||||
Self::Length(v) => Debug::fmt(v, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve for DashLength {
|
||||
type Output = DashLength<Abs>;
|
||||
|
||||
@ -382,6 +397,11 @@ impl Resolve for DashLength {
|
||||
|
||||
cast! {
|
||||
DashLength,
|
||||
self => match self {
|
||||
Self::LineWidth => "dot".into_value(),
|
||||
Self::Length(v) => v.into_value(),
|
||||
},
|
||||
|
||||
"dot" => Self::LineWidth,
|
||||
v: Length => Self::Length(v),
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ use unscanny::Scanner;
|
||||
use super::analyze::analyze_labels;
|
||||
use super::{analyze_expr, analyze_import, plain_docs_sentence, summarize_font_family};
|
||||
use crate::doc::Frame;
|
||||
use crate::eval::{format_str, methods_on, CastInfo, Library, Scope, Value};
|
||||
use crate::eval::{fields_on, format_str, methods_on, CastInfo, Library, Scope, Value};
|
||||
use crate::syntax::{
|
||||
ast, is_id_continue, is_id_start, is_ident, LinkedNode, Source, SyntaxKind,
|
||||
};
|
||||
@ -360,6 +360,20 @@ fn field_access_completions(ctx: &mut CompletionContext, value: &Value) {
|
||||
})
|
||||
}
|
||||
|
||||
for &field in fields_on(value.type_name()) {
|
||||
// Complete the field name along with its value. Notes:
|
||||
// 1. No parentheses since function fields cannot currently be called
|
||||
// with method syntax;
|
||||
// 2. We can unwrap the field's value since it's a field belonging to
|
||||
// this value's type, so accessing it should not fail.
|
||||
ctx.value_completion(
|
||||
Some(field.into()),
|
||||
&value.field(field).unwrap(),
|
||||
false,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
match value {
|
||||
Value::Symbol(symbol) => {
|
||||
for modifier in symbol.modifiers() {
|
||||
|
@ -594,12 +594,12 @@ pub trait PlainText {
|
||||
fn plain_text(&self, text: &mut EcoString);
|
||||
}
|
||||
|
||||
/// The missing key access error message when no default value was given.
|
||||
/// The missing field access error message when no default value was given.
|
||||
#[cold]
|
||||
fn missing_field_no_default(key: &str) -> EcoString {
|
||||
fn missing_field_no_default(field: &str) -> EcoString {
|
||||
eco_format!(
|
||||
"content does not contain field {:?} and \
|
||||
no default value was specified",
|
||||
Str::from(key)
|
||||
Str::from(field)
|
||||
)
|
||||
}
|
||||
|
@ -81,13 +81,64 @@ Typst supports the following length units:
|
||||
- Inches: `{1in}`
|
||||
- Relative to font size: `{2.5em}`
|
||||
|
||||
A length has the following fields:
|
||||
|
||||
- `em`: The amount of `em` units in this length, as a [float]($type/float).
|
||||
- `abs`: A length with just the absolute component of the current length
|
||||
(that is, excluding the `em` component).
|
||||
|
||||
## Example
|
||||
```example
|
||||
#rect(width: 20pt)
|
||||
#rect(width: 2em)
|
||||
#rect(width: 1in)
|
||||
|
||||
#(3em + 5pt).em
|
||||
#(20pt).em
|
||||
|
||||
#(40em + 2pt).abs
|
||||
#(5em).abs
|
||||
```
|
||||
|
||||
## Methods
|
||||
### pt()
|
||||
Converts this length to points.
|
||||
|
||||
Fails with an error if this length has non-zero `em` units
|
||||
(such as `5em + 2pt` instead of just `2pt`). Use the `abs`
|
||||
field (such as in `(5em + 2pt).abs.pt()`) to ignore the
|
||||
`em` component of the length (thus converting only its
|
||||
absolute component).
|
||||
|
||||
- returns: float
|
||||
|
||||
### mm()
|
||||
Converts this length to millimeters.
|
||||
|
||||
Fails with an error if this length has non-zero `em` units
|
||||
(such as `5em + 2pt` instead of just `2pt`). See the
|
||||
[`pt()`]($type/float.pt) method for more info.
|
||||
|
||||
- returns: float
|
||||
|
||||
### cm()
|
||||
Converts this length to centimeters.
|
||||
|
||||
Fails with an error if this length has non-zero `em` units
|
||||
(such as `5em + 2pt` instead of just `2pt`). See the
|
||||
[`pt()`]($type/float.pt) method for more info.
|
||||
|
||||
- returns: float
|
||||
|
||||
### inches()
|
||||
Converts this length to inches.
|
||||
|
||||
Fails with an error if this length has non-zero `em` units
|
||||
(such as `5em + 2pt` instead of just `2pt`). See the
|
||||
[`pt()`]($type/float.pt) method for more info.
|
||||
|
||||
- returns: float
|
||||
|
||||
# Angle
|
||||
An angle describing a rotation.
|
||||
Typst supports the following angular units:
|
||||
@ -100,6 +151,17 @@ Typst supports the following angular units:
|
||||
#rotate(10deg)[Hello there!]
|
||||
```
|
||||
|
||||
## Methods
|
||||
### deg()
|
||||
Converts this angle to degrees.
|
||||
|
||||
- returns: float
|
||||
|
||||
### rad()
|
||||
Converts this angle to radians.
|
||||
|
||||
- returns: float
|
||||
|
||||
# Ratio
|
||||
A ratio of a whole.
|
||||
|
||||
@ -121,9 +183,16 @@ This type is a combination of a [length]($type/length) with a
|
||||
of a length and a ratio. Wherever a relative length is expected, you can also
|
||||
use a bare length or ratio.
|
||||
|
||||
A relative length has the following fields:
|
||||
- `length`: Its length component.
|
||||
- `ratio`: Its ratio component.
|
||||
|
||||
## Example
|
||||
```example
|
||||
#rect(width: 100% - 50pt)
|
||||
|
||||
#(100% - 50pt).length
|
||||
#(100% - 50pt).ratio
|
||||
```
|
||||
|
||||
# Fraction
|
||||
@ -155,6 +224,16 @@ Furthermore, Typst provides the following built-in colors:
|
||||
`lime`.
|
||||
|
||||
## Methods
|
||||
### kind()
|
||||
Returns the constructor function for this color's kind
|
||||
([`rgb`]($func/rgb), [`cmyk`]($func/cmyk) or [`luma`]($func/luma)).
|
||||
|
||||
```example
|
||||
#{cmyk(1%, 2%, 3%, 4%).kind() == cmyk}
|
||||
```
|
||||
|
||||
- returns: function
|
||||
|
||||
### lighten()
|
||||
Lightens a color.
|
||||
|
||||
@ -174,6 +253,33 @@ Produces the negative of the color.
|
||||
|
||||
- returns: color
|
||||
|
||||
### hex()
|
||||
Returns the color's RGB(A) hex representation (such as `#ffaa32` or `#020304fe`).
|
||||
The alpha component (last two digits in `#020304fe`) is omitted if it is equal
|
||||
to `ff` (255 / 100%).
|
||||
|
||||
- returns: string
|
||||
|
||||
### rgba()
|
||||
Converts this color to sRGB and returns its components (R, G, B, A) as an array
|
||||
of [integers]($type/integer).
|
||||
|
||||
- returns: array
|
||||
|
||||
### cmyk()
|
||||
Converts this color to Digital CMYK and returns its components (C, M, Y, K) as an
|
||||
array of [ratio]($type/ratio). Note that this function will throw an error when
|
||||
applied to an [rgb]($func/rgb) color, since its conversion to CMYK is not available.
|
||||
|
||||
- returns: array
|
||||
|
||||
### luma()
|
||||
If this color was created with [luma]($func/luma), returns the [integer]($type/integer)
|
||||
value used on construction. Otherwise (for [rgb]($func/rgb) and [cmyk]($func/cmyk) colors),
|
||||
throws an error.
|
||||
|
||||
- returns: integer
|
||||
|
||||
# Datetime
|
||||
Represents a date, a time, or a combination of both. Can be created by either
|
||||
specifying a custom datetime using the [`datetime`]($func/datetime) function or
|
||||
|
@ -62,3 +62,89 @@
|
||||
---
|
||||
// Error: 9-13 cannot access fields on type boolean
|
||||
#{false.true}
|
||||
|
||||
---
|
||||
// Test relative length fields.
|
||||
#test((100% + 2em + 2pt).ratio, 100%)
|
||||
#test((100% + 2em + 2pt).length, 2em + 2pt)
|
||||
#test((100% + 2pt).length, 2pt)
|
||||
#test((100% + 2pt - 2pt).length, 0pt)
|
||||
#test((56% + 2pt - 56%).ratio, 0%)
|
||||
|
||||
---
|
||||
// Test length fields.
|
||||
#test((1pt).em, 0em)
|
||||
#test((1pt).abs, 1pt)
|
||||
#test((3em).em, 3em)
|
||||
#test((3em).abs, 0pt)
|
||||
#test((2em + 2pt).em, 2em)
|
||||
#test((2em + 2pt).abs, 2pt)
|
||||
|
||||
---
|
||||
// Test stroke fields for simple strokes.
|
||||
#test((1em + blue).paint, blue)
|
||||
#test((1em + blue).thickness, 1em)
|
||||
#test((1em + blue).cap, "butt")
|
||||
#test((1em + blue).join, "miter")
|
||||
#test((1em + blue).dash, none)
|
||||
#test((1em + blue).miter-limit, 4.0)
|
||||
|
||||
---
|
||||
// Test complex stroke fields.
|
||||
#let r1 = rect(stroke: (paint: cmyk(1%, 2%, 3%, 4%), thickness: 4em + 2pt, cap: "round", join: "bevel", miter-limit: 5.0, dash: none))
|
||||
#let r2 = rect(stroke: (paint: cmyk(1%, 2%, 3%, 4%), thickness: 4em + 2pt, cap: "round", join: "bevel", miter-limit: 5.0, dash: (3pt, "dot", 4em)))
|
||||
#let r3 = rect(stroke: (paint: cmyk(1%, 2%, 3%, 4%), thickness: 4em + 2pt, cap: "round", join: "bevel", dash: (array: (3pt, "dot", 4em), phase: 5em)))
|
||||
#let s1 = r1.stroke
|
||||
#let s2 = r2.stroke
|
||||
#let s3 = r3.stroke
|
||||
#test(s1.paint, cmyk(1%, 2%, 3%, 4%))
|
||||
#test(s1.thickness, 4em + 2pt)
|
||||
#test(s1.cap, "round")
|
||||
#test(s1.join, "bevel")
|
||||
#test(s1.miter-limit, 5.0)
|
||||
#test(s3.miter-limit, 4.0)
|
||||
#test(s1.dash, none)
|
||||
#test(s2.dash, (array: (3pt, "dot", 4em), phase: 0pt))
|
||||
#test(s3.dash, (array: (3pt, "dot", 4em), phase: 5em))
|
||||
|
||||
---
|
||||
// Test 2d alignment 'horizontal' field.
|
||||
#test((start + top).x, start)
|
||||
#test((end + top).x, end)
|
||||
#test((left + top).x, left)
|
||||
#test((right + top).x, right)
|
||||
#test((center + top).x, center)
|
||||
#test((start + bottom).x, start)
|
||||
#test((end + bottom).x, end)
|
||||
#test((left + bottom).x, left)
|
||||
#test((right + bottom).x, right)
|
||||
#test((center + bottom).x, center)
|
||||
#test((start + horizon).x, start)
|
||||
#test((end + horizon).x, end)
|
||||
#test((left + horizon).x, left)
|
||||
#test((right + horizon).x, right)
|
||||
#test((center + horizon).x, center)
|
||||
#test((top + start).x, start)
|
||||
#test((bottom + end).x, end)
|
||||
#test((horizon + center).x, center)
|
||||
|
||||
---
|
||||
// Test 2d alignment 'vertical' field.
|
||||
#test((start + top).y, top)
|
||||
#test((end + top).y, top)
|
||||
#test((left + top).y, top)
|
||||
#test((right + top).y, top)
|
||||
#test((center + top).y, top)
|
||||
#test((start + bottom).y, bottom)
|
||||
#test((end + bottom).y, bottom)
|
||||
#test((left + bottom).y, bottom)
|
||||
#test((right + bottom).y, bottom)
|
||||
#test((center + bottom).y, bottom)
|
||||
#test((start + horizon).y, horizon)
|
||||
#test((end + horizon).y, horizon)
|
||||
#test((left + horizon).y, horizon)
|
||||
#test((right + horizon).y, horizon)
|
||||
#test((center + horizon).y, horizon)
|
||||
#test((top + start).y, top)
|
||||
#test((bottom + end).y, bottom)
|
||||
#test((horizon + center).y, horizon)
|
||||
|
@ -53,3 +53,148 @@
|
||||
// Test content fields method.
|
||||
#test([a].fields(), (text: "a"))
|
||||
#test([a *b*].fields(), (children: ([a], [ ], strong[b])))
|
||||
|
||||
---
|
||||
// Test length unit conversions.
|
||||
#test((500.934pt).pt(), 500.934)
|
||||
#test((3.3453cm).cm(), 3.3453)
|
||||
#test((4.3452mm).mm(), 4.3452)
|
||||
#test((5.345in).inches(), 5.345)
|
||||
#test((500.333666999pt).pt(), 500.333666999)
|
||||
#test((3.5234354cm).cm(), 3.5234354)
|
||||
#test((4.12345678mm).mm(), 4.12345678)
|
||||
#test((5.333666999in).inches(), 5.333666999)
|
||||
#test((4.123456789123456mm).mm(), 4.123456789123456)
|
||||
#test((254cm).mm(), 2540.0)
|
||||
#test(calc.round((254cm).inches(), digits: 2), 100.0)
|
||||
#test((2540mm).cm(), 254.0)
|
||||
#test(calc.round((2540mm).inches(), digits: 2), 100.0)
|
||||
#test((100in).pt(), 7200.0)
|
||||
#test(calc.round((100in).cm(), digits: 2), 254.0)
|
||||
#test(calc.round((100in).mm(), digits: 2), 2540.0)
|
||||
#test(5em.abs.cm(), 0.0)
|
||||
#test((5em + 6in).abs.inches(), 6.0)
|
||||
|
||||
---
|
||||
// Error: 2-21 cannot convert a length with non-zero em units (-6pt + 10.5em) to pt
|
||||
// Hint: 2-21 use 'length.abs.pt()' instead to ignore its em component
|
||||
#(10.5em - 6pt).pt()
|
||||
|
||||
---
|
||||
// Error: 2-12 cannot convert a length with non-zero em units (3em) to cm
|
||||
// Hint: 2-12 use 'length.abs.cm()' instead to ignore its em component
|
||||
#(3em).cm()
|
||||
|
||||
---
|
||||
// Error: 2-20 cannot convert a length with non-zero em units (-226.77pt + 93em) to mm
|
||||
// Hint: 2-20 use 'length.abs.mm()' instead to ignore its em component
|
||||
#(93em - 80mm).mm()
|
||||
|
||||
---
|
||||
// Error: 2-24 cannot convert a length with non-zero em units (432pt + 4.5em) to inches
|
||||
// Hint: 2-24 use 'length.abs.inches()' instead to ignore its em component
|
||||
#(4.5em + 6in).inches()
|
||||
|
||||
---
|
||||
// Test color kind method.
|
||||
#test(rgb(1, 2, 3, 4).kind(), rgb)
|
||||
#test(cmyk(4%, 5%, 6%, 7%).kind(), cmyk)
|
||||
#test(luma(40).kind(), luma)
|
||||
#test(rgb(1, 2, 3, 4).kind() != luma, true)
|
||||
|
||||
---
|
||||
// Test color '.rgba()', '.cmyk()' and '.luma()' without conversions
|
||||
#test(rgb(1, 2, 3, 4).rgba(), (1, 2, 3, 4))
|
||||
#test(rgb(1, 2, 3).rgba(), (1, 2, 3, 255))
|
||||
#test(cmyk(20%, 20%, 40%, 20%).cmyk(), (20%, 20%, 40%, 20%))
|
||||
#test(luma(40).luma(), 40)
|
||||
|
||||
---
|
||||
// Test color conversions.
|
||||
#test(rgb(1, 2, 3).hex(), "#010203")
|
||||
#test(rgb(1, 2, 3, 4).hex(), "#01020304")
|
||||
#test(cmyk(4%, 5%, 6%, 7%).rgba(), (228, 225, 223, 255))
|
||||
#test(cmyk(4%, 5%, 6%, 7%).hex(), "#e4e1df")
|
||||
#test(luma(40).rgba(), (40, 40, 40, 255))
|
||||
#test(luma(40).hex(), "#282828")
|
||||
#test(repr(luma(40).cmyk()), repr((11.76%, 10.59%, 10.59%, 14.12%)))
|
||||
|
||||
---
|
||||
// Error: 2-24 cannot obtain cmyk values from rgba color
|
||||
#rgb(1, 2, 3, 4).cmyk()
|
||||
|
||||
---
|
||||
// Error: 2-24 cannot obtain the luma value of rgba color
|
||||
#rgb(1, 2, 3, 4).luma()
|
||||
|
||||
---
|
||||
// Error: 2-29 cannot obtain the luma value of cmyk color
|
||||
#cmyk(4%, 5%, 6%, 7%).luma()
|
||||
|
||||
---
|
||||
// Test alignment methods.
|
||||
#test(start.axis(), "horizontal")
|
||||
#test(end.axis(), "horizontal")
|
||||
#test(left.axis(), "horizontal")
|
||||
#test(right.axis(), "horizontal")
|
||||
#test(center.axis(), "horizontal")
|
||||
#test(top.axis(), "vertical")
|
||||
#test(bottom.axis(), "vertical")
|
||||
#test(horizon.axis(), "vertical")
|
||||
#test(start.inv(), end)
|
||||
#test(end.inv(), start)
|
||||
#test(left.inv(), right)
|
||||
#test(right.inv(), left)
|
||||
#test(center.inv(), center)
|
||||
#test(top.inv(), bottom)
|
||||
#test(bottom.inv(), top)
|
||||
#test(horizon.inv(), horizon)
|
||||
|
||||
---
|
||||
// Test 2d alignment methods.
|
||||
#test((start + top).inv(), (end + bottom))
|
||||
#test((end + top).inv(), (start + bottom))
|
||||
#test((left + top).inv(), (right + bottom))
|
||||
#test((right + top).inv(), (left + bottom))
|
||||
#test((center + top).inv(), (center + bottom))
|
||||
#test((start + bottom).inv(), (end + top))
|
||||
#test((end + bottom).inv(), (start + top))
|
||||
#test((left + bottom).inv(), (right + top))
|
||||
#test((right + bottom).inv(), (left + top))
|
||||
#test((center + bottom).inv(), (center + top))
|
||||
#test((start + horizon).inv(), (end + horizon))
|
||||
#test((end + horizon).inv(), (start + horizon))
|
||||
#test((left + horizon).inv(), (right + horizon))
|
||||
#test((right + horizon).inv(), (left + horizon))
|
||||
#test((center + horizon).inv(), (center + horizon))
|
||||
#test((top + start).inv(), (end + bottom))
|
||||
#test((bottom + end).inv(), (start + top))
|
||||
#test((horizon + center).inv(), (center + horizon))
|
||||
|
||||
---
|
||||
// Test direction methods.
|
||||
#test(ltr.axis(), "horizontal")
|
||||
#test(rtl.axis(), "horizontal")
|
||||
#test(ttb.axis(), "vertical")
|
||||
#test(btt.axis(), "vertical")
|
||||
#test(ltr.start(), left)
|
||||
#test(rtl.start(), right)
|
||||
#test(ttb.start(), top)
|
||||
#test(btt.start(), bottom)
|
||||
#test(ltr.end(), right)
|
||||
#test(rtl.end(), left)
|
||||
#test(ttb.end(), bottom)
|
||||
#test(btt.end(), top)
|
||||
#test(ltr.inv(), rtl)
|
||||
#test(rtl.inv(), ltr)
|
||||
#test(ttb.inv(), btt)
|
||||
#test(btt.inv(), ttb)
|
||||
|
||||
---
|
||||
// Test angle methods.
|
||||
#test(1rad.rad(), 1.0)
|
||||
#test(1.23rad.rad(), 1.23)
|
||||
#test(0deg.rad(), 0.0)
|
||||
#test(2deg.deg(), 2.0)
|
||||
#test(2.94deg.deg(), 2.94)
|
||||
#test(0rad.deg(), 0.0)
|
||||
|
Loading…
x
Reference in New Issue
Block a user