mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Reflection for castables
This commit is contained in:
parent
f3980c7045
commit
b6202b646a
@ -6,6 +6,8 @@ use crate::prelude::*;
|
|||||||
use crate::text::{SpaceNode, TextNode, TextSize};
|
use crate::text::{SpaceNode, TextNode, TextSize};
|
||||||
|
|
||||||
/// A section heading.
|
/// A section heading.
|
||||||
|
///
|
||||||
|
/// Tags: basics.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Prepare, Show, Finalize)]
|
#[capable(Prepare, Show, Finalize)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
|
@ -4,6 +4,8 @@ use crate::prelude::*;
|
|||||||
use crate::text::{SpaceNode, TextNode};
|
use crate::text::{SpaceNode, TextNode};
|
||||||
|
|
||||||
/// An unordered (bulleted) or ordered (numbered) list.
|
/// An unordered (bulleted) or ordered (numbered) list.
|
||||||
|
///
|
||||||
|
/// Tags: basics.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Layout)]
|
#[capable(Layout)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -24,7 +26,7 @@ pub type DescNode = ListNode<DESC>;
|
|||||||
impl<const L: ListKind> ListNode<L> {
|
impl<const L: ListKind> ListNode<L> {
|
||||||
/// How the list is labelled.
|
/// How the list is labelled.
|
||||||
#[property(referenced)]
|
#[property(referenced)]
|
||||||
pub const LABEL: Label = Label::Default;
|
pub const LABEL: ListLabel = ListLabel::Default;
|
||||||
/// The indentation of each item's label.
|
/// The indentation of each item's label.
|
||||||
#[property(resolve)]
|
#[property(resolve)]
|
||||||
pub const INDENT: Length = Length::zero();
|
pub const INDENT: Length = Length::zero();
|
||||||
@ -199,10 +201,10 @@ pub struct DescItem {
|
|||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
DescItem,
|
DescItem,
|
||||||
Expected: "dictionary with `term` and `body` keys",
|
mut dict: Dict => {
|
||||||
Value::Dict(dict) => {
|
let term: Content = dict.take("term")?.cast()?;
|
||||||
let term: Content = dict.get("term")?.clone().cast()?;
|
let body: Content = dict.take("body")?.cast()?;
|
||||||
let body: Content = dict.get("body")?.clone().cast()?;
|
dict.finish(&["term", "body"])?;
|
||||||
Self { term, body }
|
Self { term, body }
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -221,7 +223,7 @@ pub const DESC: ListKind = 2;
|
|||||||
|
|
||||||
/// How to label a list or enumeration.
|
/// How to label a list or enumeration.
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub enum Label {
|
pub enum ListLabel {
|
||||||
/// The default labelling.
|
/// The default labelling.
|
||||||
Default,
|
Default,
|
||||||
/// A pattern with prefix, numbering, lower / upper case and suffix.
|
/// A pattern with prefix, numbering, lower / upper case and suffix.
|
||||||
@ -232,7 +234,7 @@ pub enum Label {
|
|||||||
Func(Func, Span),
|
Func(Func, Span),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Label {
|
impl ListLabel {
|
||||||
/// Resolve the label based on the level.
|
/// Resolve the label based on the level.
|
||||||
pub fn resolve(
|
pub fn resolve(
|
||||||
&self,
|
&self,
|
||||||
@ -256,9 +258,12 @@ impl Label {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cast<Spanned<Value>> for Label {
|
impl Cast<Spanned<Value>> for ListLabel {
|
||||||
fn is(value: &Spanned<Value>) -> bool {
|
fn is(value: &Spanned<Value>) -> bool {
|
||||||
matches!(&value.v, Value::Content(_) | Value::Func(_))
|
matches!(
|
||||||
|
&value.v,
|
||||||
|
Value::None | Value::Str(_) | Value::Content(_) | Value::Func(_)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cast(value: Spanned<Value>) -> StrResult<Self> {
|
fn cast(value: Spanned<Value>) -> StrResult<Self> {
|
||||||
@ -267,10 +272,15 @@ impl Cast<Spanned<Value>> for Label {
|
|||||||
Value::Str(v) => Ok(Self::Pattern(v.parse()?)),
|
Value::Str(v) => Ok(Self::Pattern(v.parse()?)),
|
||||||
Value::Content(v) => Ok(Self::Content(v)),
|
Value::Content(v) => Ok(Self::Content(v)),
|
||||||
Value::Func(v) => Ok(Self::Func(v, value.span)),
|
Value::Func(v) => Ok(Self::Func(v, value.span)),
|
||||||
v => Err(format_eco!(
|
v => Self::error(v),
|
||||||
"expected string, content or function, found {}",
|
|
||||||
v.type_name(),
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn describe() -> CastInfo {
|
||||||
|
CastInfo::Union(vec![
|
||||||
|
CastInfo::Type("string"),
|
||||||
|
CastInfo::Type("content"),
|
||||||
|
CastInfo::Type("function"),
|
||||||
|
])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ use crate::layout::{GridNode, TrackSizing, TrackSizings};
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// A table of items.
|
/// A table of items.
|
||||||
|
///
|
||||||
|
/// Tags: basics.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Layout)]
|
#[capable(Layout)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -125,9 +127,12 @@ impl<T: Cast> Cast<Spanned<Value>> for Celled<T> {
|
|||||||
fn cast(value: Spanned<Value>) -> StrResult<Self> {
|
fn cast(value: Spanned<Value>) -> StrResult<Self> {
|
||||||
match value.v {
|
match value.v {
|
||||||
Value::Func(v) => Ok(Self::Func(v, value.span)),
|
Value::Func(v) => Ok(Self::Func(v, value.span)),
|
||||||
v => T::cast(v)
|
v if T::is(&v) => Ok(Self::Value(T::cast(v)?)),
|
||||||
.map(Self::Value)
|
v => Self::error(v),
|
||||||
.map_err(|msg| with_alternative(msg, "function")),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn describe() -> CastInfo {
|
||||||
|
T::describe() + CastInfo::Type("function")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ use std::cmp::Ordering;
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// The absolute value of a numeric value.
|
/// The absolute value of a numeric value.
|
||||||
|
///
|
||||||
|
/// Tags: calculate.
|
||||||
#[func]
|
#[func]
|
||||||
pub fn abs(args: &mut Args) -> SourceResult<Value> {
|
pub fn abs(args: &mut Args) -> SourceResult<Value> {
|
||||||
let Spanned { v, span } = args.expect("numeric value")?;
|
let Spanned { v, span } = args.expect("numeric value")?;
|
||||||
@ -20,12 +22,16 @@ pub fn abs(args: &mut Args) -> SourceResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The minimum of a sequence of values.
|
/// The minimum of a sequence of values.
|
||||||
|
///
|
||||||
|
/// Tags: calculate.
|
||||||
#[func]
|
#[func]
|
||||||
pub fn min(args: &mut Args) -> SourceResult<Value> {
|
pub fn min(args: &mut Args) -> SourceResult<Value> {
|
||||||
minmax(args, Ordering::Less)
|
minmax(args, Ordering::Less)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The maximum of a sequence of values.
|
/// The maximum of a sequence of values.
|
||||||
|
///
|
||||||
|
/// Tags: calculate.
|
||||||
#[func]
|
#[func]
|
||||||
pub fn max(args: &mut Args) -> SourceResult<Value> {
|
pub fn max(args: &mut Args) -> SourceResult<Value> {
|
||||||
minmax(args, Ordering::Greater)
|
minmax(args, Ordering::Greater)
|
||||||
@ -53,18 +59,24 @@ fn minmax(args: &mut Args, goal: Ordering) -> SourceResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Whether an integer is even.
|
/// Whether an integer is even.
|
||||||
|
///
|
||||||
|
/// Tags: calculate.
|
||||||
#[func]
|
#[func]
|
||||||
pub fn even(args: &mut Args) -> SourceResult<Value> {
|
pub fn even(args: &mut Args) -> SourceResult<Value> {
|
||||||
Ok(Value::Bool(args.expect::<i64>("integer")? % 2 == 0))
|
Ok(Value::Bool(args.expect::<i64>("integer")? % 2 == 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether an integer is odd.
|
/// Whether an integer is odd.
|
||||||
|
///
|
||||||
|
/// Tags: calculate.
|
||||||
#[func]
|
#[func]
|
||||||
pub fn odd(args: &mut Args) -> SourceResult<Value> {
|
pub fn odd(args: &mut Args) -> SourceResult<Value> {
|
||||||
Ok(Value::Bool(args.expect::<i64>("integer")? % 2 != 0))
|
Ok(Value::Bool(args.expect::<i64>("integer")? % 2 != 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The modulo of two numbers.
|
/// The modulo of two numbers.
|
||||||
|
///
|
||||||
|
/// Tags: calculate.
|
||||||
#[func]
|
#[func]
|
||||||
pub fn mod_(args: &mut Args) -> SourceResult<Value> {
|
pub fn mod_(args: &mut Args) -> SourceResult<Value> {
|
||||||
let Spanned { v: v1, span: span1 } = args.expect("integer or float")?;
|
let Spanned { v: v1, span: span1 } = args.expect("integer or float")?;
|
||||||
|
@ -5,6 +5,8 @@ use typst::model::Regex;
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// Convert a value to an integer.
|
/// Convert a value to an integer.
|
||||||
|
///
|
||||||
|
/// Tags: create.
|
||||||
#[func]
|
#[func]
|
||||||
pub fn int(args: &mut Args) -> SourceResult<Value> {
|
pub fn int(args: &mut Args) -> SourceResult<Value> {
|
||||||
let Spanned { v, span } = args.expect("value")?;
|
let Spanned { v, span } = args.expect("value")?;
|
||||||
@ -21,6 +23,8 @@ pub fn int(args: &mut Args) -> SourceResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a value to a float.
|
/// Convert a value to a float.
|
||||||
|
///
|
||||||
|
/// Tags: create.
|
||||||
#[func]
|
#[func]
|
||||||
pub fn float(args: &mut Args) -> SourceResult<Value> {
|
pub fn float(args: &mut Args) -> SourceResult<Value> {
|
||||||
let Spanned { v, span } = args.expect("value")?;
|
let Spanned { v, span } = args.expect("value")?;
|
||||||
@ -36,6 +40,8 @@ pub fn float(args: &mut Args) -> SourceResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a grayscale color.
|
/// Create a grayscale color.
|
||||||
|
///
|
||||||
|
/// Tags: create.
|
||||||
#[func]
|
#[func]
|
||||||
pub fn luma(args: &mut Args) -> SourceResult<Value> {
|
pub fn luma(args: &mut Args) -> SourceResult<Value> {
|
||||||
let Component(luma) = args.expect("gray component")?;
|
let Component(luma) = args.expect("gray component")?;
|
||||||
@ -43,6 +49,8 @@ pub fn luma(args: &mut Args) -> SourceResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create an RGB(A) color.
|
/// Create an RGB(A) color.
|
||||||
|
///
|
||||||
|
/// Tags: create.
|
||||||
#[func]
|
#[func]
|
||||||
pub fn rgb(args: &mut Args) -> SourceResult<Value> {
|
pub fn rgb(args: &mut Args) -> SourceResult<Value> {
|
||||||
Ok(Value::Color(if let Some(string) = args.find::<Spanned<EcoString>>()? {
|
Ok(Value::Color(if let Some(string) = args.find::<Spanned<EcoString>>()? {
|
||||||
@ -60,6 +68,8 @@ pub fn rgb(args: &mut Args) -> SourceResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a CMYK color.
|
/// Create a CMYK color.
|
||||||
|
///
|
||||||
|
/// Tags: create.
|
||||||
#[func]
|
#[func]
|
||||||
pub fn cmyk(args: &mut Args) -> SourceResult<Value> {
|
pub fn cmyk(args: &mut Args) -> SourceResult<Value> {
|
||||||
let RatioComponent(c) = args.expect("cyan component")?;
|
let RatioComponent(c) = args.expect("cyan component")?;
|
||||||
@ -74,12 +84,11 @@ struct Component(u8);
|
|||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
Component,
|
Component,
|
||||||
Expected: "integer or ratio",
|
v: i64 => match v {
|
||||||
Value::Int(v) => match v {
|
|
||||||
0 ..= 255 => Self(v as u8),
|
0 ..= 255 => Self(v as u8),
|
||||||
_ => Err("must be between 0 and 255")?,
|
_ => Err("must be between 0 and 255")?,
|
||||||
},
|
},
|
||||||
Value::Ratio(v) => if (0.0 ..= 1.0).contains(&v.get()) {
|
v: Ratio => if (0.0 ..= 1.0).contains(&v.get()) {
|
||||||
Self((v.get() * 255.0).round() as u8)
|
Self((v.get() * 255.0).round() as u8)
|
||||||
} else {
|
} else {
|
||||||
Err("must be between 0% and 100%")?
|
Err("must be between 0% and 100%")?
|
||||||
@ -91,8 +100,7 @@ struct RatioComponent(u8);
|
|||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
RatioComponent,
|
RatioComponent,
|
||||||
Expected: "ratio",
|
v: Ratio => if (0.0 ..= 1.0).contains(&v.get()) {
|
||||||
Value::Ratio(v) => if (0.0 ..= 1.0).contains(&v.get()) {
|
|
||||||
Self((v.get() * 255.0).round() as u8)
|
Self((v.get() * 255.0).round() as u8)
|
||||||
} else {
|
} else {
|
||||||
Err("must be between 0% and 100%")?
|
Err("must be between 0% and 100%")?
|
||||||
@ -100,6 +108,8 @@ castable! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a value to a string.
|
/// Convert a value to a string.
|
||||||
|
///
|
||||||
|
/// Tags: create.
|
||||||
#[func]
|
#[func]
|
||||||
pub fn str(args: &mut Args) -> SourceResult<Value> {
|
pub fn str(args: &mut Args) -> SourceResult<Value> {
|
||||||
let Spanned { v, span } = args.expect("value")?;
|
let Spanned { v, span } = args.expect("value")?;
|
||||||
@ -113,12 +123,16 @@ pub fn str(args: &mut Args) -> SourceResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a label from a string.
|
/// Create a label from a string.
|
||||||
|
///
|
||||||
|
/// Tags: create.
|
||||||
#[func]
|
#[func]
|
||||||
pub fn label(args: &mut Args) -> SourceResult<Value> {
|
pub fn label(args: &mut Args) -> SourceResult<Value> {
|
||||||
Ok(Value::Label(Label(args.expect("string")?)))
|
Ok(Value::Label(Label(args.expect("string")?)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a regular expression from a string.
|
/// Create a regular expression from a string.
|
||||||
|
///
|
||||||
|
/// Tags: create.
|
||||||
#[func]
|
#[func]
|
||||||
pub fn regex(args: &mut Args) -> SourceResult<Value> {
|
pub fn regex(args: &mut Args) -> SourceResult<Value> {
|
||||||
let Spanned { v, span } = args.expect::<Spanned<EcoString>>("regular expression")?;
|
let Spanned { v, span } = args.expect::<Spanned<EcoString>>("regular expression")?;
|
||||||
@ -126,6 +140,8 @@ pub fn regex(args: &mut Args) -> SourceResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create an array consisting of a sequence of numbers.
|
/// Create an array consisting of a sequence of numbers.
|
||||||
|
///
|
||||||
|
/// Tags: create.
|
||||||
#[func]
|
#[func]
|
||||||
pub fn range(args: &mut Args) -> SourceResult<Value> {
|
pub fn range(args: &mut Args) -> SourceResult<Value> {
|
||||||
let first = args.expect::<i64>("end")?;
|
let first = args.expect::<i64>("end")?;
|
||||||
|
@ -5,6 +5,8 @@ use typst::diag::{format_xml_like_error, FileError};
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// Read structured data from a CSV file.
|
/// Read structured data from a CSV file.
|
||||||
|
///
|
||||||
|
/// Tags: data-loading.
|
||||||
#[func]
|
#[func]
|
||||||
pub fn csv(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
|
pub fn csv(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
|
||||||
let Spanned { v: path, span } =
|
let Spanned { v: path, span } =
|
||||||
@ -46,6 +48,8 @@ fn format_csv_error(error: csv::Error) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Read structured data from a JSON file.
|
/// Read structured data from a JSON file.
|
||||||
|
///
|
||||||
|
/// Tags: data-loading.
|
||||||
#[func]
|
#[func]
|
||||||
pub fn json(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
|
pub fn json(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
|
||||||
let Spanned { v: path, span } =
|
let Spanned { v: path, span } =
|
||||||
@ -87,6 +91,8 @@ fn format_json_error(error: serde_json::Error) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Read structured data from an XML file.
|
/// Read structured data from an XML file.
|
||||||
|
///
|
||||||
|
/// Tags: data-loading.
|
||||||
#[func]
|
#[func]
|
||||||
pub fn xml(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
|
pub fn xml(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
|
||||||
let Spanned { v: path, span } =
|
let Spanned { v: path, span } =
|
||||||
|
@ -5,18 +5,24 @@ use typst::model;
|
|||||||
use typst::syntax::Source;
|
use typst::syntax::Source;
|
||||||
|
|
||||||
/// The name of a value's type.
|
/// The name of a value's type.
|
||||||
|
///
|
||||||
|
/// Tags: foundations.
|
||||||
#[func]
|
#[func]
|
||||||
pub fn type_(args: &mut Args) -> SourceResult<Value> {
|
pub fn type_(args: &mut Args) -> SourceResult<Value> {
|
||||||
Ok(args.expect::<Value>("value")?.type_name().into())
|
Ok(args.expect::<Value>("value")?.type_name().into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The string representation of a value.
|
/// The string representation of a value.
|
||||||
|
///
|
||||||
|
/// Tags: foundations.
|
||||||
#[func]
|
#[func]
|
||||||
pub fn repr(args: &mut Args) -> SourceResult<Value> {
|
pub fn repr(args: &mut Args) -> SourceResult<Value> {
|
||||||
Ok(args.expect::<Value>("value")?.repr().into())
|
Ok(args.expect::<Value>("value")?.repr().into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ensure that a condition is fulfilled.
|
/// Ensure that a condition is fulfilled.
|
||||||
|
///
|
||||||
|
/// Tags: foundations.
|
||||||
#[func]
|
#[func]
|
||||||
pub fn assert(args: &mut Args) -> SourceResult<Value> {
|
pub fn assert(args: &mut Args) -> SourceResult<Value> {
|
||||||
let Spanned { v, span } = args.expect::<Spanned<bool>>("condition")?;
|
let Spanned { v, span } = args.expect::<Spanned<bool>>("condition")?;
|
||||||
@ -27,6 +33,8 @@ pub fn assert(args: &mut Args) -> SourceResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a string as Typst markup.
|
/// Evaluate a string as Typst markup.
|
||||||
|
///
|
||||||
|
/// Tags: foundations.
|
||||||
#[func]
|
#[func]
|
||||||
pub fn eval(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
|
pub fn eval(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
|
||||||
let Spanned { v: text, span } = args.expect::<Spanned<String>>("source")?;
|
let Spanned { v: text, span } = args.expect::<Spanned<String>>("source")?;
|
||||||
|
@ -4,6 +4,8 @@ use crate::prelude::*;
|
|||||||
use crate::text::Case;
|
use crate::text::Case;
|
||||||
|
|
||||||
/// Create a blind text string.
|
/// Create a blind text string.
|
||||||
|
///
|
||||||
|
/// Tags: utility.
|
||||||
#[func]
|
#[func]
|
||||||
pub fn lorem(args: &mut Args) -> SourceResult<Value> {
|
pub fn lorem(args: &mut Args) -> SourceResult<Value> {
|
||||||
let words: usize = args.expect("number of words")?;
|
let words: usize = args.expect("number of words")?;
|
||||||
@ -11,6 +13,8 @@ pub fn lorem(args: &mut Args) -> SourceResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Apply a numbering pattern to a number.
|
/// Apply a numbering pattern to a number.
|
||||||
|
///
|
||||||
|
/// Tags: utility.
|
||||||
#[func]
|
#[func]
|
||||||
pub fn numbering(args: &mut Args) -> SourceResult<Value> {
|
pub fn numbering(args: &mut Args) -> SourceResult<Value> {
|
||||||
let pattern = args.expect::<NumberingPattern>("pattern")?;
|
let pattern = args.expect::<NumberingPattern>("pattern")?;
|
||||||
@ -93,8 +97,7 @@ impl FromStr for NumberingPattern {
|
|||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
NumberingPattern,
|
NumberingPattern,
|
||||||
Expected: "numbering pattern",
|
string: EcoString => string.parse()?,
|
||||||
Value::Str(s) => s.parse()?,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Different kinds of numberings.
|
/// Different kinds of numberings.
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// Align content horizontally and vertically.
|
/// Align content horizontally and vertically.
|
||||||
|
///
|
||||||
|
/// Tags: layout.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable]
|
#[capable]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
|
@ -2,6 +2,8 @@ use crate::prelude::*;
|
|||||||
use crate::text::TextNode;
|
use crate::text::TextNode;
|
||||||
|
|
||||||
/// Separate a region into multiple equally sized columns.
|
/// Separate a region into multiple equally sized columns.
|
||||||
|
///
|
||||||
|
/// Tags: layout.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Layout)]
|
#[capable(Layout)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -103,6 +105,8 @@ impl Layout for ColumnsNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A column break.
|
/// A column break.
|
||||||
|
///
|
||||||
|
/// Tags: layout.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Behave)]
|
#[capable(Behave)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
|
@ -2,6 +2,8 @@ use super::VNode;
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// An inline-level container that sizes content.
|
/// An inline-level container that sizes content.
|
||||||
|
///
|
||||||
|
/// Tags: layout.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Layout, Inline)]
|
#[capable(Layout, Inline)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -62,6 +64,8 @@ impl Layout for BoxNode {
|
|||||||
impl Inline for BoxNode {}
|
impl Inline for BoxNode {}
|
||||||
|
|
||||||
/// A block-level container that places content into a separate flow.
|
/// A block-level container that places content into a separate flow.
|
||||||
|
///
|
||||||
|
/// Tags: layout.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Layout)]
|
#[capable(Layout)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -70,10 +74,10 @@ pub struct BlockNode(pub Content);
|
|||||||
#[node]
|
#[node]
|
||||||
impl BlockNode {
|
impl BlockNode {
|
||||||
/// The spacing between the previous and this block.
|
/// The spacing between the previous and this block.
|
||||||
#[property(skip)]
|
#[property(reflect, skip)]
|
||||||
pub const ABOVE: VNode = VNode::block_spacing(Em::new(1.2).into());
|
pub const ABOVE: VNode = VNode::block_spacing(Em::new(1.2).into());
|
||||||
/// The spacing between this and the following block.
|
/// The spacing between this and the following block.
|
||||||
#[property(skip)]
|
#[property(reflect, skip)]
|
||||||
pub const BELOW: VNode = VNode::block_spacing(Em::new(1.2).into());
|
pub const BELOW: VNode = VNode::block_spacing(Em::new(1.2).into());
|
||||||
/// Whether this block must stick to the following one.
|
/// Whether this block must stick to the following one.
|
||||||
#[property(skip)]
|
#[property(skip)]
|
||||||
|
@ -3,6 +3,8 @@ use crate::prelude::*;
|
|||||||
use super::Spacing;
|
use super::Spacing;
|
||||||
|
|
||||||
/// Arrange content in a grid.
|
/// Arrange content in a grid.
|
||||||
|
///
|
||||||
|
/// Tags: layout.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Layout)]
|
#[capable(Layout)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -85,17 +87,9 @@ pub struct TrackSizings(pub Vec<TrackSizing>);
|
|||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
TrackSizings,
|
TrackSizings,
|
||||||
Expected: "integer, auto, relative length, fraction, or array of the latter three",
|
sizing: TrackSizing => Self(vec![sizing]),
|
||||||
Value::Auto => Self(vec![TrackSizing::Auto]),
|
count: NonZeroUsize => Self(vec![TrackSizing::Auto; count.get()]),
|
||||||
Value::Length(v) => Self(vec![TrackSizing::Relative(v.into())]),
|
values: Array => Self(values
|
||||||
Value::Ratio(v) => Self(vec![TrackSizing::Relative(v.into())]),
|
|
||||||
Value::Relative(v) => Self(vec![TrackSizing::Relative(v)]),
|
|
||||||
Value::Fraction(v) => Self(vec![TrackSizing::Fractional(v)]),
|
|
||||||
Value::Int(v) => Self(vec![
|
|
||||||
TrackSizing::Auto;
|
|
||||||
Value::Int(v).cast::<NonZeroUsize>()?.get()
|
|
||||||
]),
|
|
||||||
Value::Array(values) => Self(values
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|v| v.cast().ok())
|
.filter_map(|v| v.cast().ok())
|
||||||
.collect()),
|
.collect()),
|
||||||
@ -103,12 +97,9 @@ castable! {
|
|||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
TrackSizing,
|
TrackSizing,
|
||||||
Expected: "auto, relative length, or fraction",
|
_: AutoValue => Self::Auto,
|
||||||
Value::Auto => Self::Auto,
|
v: Rel<Length> => Self::Relative(v),
|
||||||
Value::Length(v) => Self::Relative(v.into()),
|
v: Fr => Self::Fractional(v),
|
||||||
Value::Ratio(v) => Self::Relative(v.into()),
|
|
||||||
Value::Relative(v) => Self::Relative(v),
|
|
||||||
Value::Fraction(v) => Self::Fractional(v),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs grid layout.
|
/// Performs grid layout.
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// Hide content without affecting layout.
|
/// Hide content without affecting layout.
|
||||||
|
///
|
||||||
|
/// Tags: layout.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Layout, Inline)]
|
#[capable(Layout, Inline)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// Pad content at the sides.
|
/// Pad content at the sides.
|
||||||
|
///
|
||||||
|
/// Tags: layout.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Layout)]
|
#[capable(Layout)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
|
@ -5,6 +5,8 @@ use crate::prelude::*;
|
|||||||
use crate::text::TextNode;
|
use crate::text::TextNode;
|
||||||
|
|
||||||
/// Layouts its child onto one or multiple pages.
|
/// Layouts its child onto one or multiple pages.
|
||||||
|
///
|
||||||
|
/// Tags: layout.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable]
|
#[capable]
|
||||||
#[derive(Clone, Hash)]
|
#[derive(Clone, Hash)]
|
||||||
@ -12,6 +14,9 @@ pub struct PageNode(pub Content);
|
|||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
impl PageNode {
|
impl PageNode {
|
||||||
|
/// The paper size.
|
||||||
|
#[property(reflect, skip, shorthand)]
|
||||||
|
pub const PAPER: Paper = Paper::A4;
|
||||||
/// The unflipped width of the page.
|
/// The unflipped width of the page.
|
||||||
#[property(resolve)]
|
#[property(resolve)]
|
||||||
pub const WIDTH: Smart<Length> = Smart::Custom(Paper::A4.width().into());
|
pub const WIDTH: Smart<Length> = Smart::Custom(Paper::A4.width().into());
|
||||||
@ -145,6 +150,8 @@ impl Debug for PageNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A page break.
|
/// A page break.
|
||||||
|
///
|
||||||
|
/// Tags: layout.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable]
|
#[capable]
|
||||||
#[derive(Debug, Copy, Clone, Hash)]
|
#[derive(Debug, Copy, Clone, Hash)]
|
||||||
@ -187,7 +194,10 @@ impl Marginal {
|
|||||||
|
|
||||||
impl Cast<Spanned<Value>> for Marginal {
|
impl Cast<Spanned<Value>> for Marginal {
|
||||||
fn is(value: &Spanned<Value>) -> bool {
|
fn is(value: &Spanned<Value>) -> bool {
|
||||||
matches!(&value.v, Value::Content(_) | Value::Func(_))
|
matches!(
|
||||||
|
&value.v,
|
||||||
|
Value::None | Value::Str(_) | Value::Content(_) | Value::Func(_)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cast(value: Spanned<Value>) -> StrResult<Self> {
|
fn cast(value: Spanned<Value>) -> StrResult<Self> {
|
||||||
@ -196,43 +206,51 @@ impl Cast<Spanned<Value>> for Marginal {
|
|||||||
Value::Str(v) => Ok(Self::Content(TextNode::packed(v))),
|
Value::Str(v) => Ok(Self::Content(TextNode::packed(v))),
|
||||||
Value::Content(v) => Ok(Self::Content(v)),
|
Value::Content(v) => Ok(Self::Content(v)),
|
||||||
Value::Func(v) => Ok(Self::Func(v, value.span)),
|
Value::Func(v) => Ok(Self::Func(v, value.span)),
|
||||||
v => Err(format_eco!(
|
v => Self::error(v),
|
||||||
"expected none, content or function, found {}",
|
|
||||||
v.type_name(),
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn describe() -> CastInfo {
|
||||||
|
CastInfo::Union(vec![
|
||||||
|
CastInfo::Type("none"),
|
||||||
|
CastInfo::Type("content"),
|
||||||
|
CastInfo::Type("function"),
|
||||||
|
])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specification of a paper.
|
/// Specification of a paper.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone, Hash)]
|
||||||
pub struct Paper {
|
pub struct Paper {
|
||||||
/// The width of the paper in millimeters.
|
/// The width of the paper in millimeters.
|
||||||
width: f64,
|
width: Scalar,
|
||||||
/// The height of the paper in millimeters.
|
/// The height of the paper in millimeters.
|
||||||
height: f64,
|
height: Scalar,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Paper {
|
impl Paper {
|
||||||
/// The width of the paper.
|
/// The width of the paper.
|
||||||
pub fn width(self) -> Abs {
|
pub fn width(self) -> Abs {
|
||||||
Abs::mm(self.width)
|
Abs::mm(self.width.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The height of the paper.
|
/// The height of the paper.
|
||||||
pub fn height(self) -> Abs {
|
pub fn height(self) -> Abs {
|
||||||
Abs::mm(self.height)
|
Abs::mm(self.height.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Defines paper constants and a paper parsing implementation.
|
/// Defines paper constants and a paper parsing implementation.
|
||||||
macro_rules! papers {
|
macro_rules! papers {
|
||||||
($(($var:ident: $width:expr, $height: expr, $($pats:tt)*))*) => {
|
($(($var:ident: $width:expr, $height: expr, $pat:literal))*) => {
|
||||||
/// Predefined papers.
|
/// Predefined papers.
|
||||||
///
|
///
|
||||||
/// Each paper is parsable from its name in kebab-case.
|
/// Each paper is parsable from its name in kebab-case.
|
||||||
impl Paper {
|
impl Paper {
|
||||||
$(pub const $var: Self = Self { width: $width, height: $height };)*
|
$(pub const $var: Self = Self {
|
||||||
|
width: Scalar($width),
|
||||||
|
height: Scalar($height),
|
||||||
|
};)*
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Paper {
|
impl FromStr for Paper {
|
||||||
@ -240,18 +258,17 @@ macro_rules! papers {
|
|||||||
|
|
||||||
fn from_str(name: &str) -> Result<Self, Self::Err> {
|
fn from_str(name: &str) -> Result<Self, Self::Err> {
|
||||||
match name.to_lowercase().as_str() {
|
match name.to_lowercase().as_str() {
|
||||||
$($($pats)* => Ok(Self::$var),)*
|
$($pat => Ok(Self::$var),)*
|
||||||
_ => Err("invalid paper name"),
|
_ => Err("invalid paper name"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
Paper,
|
Paper,
|
||||||
Expected: "string",
|
$($pat => Self::$var,)*
|
||||||
Value::Str(string) => Self::from_str(&string)?,
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// All paper sizes in mm.
|
// All paper sizes in mm.
|
||||||
|
@ -12,6 +12,8 @@ use crate::text::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// Arrange text, spacing and inline-level nodes into a paragraph.
|
/// Arrange text, spacing and inline-level nodes into a paragraph.
|
||||||
|
///
|
||||||
|
/// Tags: layout.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable]
|
#[capable]
|
||||||
#[derive(Hash)]
|
#[derive(Hash)]
|
||||||
@ -109,9 +111,8 @@ pub struct HorizontalAlign(pub GenAlign);
|
|||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
HorizontalAlign,
|
HorizontalAlign,
|
||||||
Expected: "alignment",
|
align: GenAlign => match align.axis() {
|
||||||
@align: GenAlign => match align.axis() {
|
Axis::X => Self(align),
|
||||||
Axis::X => Self(*align),
|
|
||||||
Axis::Y => Err("must be horizontal")?,
|
Axis::Y => Err("must be horizontal")?,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -135,15 +136,15 @@ pub enum Linebreaks {
|
|||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
Linebreaks,
|
Linebreaks,
|
||||||
Expected: "string",
|
/// Determine the linebreaks in a simple first-fit style.
|
||||||
Value::Str(string) => match string.as_str() {
|
"simple" => Self::Simple,
|
||||||
"simple" => Self::Simple,
|
/// Optimize the linebreaks for the whole paragraph.
|
||||||
"optimized" => Self::Optimized,
|
"optimized" => Self::Optimized,
|
||||||
_ => Err(r#"expected "simple" or "optimized""#)?,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A paragraph break.
|
/// A paragraph break.
|
||||||
|
///
|
||||||
|
/// Tags: layout.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Unlabellable)]
|
#[capable(Unlabellable)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// Place content at an absolute position.
|
/// Place content at an absolute position.
|
||||||
|
///
|
||||||
|
/// Tags: layout.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Layout, Behave)]
|
#[capable(Layout, Behave)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// Repeats content to fill a line.
|
/// Repeats content to fill a line.
|
||||||
|
///
|
||||||
|
/// Tags: layout.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Layout, Inline)]
|
#[capable(Layout, Inline)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
|
@ -3,6 +3,8 @@ use std::cmp::Ordering;
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// Horizontal spacing.
|
/// Horizontal spacing.
|
||||||
|
///
|
||||||
|
/// Tags: layout.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Behave)]
|
#[capable(Behave)]
|
||||||
#[derive(Debug, Copy, Clone, Hash)]
|
#[derive(Debug, Copy, Clone, Hash)]
|
||||||
@ -52,6 +54,8 @@ impl Behave for HNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Vertical spacing.
|
/// Vertical spacing.
|
||||||
|
///
|
||||||
|
/// Tags: layout.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Behave)]
|
#[capable(Behave)]
|
||||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd)]
|
#[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd)]
|
||||||
@ -119,6 +123,11 @@ impl Behave for VNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
castable! {
|
||||||
|
VNode,
|
||||||
|
spacing: Spacing => VNode::block_around(spacing),
|
||||||
|
}
|
||||||
|
|
||||||
/// Kinds of spacing.
|
/// Kinds of spacing.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum Spacing {
|
pub enum Spacing {
|
||||||
@ -160,9 +169,6 @@ impl PartialOrd for Spacing {
|
|||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
Spacing,
|
Spacing,
|
||||||
Expected: "relative length or fraction",
|
v: Rel<Length> => Self::Relative(v),
|
||||||
Value::Length(v) => Self::Relative(v.into()),
|
v: Fr => Self::Fractional(v),
|
||||||
Value::Ratio(v) => Self::Relative(v.into()),
|
|
||||||
Value::Relative(v) => Self::Relative(v),
|
|
||||||
Value::Fraction(v) => Self::Fractional(v),
|
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@ use super::{AlignNode, Spacing};
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// Arrange content and spacing along an axis.
|
/// Arrange content and spacing along an axis.
|
||||||
|
///
|
||||||
|
/// Tags: layout.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Layout)]
|
#[capable(Layout)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -81,12 +83,8 @@ impl Debug for StackChild {
|
|||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
StackChild,
|
StackChild,
|
||||||
Expected: "relative length, fraction, or content",
|
spacing: Spacing => Self::Spacing(spacing),
|
||||||
Value::Length(v) => Self::Spacing(Spacing::Relative(v.into())),
|
content: Content => Self::Block(content),
|
||||||
Value::Ratio(v) => Self::Spacing(Spacing::Relative(v.into())),
|
|
||||||
Value::Relative(v) => Self::Spacing(Spacing::Relative(v)),
|
|
||||||
Value::Fraction(v) => Self::Spacing(Spacing::Fractional(v)),
|
|
||||||
Value::Content(v) => Self::Block(v),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs stack layout.
|
/// Performs stack layout.
|
||||||
|
@ -3,6 +3,8 @@ use typst::geom::Transform;
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// Move content without affecting layout.
|
/// Move content without affecting layout.
|
||||||
|
///
|
||||||
|
/// Tags: layout.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Layout, Inline)]
|
#[capable(Layout, Inline)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -46,6 +48,8 @@ impl Layout for MoveNode {
|
|||||||
impl Inline for MoveNode {}
|
impl Inline for MoveNode {}
|
||||||
|
|
||||||
/// Transform content without affecting layout.
|
/// Transform content without affecting layout.
|
||||||
|
///
|
||||||
|
/// Tags: layout.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Layout, Inline)]
|
#[capable(Layout, Inline)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// A column vector.
|
/// A column vector.
|
||||||
|
///
|
||||||
|
/// Tags: math.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Texify)]
|
#[capable(Texify)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -52,17 +54,19 @@ pub enum Delimiter {
|
|||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
Delimiter,
|
Delimiter,
|
||||||
Expected: "type of bracket or bar",
|
/// Delimit matrices with parentheses.
|
||||||
Value::Str(s) => match s.as_str() {
|
"(" => Self::Paren,
|
||||||
"(" => Self::Paren,
|
/// Delimit matrices with brackets.
|
||||||
"[" => Self::Bracket,
|
"[" => Self::Bracket,
|
||||||
"{" => Self::Brace,
|
/// Delimit matrices with curly braces.
|
||||||
"|" => Self::Bar,
|
"{" => Self::Brace,
|
||||||
_ => Err("expected \"(\", \"[\", \"{\", or \"|\"")?,
|
/// Delimit matrices with vertical bars.
|
||||||
},
|
"|" => Self::Bar,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A case distinction.
|
/// A case distinction.
|
||||||
|
///
|
||||||
|
/// Tags: math.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Texify)]
|
#[capable(Texify)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
|
@ -15,6 +15,8 @@ use crate::prelude::*;
|
|||||||
use crate::text::{FontFamily, LinebreakNode, SpaceNode, SymbolNode, TextNode};
|
use crate::text::{FontFamily, LinebreakNode, SpaceNode, SymbolNode, TextNode};
|
||||||
|
|
||||||
/// A piece of a mathematical formula.
|
/// A piece of a mathematical formula.
|
||||||
|
///
|
||||||
|
/// Tags: math.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Show, Layout, Inline, Texify)]
|
#[capable(Show, Layout, Inline, Texify)]
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
@ -244,6 +246,8 @@ impl Texify for Content {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// An atom in a math formula: `x`, `+`, `12`.
|
/// An atom in a math formula: `x`, `+`, `12`.
|
||||||
|
///
|
||||||
|
/// Tags: math.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Texify)]
|
#[capable(Texify)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -283,6 +287,8 @@ impl Texify for AtomNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// An accented node.
|
/// An accented node.
|
||||||
|
///
|
||||||
|
/// Tags: math.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Texify)]
|
#[capable(Texify)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -358,6 +364,8 @@ impl Texify for AccNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A fraction.
|
/// A fraction.
|
||||||
|
///
|
||||||
|
/// Tags: math.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Texify)]
|
#[capable(Texify)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -389,6 +397,8 @@ impl Texify for FracNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A binomial.
|
/// A binomial.
|
||||||
|
///
|
||||||
|
/// Tags: math.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Texify)]
|
#[capable(Texify)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -420,6 +430,8 @@ impl Texify for BinomNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A sub- and/or superscript.
|
/// A sub- and/or superscript.
|
||||||
|
///
|
||||||
|
/// Tags: math.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Texify)]
|
#[capable(Texify)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -456,6 +468,8 @@ impl Texify for ScriptNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A math alignment point: `&`, `&&`.
|
/// A math alignment point: `&`, `&&`.
|
||||||
|
///
|
||||||
|
/// Tags: math.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Texify)]
|
#[capable(Texify)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -471,6 +485,8 @@ impl Texify for AlignPointNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A square root.
|
/// A square root.
|
||||||
|
///
|
||||||
|
/// Tags: math.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Texify)]
|
#[capable(Texify)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -493,6 +509,8 @@ impl Texify for SqrtNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A floored expression.
|
/// A floored expression.
|
||||||
|
///
|
||||||
|
/// Tags: math.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Texify)]
|
#[capable(Texify)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -515,6 +533,8 @@ impl Texify for FloorNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A ceiled expression.
|
/// A ceiled expression.
|
||||||
|
///
|
||||||
|
/// Tags: math.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Texify)]
|
#[capable(Texify)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Serif (roman) font style.
|
/// Serif (roman) font style.
|
||||||
|
///
|
||||||
|
/// Tags: math.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Texify)]
|
#[capable(Texify)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -23,6 +25,8 @@ impl Texify for SerifNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Sans-serif font style.
|
/// Sans-serif font style.
|
||||||
|
///
|
||||||
|
/// Tags: math.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Texify)]
|
#[capable(Texify)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -45,6 +49,8 @@ impl Texify for SansNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Bold font style.
|
/// Bold font style.
|
||||||
|
///
|
||||||
|
/// Tags: math.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Texify)]
|
#[capable(Texify)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -67,6 +73,8 @@ impl Texify for BoldNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Italic font style.
|
/// Italic font style.
|
||||||
|
///
|
||||||
|
/// Tags: math.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Texify)]
|
#[capable(Texify)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -89,6 +97,8 @@ impl Texify for ItalNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Calligraphic font style.
|
/// Calligraphic font style.
|
||||||
|
///
|
||||||
|
/// Tags: math.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Texify)]
|
#[capable(Texify)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -111,6 +121,8 @@ impl Texify for CalNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Fraktur font style.
|
/// Fraktur font style.
|
||||||
|
///
|
||||||
|
/// Tags: math.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Texify)]
|
#[capable(Texify)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -133,6 +145,8 @@ impl Texify for FrakNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Monospace font style.
|
/// Monospace font style.
|
||||||
|
///
|
||||||
|
/// Tags: math.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Texify)]
|
#[capable(Texify)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -155,6 +169,8 @@ impl Texify for MonoNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Blackboard bold (double-struck) font style.
|
/// Blackboard bold (double-struck) font style.
|
||||||
|
///
|
||||||
|
/// Tags: math.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Texify)]
|
#[capable(Texify)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
|
@ -2,6 +2,8 @@ use crate::layout::{LayoutRoot, PageNode};
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// The root node that represents a full document.
|
/// The root node that represents a full document.
|
||||||
|
///
|
||||||
|
/// Tags: meta.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(LayoutRoot)]
|
#[capable(LayoutRoot)]
|
||||||
#[derive(Hash)]
|
#[derive(Hash)]
|
||||||
|
@ -2,6 +2,8 @@ use crate::prelude::*;
|
|||||||
use crate::text::TextNode;
|
use crate::text::TextNode;
|
||||||
|
|
||||||
/// Link text and other elements to a destination.
|
/// Link text and other elements to a destination.
|
||||||
|
///
|
||||||
|
/// Tags: meta.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Show, Finalize)]
|
#[capable(Show, Finalize)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
|
@ -4,6 +4,8 @@ use crate::prelude::*;
|
|||||||
use crate::text::{LinebreakNode, SpaceNode, TextNode};
|
use crate::text::{LinebreakNode, SpaceNode, TextNode};
|
||||||
|
|
||||||
/// A section outline (table of contents).
|
/// A section outline (table of contents).
|
||||||
|
///
|
||||||
|
/// Tags: meta.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Prepare, Show)]
|
#[capable(Prepare, Show)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
|
@ -2,6 +2,8 @@ use crate::prelude::*;
|
|||||||
use crate::text::TextNode;
|
use crate::text::TextNode;
|
||||||
|
|
||||||
/// A reference to a label.
|
/// A reference to a label.
|
||||||
|
///
|
||||||
|
/// Tags: meta.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Show)]
|
#[capable(Show)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
|
@ -8,17 +8,17 @@ pub use std::num::NonZeroUsize;
|
|||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use comemo::{Track, Tracked, TrackedMut};
|
pub use comemo::{Track, Tracked, TrackedMut};
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use typst::diag::{bail, error, with_alternative, At, SourceResult, StrResult};
|
pub use typst::diag::{bail, error, At, SourceResult, StrResult};
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use typst::doc::*;
|
pub use typst::doc::*;
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use typst::geom::*;
|
pub use typst::geom::*;
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use typst::model::{
|
pub use typst::model::{
|
||||||
array, capability, capable, castable, dict, dynamic, format_str, func, node, Args,
|
array, capability, capable, castable, dict, format_str, func, node, Args, Array,
|
||||||
Array, Cast, Content, Dict, Finalize, Fold, Func, Introspector, Label, Node, NodeId,
|
AutoValue, Cast, CastInfo, Content, Dict, Finalize, Fold, Func, Introspector, Label,
|
||||||
Prepare, Resolve, Selector, Show, Smart, StabilityProvider, Str, StyleChain,
|
Node, NodeId, NoneValue, Prepare, Resolve, Selector, Show, StabilityProvider, Str,
|
||||||
StyleMap, StyleVec, Unlabellable, Value, Vm, Vt,
|
StyleChain, StyleMap, StyleVec, Unlabellable, Value, Vm, Vt,
|
||||||
};
|
};
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use typst::syntax::{Span, Spanned};
|
pub use typst::syntax::{Span, Spanned};
|
||||||
|
@ -5,6 +5,8 @@ use super::TextNode;
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// Typeset underline, stricken-through or overlined text.
|
/// Typeset underline, stricken-through or overlined text.
|
||||||
|
///
|
||||||
|
/// Tags: text.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Show)]
|
#[capable(Show)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
|
@ -2,6 +2,8 @@ use super::TextNode;
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// A text space.
|
/// A text space.
|
||||||
|
///
|
||||||
|
/// Tags: text.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Unlabellable, Behave)]
|
#[capable(Unlabellable, Behave)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -23,6 +25,8 @@ impl Behave for SpaceNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A line break.
|
/// A line break.
|
||||||
|
///
|
||||||
|
/// Tags: text.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Behave)]
|
#[capable(Behave)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -45,6 +49,8 @@ impl Behave for LinebreakNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Strongly emphasizes content by increasing the font weight.
|
/// Strongly emphasizes content by increasing the font weight.
|
||||||
|
///
|
||||||
|
/// Tags: text.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Show)]
|
#[capable(Show)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -79,8 +85,7 @@ pub struct Delta(pub i64);
|
|||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
Delta,
|
Delta,
|
||||||
Expected: "integer",
|
v: i64 => Self(v),
|
||||||
Value::Int(delta) => Self(delta),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fold for Delta {
|
impl Fold for Delta {
|
||||||
@ -92,6 +97,8 @@ impl Fold for Delta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Emphasizes content by flipping the italicness.
|
/// Emphasizes content by flipping the italicness.
|
||||||
|
///
|
||||||
|
/// Tags: text.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Show)]
|
#[capable(Show)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -130,12 +137,16 @@ impl Fold for Toggle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a string or content to lowercase.
|
/// Convert a string or content to lowercase.
|
||||||
|
///
|
||||||
|
/// Tags: text.
|
||||||
#[func]
|
#[func]
|
||||||
pub fn lower(args: &mut Args) -> SourceResult<Value> {
|
pub fn lower(args: &mut Args) -> SourceResult<Value> {
|
||||||
case(Case::Lower, args)
|
case(Case::Lower, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a string or content to uppercase.
|
/// Convert a string or content to uppercase.
|
||||||
|
///
|
||||||
|
/// Tags: text.
|
||||||
#[func]
|
#[func]
|
||||||
pub fn upper(args: &mut Args) -> SourceResult<Value> {
|
pub fn upper(args: &mut Args) -> SourceResult<Value> {
|
||||||
case(Case::Upper, args)
|
case(Case::Upper, args)
|
||||||
@ -171,6 +182,8 @@ impl Case {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Display text in small capitals.
|
/// Display text in small capitals.
|
||||||
|
///
|
||||||
|
/// Tags: text.
|
||||||
#[func]
|
#[func]
|
||||||
pub fn smallcaps(args: &mut Args) -> SourceResult<Value> {
|
pub fn smallcaps(args: &mut Args) -> SourceResult<Value> {
|
||||||
let body: Content = args.expect("content")?;
|
let body: Content = args.expect("content")?;
|
||||||
|
@ -26,6 +26,8 @@ use crate::layout::ParNode;
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// A single run of text with the same style.
|
/// A single run of text with the same style.
|
||||||
|
///
|
||||||
|
/// Tags: text.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable]
|
#[capable]
|
||||||
#[derive(Clone, Hash)]
|
#[derive(Clone, Hash)]
|
||||||
@ -206,8 +208,7 @@ impl Debug for FontFamily {
|
|||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
FontFamily,
|
FontFamily,
|
||||||
Expected: "string",
|
string: EcoString => Self::new(&string),
|
||||||
Value::Str(string) => Self::new(&string),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Font family fallback list.
|
/// Font family fallback list.
|
||||||
@ -216,12 +217,10 @@ pub struct FallbackList(pub Vec<FontFamily>);
|
|||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
FallbackList,
|
FallbackList,
|
||||||
Expected: "string or array of strings",
|
family: FontFamily => Self(vec![family]),
|
||||||
Value::Str(string) => Self(vec![FontFamily::new(&string)]),
|
values: Array => Self(values
|
||||||
Value::Array(values) => Self(values
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|v| v.cast().ok())
|
.filter_map(|v| v.cast().ok())
|
||||||
.map(|string: EcoString| FontFamily::new(&string))
|
|
||||||
.collect()),
|
.collect()),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,7 +236,10 @@ impl Fold for TextSize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
castable!(TextSize: Length);
|
castable! {
|
||||||
|
TextSize,
|
||||||
|
v: Length => Self(v),
|
||||||
|
}
|
||||||
|
|
||||||
/// Specifies the bottom or top edge of text.
|
/// Specifies the bottom or top edge of text.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
@ -260,16 +262,17 @@ impl TextEdge {
|
|||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
TextEdge,
|
TextEdge,
|
||||||
Expected: "string or length",
|
v: Length => Self::Length(v),
|
||||||
Value::Length(v) => Self::Length(v),
|
/// The distance from the baseline to the ascender.
|
||||||
Value::Str(string) => Self::Metric(match string.as_str() {
|
"ascender" => Self::Metric(VerticalFontMetric::Ascender),
|
||||||
"ascender" => VerticalFontMetric::Ascender,
|
/// The approximate height of uppercase letters.
|
||||||
"cap-height" => VerticalFontMetric::CapHeight,
|
"cap-height" => Self::Metric(VerticalFontMetric::CapHeight),
|
||||||
"x-height" => VerticalFontMetric::XHeight,
|
/// The approximate height of non-ascending lowercase letters.
|
||||||
"baseline" => VerticalFontMetric::Baseline,
|
"x-height" => Self::Metric(VerticalFontMetric::XHeight),
|
||||||
"descender" => VerticalFontMetric::Descender,
|
/// The baseline on which the letters rest.
|
||||||
_ => Err("unknown font metric")?,
|
"baseline" => Self::Metric(VerticalFontMetric::Baseline),
|
||||||
}),
|
/// The distance from the baseline to the descender.
|
||||||
|
"descender" => Self::Metric(VerticalFontMetric::Descender),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The direction of text and inline objects in their line.
|
/// The direction of text and inline objects in their line.
|
||||||
@ -278,10 +281,9 @@ pub struct HorizontalDir(pub Smart<Dir>);
|
|||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
HorizontalDir,
|
HorizontalDir,
|
||||||
Expected: "direction or auto",
|
_: AutoValue => Self(Smart::Auto),
|
||||||
Value::Auto => Self(Smart::Auto),
|
dir: Dir => match dir.axis() {
|
||||||
@dir: Dir => match dir.axis() {
|
Axis::X => Self(Smart::Custom(dir)),
|
||||||
Axis::X => Self(Smart::Custom(*dir)),
|
|
||||||
Axis::Y => Err("must be horizontal")?,
|
Axis::Y => Err("must be horizontal")?,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -303,9 +305,8 @@ pub struct Hyphenate(pub Smart<bool>);
|
|||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
Hyphenate,
|
Hyphenate,
|
||||||
Expected: "boolean or auto",
|
_: AutoValue => Self(Smart::Auto),
|
||||||
Value::Auto => Self(Smart::Auto),
|
v: bool => Self(Smart::Custom(v)),
|
||||||
Value::Bool(v) => Self(Smart::Custom(v)),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Resolve for Hyphenate {
|
impl Resolve for Hyphenate {
|
||||||
@ -337,8 +338,7 @@ impl StylisticSet {
|
|||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
StylisticSet,
|
StylisticSet,
|
||||||
Expected: "integer",
|
v: i64 => match v {
|
||||||
Value::Int(v) => match v {
|
|
||||||
1 ..= 20 => Self::new(v as u8),
|
1 ..= 20 => Self::new(v as u8),
|
||||||
_ => Err("must be between 1 and 20")?,
|
_ => Err("must be between 1 and 20")?,
|
||||||
},
|
},
|
||||||
@ -355,12 +355,10 @@ pub enum NumberType {
|
|||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
NumberType,
|
NumberType,
|
||||||
Expected: "string",
|
/// Numbers that fit well with capital text.
|
||||||
Value::Str(string) => match string.as_str() {
|
"lining" => Self::Lining,
|
||||||
"lining" => Self::Lining,
|
/// Numbers that fit well into a flow of upper- and lowercase text.
|
||||||
"old-style" => Self::OldStyle,
|
"old-style" => Self::OldStyle,
|
||||||
_ => Err(r#"expected "lining" or "old-style""#)?,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The width of numbers / figures.
|
/// The width of numbers / figures.
|
||||||
@ -374,12 +372,10 @@ pub enum NumberWidth {
|
|||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
NumberWidth,
|
NumberWidth,
|
||||||
Expected: "string",
|
/// Number widths are glyph specific.
|
||||||
Value::Str(string) => match string.as_str() {
|
"proportional" => Self::Proportional,
|
||||||
"proportional" => Self::Proportional,
|
/// All numbers are of equal width / monospaced.
|
||||||
"tabular" => Self::Tabular,
|
"tabular" => Self::Tabular,
|
||||||
_ => Err(r#"expected "proportional" or "tabular""#)?,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// OpenType font features settings.
|
/// OpenType font features settings.
|
||||||
@ -388,20 +384,21 @@ pub struct FontFeatures(pub Vec<(Tag, u32)>);
|
|||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
FontFeatures,
|
FontFeatures,
|
||||||
Expected: "array of strings or dictionary mapping tags to integers",
|
values: Array => Self(values
|
||||||
Value::Array(values) => Self(values
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|v| v.cast().ok())
|
.map(|v| {
|
||||||
.map(|string: EcoString| (Tag::from_bytes_lossy(string.as_bytes()), 1))
|
let tag = v.cast::<EcoString>()?;
|
||||||
.collect()),
|
Ok((Tag::from_bytes_lossy(tag.as_bytes()), 1))
|
||||||
Value::Dict(values) => Self(values
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|(k, v)| {
|
|
||||||
let tag = Tag::from_bytes_lossy(k.as_bytes());
|
|
||||||
let num = v.cast::<i64>().ok()?.try_into().ok()?;
|
|
||||||
Some((tag, num))
|
|
||||||
})
|
})
|
||||||
.collect()),
|
.collect::<StrResult<_>>()?),
|
||||||
|
values: Dict => Self(values
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, v)| {
|
||||||
|
let num = v.cast::<u32>()?;
|
||||||
|
let tag = Tag::from_bytes_lossy(k.as_bytes());
|
||||||
|
Ok((tag, num))
|
||||||
|
})
|
||||||
|
.collect::<StrResult<_>>()?),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fold for FontFeatures {
|
impl Fold for FontFeatures {
|
||||||
|
@ -3,6 +3,8 @@ use typst::syntax::is_newline;
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// A smart quote.
|
/// A smart quote.
|
||||||
|
///
|
||||||
|
/// Tags: text.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable]
|
#[capable]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
|
@ -7,6 +7,8 @@ use crate::layout::BlockNode;
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// Raw text with optional syntax highlighting.
|
/// Raw text with optional syntax highlighting.
|
||||||
|
///
|
||||||
|
/// Tags: text.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Show)]
|
#[capable(Show)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
|
@ -10,6 +10,8 @@ use crate::prelude::*;
|
|||||||
/// typography possible, we first try to transform the text to superscript
|
/// typography possible, we first try to transform the text to superscript
|
||||||
/// codepoints. If that fails, we fall back to rendering shrunk normal letters
|
/// codepoints. If that fails, we fall back to rendering shrunk normal letters
|
||||||
/// in a raised way.
|
/// in a raised way.
|
||||||
|
///
|
||||||
|
/// Tags: text.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Show)]
|
#[capable(Show)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
|
@ -2,6 +2,8 @@ use crate::prelude::*;
|
|||||||
use crate::text::TextNode;
|
use crate::text::TextNode;
|
||||||
|
|
||||||
/// A symbol identified by symmie notation.
|
/// A symbol identified by symmie notation.
|
||||||
|
///
|
||||||
|
/// Tags: text.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Show)]
|
#[capable(Show)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
|
@ -5,6 +5,8 @@ use typst::image::{Image, ImageFormat, RasterFormat, VectorFormat};
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// Show a raster or vector graphic.
|
/// Show a raster or vector graphic.
|
||||||
|
///
|
||||||
|
/// Tags: visualize.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Layout, Inline)]
|
#[capable(Layout, Inline)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -112,11 +114,10 @@ pub enum ImageFit {
|
|||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
ImageFit,
|
ImageFit,
|
||||||
Expected: "string",
|
/// The image should completely cover the area.
|
||||||
Value::Str(string) => match string.as_str() {
|
"cover" => Self::Cover,
|
||||||
"cover" => Self::Cover,
|
/// The image should be fully contained in the area.
|
||||||
"contain" => Self::Contain,
|
"contain" => Self::Contain,
|
||||||
"stretch" => Self::Stretch,
|
/// The image should be stretched so that it exactly fills the area.
|
||||||
_ => Err(r#"expected "cover", "contain" or "stretch""#)?,
|
"stretch" => Self::Stretch,
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// Display a line without affecting the layout.
|
/// Display a line without affecting the layout.
|
||||||
|
///
|
||||||
|
/// Tags: visualize.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Layout, Inline)]
|
#[capable(Layout, Inline)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
|
@ -3,6 +3,8 @@ use std::f64::consts::SQRT_2;
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// A sizable and fillable shape with optional content.
|
/// A sizable and fillable shape with optional content.
|
||||||
|
///
|
||||||
|
/// Tags: visualize.
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(Layout, Inline)]
|
#[capable(Layout, Inline)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -25,7 +27,7 @@ impl<const S: ShapeKind> ShapeNode<S> {
|
|||||||
/// How to fill the shape.
|
/// How to fill the shape.
|
||||||
pub const FILL: Option<Paint> = None;
|
pub const FILL: Option<Paint> = None;
|
||||||
/// How to stroke the shape.
|
/// How to stroke the shape.
|
||||||
#[property(skip, resolve, fold)]
|
#[property(reflect, skip, resolve, fold)]
|
||||||
pub const STROKE: Smart<Sides<Option<PartialStroke>>> = Smart::Auto;
|
pub const STROKE: Smart<Sides<Option<PartialStroke>>> = Smart::Auto;
|
||||||
|
|
||||||
/// How much to pad the shape's content.
|
/// How much to pad the shape's content.
|
||||||
@ -36,7 +38,7 @@ impl<const S: ShapeKind> ShapeNode<S> {
|
|||||||
pub const OUTSET: Sides<Option<Rel<Length>>> = Sides::splat(Rel::zero());
|
pub const OUTSET: Sides<Option<Rel<Length>>> = Sides::splat(Rel::zero());
|
||||||
|
|
||||||
/// How much to round the shape's corners.
|
/// How much to round the shape's corners.
|
||||||
#[property(skip, resolve, fold)]
|
#[property(reflect, skip, resolve, fold)]
|
||||||
pub const RADIUS: Corners<Option<Rel<Length>>> = Corners::splat(Rel::zero());
|
pub const RADIUS: Corners<Option<Rel<Length>>> = Corners::splat(Rel::zero());
|
||||||
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use super::*;
|
|
||||||
|
|
||||||
use syn::parse::Parser;
|
use syn::parse::Parser;
|
||||||
use syn::punctuated::Punctuated;
|
use syn::punctuated::Punctuated;
|
||||||
use syn::Token;
|
use syn::Token;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
/// Expand the `#[capability]` macro.
|
/// Expand the `#[capability]` macro.
|
||||||
pub fn capability(item: syn::ItemTrait) -> Result<TokenStream> {
|
pub fn capability(item: syn::ItemTrait) -> Result<TokenStream> {
|
||||||
let ident = &item.ident;
|
let ident = &item.ident;
|
229
macros/src/castable.rs
Normal file
229
macros/src/castable.rs
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
use syn::parse::{Parse, ParseStream};
|
||||||
|
use syn::punctuated::Punctuated;
|
||||||
|
use syn::Token;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Expand the `castable!` macro.
|
||||||
|
pub fn castable(stream: TokenStream) -> Result<TokenStream> {
|
||||||
|
let castable: Castable = syn::parse2(stream)?;
|
||||||
|
let ty = &castable.ty;
|
||||||
|
|
||||||
|
if castable.casts.is_empty() && castable.name.is_none() {
|
||||||
|
bail!(castable.ty, "expected at least one pattern");
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_func = create_is_func(&castable);
|
||||||
|
let cast_func = create_cast_func(&castable);
|
||||||
|
let describe_func = create_describe_func(&castable);
|
||||||
|
let dynamic_impls = castable.name.as_ref().map(|name| {
|
||||||
|
quote! {
|
||||||
|
impl ::typst::model::Type for #ty {
|
||||||
|
const TYPE_NAME: &'static str = #name;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<#ty> for ::typst::model::Value {
|
||||||
|
fn from(v: #ty) -> Self {
|
||||||
|
::typst::model::Value::Dyn(::typst::model::Dynamic::new(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(quote! {
|
||||||
|
impl ::typst::model::Cast for #ty {
|
||||||
|
#is_func
|
||||||
|
#cast_func
|
||||||
|
#describe_func
|
||||||
|
}
|
||||||
|
|
||||||
|
#dynamic_impls
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create the castable's `is` function.
|
||||||
|
fn create_is_func(castable: &Castable) -> TokenStream {
|
||||||
|
let mut string_arms = vec![];
|
||||||
|
let mut cast_checks = vec![];
|
||||||
|
|
||||||
|
for cast in &castable.casts {
|
||||||
|
match &cast.pattern {
|
||||||
|
Pattern::Str(lit) => {
|
||||||
|
string_arms.push(quote! { #lit => return true });
|
||||||
|
}
|
||||||
|
Pattern::Ty(_, ty) => {
|
||||||
|
cast_checks.push(quote! {
|
||||||
|
if <#ty as ::typst::model::Cast>::is(value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let dynamic_check = castable.name.is_some().then(|| {
|
||||||
|
quote! {
|
||||||
|
if let ::typst::model::Value::Dyn(dynamic) = &value {
|
||||||
|
if dynamic.is::<Self>() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let str_check = (!string_arms.is_empty()).then(|| {
|
||||||
|
quote! {
|
||||||
|
if let ::typst::model::Value::Str(string) = &value {
|
||||||
|
match string.as_str() {
|
||||||
|
#(#string_arms,)*
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
fn is(value: &typst::model::Value) -> bool {
|
||||||
|
#dynamic_check
|
||||||
|
#str_check
|
||||||
|
#(#cast_checks)*
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create the castable's `cast` function.
|
||||||
|
fn create_cast_func(castable: &Castable) -> TokenStream {
|
||||||
|
let mut string_arms = vec![];
|
||||||
|
let mut cast_checks = vec![];
|
||||||
|
|
||||||
|
for cast in &castable.casts {
|
||||||
|
let expr = &cast.expr;
|
||||||
|
match &cast.pattern {
|
||||||
|
Pattern::Str(lit) => {
|
||||||
|
string_arms.push(quote! { #lit => return Ok(#expr) });
|
||||||
|
}
|
||||||
|
Pattern::Ty(binding, ty) => {
|
||||||
|
cast_checks.push(quote! {
|
||||||
|
if <#ty as ::typst::model::Cast>::is(&value) {
|
||||||
|
let #binding = <#ty as ::typst::model::Cast>::cast(value)?;
|
||||||
|
return Ok(#expr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let dynamic_check = castable.name.is_some().then(|| {
|
||||||
|
quote! {
|
||||||
|
if let ::typst::model::Value::Dyn(dynamic) = &value {
|
||||||
|
if let Some(concrete) = dynamic.downcast::<Self>() {
|
||||||
|
return Ok(concrete.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let str_check = (!string_arms.is_empty()).then(|| {
|
||||||
|
quote! {
|
||||||
|
if let ::typst::model::Value::Str(string) = &value {
|
||||||
|
match string.as_str() {
|
||||||
|
#(#string_arms,)*
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
fn cast(value: ::typst::model::Value) -> ::typst::diag::StrResult<Self> {
|
||||||
|
#dynamic_check
|
||||||
|
#str_check
|
||||||
|
#(#cast_checks)*
|
||||||
|
<Self as ::typst::model::Cast>::error(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create the castable's `describe` function.
|
||||||
|
fn create_describe_func(castable: &Castable) -> TokenStream {
|
||||||
|
let mut infos = vec![];
|
||||||
|
|
||||||
|
for cast in &castable.casts {
|
||||||
|
let docs = doc_comment(&cast.attrs);
|
||||||
|
infos.push(match &cast.pattern {
|
||||||
|
Pattern::Str(lit) => {
|
||||||
|
quote! { ::typst::model::CastInfo::Value(#lit.into(), #docs) }
|
||||||
|
}
|
||||||
|
Pattern::Ty(_, ty) => {
|
||||||
|
quote! { <#ty as ::typst::model::Cast>::describe() }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(name) = &castable.name {
|
||||||
|
infos.push(quote! {
|
||||||
|
CastInfo::Type(#name)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
fn describe() -> ::typst::model::CastInfo {
|
||||||
|
#(#infos)+*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Castable {
|
||||||
|
ty: syn::Type,
|
||||||
|
name: Option<syn::LitStr>,
|
||||||
|
casts: Punctuated<Cast, Token![,]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Castable {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
let ty = input.parse()?;
|
||||||
|
let mut name = None;
|
||||||
|
if input.peek(Token![:]) {
|
||||||
|
let _: syn::Token![:] = input.parse()?;
|
||||||
|
name = Some(input.parse()?);
|
||||||
|
}
|
||||||
|
let _: syn::Token![,] = input.parse()?;
|
||||||
|
let casts = Punctuated::parse_terminated(input)?;
|
||||||
|
Ok(Self { ty, name, casts })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Cast {
|
||||||
|
attrs: Vec<syn::Attribute>,
|
||||||
|
pattern: Pattern,
|
||||||
|
expr: syn::Expr,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Cast {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
let attrs = input.call(syn::Attribute::parse_outer)?;
|
||||||
|
let pattern = input.parse()?;
|
||||||
|
let _: syn::Token![=>] = input.parse()?;
|
||||||
|
let expr = input.parse()?;
|
||||||
|
Ok(Self { attrs, pattern, expr })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Pattern {
|
||||||
|
Str(syn::LitStr),
|
||||||
|
Ty(syn::Pat, syn::Type),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Pattern {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
if input.peek(syn::LitStr) {
|
||||||
|
Ok(Pattern::Str(input.parse()?))
|
||||||
|
} else {
|
||||||
|
let pat = input.parse()?;
|
||||||
|
let _: syn::Token![:] = input.parse()?;
|
||||||
|
let ty = input.parse()?;
|
||||||
|
Ok(Pattern::Ty(pat, ty))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,37 @@ use super::*;
|
|||||||
|
|
||||||
/// Expand the `#[func]` macro.
|
/// Expand the `#[func]` macro.
|
||||||
pub fn func(item: syn::Item) -> Result<TokenStream> {
|
pub fn func(item: syn::Item) -> Result<TokenStream> {
|
||||||
let doc = documentation(&item)?;
|
let doc_comment = match &item {
|
||||||
|
syn::Item::Struct(item) => doc_comment(&item.attrs),
|
||||||
|
syn::Item::Enum(item) => doc_comment(&item.attrs),
|
||||||
|
syn::Item::Fn(item) => doc_comment(&item.attrs),
|
||||||
|
_ => String::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut tags = vec![];
|
||||||
|
let mut kept = vec![];
|
||||||
|
for line in doc_comment.lines() {
|
||||||
|
let line = line.trim();
|
||||||
|
if let Some(suffix) = line.trim_end_matches(".").strip_prefix("Tags: ") {
|
||||||
|
tags.extend(suffix.split(", "));
|
||||||
|
} else {
|
||||||
|
kept.push(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while kept.last().map_or(false, |line| line.is_empty()) {
|
||||||
|
kept.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
let docs = kept.join("\n");
|
||||||
|
let info = quote! {
|
||||||
|
::typst::model::FuncInfo {
|
||||||
|
name,
|
||||||
|
docs: #docs,
|
||||||
|
tags: &[#(#tags),*],
|
||||||
|
params: ::std::vec![],
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if let syn::Item::Fn(item) = &item {
|
if let syn::Item::Fn(item) = &item {
|
||||||
let vis = &item.vis;
|
let vis = &item.vis;
|
||||||
@ -29,7 +59,7 @@ pub fn func(item: syn::Item) -> Result<TokenStream> {
|
|||||||
|
|
||||||
impl::typst::model::FuncType for #ty {
|
impl::typst::model::FuncType for #ty {
|
||||||
fn create_func(name: &'static str) -> ::typst::model::Func {
|
fn create_func(name: &'static str) -> ::typst::model::Func {
|
||||||
::typst::model::Func::from_fn(name, #full, #doc)
|
::typst::model::Func::from_fn(name, #full, #info)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -47,36 +77,9 @@ pub fn func(item: syn::Item) -> Result<TokenStream> {
|
|||||||
|
|
||||||
impl #params ::typst::model::FuncType for #ident #args #clause {
|
impl #params ::typst::model::FuncType for #ident #args #clause {
|
||||||
fn create_func(name: &'static str) -> ::typst::model::Func {
|
fn create_func(name: &'static str) -> ::typst::model::Func {
|
||||||
::typst::model::Func::from_node::<Self>(name, #doc)
|
::typst::model::Func::from_node::<Self>(name, #info)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract the item's documentation.
|
|
||||||
fn documentation(item: &syn::Item) -> Result<String> {
|
|
||||||
let mut doc = String::new();
|
|
||||||
|
|
||||||
// Extract attributes.
|
|
||||||
let attrs = match item {
|
|
||||||
syn::Item::Struct(item) => &item.attrs,
|
|
||||||
syn::Item::Enum(item) => &item.attrs,
|
|
||||||
syn::Item::Fn(item) => &item.attrs,
|
|
||||||
_ => return Ok(doc),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Parse doc comments.
|
|
||||||
for attr in attrs {
|
|
||||||
if let syn::Meta::NameValue(meta) = attr.parse_meta()? {
|
|
||||||
if meta.path.is_ident("doc") {
|
|
||||||
if let syn::Lit::Str(string) = &meta.lit {
|
|
||||||
doc.push_str(&string.value());
|
|
||||||
doc.push('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(doc.trim().into())
|
|
||||||
}
|
|
||||||
|
@ -12,7 +12,8 @@ macro_rules! bail {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod capability;
|
mod capable;
|
||||||
|
mod castable;
|
||||||
mod func;
|
mod func;
|
||||||
mod node;
|
mod node;
|
||||||
|
|
||||||
@ -40,7 +41,7 @@ pub fn node(_: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
|
|||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn capability(_: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
|
pub fn capability(_: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
|
||||||
let item = syn::parse_macro_input!(item as syn::ItemTrait);
|
let item = syn::parse_macro_input!(item as syn::ItemTrait);
|
||||||
capability::capability(item)
|
capable::capability(item)
|
||||||
.unwrap_or_else(|err| err.to_compile_error())
|
.unwrap_or_else(|err| err.to_compile_error())
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
@ -49,7 +50,34 @@ pub fn capability(_: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
|
|||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn capable(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
|
pub fn capable(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
|
||||||
let item = syn::parse_macro_input!(item as syn::Item);
|
let item = syn::parse_macro_input!(item as syn::Item);
|
||||||
capability::capable(stream.into(), item)
|
capable::capable(stream.into(), item)
|
||||||
.unwrap_or_else(|err| err.to_compile_error())
|
.unwrap_or_else(|err| err.to_compile_error())
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implement `Cast` and optionally `Type` for a type.
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn castable(stream: BoundaryStream) -> BoundaryStream {
|
||||||
|
castable::castable(stream.into())
|
||||||
|
.unwrap_or_else(|err| err.to_compile_error())
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract documentation comments from an attribute list.
|
||||||
|
fn doc_comment(attrs: &[syn::Attribute]) -> String {
|
||||||
|
let mut doc = String::new();
|
||||||
|
|
||||||
|
// Parse doc comments.
|
||||||
|
for attr in attrs {
|
||||||
|
if let Ok(syn::Meta::NameValue(meta)) = attr.parse_meta() {
|
||||||
|
if meta.path.is_ident("doc") {
|
||||||
|
if let syn::Lit::Str(string) = &meta.lit {
|
||||||
|
doc.push_str(&string.value());
|
||||||
|
doc.push('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
doc.trim().into()
|
||||||
|
}
|
||||||
|
@ -36,6 +36,7 @@ struct Property {
|
|||||||
shorthand: Option<Shorthand>,
|
shorthand: Option<Shorthand>,
|
||||||
resolve: bool,
|
resolve: bool,
|
||||||
fold: bool,
|
fold: bool,
|
||||||
|
reflect: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The shorthand form of a style property.
|
/// The shorthand form of a style property.
|
||||||
@ -117,6 +118,7 @@ fn prepare_property(item: &syn::ImplItemConst) -> Result<Property> {
|
|||||||
let mut referenced = false;
|
let mut referenced = false;
|
||||||
let mut resolve = false;
|
let mut resolve = false;
|
||||||
let mut fold = false;
|
let mut fold = false;
|
||||||
|
let mut reflect = false;
|
||||||
|
|
||||||
// Parse the `#[property(..)]` attribute.
|
// Parse the `#[property(..)]` attribute.
|
||||||
let mut stream = tokens.into_iter().peekable();
|
let mut stream = tokens.into_iter().peekable();
|
||||||
@ -150,14 +152,11 @@ fn prepare_property(item: &syn::ImplItemConst) -> Result<Property> {
|
|||||||
"referenced" => referenced = true,
|
"referenced" => referenced = true,
|
||||||
"resolve" => resolve = true,
|
"resolve" => resolve = true,
|
||||||
"fold" => fold = true,
|
"fold" => fold = true,
|
||||||
|
"reflect" => reflect = true,
|
||||||
_ => bail!(ident, "invalid attribute"),
|
_ => bail!(ident, "invalid attribute"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if skip && shorthand.is_some() {
|
|
||||||
bail!(item.ident, "skip and shorthand are mutually exclusive");
|
|
||||||
}
|
|
||||||
|
|
||||||
if referenced && (fold || resolve) {
|
if referenced && (fold || resolve) {
|
||||||
bail!(item.ident, "referenced is mutually exclusive with fold and resolve");
|
bail!(item.ident, "referenced is mutually exclusive with fold and resolve");
|
||||||
}
|
}
|
||||||
@ -193,6 +192,7 @@ fn prepare_property(item: &syn::ImplItemConst) -> Result<Property> {
|
|||||||
referenced,
|
referenced,
|
||||||
resolve,
|
resolve,
|
||||||
fold,
|
fold,
|
||||||
|
reflect,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,6 +205,7 @@ fn create(node: &Node) -> Result<TokenStream> {
|
|||||||
let name_method = create_node_name_method(node);
|
let name_method = create_node_name_method(node);
|
||||||
let construct_func = create_node_construct_func(node);
|
let construct_func = create_node_construct_func(node);
|
||||||
let set_func = create_node_set_func(node);
|
let set_func = create_node_set_func(node);
|
||||||
|
let properties_func = create_node_properties_func(node);
|
||||||
let field_method = create_node_field_method(node);
|
let field_method = create_node_field_method(node);
|
||||||
|
|
||||||
let node_impl = quote! {
|
let node_impl = quote! {
|
||||||
@ -213,6 +214,7 @@ fn create(node: &Node) -> Result<TokenStream> {
|
|||||||
#name_method
|
#name_method
|
||||||
#construct_func
|
#construct_func
|
||||||
#set_func
|
#set_func
|
||||||
|
#properties_func
|
||||||
#field_method
|
#field_method
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -221,7 +223,7 @@ fn create(node: &Node) -> Result<TokenStream> {
|
|||||||
let mut items: Vec<syn::ImplItem> = vec![];
|
let mut items: Vec<syn::ImplItem> = vec![];
|
||||||
let scope = quote::format_ident!("__{}_keys", node.self_name);
|
let scope = quote::format_ident!("__{}_keys", node.self_name);
|
||||||
|
|
||||||
for property in &node.properties {
|
for property in node.properties.iter() {
|
||||||
let (key, module) = create_property_module(node, &property);
|
let (key, module) = create_property_module(node, &property);
|
||||||
modules.push(module);
|
modules.push(module);
|
||||||
|
|
||||||
@ -331,6 +333,40 @@ fn create_node_set_func(node: &Node) -> syn::ImplItemMethod {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create the node's `properties` function.
|
||||||
|
fn create_node_properties_func(node: &Node) -> syn::ImplItemMethod {
|
||||||
|
let infos = node
|
||||||
|
.properties
|
||||||
|
.iter()
|
||||||
|
.filter(|p| !p.skip || p.reflect)
|
||||||
|
.map(|property| {
|
||||||
|
let name = property.name.to_string().replace('_', "-").to_lowercase();
|
||||||
|
let docs = doc_comment(&property.attrs);
|
||||||
|
let value_ty = &property.value_ty;
|
||||||
|
let shorthand = matches!(property.shorthand, Some(Shorthand::Positional));
|
||||||
|
quote! {
|
||||||
|
::typst::model::ParamInfo {
|
||||||
|
name: #name,
|
||||||
|
docs: #docs,
|
||||||
|
settable: true,
|
||||||
|
shorthand: #shorthand,
|
||||||
|
cast: <#value_ty as ::typst::model::Cast<
|
||||||
|
::typst::syntax::Spanned<::typst::model::Value>
|
||||||
|
>>::describe(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
parse_quote! {
|
||||||
|
fn properties() -> ::std::vec::Vec<::typst::model::ParamInfo>
|
||||||
|
where
|
||||||
|
Self: Sized
|
||||||
|
{
|
||||||
|
::std::vec![#(#infos),*]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Create the node's `field` method.
|
/// Create the node's `field` method.
|
||||||
fn create_node_field_method(node: &Node) -> syn::ImplItemMethod {
|
fn create_node_field_method(node: &Node) -> syn::ImplItemMethod {
|
||||||
node.field.clone().unwrap_or_else(|| {
|
node.field.clone().unwrap_or_else(|| {
|
||||||
|
34
src/diag.rs
34
src/diag.rs
@ -156,16 +156,6 @@ impl<T> Trace<T> for SourceResult<T> {
|
|||||||
/// A result type with a string error message.
|
/// A result type with a string error message.
|
||||||
pub type StrResult<T> = Result<T, EcoString>;
|
pub type StrResult<T> = Result<T, EcoString>;
|
||||||
|
|
||||||
/// Transform `expected X, found Y` into `expected X or A, found Y`.
|
|
||||||
pub fn with_alternative(msg: EcoString, alt: &str) -> EcoString {
|
|
||||||
let mut parts = msg.split(", found ");
|
|
||||||
if let (Some(a), Some(b)) = (parts.next(), parts.next()) {
|
|
||||||
format_eco!("{} or {}, found {}", a, alt, b)
|
|
||||||
} else {
|
|
||||||
msg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert a [`StrResult`] to a [`SourceResult`] by adding span information.
|
/// Convert a [`StrResult`] to a [`SourceResult`] by adding span information.
|
||||||
pub trait At<T> {
|
pub trait At<T> {
|
||||||
/// Add the span information.
|
/// Add the span information.
|
||||||
@ -181,6 +171,30 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Format the parts separated with commas and a final "and" or "or".
|
||||||
|
pub(crate) fn comma_list<S>(buf: &mut String, parts: &[S], last: &str)
|
||||||
|
where
|
||||||
|
S: AsRef<str>,
|
||||||
|
{
|
||||||
|
for (i, part) in parts.iter().enumerate() {
|
||||||
|
match i {
|
||||||
|
0 => {}
|
||||||
|
1 if parts.len() == 2 => {
|
||||||
|
buf.push(' ');
|
||||||
|
buf.push_str(last);
|
||||||
|
buf.push(' ');
|
||||||
|
}
|
||||||
|
i if i + 1 == parts.len() => {
|
||||||
|
buf.push_str(", ");
|
||||||
|
buf.push_str(last);
|
||||||
|
buf.push(' ');
|
||||||
|
}
|
||||||
|
_ => buf.push_str(", "),
|
||||||
|
}
|
||||||
|
buf.push_str(part.as_ref());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A result type with a file-related error.
|
/// A result type with a file-related error.
|
||||||
pub type FileResult<T> = Result<T, FileError>;
|
pub type FileResult<T> = Result<T, FileError>;
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ mod rounded;
|
|||||||
mod scalar;
|
mod scalar;
|
||||||
mod sides;
|
mod sides;
|
||||||
mod size;
|
mod size;
|
||||||
|
mod smart;
|
||||||
mod stroke;
|
mod stroke;
|
||||||
mod transform;
|
mod transform;
|
||||||
|
|
||||||
@ -43,6 +44,7 @@ pub use self::rounded::*;
|
|||||||
pub use self::scalar::*;
|
pub use self::scalar::*;
|
||||||
pub use self::sides::*;
|
pub use self::sides::*;
|
||||||
pub use self::size::*;
|
pub use self::size::*;
|
||||||
|
pub use self::smart::*;
|
||||||
pub use self::stroke::*;
|
pub use self::stroke::*;
|
||||||
pub use self::transform::*;
|
pub use self::transform::*;
|
||||||
|
|
||||||
|
64
src/geom/smart.rs
Normal file
64
src/geom/smart.rs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// A value that can be automatically determined.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
|
pub enum Smart<T> {
|
||||||
|
/// The value should be determined smartly based on the circumstances.
|
||||||
|
Auto,
|
||||||
|
/// A specific value.
|
||||||
|
Custom(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Smart<T> {
|
||||||
|
/// Map the contained custom value with `f`.
|
||||||
|
pub fn map<F, U>(self, f: F) -> Smart<U>
|
||||||
|
where
|
||||||
|
F: FnOnce(T) -> U,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::Auto => Smart::Auto,
|
||||||
|
Self::Custom(x) => Smart::Custom(f(x)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Keeps `self` if it contains a custom value, otherwise returns `other`.
|
||||||
|
pub fn or(self, other: Smart<T>) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Custom(x) => Self::Custom(x),
|
||||||
|
Self::Auto => other,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the contained custom value or a provided default value.
|
||||||
|
pub fn unwrap_or(self, default: T) -> T {
|
||||||
|
match self {
|
||||||
|
Self::Auto => default,
|
||||||
|
Self::Custom(x) => x,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the contained custom value or computes a default value.
|
||||||
|
pub fn unwrap_or_else<F>(self, f: F) -> T
|
||||||
|
where
|
||||||
|
F: FnOnce() -> T,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::Auto => f(),
|
||||||
|
Self::Custom(x) => x,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the contained custom value or the default value.
|
||||||
|
pub fn unwrap_or_default(self) -> T
|
||||||
|
where
|
||||||
|
T: Default,
|
||||||
|
{
|
||||||
|
self.unwrap_or_else(T::default)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Default for Smart<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Auto
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,4 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::model::Smart;
|
|
||||||
|
|
||||||
/// A stroke of a geometric shape.
|
/// A stroke of a geometric shape.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,3 @@
|
|||||||
//! Syntax highlighting for Typst source code.
|
|
||||||
|
|
||||||
use crate::syntax::{LinkedNode, SyntaxKind};
|
use crate::syntax::{LinkedNode, SyntaxKind};
|
||||||
|
|
||||||
/// Syntax highlighting categories.
|
/// Syntax highlighting categories.
|
||||||
@ -162,7 +160,8 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
|
|||||||
|
|
||||||
SyntaxKind::Markup { .. }
|
SyntaxKind::Markup { .. }
|
||||||
if node.parent_kind() == Some(&SyntaxKind::DescItem)
|
if node.parent_kind() == Some(&SyntaxKind::DescItem)
|
||||||
&& node.next_sibling_kind() == Some(&SyntaxKind::Colon) =>
|
&& node.next_sibling().as_ref().map(|v| v.kind())
|
||||||
|
== Some(&SyntaxKind::Colon) =>
|
||||||
{
|
{
|
||||||
Some(Category::ListTerm)
|
Some(Category::ListTerm)
|
||||||
}
|
}
|
||||||
@ -207,7 +206,8 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
|
|||||||
}
|
}
|
||||||
Some(SyntaxKind::SetRule) => Some(Category::Function),
|
Some(SyntaxKind::SetRule) => Some(Category::Function),
|
||||||
Some(SyntaxKind::ShowRule)
|
Some(SyntaxKind::ShowRule)
|
||||||
if node.prev_sibling_kind() == Some(&SyntaxKind::Show) =>
|
if node.prev_sibling().as_ref().map(|v| v.kind())
|
||||||
|
== Some(&SyntaxKind::Show) =>
|
||||||
{
|
{
|
||||||
Some(Category::Function)
|
Some(Category::Function)
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,162 @@
|
|||||||
use crate::model::Value;
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
use if_chain::if_chain;
|
||||||
|
|
||||||
|
use crate::font::{FontInfo, FontStyle};
|
||||||
|
use crate::model::{CastInfo, Value};
|
||||||
|
use crate::syntax::ast::{self, AstNode};
|
||||||
use crate::syntax::{LinkedNode, Source, SyntaxKind};
|
use crate::syntax::{LinkedNode, Source, SyntaxKind};
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
/// Produce a tooltip which can be shown when a cursor position is hovered.
|
/// Describe the item under the cursor.
|
||||||
pub fn tooltip(world: &dyn World, source: &Source, cursor: usize) -> Option<String> {
|
pub fn tooltip(world: &dyn World, source: &Source, cursor: usize) -> Option<String> {
|
||||||
let leaf = LinkedNode::new(source.root()).leaf_at(cursor)?;
|
let leaf = LinkedNode::new(source.root()).leaf_at(cursor)?;
|
||||||
|
|
||||||
// If a known identifier is under the cursor, provide its documentation.
|
function_tooltip(world, &leaf)
|
||||||
if let SyntaxKind::Ident(ident) = leaf.kind() {
|
.or_else(|| named_param_tooltip(world, &leaf))
|
||||||
if let Some(value) = world.library().scope.get(ident) {
|
.or_else(|| font_family_tooltip(world, &leaf))
|
||||||
if let Value::Func(func) = value {
|
}
|
||||||
return func.doc().map(Into::into);
|
|
||||||
}
|
/// Tooltip for a function or set rule name.
|
||||||
|
fn function_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<String> {
|
||||||
|
if_chain! {
|
||||||
|
if let SyntaxKind::Ident(ident) = leaf.kind();
|
||||||
|
if matches!(
|
||||||
|
leaf.parent_kind(),
|
||||||
|
Some(SyntaxKind::FuncCall | SyntaxKind::SetRule),
|
||||||
|
);
|
||||||
|
if let Some(Value::Func(func)) = world.library().scope.get(ident);
|
||||||
|
if let Some(info) = func.info();
|
||||||
|
then {
|
||||||
|
return Some(info.docs.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tooltips for components of a named parameter.
|
||||||
|
fn named_param_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<String> {
|
||||||
|
let (info, named) = if_chain! {
|
||||||
|
// Ensure that we are in a named pair in the arguments to a function
|
||||||
|
// call or set rule.
|
||||||
|
if let Some(parent) = leaf.parent();
|
||||||
|
if let Some(named) = parent.cast::<ast::Named>();
|
||||||
|
if let Some(grand) = parent.parent();
|
||||||
|
if matches!(grand.kind(), SyntaxKind::Args);
|
||||||
|
if let Some(grand_grand) = grand.parent();
|
||||||
|
if let Some(expr) = grand_grand.cast::<ast::Expr>();
|
||||||
|
if let Some(callee) = match expr {
|
||||||
|
ast::Expr::FuncCall(call) => call.callee().as_untyped().cast(),
|
||||||
|
ast::Expr::Set(set) => Some(set.target()),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find metadata about the function.
|
||||||
|
if let Some(Value::Func(func)) = world.library().scope.get(&callee);
|
||||||
|
if let Some(info) = func.info();
|
||||||
|
then { (info, named) }
|
||||||
|
else { return None; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Hovering over the parameter name.
|
||||||
|
if_chain! {
|
||||||
|
if leaf.index() == 0;
|
||||||
|
if let SyntaxKind::Ident(ident) = leaf.kind();
|
||||||
|
if let Some(param) = info.param(ident);
|
||||||
|
then {
|
||||||
|
return Some(param.docs.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hovering over a string parameter value.
|
||||||
|
if_chain! {
|
||||||
|
if let SyntaxKind::Str(string) = leaf.kind();
|
||||||
|
if let Some(param) = info.param(&named.name());
|
||||||
|
if let Some(docs) = find_string_doc(¶m.cast, string);
|
||||||
|
then {
|
||||||
|
return Some(docs.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find documentation for a castable string.
|
||||||
|
fn find_string_doc(info: &CastInfo, string: &str) -> Option<&'static str> {
|
||||||
|
match info {
|
||||||
|
CastInfo::Value(Value::Str(s), docs) if s.as_str() == string => Some(docs),
|
||||||
|
CastInfo::Union(options) => {
|
||||||
|
options.iter().find_map(|option| find_string_doc(option, string))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tooltip for font family.
|
||||||
|
fn font_family_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<String> {
|
||||||
|
if_chain! {
|
||||||
|
// Ensure that we are on top of a string.
|
||||||
|
if let SyntaxKind::Str(string) = leaf.kind();
|
||||||
|
let lower = string.to_lowercase();
|
||||||
|
|
||||||
|
// Ensure that we are in the arguments to the text function.
|
||||||
|
if let Some(parent) = leaf.parent();
|
||||||
|
if matches!(parent.kind(), SyntaxKind::Args);
|
||||||
|
if let Some(grand) = parent.parent();
|
||||||
|
if let Some(expr) = grand.cast::<ast::Expr>();
|
||||||
|
if let Some(callee) = match expr {
|
||||||
|
ast::Expr::FuncCall(call) => call.callee().as_untyped().cast(),
|
||||||
|
ast::Expr::Set(set) => Some(set.target()),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find the font family.
|
||||||
|
if callee.as_str() == "text";
|
||||||
|
if let Some((_, iter)) = world
|
||||||
|
.book()
|
||||||
|
.families()
|
||||||
|
.find(|&(family, _)| family.to_lowercase().as_str() == lower.as_str());
|
||||||
|
|
||||||
|
then {
|
||||||
|
let detail = summarize_font_family(iter);
|
||||||
|
return Some(detail);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a short description of a font family.
|
||||||
|
pub(super) fn summarize_font_family<'a>(
|
||||||
|
variants: impl Iterator<Item = &'a FontInfo>,
|
||||||
|
) -> String {
|
||||||
|
let mut infos: Vec<_> = variants.collect();
|
||||||
|
infos.sort_by_key(|info| info.variant);
|
||||||
|
|
||||||
|
let mut has_italic = false;
|
||||||
|
let mut min_weight = u16::MAX;
|
||||||
|
let mut max_weight = 0;
|
||||||
|
for info in &infos {
|
||||||
|
let weight = info.variant.weight.to_number();
|
||||||
|
has_italic |= info.variant.style == FontStyle::Italic;
|
||||||
|
min_weight = min_weight.min(weight);
|
||||||
|
max_weight = min_weight.max(weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
let count = infos.len();
|
||||||
|
let s = if count == 1 { "" } else { "s" };
|
||||||
|
let mut detail = format!("{count} variant{s}.");
|
||||||
|
|
||||||
|
if min_weight == max_weight {
|
||||||
|
write!(detail, " Weight {min_weight}.").unwrap();
|
||||||
|
} else {
|
||||||
|
write!(detail, " Weights {min_weight}–{max_weight}.").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if has_italic {
|
||||||
|
detail.push_str(" Has italics.");
|
||||||
|
}
|
||||||
|
|
||||||
|
detail
|
||||||
|
}
|
||||||
|
@ -29,6 +29,8 @@
|
|||||||
//! [PDF]: export::pdf
|
//! [PDF]: export::pdf
|
||||||
//! [raster images]: export::render
|
//! [raster images]: export::render
|
||||||
|
|
||||||
|
#![recursion_limit = "1000"]
|
||||||
|
|
||||||
extern crate self as typst;
|
extern crate self as typst;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
use std::ops::Add;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use super::{Content, Regex, Selector, Transform, Value};
|
use super::{
|
||||||
use crate::diag::{with_alternative, StrResult};
|
castable, Array, Content, Dict, Func, Label, Regex, Selector, Str, Transform, Value,
|
||||||
|
};
|
||||||
|
use crate::diag::StrResult;
|
||||||
use crate::doc::{Destination, Lang, Location, Region};
|
use crate::doc::{Destination, Lang, Location, Region};
|
||||||
use crate::font::{FontStretch, FontStyle, FontWeight};
|
use crate::font::{FontStretch, FontStyle, FontWeight};
|
||||||
use crate::geom::{
|
use crate::geom::{
|
||||||
Axes, Corners, Dir, GenAlign, Get, Length, Paint, PartialStroke, Point, Rel, Sides,
|
Axes, Color, Corners, Dir, GenAlign, Get, Length, Paint, PartialStroke, Point, Ratio,
|
||||||
|
Rel, Sides, Smart,
|
||||||
};
|
};
|
||||||
use crate::syntax::Spanned;
|
use crate::syntax::Spanned;
|
||||||
use crate::util::{format_eco, EcoString};
|
use crate::util::EcoString;
|
||||||
|
|
||||||
/// Cast from a value to a specific type.
|
/// Cast from a value to a specific type.
|
||||||
pub trait Cast<V = Value>: Sized {
|
pub trait Cast<V = Value>: Sized {
|
||||||
@ -18,94 +22,97 @@ pub trait Cast<V = Value>: Sized {
|
|||||||
|
|
||||||
/// Try to cast the value into an instance of `Self`.
|
/// Try to cast the value into an instance of `Self`.
|
||||||
fn cast(value: V) -> StrResult<Self>;
|
fn cast(value: V) -> StrResult<Self>;
|
||||||
|
|
||||||
|
/// Describe the acceptable values.
|
||||||
|
fn describe() -> CastInfo;
|
||||||
|
|
||||||
|
/// Produce an error for an inacceptable value.
|
||||||
|
fn error(value: Value) -> StrResult<Self> {
|
||||||
|
Err(Self::describe().error(&value))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implement traits for dynamic types.
|
/// Describes a possible value for a cast.
|
||||||
#[macro_export]
|
#[derive(Debug, Clone)]
|
||||||
#[doc(hidden)]
|
pub enum CastInfo {
|
||||||
macro_rules! __dynamic {
|
/// Any value is okay.
|
||||||
($type:ty: $name:literal, $($tts:tt)*) => {
|
Any,
|
||||||
impl $crate::model::Type for $type {
|
/// A specific value, plus short documentation for that value.
|
||||||
const TYPE_NAME: &'static str = $name;
|
Value(Value, &'static str),
|
||||||
}
|
/// Any value of a type.
|
||||||
|
Type(&'static str),
|
||||||
castable! {
|
/// Multiple alternatives.
|
||||||
$type,
|
Union(Vec<Self>),
|
||||||
Expected: <Self as $crate::model::Type>::TYPE_NAME,
|
|
||||||
$($tts)*
|
|
||||||
@this: Self => this.clone(),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<$type> for $crate::model::Value {
|
|
||||||
fn from(v: $type) -> Self {
|
|
||||||
$crate::model::Value::Dyn($crate::model::Dynamic::new(v))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(inline)]
|
impl CastInfo {
|
||||||
pub use crate::__dynamic as dynamic;
|
/// Produce an error message describing what was expected and what was
|
||||||
|
/// found.
|
||||||
/// Make a type castable from a value.
|
pub fn error(&self, found: &Value) -> EcoString {
|
||||||
#[macro_export]
|
fn accumulate(
|
||||||
#[doc(hidden)]
|
info: &CastInfo,
|
||||||
macro_rules! __castable {
|
found: &Value,
|
||||||
($type:ty: $inner:ty) => {
|
parts: &mut Vec<EcoString>,
|
||||||
impl $crate::model::Cast<$crate::model::Value> for $type {
|
matching_type: &mut bool,
|
||||||
fn is(value: &$crate::model::Value) -> bool {
|
) {
|
||||||
<$inner>::is(value)
|
match info {
|
||||||
}
|
CastInfo::Any => parts.push("anything".into()),
|
||||||
|
CastInfo::Value(value, _) => {
|
||||||
fn cast(value: $crate::model::Value) -> $crate::diag::StrResult<Self> {
|
parts.push(value.repr().into());
|
||||||
<$inner>::cast(value).map(Self)
|
if value.type_name() == found.type_name() {
|
||||||
}
|
*matching_type = true;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
CastInfo::Type(ty) => parts.push((*ty).into()),
|
||||||
(
|
CastInfo::Union(options) => {
|
||||||
$type:ty,
|
for option in options {
|
||||||
Expected: $expected:expr,
|
accumulate(option, found, parts, matching_type);
|
||||||
$($pattern:pat => $out:expr,)*
|
|
||||||
$(@$dyn_in:ident: $dyn_type:ty => $dyn_out:expr,)*
|
|
||||||
) => {
|
|
||||||
#[allow(unreachable_patterns)]
|
|
||||||
impl $crate::model::Cast<$crate::model::Value> for $type {
|
|
||||||
fn is(value: &$crate::model::Value) -> bool {
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
match value {
|
|
||||||
$($pattern => true,)*
|
|
||||||
$crate::model::Value::Dyn(dynamic) => {
|
|
||||||
false $(|| dynamic.is::<$dyn_type>())*
|
|
||||||
}
|
}
|
||||||
_ => false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cast(value: $crate::model::Value) -> $crate::diag::StrResult<Self> {
|
|
||||||
let found = match value {
|
|
||||||
$($pattern => return Ok($out),)*
|
|
||||||
$crate::model::Value::Dyn(dynamic) => {
|
|
||||||
$(if let Some($dyn_in) = dynamic.downcast::<$dyn_type>() {
|
|
||||||
return Ok($dyn_out);
|
|
||||||
})*
|
|
||||||
dynamic.type_name()
|
|
||||||
}
|
|
||||||
v => v.type_name(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Err($crate::util::format_eco!(
|
|
||||||
"expected {}, found {}",
|
|
||||||
$expected,
|
|
||||||
found,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
let mut matching_type = false;
|
||||||
|
let mut parts = vec![];
|
||||||
|
accumulate(self, found, &mut parts, &mut matching_type);
|
||||||
|
|
||||||
|
let mut msg = String::from("expected ");
|
||||||
|
if parts.is_empty() {
|
||||||
|
msg.push_str(" nothing");
|
||||||
|
}
|
||||||
|
|
||||||
|
crate::diag::comma_list(&mut msg, &parts, "or");
|
||||||
|
|
||||||
|
if !matching_type {
|
||||||
|
msg.push_str(", found ");
|
||||||
|
msg.push_str(found.type_name());
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(inline)]
|
impl Add for CastInfo {
|
||||||
pub use crate::__castable as castable;
|
type Output = Self;
|
||||||
|
|
||||||
|
fn add(self, rhs: Self) -> Self {
|
||||||
|
Self::Union(match (self, rhs) {
|
||||||
|
(Self::Union(mut lhs), Self::Union(rhs)) => {
|
||||||
|
lhs.extend(rhs);
|
||||||
|
lhs
|
||||||
|
}
|
||||||
|
(Self::Union(mut lhs), rhs) => {
|
||||||
|
lhs.push(rhs);
|
||||||
|
lhs
|
||||||
|
}
|
||||||
|
(lhs, Self::Union(mut rhs)) => {
|
||||||
|
rhs.insert(0, lhs);
|
||||||
|
rhs
|
||||||
|
}
|
||||||
|
(lhs, rhs) => vec![lhs, rhs],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Cast for Value {
|
impl Cast for Value {
|
||||||
fn is(_: &Value) -> bool {
|
fn is(_: &Value) -> bool {
|
||||||
@ -115,6 +122,10 @@ impl Cast for Value {
|
|||||||
fn cast(value: Value) -> StrResult<Self> {
|
fn cast(value: Value) -> StrResult<Self> {
|
||||||
Ok(value)
|
Ok(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn describe() -> CastInfo {
|
||||||
|
CastInfo::Any
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Cast> Cast<Spanned<Value>> for T {
|
impl<T: Cast> Cast<Spanned<Value>> for T {
|
||||||
@ -125,6 +136,10 @@ impl<T: Cast> Cast<Spanned<Value>> for T {
|
|||||||
fn cast(value: Spanned<Value>) -> StrResult<Self> {
|
fn cast(value: Spanned<Value>) -> StrResult<Self> {
|
||||||
T::cast(value.v)
|
T::cast(value.v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn describe() -> CastInfo {
|
||||||
|
T::describe()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Cast> Cast<Spanned<Value>> for Spanned<T> {
|
impl<T: Cast> Cast<Spanned<Value>> for Spanned<T> {
|
||||||
@ -136,14 +151,64 @@ impl<T: Cast> Cast<Spanned<Value>> for Spanned<T> {
|
|||||||
let span = value.span;
|
let span = value.span;
|
||||||
T::cast(value.v).map(|t| Spanned::new(t, span))
|
T::cast(value.v).map(|t| Spanned::new(t, span))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn describe() -> CastInfo {
|
||||||
|
T::describe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
castable! {
|
||||||
|
Dir: "direction",
|
||||||
|
}
|
||||||
|
|
||||||
|
castable! {
|
||||||
|
GenAlign: "alignment",
|
||||||
|
}
|
||||||
|
|
||||||
|
castable! {
|
||||||
|
Regex: "regular expression",
|
||||||
|
}
|
||||||
|
|
||||||
|
castable! {
|
||||||
|
Selector: "selector",
|
||||||
|
text: EcoString => Self::text(&text),
|
||||||
|
label: Label => Self::Label(label),
|
||||||
|
func: Func => func.select(None)?,
|
||||||
|
regex: Regex => Self::Regex(regex),
|
||||||
|
}
|
||||||
|
|
||||||
|
castable! {
|
||||||
|
Axes<GenAlign>: "2d alignment",
|
||||||
|
}
|
||||||
|
|
||||||
|
castable! {
|
||||||
|
PartialStroke: "stroke",
|
||||||
|
thickness: Length => Self {
|
||||||
|
paint: Smart::Auto,
|
||||||
|
thickness: Smart::Custom(thickness),
|
||||||
|
},
|
||||||
|
color: Color => Self {
|
||||||
|
paint: Smart::Custom(color.into()),
|
||||||
|
thickness: Smart::Auto,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
castable! {
|
||||||
|
u32,
|
||||||
|
int: i64 => int.try_into().map_err(|_| {
|
||||||
|
if int < 0 {
|
||||||
|
"number must be at least zero"
|
||||||
|
} else {
|
||||||
|
"number too large"
|
||||||
|
}
|
||||||
|
})?,
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
usize,
|
usize,
|
||||||
Expected: "non-negative integer",
|
int: i64 => int.try_into().map_err(|_| {
|
||||||
Value::Int(int) => int.try_into().map_err(|_| {
|
|
||||||
if int < 0 {
|
if int < 0 {
|
||||||
"must be at least zero"
|
"number must be at least zero"
|
||||||
} else {
|
} else {
|
||||||
"number too large"
|
"number too large"
|
||||||
}
|
}
|
||||||
@ -152,12 +217,11 @@ castable! {
|
|||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
NonZeroUsize,
|
NonZeroUsize,
|
||||||
Expected: "positive integer",
|
int: i64 => int
|
||||||
Value::Int(int) => int
|
|
||||||
.try_into()
|
.try_into()
|
||||||
.and_then(|int: usize| int.try_into())
|
.and_then(|int: usize| int.try_into())
|
||||||
.map_err(|_| if int <= 0 {
|
.map_err(|_| if int <= 0 {
|
||||||
"must be positive"
|
"number must be positive"
|
||||||
} else {
|
} else {
|
||||||
"number too large"
|
"number too large"
|
||||||
})?,
|
})?,
|
||||||
@ -165,41 +229,23 @@ castable! {
|
|||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
Paint,
|
Paint,
|
||||||
Expected: "color",
|
color: Color => Self::Solid(color),
|
||||||
Value::Color(color) => Paint::Solid(color),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
EcoString,
|
EcoString,
|
||||||
Expected: "string",
|
string: Str => string.into(),
|
||||||
Value::Str(str) => str.into(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
String,
|
String,
|
||||||
Expected: "string",
|
string: Str => string.into(),
|
||||||
Value::Str(string) => string.into(),
|
|
||||||
}
|
|
||||||
|
|
||||||
dynamic! {
|
|
||||||
Regex: "regular expression",
|
|
||||||
}
|
|
||||||
|
|
||||||
dynamic! {
|
|
||||||
Selector: "selector",
|
|
||||||
Value::Str(text) => Self::text(&text),
|
|
||||||
Value::Label(label) => Self::Label(label),
|
|
||||||
Value::Func(func) => func.select(None)?,
|
|
||||||
@regex: Regex => Self::Regex(regex.clone()),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
Transform,
|
Transform,
|
||||||
Expected: "content or function",
|
content: Content => Self::Content(content),
|
||||||
Value::None => Self::Content(Content::empty()),
|
func: Func => {
|
||||||
Value::Str(text) => Self::Content(item!(text)(text.into())),
|
|
||||||
Value::Content(content) => Self::Content(content),
|
|
||||||
Value::Func(func) => {
|
|
||||||
if func.argc().map_or(false, |count| count != 1) {
|
if func.argc().map_or(false, |count| count != 1) {
|
||||||
Err("function must have exactly one parameter")?
|
Err("function must have exactly one parameter")?
|
||||||
}
|
}
|
||||||
@ -207,45 +253,19 @@ castable! {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
dynamic! {
|
|
||||||
Dir: "direction",
|
|
||||||
}
|
|
||||||
|
|
||||||
dynamic! {
|
|
||||||
GenAlign: "alignment",
|
|
||||||
}
|
|
||||||
|
|
||||||
dynamic! {
|
|
||||||
Axes<GenAlign>: "2d alignment",
|
|
||||||
}
|
|
||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
Axes<Option<GenAlign>>,
|
Axes<Option<GenAlign>>,
|
||||||
Expected: "1d or 2d alignment",
|
align: GenAlign => {
|
||||||
@align: GenAlign => {
|
|
||||||
let mut aligns = Axes::default();
|
let mut aligns = Axes::default();
|
||||||
aligns.set(align.axis(), Some(*align));
|
aligns.set(align.axis(), Some(align));
|
||||||
aligns
|
aligns
|
||||||
},
|
},
|
||||||
@aligns: Axes<GenAlign> => aligns.map(Some),
|
aligns: Axes<GenAlign> => aligns.map(Some),
|
||||||
}
|
|
||||||
|
|
||||||
dynamic! {
|
|
||||||
PartialStroke: "stroke",
|
|
||||||
Value::Length(thickness) => Self {
|
|
||||||
paint: Smart::Auto,
|
|
||||||
thickness: Smart::Custom(thickness),
|
|
||||||
},
|
|
||||||
Value::Color(color) => Self {
|
|
||||||
paint: Smart::Custom(color.into()),
|
|
||||||
thickness: Smart::Auto,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
Axes<Rel<Length>>,
|
Axes<Rel<Length>>,
|
||||||
Expected: "array of two relative lengths",
|
array: Array => {
|
||||||
Value::Array(array) => {
|
|
||||||
let mut iter = array.into_iter();
|
let mut iter = array.into_iter();
|
||||||
match (iter.next(), iter.next(), iter.next()) {
|
match (iter.next(), iter.next(), iter.next()) {
|
||||||
(Some(a), Some(b), None) => Axes::new(a.cast()?, b.cast()?),
|
(Some(a), Some(b), None) => Axes::new(a.cast()?, b.cast()?),
|
||||||
@ -256,67 +276,87 @@ castable! {
|
|||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
Location,
|
Location,
|
||||||
Expected: "dictionary with `page`, `x`, and `y` keys",
|
mut dict: Dict => {
|
||||||
Value::Dict(dict) => {
|
let page = dict.take("page")?.cast()?;
|
||||||
let page = dict.get("page")?.clone().cast()?;
|
let x: Length = dict.take("x")?.cast()?;
|
||||||
let x: Length = dict.get("x")?.clone().cast()?;
|
let y: Length = dict.take("y")?.cast()?;
|
||||||
let y: Length = dict.get("y")?.clone().cast()?;
|
dict.finish(&["page", "x", "y"])?;
|
||||||
Self { page, pos: Point::new(x.abs, y.abs) }
|
Self { page, pos: Point::new(x.abs, y.abs) }
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
Destination,
|
Destination,
|
||||||
Expected: "string or dictionary with `page`, `x`, and `y` keys",
|
loc: Location => Self::Internal(loc),
|
||||||
Value::Str(string) => Self::Url(string.into()),
|
string: EcoString => Self::Url(string),
|
||||||
v @ Value::Dict(_) => Self::Internal(v.cast()?),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
FontStyle,
|
FontStyle,
|
||||||
Expected: "string",
|
/// The default style.
|
||||||
Value::Str(string) => match string.as_str() {
|
"normal" => Self::Normal,
|
||||||
"normal" => Self::Normal,
|
/// A cursive style.
|
||||||
"italic" => Self::Italic,
|
"italic" => Self::Italic,
|
||||||
"oblique" => Self::Oblique,
|
/// A slanted style.
|
||||||
_ => Err(r#"expected "normal", "italic" or "oblique""#)?,
|
"oblique" => Self::Oblique,
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
FontWeight,
|
FontWeight,
|
||||||
Expected: "integer or string",
|
v: i64 => Self::from_number(v.clamp(0, u16::MAX as i64) as u16),
|
||||||
Value::Int(v) => Self::from_number(v.clamp(0, u16::MAX as i64) as u16),
|
/// Thin weight (100).
|
||||||
Value::Str(string) => match string.as_str() {
|
"thin" => Self::THIN,
|
||||||
"thin" => Self::THIN,
|
/// Extra light weight (200).
|
||||||
"extralight" => Self::EXTRALIGHT,
|
"extralight" => Self::EXTRALIGHT,
|
||||||
"light" => Self::LIGHT,
|
/// Light weight (300).
|
||||||
"regular" => Self::REGULAR,
|
"light" => Self::LIGHT,
|
||||||
"medium" => Self::MEDIUM,
|
/// Regular weight (400).
|
||||||
"semibold" => Self::SEMIBOLD,
|
"regular" => Self::REGULAR,
|
||||||
"bold" => Self::BOLD,
|
/// Medium weight (500).
|
||||||
"extrabold" => Self::EXTRABOLD,
|
"medium" => Self::MEDIUM,
|
||||||
"black" => Self::BLACK,
|
/// Semibold weight (600).
|
||||||
_ => Err("unknown font weight")?,
|
"semibold" => Self::SEMIBOLD,
|
||||||
},
|
/// Bold weight (700).
|
||||||
|
"bold" => Self::BOLD,
|
||||||
|
/// Extrabold weight (800).
|
||||||
|
"extrabold" => Self::EXTRABOLD,
|
||||||
|
/// Black weight (900).
|
||||||
|
"black" => Self::BLACK,
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
FontStretch,
|
FontStretch,
|
||||||
Expected: "ratio",
|
v: Ratio => Self::from_ratio(v.get() as f32),
|
||||||
Value::Ratio(v) => Self::from_ratio(v.get() as f32),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
Lang,
|
Lang,
|
||||||
Expected: "string",
|
string: EcoString => Self::from_str(&string)?,
|
||||||
Value::Str(string) => Self::from_str(&string)?,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
Region,
|
Region,
|
||||||
Expected: "string",
|
string: EcoString => Self::from_str(&string)?,
|
||||||
Value::Str(string) => Self::from_str(&string)?,
|
}
|
||||||
|
|
||||||
|
/// Castable from [`Value::None`].
|
||||||
|
pub struct NoneValue;
|
||||||
|
|
||||||
|
impl Cast for NoneValue {
|
||||||
|
fn is(value: &Value) -> bool {
|
||||||
|
matches!(value, Value::None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cast(value: Value) -> StrResult<Self> {
|
||||||
|
match value {
|
||||||
|
Value::None => Ok(Self),
|
||||||
|
_ => <Self as Cast>::error(value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn describe() -> CastInfo {
|
||||||
|
CastInfo::Type("none")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Cast> Cast for Option<T> {
|
impl<T: Cast> Cast for Option<T> {
|
||||||
@ -327,71 +367,33 @@ impl<T: Cast> Cast for Option<T> {
|
|||||||
fn cast(value: Value) -> StrResult<Self> {
|
fn cast(value: Value) -> StrResult<Self> {
|
||||||
match value {
|
match value {
|
||||||
Value::None => Ok(None),
|
Value::None => Ok(None),
|
||||||
v => T::cast(v).map(Some).map_err(|msg| with_alternative(msg, "none")),
|
v if T::is(&v) => Ok(Some(T::cast(v)?)),
|
||||||
|
_ => <Self as Cast>::error(value),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn describe() -> CastInfo {
|
||||||
|
T::describe() + CastInfo::Type("none")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A value that can be automatically determined.
|
/// Castable from [`Value::Auto`].
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
pub struct AutoValue;
|
||||||
pub enum Smart<T> {
|
|
||||||
/// The value should be determined smartly based on the circumstances.
|
|
||||||
Auto,
|
|
||||||
/// A specific value.
|
|
||||||
Custom(T),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Smart<T> {
|
impl Cast for AutoValue {
|
||||||
/// Map the contained custom value with `f`.
|
fn is(value: &Value) -> bool {
|
||||||
pub fn map<F, U>(self, f: F) -> Smart<U>
|
matches!(value, Value::Auto)
|
||||||
where
|
}
|
||||||
F: FnOnce(T) -> U,
|
|
||||||
{
|
fn cast(value: Value) -> StrResult<Self> {
|
||||||
match self {
|
match value {
|
||||||
Self::Auto => Smart::Auto,
|
Value::Auto => Ok(Self),
|
||||||
Self::Custom(x) => Smart::Custom(f(x)),
|
_ => <Self as Cast>::error(value),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Keeps `self` if it contains a custom value, otherwise returns `other`.
|
fn describe() -> CastInfo {
|
||||||
pub fn or(self, other: Smart<T>) -> Self {
|
CastInfo::Type("auto")
|
||||||
match self {
|
|
||||||
Self::Custom(x) => Self::Custom(x),
|
|
||||||
Self::Auto => other,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the contained custom value or a provided default value.
|
|
||||||
pub fn unwrap_or(self, default: T) -> T {
|
|
||||||
match self {
|
|
||||||
Self::Auto => default,
|
|
||||||
Self::Custom(x) => x,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the contained custom value or computes a default value.
|
|
||||||
pub fn unwrap_or_else<F>(self, f: F) -> T
|
|
||||||
where
|
|
||||||
F: FnOnce() -> T,
|
|
||||||
{
|
|
||||||
match self {
|
|
||||||
Self::Auto => f(),
|
|
||||||
Self::Custom(x) => x,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the contained custom value or the default value.
|
|
||||||
pub fn unwrap_or_default(self) -> T
|
|
||||||
where
|
|
||||||
T: Default,
|
|
||||||
{
|
|
||||||
self.unwrap_or_else(T::default)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Default for Smart<T> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Auto
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -403,11 +405,14 @@ impl<T: Cast> Cast for Smart<T> {
|
|||||||
fn cast(value: Value) -> StrResult<Self> {
|
fn cast(value: Value) -> StrResult<Self> {
|
||||||
match value {
|
match value {
|
||||||
Value::Auto => Ok(Self::Auto),
|
Value::Auto => Ok(Self::Auto),
|
||||||
v => T::cast(v)
|
v if T::is(&v) => Ok(Self::Custom(T::cast(v)?)),
|
||||||
.map(Self::Custom)
|
_ => <Self as Cast>::error(value),
|
||||||
.map_err(|msg| with_alternative(msg, "auto")),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn describe() -> CastInfo {
|
||||||
|
T::describe() + CastInfo::Type("auto")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Cast for Sides<T>
|
impl<T> Cast for Sides<T>
|
||||||
@ -420,7 +425,7 @@ where
|
|||||||
|
|
||||||
fn cast(mut value: Value) -> StrResult<Self> {
|
fn cast(mut value: Value) -> StrResult<Self> {
|
||||||
if let Value::Dict(dict) = &mut value {
|
if let Value::Dict(dict) = &mut value {
|
||||||
let mut take = |key| dict.take(key).map(T::cast).transpose();
|
let mut take = |key| dict.take(key).ok().map(T::cast).transpose();
|
||||||
|
|
||||||
let rest = take("rest")?;
|
let rest = take("rest")?;
|
||||||
let x = take("x")?.or(rest);
|
let x = take("x")?.or(rest);
|
||||||
@ -432,22 +437,19 @@ where
|
|||||||
bottom: take("bottom")?.or(y),
|
bottom: take("bottom")?.or(y),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some((key, _)) = dict.iter().next() {
|
dict.finish(&["left", "top", "right", "bottom", "x", "y", "rest"])?;
|
||||||
return Err(format_eco!("unexpected key {key:?}"));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(sides.map(Option::unwrap_or_default))
|
Ok(sides.map(Option::unwrap_or_default))
|
||||||
|
} else if T::is(&value) {
|
||||||
|
Ok(Self::splat(T::cast(value)?))
|
||||||
} else {
|
} else {
|
||||||
T::cast(value).map(Self::splat).map_err(|msg| {
|
<Self as Cast>::error(value)
|
||||||
with_alternative(
|
|
||||||
msg,
|
|
||||||
"dictionary with any of \
|
|
||||||
`left`, `top`, `right`, `bottom`, \
|
|
||||||
`x`, `y`, or `rest` as keys",
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn describe() -> CastInfo {
|
||||||
|
T::describe() + CastInfo::Type("dictionary")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Cast for Corners<T>
|
impl<T> Cast for Corners<T>
|
||||||
@ -460,7 +462,7 @@ where
|
|||||||
|
|
||||||
fn cast(mut value: Value) -> StrResult<Self> {
|
fn cast(mut value: Value) -> StrResult<Self> {
|
||||||
if let Value::Dict(dict) = &mut value {
|
if let Value::Dict(dict) = &mut value {
|
||||||
let mut take = |key| dict.take(key).map(T::cast).transpose();
|
let mut take = |key| dict.take(key).ok().map(T::cast).transpose();
|
||||||
|
|
||||||
let rest = take("rest")?;
|
let rest = take("rest")?;
|
||||||
let left = take("left")?.or(rest);
|
let left = take("left")?.or(rest);
|
||||||
@ -474,20 +476,27 @@ where
|
|||||||
bottom_left: take("bottom-left")?.or(bottom).or(left),
|
bottom_left: take("bottom-left")?.or(bottom).or(left),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some((key, _)) = dict.iter().next() {
|
dict.finish(&[
|
||||||
return Err(format_eco!("unexpected key {key:?}"));
|
"top-left",
|
||||||
}
|
"top-right",
|
||||||
|
"bottom-right",
|
||||||
|
"bottom-left",
|
||||||
|
"left",
|
||||||
|
"top",
|
||||||
|
"right",
|
||||||
|
"bottom",
|
||||||
|
"rest",
|
||||||
|
])?;
|
||||||
|
|
||||||
Ok(corners.map(Option::unwrap_or_default))
|
Ok(corners.map(Option::unwrap_or_default))
|
||||||
|
} else if T::is(&value) {
|
||||||
|
Ok(Self::splat(T::cast(value)?))
|
||||||
} else {
|
} else {
|
||||||
T::cast(value).map(Self::splat).map_err(|msg| {
|
<Self as Cast>::error(value)
|
||||||
with_alternative(
|
|
||||||
msg,
|
|
||||||
"dictionary with any of \
|
|
||||||
`top-left`, `top-right`, `bottom-right`, `bottom-left`, \
|
|
||||||
`left`, `top`, `right`, `bottom`, or `rest` as keys",
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn describe() -> CastInfo {
|
||||||
|
T::describe() + CastInfo::Type("dictionary")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,8 @@ use thin_vec::ThinVec;
|
|||||||
use typst_macros::node;
|
use typst_macros::node;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
capability, capable, Args, Guard, Key, Property, Recipe, Style, StyleMap, Value, Vm,
|
capability, capable, Args, Guard, Key, ParamInfo, Property, Recipe, Style, StyleMap,
|
||||||
|
Value, Vm,
|
||||||
};
|
};
|
||||||
use crate::diag::{SourceResult, StrResult};
|
use crate::diag::{SourceResult, StrResult};
|
||||||
use crate::syntax::Span;
|
use crate::syntax::Span;
|
||||||
@ -426,6 +427,11 @@ pub trait Node: 'static + Capable {
|
|||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
|
|
||||||
|
/// List the settable properties.
|
||||||
|
fn properties() -> Vec<ParamInfo>
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
|
||||||
/// Access a field on this node.
|
/// Access a field on this node.
|
||||||
fn field(&self, name: &str) -> Option<Value>;
|
fn field(&self, name: &str) -> Option<Value>;
|
||||||
}
|
}
|
||||||
|
@ -62,6 +62,13 @@ impl Dict {
|
|||||||
Arc::make_mut(&mut self.0).entry(key).or_default()
|
Arc::make_mut(&mut self.0).entry(key).or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Remove the value if the dictionary contains the given key.
|
||||||
|
pub fn take(&mut self, key: &str) -> StrResult<Value> {
|
||||||
|
Arc::make_mut(&mut self.0)
|
||||||
|
.remove(key)
|
||||||
|
.ok_or_else(|| format_eco!("missing key: {:?}", Str::from(key)))
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether the dictionary contains a specific key.
|
/// Whether the dictionary contains a specific key.
|
||||||
pub fn contains(&self, key: &str) -> bool {
|
pub fn contains(&self, key: &str) -> bool {
|
||||||
self.0.contains_key(key)
|
self.0.contains_key(key)
|
||||||
@ -80,11 +87,6 @@ impl Dict {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove the value if the dictionary contains the given key.
|
|
||||||
pub fn take(&mut self, key: &str) -> Option<Value> {
|
|
||||||
Arc::make_mut(&mut self.0).remove(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clear the dictionary.
|
/// Clear the dictionary.
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
if Arc::strong_count(&self.0) == 1 {
|
if Arc::strong_count(&self.0) == 1 {
|
||||||
@ -118,6 +120,17 @@ impl Dict {
|
|||||||
pub fn iter(&self) -> std::collections::btree_map::Iter<Str, Value> {
|
pub fn iter(&self) -> std::collections::btree_map::Iter<Str, Value> {
|
||||||
self.0.iter()
|
self.0.iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return an "unexpected key" error if there is any remaining pair.
|
||||||
|
pub fn finish(&self, expected: &[&str]) -> StrResult<()> {
|
||||||
|
if let Some((key, _)) = self.iter().next() {
|
||||||
|
let parts: Vec<_> = expected.iter().map(|s| format_eco!("\"{s}\"")).collect();
|
||||||
|
let mut msg = format!("unexpected key {key:?}, valid keys are ");
|
||||||
|
crate::diag::comma_list(&mut msg, &parts, "and");
|
||||||
|
return Err(msg.into());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The missing key access error message.
|
/// The missing key access error message.
|
||||||
|
@ -5,8 +5,8 @@ use std::sync::Arc;
|
|||||||
use comemo::{Track, Tracked};
|
use comemo::{Track, Tracked};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
Args, Dict, Eval, Flow, Node, NodeId, Route, Scope, Scopes, Selector, StyleMap,
|
Args, CastInfo, Dict, Eval, Flow, Node, NodeId, Route, Scope, Scopes, Selector,
|
||||||
Value, Vm,
|
StyleMap, Value, Vm,
|
||||||
};
|
};
|
||||||
use crate::diag::{bail, SourceResult, StrResult};
|
use crate::diag::{bail, SourceResult, StrResult};
|
||||||
use crate::syntax::ast::{self, AstNode, Expr};
|
use crate::syntax::ast::{self, AstNode, Expr};
|
||||||
@ -39,13 +39,14 @@ impl Func {
|
|||||||
pub fn from_fn(
|
pub fn from_fn(
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
func: fn(&Vm, &mut Args) -> SourceResult<Value>,
|
func: fn(&Vm, &mut Args) -> SourceResult<Value>,
|
||||||
doc: &'static str,
|
info: FuncInfo,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self(Arc::new(Repr::Native(Native { name, func, set: None, node: None, doc })))
|
Self(Arc::new(Repr::Native(Native { name, func, set: None, node: None, info })))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new function from a native rust node.
|
/// Create a new function from a native rust node.
|
||||||
pub fn from_node<T: Node>(name: &'static str, doc: &'static str) -> Self {
|
pub fn from_node<T: Node>(name: &'static str, mut info: FuncInfo) -> Self {
|
||||||
|
info.params.extend(T::properties());
|
||||||
Self(Arc::new(Repr::Native(Native {
|
Self(Arc::new(Repr::Native(Native {
|
||||||
name,
|
name,
|
||||||
func: |ctx, args| {
|
func: |ctx, args| {
|
||||||
@ -55,7 +56,7 @@ impl Func {
|
|||||||
},
|
},
|
||||||
set: Some(|args| T::set(args, false)),
|
set: Some(|args| T::set(args, false)),
|
||||||
node: Some(NodeId::of::<T>()),
|
node: Some(NodeId::of::<T>()),
|
||||||
doc,
|
info,
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,11 +74,11 @@ impl Func {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Documentation for the function.
|
/// Extract details the function.
|
||||||
pub fn doc(&self) -> Option<&str> {
|
pub fn info(&self) -> Option<&FuncInfo> {
|
||||||
match self.0.as_ref() {
|
match self.0.as_ref() {
|
||||||
Repr::Native(native) => Some(native.doc),
|
Repr::Native(native) => Some(&native.info),
|
||||||
Repr::With(func, _) => func.doc(),
|
Repr::With(func, _) => func.info(),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -192,7 +193,7 @@ struct Native {
|
|||||||
/// The id of the node to customize with this function's show rule.
|
/// The id of the node to customize with this function's show rule.
|
||||||
node: Option<NodeId>,
|
node: Option<NodeId>,
|
||||||
/// Documentation of the function.
|
/// Documentation of the function.
|
||||||
doc: &'static str,
|
info: FuncInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for Native {
|
impl Hash for Native {
|
||||||
@ -201,10 +202,44 @@ impl Hash for Native {
|
|||||||
(self.func as usize).hash(state);
|
(self.func as usize).hash(state);
|
||||||
self.set.map(|set| set as usize).hash(state);
|
self.set.map(|set| set as usize).hash(state);
|
||||||
self.node.hash(state);
|
self.node.hash(state);
|
||||||
self.doc.hash(state);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Details about a function.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FuncInfo {
|
||||||
|
/// The function's name.
|
||||||
|
pub name: &'static str,
|
||||||
|
/// Tags that categorize the function.
|
||||||
|
pub tags: &'static [&'static str],
|
||||||
|
/// Documentation for the function.
|
||||||
|
pub docs: &'static str,
|
||||||
|
/// Details about the function's parameters.
|
||||||
|
pub params: Vec<ParamInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FuncInfo {
|
||||||
|
/// Get the parameter info for a parameter with the given name
|
||||||
|
pub fn param(&self, name: &str) -> Option<&ParamInfo> {
|
||||||
|
self.params.iter().find(|param| param.name == name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Describes a named parameter.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ParamInfo {
|
||||||
|
/// The parameter's name.
|
||||||
|
pub name: &'static str,
|
||||||
|
/// Documentation for the parameter.
|
||||||
|
pub docs: &'static str,
|
||||||
|
/// Is the parameter settable with a set rule?
|
||||||
|
pub settable: bool,
|
||||||
|
/// Can the name be omitted?
|
||||||
|
pub shorthand: bool,
|
||||||
|
/// Valid values for the parameter.
|
||||||
|
pub cast: CastInfo,
|
||||||
|
}
|
||||||
|
|
||||||
/// A user-defined closure.
|
/// A user-defined closure.
|
||||||
#[derive(Hash)]
|
#[derive(Hash)]
|
||||||
pub(super) struct Closure {
|
pub(super) struct Closure {
|
||||||
|
@ -26,7 +26,7 @@ mod typeset;
|
|||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use once_cell;
|
pub use once_cell;
|
||||||
pub use typst_macros::{capability, capable, func, node};
|
pub use typst_macros::{capability, capable, castable, func, node};
|
||||||
|
|
||||||
pub use self::args::*;
|
pub use self::args::*;
|
||||||
pub use self::array::*;
|
pub use self::array::*;
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
//! Operations on values.
|
//! Operations on values.
|
||||||
|
|
||||||
use super::{Regex, Smart, Value};
|
use super::{Regex, Value};
|
||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
use crate::geom::{Axes, Axis, GenAlign, Length, Numeric, PartialStroke, Rel};
|
use crate::geom::{Axes, Axis, GenAlign, Length, Numeric, PartialStroke, Rel, Smart};
|
||||||
use crate::util::format_eco;
|
use crate::util::format_eco;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use Value::*;
|
use Value::*;
|
||||||
|
@ -442,9 +442,8 @@ pub enum StrPattern {
|
|||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
StrPattern,
|
StrPattern,
|
||||||
Expected: "string or regular expression",
|
text: Str => Self::Str(text),
|
||||||
Value::Str(text) => Self::Str(text),
|
regex: Regex => Self::Regex(regex),
|
||||||
@regex: Regex => Self::Regex(regex.clone()),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A side of a string.
|
/// A side of a string.
|
||||||
@ -459,8 +458,7 @@ pub enum StrSide {
|
|||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
StrSide,
|
StrSide,
|
||||||
Expected: "start or end",
|
align: GenAlign => match align {
|
||||||
@align: GenAlign => match align {
|
|
||||||
GenAlign::Start => Self::Start,
|
GenAlign::Start => Self::Start,
|
||||||
GenAlign::End => Self::End,
|
GenAlign::End => Self::End,
|
||||||
_ => Err("expected either `start` or `end`")?,
|
_ => Err("expected either `start` or `end`")?,
|
||||||
|
@ -7,10 +7,11 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use comemo::{Prehashed, Tracked};
|
use comemo::{Prehashed, Tracked};
|
||||||
|
|
||||||
use super::{Args, Content, Dict, Func, Label, NodeId, Regex, Smart, Value};
|
use super::{Args, Content, Dict, Func, Label, NodeId, Regex, Value};
|
||||||
use crate::diag::{SourceResult, Trace, Tracepoint};
|
use crate::diag::{SourceResult, Trace, Tracepoint};
|
||||||
use crate::geom::{
|
use crate::geom::{
|
||||||
Abs, Align, Axes, Corners, Em, GenAlign, Length, Numeric, PartialStroke, Rel, Sides,
|
Abs, Align, Axes, Corners, Em, GenAlign, Length, Numeric, PartialStroke, Rel, Sides,
|
||||||
|
Smart,
|
||||||
};
|
};
|
||||||
use crate::syntax::Span;
|
use crate::syntax::Span;
|
||||||
use crate::util::ReadableTypeId;
|
use crate::util::ReadableTypeId;
|
||||||
|
@ -6,7 +6,9 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use siphasher::sip128::{Hasher128, SipHasher};
|
use siphasher::sip128::{Hasher128, SipHasher};
|
||||||
|
|
||||||
use super::{format_str, ops, Args, Array, Cast, Content, Dict, Func, Label, Str};
|
use super::{
|
||||||
|
format_str, ops, Args, Array, Cast, CastInfo, Content, Dict, Func, Label, Str,
|
||||||
|
};
|
||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
use crate::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel, RgbaColor};
|
use crate::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel, RgbaColor};
|
||||||
use crate::util::{format_eco, EcoString};
|
use crate::util::{format_eco, EcoString};
|
||||||
@ -351,6 +353,10 @@ macro_rules! primitive {
|
|||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn describe() -> CastInfo {
|
||||||
|
CastInfo::Type(Self::TYPE_NAME)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<$type> for Value {
|
impl From<$type> for Value {
|
||||||
|
@ -1577,12 +1577,17 @@ impl Ident {
|
|||||||
_ => panic!("identifier is of wrong kind"),
|
_ => panic!("identifier is of wrong kind"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the identifier as a string slice.
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
self.get()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Ident {
|
impl Deref for Ident {
|
||||||
type Target = str;
|
type Target = str;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
self.get()
|
self.as_str()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,11 @@ impl<'a> LinkedNode<'a> {
|
|||||||
self.node
|
self.node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The index of this node in its parent's children list.
|
||||||
|
pub fn index(&self) -> usize {
|
||||||
|
self.index
|
||||||
|
}
|
||||||
|
|
||||||
/// The absolute byte offset of the this node in the source file.
|
/// The absolute byte offset of the this node in the source file.
|
||||||
pub fn offset(&self) -> usize {
|
pub fn offset(&self) -> usize {
|
||||||
self.offset
|
self.offset
|
||||||
@ -40,18 +45,13 @@ impl<'a> LinkedNode<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get this node's children.
|
/// Get this node's children.
|
||||||
pub fn children(
|
pub fn children(&self) -> LinkedChildren<'a> {
|
||||||
&self,
|
LinkedChildren {
|
||||||
) -> impl DoubleEndedIterator<Item = LinkedNode<'a>>
|
parent: Rc::new(self.clone()),
|
||||||
+ ExactSizeIterator<Item = LinkedNode<'a>>
|
iter: self.node.children().enumerate(),
|
||||||
+ '_ {
|
front: self.offset,
|
||||||
let parent = Rc::new(self.clone());
|
back: self.offset + self.len(),
|
||||||
let mut offset = self.offset;
|
}
|
||||||
self.node.children().enumerate().map(move |(index, node)| {
|
|
||||||
let child = Self { node, parent: Some(parent.clone()), index, offset };
|
|
||||||
offset += node.len();
|
|
||||||
child
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +64,7 @@ impl<'a> LinkedNode<'a> {
|
|||||||
|
|
||||||
/// Get the kind of this node's parent.
|
/// Get the kind of this node's parent.
|
||||||
pub fn parent_kind(&self) -> Option<&'a SyntaxKind> {
|
pub fn parent_kind(&self) -> Option<&'a SyntaxKind> {
|
||||||
self.parent().map(|parent| parent.node.kind())
|
Some(self.parent()?.node.kind())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the first previous non-trivia sibling node.
|
/// Get the first previous non-trivia sibling node.
|
||||||
@ -81,11 +81,6 @@ impl<'a> LinkedNode<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the kind of this node's first previous non-trivia sibling.
|
|
||||||
pub fn prev_sibling_kind(&self) -> Option<&'a SyntaxKind> {
|
|
||||||
self.prev_sibling().map(|parent| parent.node.kind())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the next non-trivia sibling node.
|
/// Get the next non-trivia sibling node.
|
||||||
pub fn next_sibling(&self) -> Option<Self> {
|
pub fn next_sibling(&self) -> Option<Self> {
|
||||||
let parent = self.parent()?;
|
let parent = self.parent()?;
|
||||||
@ -99,11 +94,6 @@ impl<'a> LinkedNode<'a> {
|
|||||||
Some(next)
|
Some(next)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the kind of this node's next non-trivia sibling.
|
|
||||||
pub fn next_sibling_kind(&self) -> Option<&'a SyntaxKind> {
|
|
||||||
self.next_sibling().map(|parent| parent.node.kind())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access to leafs.
|
/// Access to leafs.
|
||||||
@ -198,6 +188,51 @@ impl Debug for LinkedNode<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An iterator over the children of a linked node.
|
||||||
|
pub struct LinkedChildren<'a> {
|
||||||
|
parent: Rc<LinkedNode<'a>>,
|
||||||
|
iter: std::iter::Enumerate<std::slice::Iter<'a, SyntaxNode>>,
|
||||||
|
front: usize,
|
||||||
|
back: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for LinkedChildren<'a> {
|
||||||
|
type Item = LinkedNode<'a>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.iter.next().map(|(index, node)| {
|
||||||
|
let offset = self.front;
|
||||||
|
self.front += node.len();
|
||||||
|
LinkedNode {
|
||||||
|
node,
|
||||||
|
parent: Some(self.parent.clone()),
|
||||||
|
index,
|
||||||
|
offset,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
self.iter.size_hint()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DoubleEndedIterator for LinkedChildren<'_> {
|
||||||
|
fn next_back(&mut self) -> Option<Self::Item> {
|
||||||
|
self.iter.next_back().map(|(index, node)| {
|
||||||
|
self.back -= node.len();
|
||||||
|
LinkedNode {
|
||||||
|
node,
|
||||||
|
parent: Some(self.parent.clone()),
|
||||||
|
index,
|
||||||
|
offset: self.back,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExactSizeIterator for LinkedChildren<'_> {}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -236,15 +271,4 @@ mod tests {
|
|||||||
assert_eq!(leaf.kind(), &SyntaxKind::Space { newlines: 0 });
|
assert_eq!(leaf.kind(), &SyntaxKind::Space { newlines: 0 });
|
||||||
assert_eq!(next.kind(), &SyntaxKind::Int(10));
|
assert_eq!(next.kind(), &SyntaxKind::Int(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_linked_node_leaf_at() {
|
|
||||||
let source = Source::detached("");
|
|
||||||
let leaf = LinkedNode::new(source.root()).leaf_at(0).unwrap();
|
|
||||||
assert_eq!(leaf.kind(), &SyntaxKind::Markup { min_indent: 0 });
|
|
||||||
|
|
||||||
let source = Source::detached("Hello\n");
|
|
||||||
let leaf = LinkedNode::new(source.root()).leaf_at(6).unwrap();
|
|
||||||
assert_eq!(leaf.kind(), &SyntaxKind::Space { newlines: 1 });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -14,8 +14,8 @@ use tiny_skia as sk;
|
|||||||
use typst::diag::{bail, FileError, FileResult, SourceResult};
|
use typst::diag::{bail, FileError, FileResult, SourceResult};
|
||||||
use typst::doc::{Document, Element, Frame, Meta};
|
use typst::doc::{Document, Element, Frame, Meta};
|
||||||
use typst::font::{Font, FontBook};
|
use typst::font::{Font, FontBook};
|
||||||
use typst::geom::{Abs, RgbaColor, Sides};
|
use typst::geom::{Abs, RgbaColor, Sides, Smart};
|
||||||
use typst::model::{func, Library, Smart, Value};
|
use typst::model::{func, Library, Value};
|
||||||
use typst::syntax::{Source, SourceId, SyntaxNode};
|
use typst::syntax::{Source, SourceId, SyntaxNode};
|
||||||
use typst::util::{Buffer, PathExt};
|
use typst::util::{Buffer, PathExt};
|
||||||
use typst::World;
|
use typst::World;
|
||||||
|
@ -18,5 +18,5 @@
|
|||||||
#table()
|
#table()
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 14-19 expected color or none or function, found string
|
// Error: 14-19 expected color, none, or function, found string
|
||||||
#table(fill: "hey")
|
#table(fill: "hey")
|
||||||
|
@ -96,7 +96,7 @@ Another text.
|
|||||||
= Heading
|
= Heading
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 7-10 expected selector, found color
|
// Error: 7-10 expected string, label, function, regular expression, or selector, found color
|
||||||
#show red: []
|
#show red: []
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -41,9 +41,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 17-18 must be positive
|
// Error: 17-18 number must be positive
|
||||||
#numbering("1", 0)
|
#numbering("1", 0)
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 17-19 must be positive
|
// Error: 17-19 number must be positive
|
||||||
#numbering("1", -1)
|
#numbering("1", -1)
|
||||||
|
@ -101,5 +101,5 @@ This is a normal page. Very normal.
|
|||||||
|
|
||||||
---
|
---
|
||||||
// Test a page with zero columns.
|
// Test a page with zero columns.
|
||||||
// Error: 49-50 must be positive
|
// Error: 49-50 number must be positive
|
||||||
#set page(height: auto, width: 7.05cm, columns: 0)
|
#set page(height: auto, width: 7.05cm, columns: 0)
|
||||||
|
@ -17,9 +17,9 @@
|
|||||||
#try(1pt + 0.3em, -0.15em)
|
#try(1pt + 0.3em, -0.15em)
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 21-23 expected string or length, found array
|
// Error: 21-23 expected length, "ascender", "cap-height", "x-height", "baseline", or "descender", found array
|
||||||
#set text(top-edge: ())
|
#set text(top-edge: ())
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 24-26 unknown font metric
|
// Error: 24-26 expected length, "ascender", "cap-height", "x-height", "baseline", or "descender"
|
||||||
#set text(bottom-edge: "")
|
#set text(bottom-edge: "")
|
||||||
|
@ -55,9 +55,13 @@ fi vs. #text(features: (liga: 0))[No fi]
|
|||||||
#set text(stylistic-set: 25)
|
#set text(stylistic-set: 25)
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 24-25 expected string or auto, found integer
|
// Error: 24-25 expected "lining", "old-style", or auto, found integer
|
||||||
#set text(number-type: 2)
|
#set text(number-type: 2)
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 21-26 expected array of strings or dictionary mapping tags to integers, found boolean
|
// Error: 21-26 expected array or dictionary, found boolean
|
||||||
#set text(features: false)
|
#set text(features: false)
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 21-35 expected string, found boolean
|
||||||
|
#set text(features: ("tag", false))
|
||||||
|
@ -48,7 +48,7 @@ Emoji: 🐪, 🌋, 🏞
|
|||||||
#set text(false)
|
#set text(false)
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 18-24 expected "normal", "italic" or "oblique"
|
// Error: 18-24 expected "normal", "italic", or "oblique"
|
||||||
#set text(style: "bold", weight: "thin")
|
#set text(style: "bold", weight: "thin")
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -57,13 +57,9 @@
|
|||||||
Use the `*const T` pointer or the `&mut T` reference.
|
Use the `*const T` pointer or the `&mut T` reference.
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 15-38 unexpected key "cake"
|
// Error: 15-38 unexpected key "cake", valid keys are "top-left", "top-right", "bottom-right", "bottom-left", "left", "top", "right", "bottom", and "rest"
|
||||||
#rect(radius: (left: 10pt, cake: 5pt))
|
#rect(radius: (left: 10pt, cake: 5pt))
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 15-21 expected stroke or none or dictionary with any of `left`, `top`, `right`, `bottom`, `x`, `y`, or `rest` as keys or auto, found array
|
// Error: 15-21 expected length, color, stroke, none, dictionary, or auto, found array
|
||||||
#rect(stroke: (1, 2))
|
#rect(stroke: (1, 2))
|
||||||
|
|
||||||
---
|
|
||||||
// Error: 15-19 expected relative length or none or dictionary with any of `top-left`, `top-right`, `bottom-right`, `bottom-left`, `left`, `top`, `right`, `bottom`, or `rest` as keys, found color
|
|
||||||
#rect(radius: blue)
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user