Fully untyped model
This commit is contained in:
parent
6ab7760822
commit
25b5bd1175
|
|
@ -1,3 +1,5 @@
|
||||||
|
//! Calculations and processing of numeric values.
|
||||||
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::ops::Rem;
|
use std::ops::Rem;
|
||||||
|
|
||||||
|
|
@ -6,7 +8,7 @@ use typst::eval::{Module, Scope};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// A module with computational functions.
|
/// A module with computational functions.
|
||||||
pub fn calc() -> Module {
|
pub fn module() -> Module {
|
||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
scope.def_func::<AbsFunc>("abs");
|
scope.def_func::<AbsFunc>("abs");
|
||||||
scope.def_func::<PowFunc>("pow");
|
scope.def_func::<PowFunc>("pow");
|
||||||
|
|
@ -37,7 +39,6 @@ pub fn calc() -> Module {
|
||||||
Module::new("calc").with_scope(scope)
|
Module::new("calc").with_scope(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Absolute
|
|
||||||
/// Calculate the absolute value of a numeric value.
|
/// Calculate the absolute value of a numeric value.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -51,8 +52,8 @@ pub fn calc() -> Module {
|
||||||
/// - value: `ToAbs` (positional, required)
|
/// - value: `ToAbs` (positional, required)
|
||||||
/// The value whose absolute value to calculate.
|
/// The value whose absolute value to calculate.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Absolute
|
||||||
/// calculate
|
/// Category: calculate
|
||||||
#[func]
|
#[func]
|
||||||
pub fn abs(args: &mut Args) -> SourceResult<Value> {
|
pub fn abs(args: &mut Args) -> SourceResult<Value> {
|
||||||
Ok(args.expect::<ToAbs>("value")?.0)
|
Ok(args.expect::<ToAbs>("value")?.0)
|
||||||
|
|
@ -61,7 +62,7 @@ pub fn abs(args: &mut Args) -> SourceResult<Value> {
|
||||||
/// A value of which the absolute value can be taken.
|
/// A value of which the absolute value can be taken.
|
||||||
struct ToAbs(Value);
|
struct ToAbs(Value);
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
ToAbs,
|
ToAbs,
|
||||||
v: i64 => Self(Value::Int(v.abs())),
|
v: i64 => Self(Value::Int(v.abs())),
|
||||||
v: f64 => Self(Value::Float(v.abs())),
|
v: f64 => Self(Value::Float(v.abs())),
|
||||||
|
|
@ -72,7 +73,6 @@ castable! {
|
||||||
v: Fr => Self(Value::Fraction(v.abs())),
|
v: Fr => Self(Value::Fraction(v.abs())),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Power
|
|
||||||
/// Raise a value to some exponent.
|
/// Raise a value to some exponent.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -86,8 +86,8 @@ castable! {
|
||||||
/// - exponent: `Num` (positional, required)
|
/// - exponent: `Num` (positional, required)
|
||||||
/// The exponent of the power. Must be non-negative.
|
/// The exponent of the power. Must be non-negative.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Power
|
||||||
/// calculate
|
/// Category: calculate
|
||||||
#[func]
|
#[func]
|
||||||
pub fn pow(args: &mut Args) -> SourceResult<Value> {
|
pub fn pow(args: &mut Args) -> SourceResult<Value> {
|
||||||
let base = args.expect::<Num>("base")?;
|
let base = args.expect::<Num>("base")?;
|
||||||
|
|
@ -103,7 +103,6 @@ pub fn pow(args: &mut Args) -> SourceResult<Value> {
|
||||||
Ok(base.apply2(exponent, |a, b| a.pow(b as u32), f64::powf))
|
Ok(base.apply2(exponent, |a, b| a.pow(b as u32), f64::powf))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Square Root
|
|
||||||
/// Calculate the square root of a number.
|
/// Calculate the square root of a number.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -116,8 +115,8 @@ pub fn pow(args: &mut Args) -> SourceResult<Value> {
|
||||||
/// - value: `Num` (positional, required)
|
/// - value: `Num` (positional, required)
|
||||||
/// The number whose square root to calculate. Must be non-negative.
|
/// The number whose square root to calculate. Must be non-negative.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Square Root
|
||||||
/// calculate
|
/// Category: calculate
|
||||||
#[func]
|
#[func]
|
||||||
pub fn sqrt(args: &mut Args) -> SourceResult<Value> {
|
pub fn sqrt(args: &mut Args) -> SourceResult<Value> {
|
||||||
let value = args.expect::<Spanned<Num>>("value")?;
|
let value = args.expect::<Spanned<Num>>("value")?;
|
||||||
|
|
@ -127,7 +126,6 @@ pub fn sqrt(args: &mut Args) -> SourceResult<Value> {
|
||||||
Ok(Value::Float(value.v.float().sqrt()))
|
Ok(Value::Float(value.v.float().sqrt()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Sine
|
|
||||||
/// Calculate the sine of an angle.
|
/// Calculate the sine of an angle.
|
||||||
///
|
///
|
||||||
/// When called with an integer or a float, they will be interpreted as
|
/// When called with an integer or a float, they will be interpreted as
|
||||||
|
|
@ -144,8 +142,8 @@ pub fn sqrt(args: &mut Args) -> SourceResult<Value> {
|
||||||
/// - angle: `AngleLike` (positional, required)
|
/// - angle: `AngleLike` (positional, required)
|
||||||
/// The angle whose sine to calculate.
|
/// The angle whose sine to calculate.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Sine
|
||||||
/// calculate
|
/// Category: calculate
|
||||||
#[func]
|
#[func]
|
||||||
pub fn sin(args: &mut Args) -> SourceResult<Value> {
|
pub fn sin(args: &mut Args) -> SourceResult<Value> {
|
||||||
let arg = args.expect::<AngleLike>("angle")?;
|
let arg = args.expect::<AngleLike>("angle")?;
|
||||||
|
|
@ -156,7 +154,6 @@ pub fn sin(args: &mut Args) -> SourceResult<Value> {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Cosine
|
|
||||||
/// Calculate the cosine of an angle.
|
/// Calculate the cosine of an angle.
|
||||||
///
|
///
|
||||||
/// When called with an integer or a float, they will be interpreted as
|
/// When called with an integer or a float, they will be interpreted as
|
||||||
|
|
@ -173,8 +170,8 @@ pub fn sin(args: &mut Args) -> SourceResult<Value> {
|
||||||
/// - angle: `AngleLike` (positional, required)
|
/// - angle: `AngleLike` (positional, required)
|
||||||
/// The angle whose cosine to calculate.
|
/// The angle whose cosine to calculate.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Cosine
|
||||||
/// calculate
|
/// Category: calculate
|
||||||
#[func]
|
#[func]
|
||||||
pub fn cos(args: &mut Args) -> SourceResult<Value> {
|
pub fn cos(args: &mut Args) -> SourceResult<Value> {
|
||||||
let arg = args.expect::<AngleLike>("angle")?;
|
let arg = args.expect::<AngleLike>("angle")?;
|
||||||
|
|
@ -185,7 +182,6 @@ pub fn cos(args: &mut Args) -> SourceResult<Value> {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Tangent
|
|
||||||
/// Calculate the tangent of an angle.
|
/// Calculate the tangent of an angle.
|
||||||
///
|
///
|
||||||
/// When called with an integer or a float, they will be interpreted as
|
/// When called with an integer or a float, they will be interpreted as
|
||||||
|
|
@ -201,8 +197,8 @@ pub fn cos(args: &mut Args) -> SourceResult<Value> {
|
||||||
/// - angle: `AngleLike` (positional, required)
|
/// - angle: `AngleLike` (positional, required)
|
||||||
/// The angle whose tangent to calculate.
|
/// The angle whose tangent to calculate.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Tangent
|
||||||
/// calculate
|
/// Category: calculate
|
||||||
#[func]
|
#[func]
|
||||||
pub fn tan(args: &mut Args) -> SourceResult<Value> {
|
pub fn tan(args: &mut Args) -> SourceResult<Value> {
|
||||||
let arg = args.expect::<AngleLike>("angle")?;
|
let arg = args.expect::<AngleLike>("angle")?;
|
||||||
|
|
@ -213,7 +209,6 @@ pub fn tan(args: &mut Args) -> SourceResult<Value> {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Arcsine
|
|
||||||
/// Calculate the arcsine of a number.
|
/// Calculate the arcsine of a number.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -226,8 +221,8 @@ pub fn tan(args: &mut Args) -> SourceResult<Value> {
|
||||||
/// - value: `Num` (positional, required)
|
/// - value: `Num` (positional, required)
|
||||||
/// The number whose arcsine to calculate. Must be between -1 and 1.
|
/// The number whose arcsine to calculate. Must be between -1 and 1.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Arcsine
|
||||||
/// calculate
|
/// Category: calculate
|
||||||
#[func]
|
#[func]
|
||||||
pub fn asin(args: &mut Args) -> SourceResult<Value> {
|
pub fn asin(args: &mut Args) -> SourceResult<Value> {
|
||||||
let Spanned { v, span } = args.expect::<Spanned<Num>>("value")?;
|
let Spanned { v, span } = args.expect::<Spanned<Num>>("value")?;
|
||||||
|
|
@ -238,7 +233,6 @@ pub fn asin(args: &mut Args) -> SourceResult<Value> {
|
||||||
Ok(Value::Angle(Angle::rad(val.asin())))
|
Ok(Value::Angle(Angle::rad(val.asin())))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Arccosine
|
|
||||||
/// Calculate the arccosine of a number.
|
/// Calculate the arccosine of a number.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -251,8 +245,8 @@ pub fn asin(args: &mut Args) -> SourceResult<Value> {
|
||||||
/// - value: `Num` (positional, required)
|
/// - value: `Num` (positional, required)
|
||||||
/// The number whose arccosine to calculate. Must be between -1 and 1.
|
/// The number whose arccosine to calculate. Must be between -1 and 1.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Arccosine
|
||||||
/// calculate
|
/// Category: calculate
|
||||||
#[func]
|
#[func]
|
||||||
pub fn acos(args: &mut Args) -> SourceResult<Value> {
|
pub fn acos(args: &mut Args) -> SourceResult<Value> {
|
||||||
let Spanned { v, span } = args.expect::<Spanned<Num>>("value")?;
|
let Spanned { v, span } = args.expect::<Spanned<Num>>("value")?;
|
||||||
|
|
@ -263,7 +257,6 @@ pub fn acos(args: &mut Args) -> SourceResult<Value> {
|
||||||
Ok(Value::Angle(Angle::rad(val.acos())))
|
Ok(Value::Angle(Angle::rad(val.acos())))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Arctangent
|
|
||||||
/// Calculate the arctangent of a number.
|
/// Calculate the arctangent of a number.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -276,15 +269,14 @@ pub fn acos(args: &mut Args) -> SourceResult<Value> {
|
||||||
/// - value: `Num` (positional, required)
|
/// - value: `Num` (positional, required)
|
||||||
/// The number whose arctangent to calculate.
|
/// The number whose arctangent to calculate.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Arctangent
|
||||||
/// calculate
|
/// Category: calculate
|
||||||
#[func]
|
#[func]
|
||||||
pub fn atan(args: &mut Args) -> SourceResult<Value> {
|
pub fn atan(args: &mut Args) -> SourceResult<Value> {
|
||||||
let value = args.expect::<Num>("value")?;
|
let value = args.expect::<Num>("value")?;
|
||||||
Ok(Value::Angle(Angle::rad(value.float().atan())))
|
Ok(Value::Angle(Angle::rad(value.float().atan())))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Hyperbolic sine
|
|
||||||
/// Calculate the hyperbolic sine of an angle.
|
/// Calculate the hyperbolic sine of an angle.
|
||||||
///
|
///
|
||||||
/// When called with an integer or a float, they will be interpreted as radians.
|
/// When called with an integer or a float, they will be interpreted as radians.
|
||||||
|
|
@ -299,8 +291,8 @@ pub fn atan(args: &mut Args) -> SourceResult<Value> {
|
||||||
/// - angle: `AngleLike` (positional, required)
|
/// - angle: `AngleLike` (positional, required)
|
||||||
/// The angle whose hyperbolic sine to calculate.
|
/// The angle whose hyperbolic sine to calculate.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Hyperbolic sine
|
||||||
/// calculate
|
/// Category: calculate
|
||||||
#[func]
|
#[func]
|
||||||
pub fn sinh(args: &mut Args) -> SourceResult<Value> {
|
pub fn sinh(args: &mut Args) -> SourceResult<Value> {
|
||||||
let arg = args.expect::<AngleLike>("angle")?;
|
let arg = args.expect::<AngleLike>("angle")?;
|
||||||
|
|
@ -311,7 +303,6 @@ pub fn sinh(args: &mut Args) -> SourceResult<Value> {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Hyperbolic cosine
|
|
||||||
/// Calculate the hyperbolic cosine of an angle.
|
/// Calculate the hyperbolic cosine of an angle.
|
||||||
///
|
///
|
||||||
/// When called with an integer or a float, they will be interpreted as radians.
|
/// When called with an integer or a float, they will be interpreted as radians.
|
||||||
|
|
@ -326,8 +317,8 @@ pub fn sinh(args: &mut Args) -> SourceResult<Value> {
|
||||||
/// - angle: `AngleLike` (positional, required)
|
/// - angle: `AngleLike` (positional, required)
|
||||||
/// The angle whose hyperbolic cosine to calculate.
|
/// The angle whose hyperbolic cosine to calculate.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Hyperbolic cosine
|
||||||
/// calculate
|
/// Category: calculate
|
||||||
#[func]
|
#[func]
|
||||||
pub fn cosh(args: &mut Args) -> SourceResult<Value> {
|
pub fn cosh(args: &mut Args) -> SourceResult<Value> {
|
||||||
let arg = args.expect::<AngleLike>("angle")?;
|
let arg = args.expect::<AngleLike>("angle")?;
|
||||||
|
|
@ -338,7 +329,6 @@ pub fn cosh(args: &mut Args) -> SourceResult<Value> {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Hyperbolic tangent
|
|
||||||
/// Calculate the hyperbolic tangent of an angle.
|
/// Calculate the hyperbolic tangent of an angle.
|
||||||
///
|
///
|
||||||
/// When called with an integer or a float, they will be interpreted as radians.
|
/// When called with an integer or a float, they will be interpreted as radians.
|
||||||
|
|
@ -353,8 +343,8 @@ pub fn cosh(args: &mut Args) -> SourceResult<Value> {
|
||||||
/// - angle: `AngleLike` (positional, required)
|
/// - angle: `AngleLike` (positional, required)
|
||||||
/// The angle whose hyperbolic tangent to calculate.
|
/// The angle whose hyperbolic tangent to calculate.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Hyperbolic tangent
|
||||||
/// calculate
|
/// Category: calculate
|
||||||
#[func]
|
#[func]
|
||||||
pub fn tanh(args: &mut Args) -> SourceResult<Value> {
|
pub fn tanh(args: &mut Args) -> SourceResult<Value> {
|
||||||
let arg = args.expect::<AngleLike>("angle")?;
|
let arg = args.expect::<AngleLike>("angle")?;
|
||||||
|
|
@ -365,7 +355,6 @@ pub fn tanh(args: &mut Args) -> SourceResult<Value> {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Logarithm
|
|
||||||
/// Calculate the logarithm of a number.
|
/// Calculate the logarithm of a number.
|
||||||
///
|
///
|
||||||
/// If the base is not specified, the logarithm is calculated in base 10.
|
/// If the base is not specified, the logarithm is calculated in base 10.
|
||||||
|
|
@ -381,8 +370,8 @@ pub fn tanh(args: &mut Args) -> SourceResult<Value> {
|
||||||
/// - base: `Num` (named)
|
/// - base: `Num` (named)
|
||||||
/// The base of the logarithm.
|
/// The base of the logarithm.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Logarithm
|
||||||
/// calculate
|
/// Category: calculate
|
||||||
#[func]
|
#[func]
|
||||||
pub fn log(args: &mut Args) -> SourceResult<Value> {
|
pub fn log(args: &mut Args) -> SourceResult<Value> {
|
||||||
let value = args.expect::<f64>("value")?;
|
let value = args.expect::<f64>("value")?;
|
||||||
|
|
@ -390,7 +379,6 @@ pub fn log(args: &mut Args) -> SourceResult<Value> {
|
||||||
Ok(Value::Float(value.log(base)))
|
Ok(Value::Float(value.log(base)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Round down
|
|
||||||
/// Round a number down to the nearest integer.
|
/// Round a number down to the nearest integer.
|
||||||
///
|
///
|
||||||
/// If the number is already an integer, it is returned unchanged.
|
/// If the number is already an integer, it is returned unchanged.
|
||||||
|
|
@ -406,8 +394,8 @@ pub fn log(args: &mut Args) -> SourceResult<Value> {
|
||||||
/// - value: `Num` (positional, required)
|
/// - value: `Num` (positional, required)
|
||||||
/// The number to round down.
|
/// The number to round down.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Round down
|
||||||
/// calculate
|
/// Category: calculate
|
||||||
#[func]
|
#[func]
|
||||||
pub fn floor(args: &mut Args) -> SourceResult<Value> {
|
pub fn floor(args: &mut Args) -> SourceResult<Value> {
|
||||||
let value = args.expect::<Num>("value")?;
|
let value = args.expect::<Num>("value")?;
|
||||||
|
|
@ -417,7 +405,6 @@ pub fn floor(args: &mut Args) -> SourceResult<Value> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Round up
|
|
||||||
/// Round a number up to the nearest integer.
|
/// Round a number up to the nearest integer.
|
||||||
///
|
///
|
||||||
/// If the number is already an integer, it is returned unchanged.
|
/// If the number is already an integer, it is returned unchanged.
|
||||||
|
|
@ -433,8 +420,8 @@ pub fn floor(args: &mut Args) -> SourceResult<Value> {
|
||||||
/// - value: `Num` (positional, required)
|
/// - value: `Num` (positional, required)
|
||||||
/// The number to round up.
|
/// The number to round up.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Round up
|
||||||
/// calculate
|
/// Category: calculate
|
||||||
#[func]
|
#[func]
|
||||||
pub fn ceil(args: &mut Args) -> SourceResult<Value> {
|
pub fn ceil(args: &mut Args) -> SourceResult<Value> {
|
||||||
let value = args.expect::<Num>("value")?;
|
let value = args.expect::<Num>("value")?;
|
||||||
|
|
@ -444,7 +431,6 @@ pub fn ceil(args: &mut Args) -> SourceResult<Value> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Round
|
|
||||||
/// Round a number to the nearest integer.
|
/// Round a number to the nearest integer.
|
||||||
///
|
///
|
||||||
/// Optionally, a number of decimal places can be specified.
|
/// Optionally, a number of decimal places can be specified.
|
||||||
|
|
@ -461,8 +447,8 @@ pub fn ceil(args: &mut Args) -> SourceResult<Value> {
|
||||||
/// The number to round.
|
/// The number to round.
|
||||||
/// - digits: `i64` (named)
|
/// - digits: `i64` (named)
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Round
|
||||||
/// calculate
|
/// Category: calculate
|
||||||
#[func]
|
#[func]
|
||||||
pub fn round(args: &mut Args) -> SourceResult<Value> {
|
pub fn round(args: &mut Args) -> SourceResult<Value> {
|
||||||
let value = args.expect::<Num>("value")?;
|
let value = args.expect::<Num>("value")?;
|
||||||
|
|
@ -477,7 +463,6 @@ pub fn round(args: &mut Args) -> SourceResult<Value> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Clamp
|
|
||||||
/// Clamp a number between a minimum and maximum value.
|
/// Clamp a number between a minimum and maximum value.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -495,8 +480,8 @@ pub fn round(args: &mut Args) -> SourceResult<Value> {
|
||||||
/// - max: `Num` (positional, required)
|
/// - max: `Num` (positional, required)
|
||||||
/// The inclusive maximum value.
|
/// The inclusive maximum value.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Clamp
|
||||||
/// calculate
|
/// Category: calculate
|
||||||
#[func]
|
#[func]
|
||||||
pub fn clamp(args: &mut Args) -> SourceResult<Value> {
|
pub fn clamp(args: &mut Args) -> SourceResult<Value> {
|
||||||
let value = args.expect::<Num>("value")?;
|
let value = args.expect::<Num>("value")?;
|
||||||
|
|
@ -508,7 +493,6 @@ pub fn clamp(args: &mut Args) -> SourceResult<Value> {
|
||||||
Ok(value.apply3(min, max.v, i64::clamp, f64::clamp))
|
Ok(value.apply3(min, max.v, i64::clamp, f64::clamp))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Minimum
|
|
||||||
/// Determine the minimum of a sequence of values.
|
/// Determine the minimum of a sequence of values.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -524,14 +508,13 @@ pub fn clamp(args: &mut Args) -> SourceResult<Value> {
|
||||||
///
|
///
|
||||||
/// - returns: any
|
/// - returns: any
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Minimum
|
||||||
/// calculate
|
/// Category: 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Maximum
|
|
||||||
/// Determine the maximum of a sequence of values.
|
/// Determine the maximum of a sequence of values.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -547,8 +530,8 @@ pub fn min(args: &mut Args) -> SourceResult<Value> {
|
||||||
///
|
///
|
||||||
/// - returns: any
|
/// - returns: any
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Maximum
|
||||||
/// calculate
|
/// Category: 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)
|
||||||
|
|
@ -575,7 +558,6 @@ fn minmax(args: &mut Args, goal: Ordering) -> SourceResult<Value> {
|
||||||
Ok(extremum)
|
Ok(extremum)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Even
|
|
||||||
/// Determine whether an integer is even.
|
/// Determine whether an integer is even.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -591,14 +573,13 @@ fn minmax(args: &mut Args, goal: Ordering) -> SourceResult<Value> {
|
||||||
///
|
///
|
||||||
/// - returns: boolean
|
/// - returns: boolean
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Even
|
||||||
/// calculate
|
/// Category: 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>("value")? % 2 == 0))
|
Ok(Value::Bool(args.expect::<i64>("value")? % 2 == 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Odd
|
|
||||||
/// Determine whether an integer is odd.
|
/// Determine whether an integer is odd.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -615,14 +596,13 @@ pub fn even(args: &mut Args) -> SourceResult<Value> {
|
||||||
///
|
///
|
||||||
/// - returns: boolean
|
/// - returns: boolean
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Odd
|
||||||
/// calculate
|
/// Category: 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>("value")? % 2 != 0))
|
Ok(Value::Bool(args.expect::<i64>("value")? % 2 != 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Modulus
|
|
||||||
/// Calculate the modulus of two numbers.
|
/// Calculate the modulus of two numbers.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -640,8 +620,8 @@ pub fn odd(args: &mut Args) -> SourceResult<Value> {
|
||||||
///
|
///
|
||||||
/// - returns: integer or float
|
/// - returns: integer or float
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Modulus
|
||||||
/// calculate
|
/// Category: calculate
|
||||||
#[func]
|
#[func]
|
||||||
pub fn mod_(args: &mut Args) -> SourceResult<Value> {
|
pub fn mod_(args: &mut Args) -> SourceResult<Value> {
|
||||||
let dividend = args.expect::<Num>("dividend")?;
|
let dividend = args.expect::<Num>("dividend")?;
|
||||||
|
|
@ -693,7 +673,7 @@ impl Num {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
Num,
|
Num,
|
||||||
v: i64 => Self::Int(v),
|
v: i64 => Self::Int(v),
|
||||||
v: f64 => Self::Float(v),
|
v: f64 => Self::Float(v),
|
||||||
|
|
@ -706,7 +686,7 @@ enum AngleLike {
|
||||||
Angle(Angle),
|
Angle(Angle),
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
AngleLike,
|
AngleLike,
|
||||||
v: i64 => Self::Int(v),
|
v: i64 => Self::Int(v),
|
||||||
v: f64 => Self::Float(v),
|
v: f64 => Self::Float(v),
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ use typst::eval::Regex;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// # Integer
|
|
||||||
/// Convert a value to an integer.
|
/// Convert a value to an integer.
|
||||||
///
|
///
|
||||||
/// - Booleans are converted to `0` or `1`.
|
/// - Booleans are converted to `0` or `1`.
|
||||||
|
|
@ -26,8 +25,8 @@ use crate::prelude::*;
|
||||||
///
|
///
|
||||||
/// - returns: integer
|
/// - returns: integer
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Integer
|
||||||
/// construct
|
/// Category: construct
|
||||||
#[func]
|
#[func]
|
||||||
pub fn int(args: &mut Args) -> SourceResult<Value> {
|
pub fn int(args: &mut Args) -> SourceResult<Value> {
|
||||||
Ok(Value::Int(args.expect::<ToInt>("value")?.0))
|
Ok(Value::Int(args.expect::<ToInt>("value")?.0))
|
||||||
|
|
@ -36,7 +35,7 @@ pub fn int(args: &mut Args) -> SourceResult<Value> {
|
||||||
/// A value that can be cast to an integer.
|
/// A value that can be cast to an integer.
|
||||||
struct ToInt(i64);
|
struct ToInt(i64);
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
ToInt,
|
ToInt,
|
||||||
v: bool => Self(v as i64),
|
v: bool => Self(v as i64),
|
||||||
v: i64 => Self(v),
|
v: i64 => Self(v),
|
||||||
|
|
@ -44,7 +43,6 @@ castable! {
|
||||||
v: EcoString => Self(v.parse().map_err(|_| "not a valid integer")?),
|
v: EcoString => Self(v.parse().map_err(|_| "not a valid integer")?),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Float
|
|
||||||
/// Convert a value to a float.
|
/// Convert a value to a float.
|
||||||
///
|
///
|
||||||
/// - Booleans are converted to `0.0` or `1.0`.
|
/// - Booleans are converted to `0.0` or `1.0`.
|
||||||
|
|
@ -67,8 +65,8 @@ castable! {
|
||||||
///
|
///
|
||||||
/// - returns: float
|
/// - returns: float
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Float
|
||||||
/// construct
|
/// Category: construct
|
||||||
#[func]
|
#[func]
|
||||||
pub fn float(args: &mut Args) -> SourceResult<Value> {
|
pub fn float(args: &mut Args) -> SourceResult<Value> {
|
||||||
Ok(Value::Float(args.expect::<ToFloat>("value")?.0))
|
Ok(Value::Float(args.expect::<ToFloat>("value")?.0))
|
||||||
|
|
@ -77,7 +75,7 @@ pub fn float(args: &mut Args) -> SourceResult<Value> {
|
||||||
/// A value that can be cast to a float.
|
/// A value that can be cast to a float.
|
||||||
struct ToFloat(f64);
|
struct ToFloat(f64);
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
ToFloat,
|
ToFloat,
|
||||||
v: bool => Self(v as i64 as f64),
|
v: bool => Self(v as i64 as f64),
|
||||||
v: i64 => Self(v as f64),
|
v: i64 => Self(v as f64),
|
||||||
|
|
@ -85,7 +83,6 @@ castable! {
|
||||||
v: EcoString => Self(v.parse().map_err(|_| "not a valid float")?),
|
v: EcoString => Self(v.parse().map_err(|_| "not a valid float")?),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Luma
|
|
||||||
/// Create a grayscale color.
|
/// Create a grayscale color.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -101,15 +98,14 @@ castable! {
|
||||||
///
|
///
|
||||||
/// - returns: color
|
/// - returns: color
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Luma
|
||||||
/// construct
|
/// Category: construct
|
||||||
#[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")?;
|
||||||
Ok(Value::Color(LumaColor::new(luma).into()))
|
Ok(Value::Color(LumaColor::new(luma).into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # RGBA
|
|
||||||
/// Create an RGB(A) color.
|
/// Create an RGB(A) color.
|
||||||
///
|
///
|
||||||
/// The color is specified in the sRGB color space.
|
/// The color is specified in the sRGB color space.
|
||||||
|
|
@ -154,8 +150,8 @@ pub fn luma(args: &mut Args) -> SourceResult<Value> {
|
||||||
///
|
///
|
||||||
/// - returns: color
|
/// - returns: color
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: RGBA
|
||||||
/// construct
|
/// Category: construct
|
||||||
#[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>>()? {
|
||||||
|
|
@ -175,7 +171,7 @@ pub fn rgb(args: &mut Args) -> SourceResult<Value> {
|
||||||
/// An integer or ratio component.
|
/// An integer or ratio component.
|
||||||
struct Component(u8);
|
struct Component(u8);
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
Component,
|
Component,
|
||||||
v: i64 => match v {
|
v: i64 => match v {
|
||||||
0 ..= 255 => Self(v as u8),
|
0 ..= 255 => Self(v as u8),
|
||||||
|
|
@ -188,7 +184,6 @@ castable! {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # CMYK
|
|
||||||
/// Create a CMYK color.
|
/// Create a CMYK color.
|
||||||
///
|
///
|
||||||
/// This is useful if you want to target a specific printer. The conversion
|
/// This is useful if you want to target a specific printer. The conversion
|
||||||
|
|
@ -217,8 +212,8 @@ castable! {
|
||||||
///
|
///
|
||||||
/// - returns: color
|
/// - returns: color
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: CMYK
|
||||||
/// construct
|
/// Category: construct
|
||||||
#[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")?;
|
||||||
|
|
@ -231,7 +226,7 @@ pub fn cmyk(args: &mut Args) -> SourceResult<Value> {
|
||||||
/// A component that must be a ratio.
|
/// A component that must be a ratio.
|
||||||
struct RatioComponent(u8);
|
struct RatioComponent(u8);
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
RatioComponent,
|
RatioComponent,
|
||||||
v: Ratio => 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)
|
||||||
|
|
@ -240,7 +235,6 @@ castable! {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Symbol
|
|
||||||
/// Create a custom symbol with modifiers.
|
/// Create a custom symbol with modifiers.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -272,8 +266,8 @@ castable! {
|
||||||
///
|
///
|
||||||
/// - returns: symbol
|
/// - returns: symbol
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Symbol
|
||||||
/// construct
|
/// Category: construct
|
||||||
#[func]
|
#[func]
|
||||||
pub fn symbol(args: &mut Args) -> SourceResult<Value> {
|
pub fn symbol(args: &mut Args) -> SourceResult<Value> {
|
||||||
let mut list = EcoVec::new();
|
let mut list = EcoVec::new();
|
||||||
|
|
@ -289,7 +283,7 @@ pub fn symbol(args: &mut Args) -> SourceResult<Value> {
|
||||||
/// A value that can be cast to a symbol.
|
/// A value that can be cast to a symbol.
|
||||||
struct Variant(EcoString, char);
|
struct Variant(EcoString, char);
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
Variant,
|
Variant,
|
||||||
c: char => Self(EcoString::new(), c),
|
c: char => Self(EcoString::new(), c),
|
||||||
array: Array => {
|
array: Array => {
|
||||||
|
|
@ -301,7 +295,6 @@ castable! {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # String
|
|
||||||
/// Convert a value to a string.
|
/// Convert a value to a string.
|
||||||
///
|
///
|
||||||
/// - Integers are formatted in base 10.
|
/// - Integers are formatted in base 10.
|
||||||
|
|
@ -322,8 +315,8 @@ castable! {
|
||||||
///
|
///
|
||||||
/// - returns: string
|
/// - returns: string
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: String
|
||||||
/// construct
|
/// Category: construct
|
||||||
#[func]
|
#[func]
|
||||||
pub fn str(args: &mut Args) -> SourceResult<Value> {
|
pub fn str(args: &mut Args) -> SourceResult<Value> {
|
||||||
Ok(Value::Str(args.expect::<ToStr>("value")?.0))
|
Ok(Value::Str(args.expect::<ToStr>("value")?.0))
|
||||||
|
|
@ -332,7 +325,7 @@ pub fn str(args: &mut Args) -> SourceResult<Value> {
|
||||||
/// A value that can be cast to a string.
|
/// A value that can be cast to a string.
|
||||||
struct ToStr(Str);
|
struct ToStr(Str);
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
ToStr,
|
ToStr,
|
||||||
v: i64 => Self(format_str!("{}", v)),
|
v: i64 => Self(format_str!("{}", v)),
|
||||||
v: f64 => Self(format_str!("{}", v)),
|
v: f64 => Self(format_str!("{}", v)),
|
||||||
|
|
@ -340,7 +333,6 @@ castable! {
|
||||||
v: Str => Self(v),
|
v: Str => Self(v),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Label
|
|
||||||
/// Create a label from a string.
|
/// Create a label from a string.
|
||||||
///
|
///
|
||||||
/// Inserting a label into content attaches it to the closest previous element
|
/// Inserting a label into content attaches it to the closest previous element
|
||||||
|
|
@ -366,14 +358,13 @@ castable! {
|
||||||
///
|
///
|
||||||
/// - returns: label
|
/// - returns: label
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Label
|
||||||
/// construct
|
/// Category: construct
|
||||||
#[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")?)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Regex
|
|
||||||
/// Create a regular expression from a string.
|
/// Create a regular expression from a string.
|
||||||
///
|
///
|
||||||
/// The result can be used as a
|
/// The result can be used as a
|
||||||
|
|
@ -406,15 +397,14 @@ pub fn label(args: &mut Args) -> SourceResult<Value> {
|
||||||
///
|
///
|
||||||
/// - returns: regex
|
/// - returns: regex
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Regex
|
||||||
/// construct
|
/// Category: construct
|
||||||
#[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")?;
|
||||||
Ok(Regex::new(&v).at(span)?.into())
|
Ok(Regex::new(&v).at(span)?.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Range
|
|
||||||
/// Create an array consisting of a sequence of numbers.
|
/// Create an array consisting of a sequence of numbers.
|
||||||
///
|
///
|
||||||
/// If you pass just one positional parameter, it is interpreted as the `end` of
|
/// If you pass just one positional parameter, it is interpreted as the `end` of
|
||||||
|
|
@ -442,8 +432,8 @@ pub fn regex(args: &mut Args) -> SourceResult<Value> {
|
||||||
///
|
///
|
||||||
/// - returns: array
|
/// - returns: array
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Range
|
||||||
/// construct
|
/// Category: construct
|
||||||
#[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")?;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ use typst::diag::{format_xml_like_error, FileError};
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// # Plain text
|
|
||||||
/// Read plain text from a file.
|
/// Read plain text from a file.
|
||||||
///
|
///
|
||||||
/// The file will be read and returned as a string.
|
/// The file will be read and returned as a string.
|
||||||
|
|
@ -23,8 +22,8 @@ use crate::prelude::*;
|
||||||
///
|
///
|
||||||
/// - returns: string
|
/// - returns: string
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Plain text
|
||||||
/// data-loading
|
/// Category: data-loading
|
||||||
#[func]
|
#[func]
|
||||||
pub fn read(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
|
pub fn read(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
|
||||||
let Spanned { v: path, span } = args.expect::<Spanned<EcoString>>("path to file")?;
|
let Spanned { v: path, span } = args.expect::<Spanned<EcoString>>("path to file")?;
|
||||||
|
|
@ -38,7 +37,6 @@ pub fn read(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
|
||||||
Ok(Value::Str(text.into()))
|
Ok(Value::Str(text.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # CSV
|
|
||||||
/// Read structured data from a CSV file.
|
/// Read structured data from a CSV file.
|
||||||
///
|
///
|
||||||
/// The CSV file will be read and parsed into a 2-dimensional array of strings:
|
/// The CSV file will be read and parsed into a 2-dimensional array of strings:
|
||||||
|
|
@ -68,8 +66,8 @@ pub fn read(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
|
||||||
///
|
///
|
||||||
/// - returns: array
|
/// - returns: array
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: CSV
|
||||||
/// data-loading
|
/// Category: 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 } =
|
||||||
|
|
@ -100,7 +98,7 @@ pub fn csv(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
|
||||||
/// The delimiter to use when parsing CSV files.
|
/// The delimiter to use when parsing CSV files.
|
||||||
struct Delimiter(u8);
|
struct Delimiter(u8);
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
Delimiter,
|
Delimiter,
|
||||||
v: EcoString => {
|
v: EcoString => {
|
||||||
let mut chars = v.chars();
|
let mut chars = v.chars();
|
||||||
|
|
@ -134,7 +132,6 @@ fn format_csv_error(error: csv::Error) -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # JSON
|
|
||||||
/// Read structured data from a JSON file.
|
/// Read structured data from a JSON file.
|
||||||
///
|
///
|
||||||
/// The file must contain a valid JSON object or array. JSON objects will be
|
/// The file must contain a valid JSON object or array. JSON objects will be
|
||||||
|
|
@ -179,8 +176,8 @@ fn format_csv_error(error: csv::Error) -> String {
|
||||||
///
|
///
|
||||||
/// - returns: dictionary or array
|
/// - returns: dictionary or array
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: JSON
|
||||||
/// data-loading
|
/// Category: 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 } =
|
||||||
|
|
@ -222,7 +219,6 @@ fn format_json_error(error: serde_json::Error) -> String {
|
||||||
format!("failed to parse json file: syntax error in line {}", error.line())
|
format!("failed to parse json file: syntax error in line {}", error.line())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # XML
|
|
||||||
/// Read structured data from an XML file.
|
/// Read structured data from an XML file.
|
||||||
///
|
///
|
||||||
/// The XML file is parsed into an array of dictionaries and strings. XML nodes
|
/// The XML file is parsed into an array of dictionaries and strings. XML nodes
|
||||||
|
|
@ -278,8 +274,8 @@ fn format_json_error(error: serde_json::Error) -> String {
|
||||||
///
|
///
|
||||||
/// - returns: array
|
/// - returns: array
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: XML
|
||||||
/// data-loading
|
/// Category: 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 } =
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// # Type
|
|
||||||
/// Determine a value's type.
|
/// Determine a value's type.
|
||||||
///
|
///
|
||||||
/// Returns the name of the value's type.
|
/// Returns the name of the value's type.
|
||||||
|
|
@ -21,14 +20,13 @@ use crate::prelude::*;
|
||||||
///
|
///
|
||||||
/// - returns: string
|
/// - returns: string
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Type
|
||||||
/// foundations
|
/// Category: 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())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Representation
|
|
||||||
/// The string representation of a value.
|
/// The string representation of a value.
|
||||||
///
|
///
|
||||||
/// When inserted into content, most values are displayed as this representation
|
/// When inserted into content, most values are displayed as this representation
|
||||||
|
|
@ -49,14 +47,13 @@ pub fn type_(args: &mut Args) -> SourceResult<Value> {
|
||||||
///
|
///
|
||||||
/// - returns: string
|
/// - returns: string
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Representation
|
||||||
/// foundations
|
/// Category: 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())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Panic
|
|
||||||
/// Fail with an error.
|
/// Fail with an error.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -69,8 +66,8 @@ pub fn repr(args: &mut Args) -> SourceResult<Value> {
|
||||||
/// - payload: `Value` (positional)
|
/// - payload: `Value` (positional)
|
||||||
/// The value (or message) to panic with.
|
/// The value (or message) to panic with.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Panic
|
||||||
/// foundations
|
/// Category: foundations
|
||||||
#[func]
|
#[func]
|
||||||
pub fn panic(args: &mut Args) -> SourceResult<Value> {
|
pub fn panic(args: &mut Args) -> SourceResult<Value> {
|
||||||
match args.eat::<Value>()? {
|
match args.eat::<Value>()? {
|
||||||
|
|
@ -79,7 +76,6 @@ pub fn panic(args: &mut Args) -> SourceResult<Value> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Assert
|
|
||||||
/// Ensure that a condition is fulfilled.
|
/// Ensure that a condition is fulfilled.
|
||||||
///
|
///
|
||||||
/// Fails with an error if the condition is not fulfilled. Does not
|
/// Fails with an error if the condition is not fulfilled. Does not
|
||||||
|
|
@ -96,8 +92,8 @@ pub fn panic(args: &mut Args) -> SourceResult<Value> {
|
||||||
/// - message: `EcoString` (named)
|
/// - message: `EcoString` (named)
|
||||||
/// The error message when the assertion fails.
|
/// The error message when the assertion fails.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Assert
|
||||||
/// foundations
|
/// Category: foundations
|
||||||
#[func]
|
#[func]
|
||||||
pub fn assert(args: &mut Args) -> SourceResult<Value> {
|
pub fn assert(args: &mut Args) -> SourceResult<Value> {
|
||||||
let check = args.expect::<bool>("condition")?;
|
let check = args.expect::<bool>("condition")?;
|
||||||
|
|
@ -112,7 +108,6 @@ pub fn assert(args: &mut Args) -> SourceResult<Value> {
|
||||||
Ok(Value::None)
|
Ok(Value::None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Evaluate
|
|
||||||
/// Evaluate a string as Typst code.
|
/// Evaluate a string as Typst code.
|
||||||
///
|
///
|
||||||
/// This function should only be used as a last resort.
|
/// This function should only be used as a last resort.
|
||||||
|
|
@ -132,8 +127,8 @@ pub fn assert(args: &mut Args) -> SourceResult<Value> {
|
||||||
///
|
///
|
||||||
/// - returns: any
|
/// - returns: any
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Evaluate
|
||||||
/// foundations
|
/// Category: 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")?;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
//! Computational functions.
|
//! Computational functions.
|
||||||
|
|
||||||
mod calc;
|
pub mod calc;
|
||||||
mod construct;
|
mod construct;
|
||||||
mod data;
|
mod data;
|
||||||
mod foundations;
|
mod foundations;
|
||||||
|
|
||||||
pub use self::calc::*;
|
|
||||||
pub use self::construct::*;
|
pub use self::construct::*;
|
||||||
pub use self::data::*;
|
pub use self::data::*;
|
||||||
pub use self::foundations::*;
|
pub use self::foundations::*;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// # Align
|
|
||||||
/// Align content horizontally and vertically.
|
/// Align content horizontally and vertically.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -13,63 +12,59 @@ use crate::prelude::*;
|
||||||
/// A work of art, a visual throne
|
/// A work of art, a visual throne
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Align
|
||||||
/// - body: `Content` (positional, required)
|
/// Category: layout
|
||||||
/// The content to align.
|
#[node(Show)]
|
||||||
///
|
#[set({
|
||||||
/// - alignment: `Axes<Option<GenAlign>>` (positional, settable)
|
let aligns: Axes<Option<GenAlign>> = args.find()?.unwrap_or_default();
|
||||||
/// The alignment along both axes.
|
styles.set(Self::ALIGNMENT, aligns);
|
||||||
///
|
})]
|
||||||
/// Possible values for horizontal alignments are:
|
pub struct AlignNode {
|
||||||
/// - `start`
|
/// The content to align.
|
||||||
/// - `end`
|
#[positional]
|
||||||
/// - `left`
|
#[required]
|
||||||
/// - `center`
|
pub body: Content,
|
||||||
/// - `right`
|
|
||||||
///
|
|
||||||
/// The `start` and `end` alignments are relative to the current [text
|
|
||||||
/// direction]($func/text.dir).
|
|
||||||
///
|
|
||||||
/// Possible values for vertical alignments are:
|
|
||||||
/// - `top`
|
|
||||||
/// - `horizon`
|
|
||||||
/// - `bottom`
|
|
||||||
///
|
|
||||||
/// To align along both axes at the same time, add the two alignments using
|
|
||||||
/// the `+` operator to get a `2d alignment`. For example, `top + right`
|
|
||||||
/// aligns the content to the top right corner.
|
|
||||||
///
|
|
||||||
/// ```example
|
|
||||||
/// #set page(height: 6cm)
|
|
||||||
/// #set text(lang: "ar")
|
|
||||||
///
|
|
||||||
/// مثال
|
|
||||||
/// #align(
|
|
||||||
/// end + horizon,
|
|
||||||
/// rect(inset: 12pt)[ركن]
|
|
||||||
/// )
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// layout
|
|
||||||
#[func]
|
|
||||||
#[capable]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub enum AlignNode {}
|
|
||||||
|
|
||||||
#[node]
|
/// The alignment along both axes.
|
||||||
impl AlignNode {
|
///
|
||||||
/// The alignment.
|
/// Possible values for horizontal alignments are:
|
||||||
#[property(fold, skip)]
|
/// - `start`
|
||||||
pub const ALIGNS: Axes<Option<GenAlign>> =
|
/// - `end`
|
||||||
Axes::new(GenAlign::Start, GenAlign::Specific(Align::Top));
|
/// - `left`
|
||||||
|
/// - `center`
|
||||||
|
/// - `right`
|
||||||
|
///
|
||||||
|
/// The `start` and `end` alignments are relative to the current [text
|
||||||
|
/// direction]($func/text.dir).
|
||||||
|
///
|
||||||
|
/// Possible values for vertical alignments are:
|
||||||
|
/// - `top`
|
||||||
|
/// - `horizon`
|
||||||
|
/// - `bottom`
|
||||||
|
///
|
||||||
|
/// To align along both axes at the same time, add the two alignments using
|
||||||
|
/// the `+` operator to get a `2d alignment`. For example, `top + right`
|
||||||
|
/// aligns the content to the top right corner.
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// #set page(height: 6cm)
|
||||||
|
/// #set text(lang: "ar")
|
||||||
|
///
|
||||||
|
/// مثال
|
||||||
|
/// #align(
|
||||||
|
/// end + horizon,
|
||||||
|
/// rect(inset: 12pt)[ركن]
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
|
#[settable]
|
||||||
|
#[fold]
|
||||||
|
#[skip]
|
||||||
|
#[default(Axes::new(GenAlign::Start, GenAlign::Specific(Align::Top)))]
|
||||||
|
pub alignment: Axes<Option<GenAlign>>,
|
||||||
|
}
|
||||||
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
impl Show for AlignNode {
|
||||||
args.expect("body")
|
fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> SourceResult<Content> {
|
||||||
}
|
Ok(self.body())
|
||||||
|
|
||||||
fn set(...) {
|
|
||||||
let aligns: Axes<Option<GenAlign>> = args.find()?.unwrap_or_default();
|
|
||||||
styles.set(Self::ALIGNS, aligns);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::text::TextNode;
|
use crate::text::TextNode;
|
||||||
|
|
||||||
/// # Columns
|
|
||||||
/// Separate a region into multiple equally sized columns.
|
/// Separate a region into multiple equally sized columns.
|
||||||
///
|
///
|
||||||
/// The `column` function allows to separate the interior of any container into
|
/// The `column` function allows to separate the interior of any container into
|
||||||
|
|
@ -31,39 +30,25 @@ use crate::text::TextNode;
|
||||||
/// variety of problems.
|
/// variety of problems.
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Columns
|
||||||
/// - count: `usize` (positional, required)
|
/// Category: layout
|
||||||
/// The number of columns.
|
#[node(Layout)]
|
||||||
///
|
|
||||||
/// - body: `Content` (positional, required)
|
|
||||||
/// The content that should be layouted into the columns.
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// layout
|
|
||||||
#[func]
|
|
||||||
#[capable(Layout)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct ColumnsNode {
|
pub struct ColumnsNode {
|
||||||
/// How many columns there should be.
|
/// The number of columns.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
pub count: NonZeroUsize,
|
pub count: NonZeroUsize,
|
||||||
/// The child to be layouted into the columns. Most likely, this should be a
|
|
||||||
/// flow or stack node.
|
/// The content that should be layouted into the columns.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
pub body: Content,
|
pub body: Content,
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl ColumnsNode {
|
|
||||||
/// The size of the gutter space between each column.
|
/// The size of the gutter space between each column.
|
||||||
#[property(resolve)]
|
#[settable]
|
||||||
pub const GUTTER: Rel<Length> = Ratio::new(0.04).into();
|
#[resolve]
|
||||||
|
#[default(Ratio::new(0.04).into())]
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
pub gutter: Rel<Length>,
|
||||||
Ok(Self {
|
|
||||||
count: args.expect("column count")?,
|
|
||||||
body: args.expect("body")?,
|
|
||||||
}
|
|
||||||
.pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for ColumnsNode {
|
impl Layout for ColumnsNode {
|
||||||
|
|
@ -73,14 +58,16 @@ impl Layout for ColumnsNode {
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
|
let body = self.body();
|
||||||
|
|
||||||
// Separating the infinite space into infinite columns does not make
|
// Separating the infinite space into infinite columns does not make
|
||||||
// much sense.
|
// much sense.
|
||||||
if !regions.size.x.is_finite() {
|
if !regions.size.x.is_finite() {
|
||||||
return self.body.layout(vt, styles, regions);
|
return body.layout(vt, styles, regions);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the width of the gutter and each column.
|
// Determine the width of the gutter and each column.
|
||||||
let columns = self.count.get();
|
let columns = self.count().get();
|
||||||
let gutter = styles.get(Self::GUTTER).relative_to(regions.base().x);
|
let gutter = styles.get(Self::GUTTER).relative_to(regions.base().x);
|
||||||
let width = (regions.size.x - gutter * (columns - 1) as f64) / columns as f64;
|
let width = (regions.size.x - gutter * (columns - 1) as f64) / columns as f64;
|
||||||
|
|
||||||
|
|
@ -100,7 +87,7 @@ impl Layout for ColumnsNode {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Layout the children.
|
// Layout the children.
|
||||||
let mut frames = self.body.layout(vt, styles, pod)?.into_iter();
|
let mut frames = body.layout(vt, styles, pod)?.into_iter();
|
||||||
let mut finished = vec![];
|
let mut finished = vec![];
|
||||||
|
|
||||||
let dir = styles.get(TextNode::DIR);
|
let dir = styles.get(TextNode::DIR);
|
||||||
|
|
@ -140,7 +127,6 @@ impl Layout for ColumnsNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Column Break
|
|
||||||
/// A forced column break.
|
/// A forced column break.
|
||||||
///
|
///
|
||||||
/// The function will behave like a [page break]($func/pagebreak) when used in a
|
/// The function will behave like a [page break]($func/pagebreak) when used in a
|
||||||
|
|
@ -165,31 +151,20 @@ impl Layout for ColumnsNode {
|
||||||
/// laws of nature.
|
/// laws of nature.
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Column Break
|
||||||
/// - weak: `bool` (named)
|
/// Category: layout
|
||||||
/// If `{true}`, the column break is skipped if the current column is already
|
#[node(Behave)]
|
||||||
/// empty.
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// layout
|
|
||||||
#[func]
|
|
||||||
#[capable(Behave)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct ColbreakNode {
|
pub struct ColbreakNode {
|
||||||
|
/// If `{true}`, the column break is skipped if the current column is
|
||||||
|
/// already empty.
|
||||||
|
#[named]
|
||||||
|
#[default(false)]
|
||||||
pub weak: bool,
|
pub weak: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl ColbreakNode {
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
let weak = args.named("weak")?.unwrap_or(false);
|
|
||||||
Ok(Self { weak }.pack())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Behave for ColbreakNode {
|
impl Behave for ColbreakNode {
|
||||||
fn behaviour(&self) -> Behaviour {
|
fn behaviour(&self) -> Behaviour {
|
||||||
if self.weak {
|
if self.weak() {
|
||||||
Behaviour::Weak(1)
|
Behaviour::Weak(1)
|
||||||
} else {
|
} else {
|
||||||
Behaviour::Destructive
|
Behaviour::Destructive
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ use super::VNode;
|
||||||
use crate::layout::Spacing;
|
use crate::layout::Spacing;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// # Box
|
|
||||||
/// An inline-level container that sizes content.
|
/// An inline-level container that sizes content.
|
||||||
///
|
///
|
||||||
/// All elements except inline math, text, and boxes are block-level and cannot
|
/// All elements except inline math, text, and boxes are block-level and cannot
|
||||||
|
|
@ -20,69 +19,75 @@ use crate::prelude::*;
|
||||||
/// for more information.
|
/// for more information.
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Box
|
||||||
/// - body: `Content` (positional)
|
/// Category: layout
|
||||||
/// The contents of the box.
|
#[node(Layout)]
|
||||||
///
|
|
||||||
/// - width: `Sizing` (named)
|
|
||||||
/// The width of the box.
|
|
||||||
///
|
|
||||||
/// Boxes can have [fractional]($type/fraction) widths, as the example
|
|
||||||
/// below demonstrates.
|
|
||||||
///
|
|
||||||
/// _Note:_ Currently, only boxes and only their widths might be fractionally
|
|
||||||
/// sized within paragraphs. Support for fractionally sized images, shapes,
|
|
||||||
/// and more might be added in the future.
|
|
||||||
///
|
|
||||||
/// ```example
|
|
||||||
/// Line in #box(width: 1fr, line(length: 100%)) between.
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// - height: `Rel<Length>` (named)
|
|
||||||
/// The height of the box.
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// layout
|
|
||||||
#[func]
|
|
||||||
#[capable(Layout)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct BoxNode {
|
pub struct BoxNode {
|
||||||
/// The box's content.
|
/// The contents of the box.
|
||||||
|
#[positional]
|
||||||
|
#[default]
|
||||||
pub body: Content,
|
pub body: Content,
|
||||||
/// The box's width.
|
|
||||||
pub width: Sizing,
|
|
||||||
/// The box's height.
|
|
||||||
pub height: Smart<Rel<Length>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
/// The width of the box.
|
||||||
impl BoxNode {
|
///
|
||||||
|
/// Boxes can have [fractional]($type/fraction) widths, as the example
|
||||||
|
/// below demonstrates.
|
||||||
|
///
|
||||||
|
/// _Note:_ Currently, only boxes and only their widths might be fractionally
|
||||||
|
/// sized within paragraphs. Support for fractionally sized images, shapes,
|
||||||
|
/// and more might be added in the future.
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// Line in #box(width: 1fr, line(length: 100%)) between.
|
||||||
|
/// ```
|
||||||
|
#[named]
|
||||||
|
#[default]
|
||||||
|
pub width: Sizing,
|
||||||
|
|
||||||
|
/// The height of the box.
|
||||||
|
#[named]
|
||||||
|
#[default]
|
||||||
|
pub height: Smart<Rel<Length>>,
|
||||||
|
|
||||||
/// An amount to shift the box's baseline by.
|
/// An amount to shift the box's baseline by.
|
||||||
///
|
///
|
||||||
/// ```example
|
/// ```example
|
||||||
/// Image: #box(baseline: 40%, image("tiger.jpg", width: 2cm)).
|
/// Image: #box(baseline: 40%, image("tiger.jpg", width: 2cm)).
|
||||||
/// ```
|
/// ```
|
||||||
#[property(resolve)]
|
#[settable]
|
||||||
pub const BASELINE: Rel<Length> = Rel::zero();
|
#[resolve]
|
||||||
|
#[default]
|
||||||
|
pub baseline: Rel<Length>,
|
||||||
|
|
||||||
/// The box's background color. See the
|
/// The box's background color. See the
|
||||||
/// [rectangle's documentation]($func/rect.fill) for more details.
|
/// [rectangle's documentation]($func/rect.fill) for more details.
|
||||||
pub const FILL: Option<Paint> = None;
|
#[settable]
|
||||||
|
#[default]
|
||||||
|
pub fill: Option<Paint>,
|
||||||
|
|
||||||
/// The box's border color. See the
|
/// The box's border color. See the
|
||||||
/// [rectangle's documentation]($func/rect.stroke) for more details.
|
/// [rectangle's documentation]($func/rect.stroke) for more details.
|
||||||
#[property(resolve, fold)]
|
#[settable]
|
||||||
pub const STROKE: Sides<Option<Option<PartialStroke>>> = Sides::splat(None);
|
#[resolve]
|
||||||
|
#[fold]
|
||||||
|
#[default]
|
||||||
|
pub stroke: Sides<Option<Option<PartialStroke>>>,
|
||||||
|
|
||||||
/// How much to round the box's corners. See the [rectangle's
|
/// How much to round the box's corners. See the [rectangle's
|
||||||
/// documentation]($func/rect.radius) for more details.
|
/// documentation]($func/rect.radius) for more details.
|
||||||
#[property(resolve, fold)]
|
#[settable]
|
||||||
pub const RADIUS: Corners<Option<Rel<Length>>> = Corners::splat(Rel::zero());
|
#[resolve]
|
||||||
|
#[fold]
|
||||||
|
#[default]
|
||||||
|
pub radius: Corners<Option<Rel<Length>>>,
|
||||||
|
|
||||||
/// How much to pad the box's content. See the [rectangle's
|
/// How much to pad the box's content. See the [rectangle's
|
||||||
/// documentation]($func/rect.inset) for more details.
|
/// documentation]($func/rect.inset) for more details.
|
||||||
#[property(resolve, fold)]
|
#[settable]
|
||||||
pub const INSET: Sides<Option<Rel<Length>>> = Sides::splat(Rel::zero());
|
#[resolve]
|
||||||
|
#[fold]
|
||||||
|
#[default]
|
||||||
|
pub inset: Sides<Option<Rel<Length>>>,
|
||||||
|
|
||||||
/// How much to expand the box's size without affecting the layout.
|
/// How much to expand the box's size without affecting the layout.
|
||||||
///
|
///
|
||||||
|
|
@ -98,15 +103,11 @@ impl BoxNode {
|
||||||
/// outset: (y: 3pt),
|
/// outset: (y: 3pt),
|
||||||
/// radius: 2pt,
|
/// radius: 2pt,
|
||||||
/// )[rectangle].
|
/// )[rectangle].
|
||||||
#[property(resolve, fold)]
|
#[settable]
|
||||||
pub const OUTSET: Sides<Option<Rel<Length>>> = Sides::splat(Rel::zero());
|
#[resolve]
|
||||||
|
#[fold]
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
#[default]
|
||||||
let body = args.eat()?.unwrap_or_default();
|
pub outset: Sides<Option<Rel<Length>>>,
|
||||||
let width = args.named("width")?.unwrap_or_default();
|
|
||||||
let height = args.named("height")?.unwrap_or_default();
|
|
||||||
Ok(Self { body, width, height }.pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for BoxNode {
|
impl Layout for BoxNode {
|
||||||
|
|
@ -116,14 +117,14 @@ impl Layout for BoxNode {
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
let width = match self.width {
|
let width = match self.width() {
|
||||||
Sizing::Auto => Smart::Auto,
|
Sizing::Auto => Smart::Auto,
|
||||||
Sizing::Rel(rel) => Smart::Custom(rel),
|
Sizing::Rel(rel) => Smart::Custom(rel),
|
||||||
Sizing::Fr(_) => Smart::Custom(Ratio::one().into()),
|
Sizing::Fr(_) => Smart::Custom(Ratio::one().into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Resolve the sizing to a concrete size.
|
// Resolve the sizing to a concrete size.
|
||||||
let sizing = Axes::new(width, self.height);
|
let sizing = Axes::new(width, self.height());
|
||||||
let expand = sizing.as_ref().map(Smart::is_custom);
|
let expand = sizing.as_ref().map(Smart::is_custom);
|
||||||
let size = sizing
|
let size = sizing
|
||||||
.resolve(styles)
|
.resolve(styles)
|
||||||
|
|
@ -132,10 +133,10 @@ impl Layout for BoxNode {
|
||||||
.unwrap_or(regions.base());
|
.unwrap_or(regions.base());
|
||||||
|
|
||||||
// Apply inset.
|
// Apply inset.
|
||||||
let mut child = self.body.clone();
|
let mut child = self.body();
|
||||||
let inset = styles.get(Self::INSET);
|
let inset = styles.get(Self::INSET);
|
||||||
if inset.iter().any(|v| !v.is_zero()) {
|
if inset.iter().any(|v| !v.is_zero()) {
|
||||||
child = child.clone().padded(inset.map(|side| side.map(Length::from)));
|
child = child.padded(inset.map(|side| side.map(Length::from)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select the appropriate base and expansion for the child depending
|
// Select the appropriate base and expansion for the child depending
|
||||||
|
|
@ -169,7 +170,6 @@ impl Layout for BoxNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Block
|
|
||||||
/// A block-level container.
|
/// A block-level container.
|
||||||
///
|
///
|
||||||
/// Such a container can be used to separate content, size it and give it a
|
/// Such a container can be used to separate content, size it and give it a
|
||||||
|
|
@ -201,37 +201,6 @@ impl Layout for BoxNode {
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// ## Parameters
|
||||||
/// - body: `Content` (positional)
|
|
||||||
/// The contents of the block.
|
|
||||||
///
|
|
||||||
/// - width: `Smart<Rel<Length>>` (named)
|
|
||||||
/// The block's width.
|
|
||||||
///
|
|
||||||
/// ```example
|
|
||||||
/// #set align(center)
|
|
||||||
/// #block(
|
|
||||||
/// width: 60%,
|
|
||||||
/// inset: 8pt,
|
|
||||||
/// fill: silver,
|
|
||||||
/// lorem(10),
|
|
||||||
/// )
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// - height: `Smart<Rel<Length>>` (named)
|
|
||||||
/// The block's height. When the height is larger than the remaining space on
|
|
||||||
/// a page and [`breakable`]($func/block.breakable) is `{true}`, the block
|
|
||||||
/// will continue on the next page with the remaining height.
|
|
||||||
///
|
|
||||||
/// ```example
|
|
||||||
/// #set page(height: 80pt)
|
|
||||||
/// #set align(center)
|
|
||||||
/// #block(
|
|
||||||
/// width: 80%,
|
|
||||||
/// height: 150%,
|
|
||||||
/// fill: aqua,
|
|
||||||
/// )
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// - spacing: `Spacing` (named, settable)
|
/// - spacing: `Spacing` (named, settable)
|
||||||
/// The spacing around this block. This is shorthand to set `above` and
|
/// The spacing around this block. This is shorthand to set `above` and
|
||||||
/// `below` to the same value.
|
/// `below` to the same value.
|
||||||
|
|
@ -245,35 +214,62 @@ impl Layout for BoxNode {
|
||||||
/// A second paragraph.
|
/// A second paragraph.
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// - above: `Spacing` (named, settable)
|
/// Display: Block
|
||||||
/// The spacing between this block and its predecessor. Takes precedence over
|
/// Category: layout
|
||||||
/// `spacing`. Can be used in combination with a show rule to adjust the
|
#[node(Layout)]
|
||||||
/// spacing around arbitrary block-level elements.
|
#[set({
|
||||||
///
|
let spacing = args.named("spacing")?;
|
||||||
/// The default value is `{1.2em}`.
|
styles.set_opt(
|
||||||
///
|
Self::ABOVE,
|
||||||
/// - below: `Spacing` (named, settable)
|
args.named("above")?
|
||||||
/// The spacing between this block and its successor. Takes precedence
|
.map(VNode::block_around)
|
||||||
/// over `spacing`.
|
.or_else(|| spacing.map(VNode::block_spacing)),
|
||||||
///
|
);
|
||||||
/// The default value is `{1.2em}`.
|
styles.set_opt(
|
||||||
///
|
Self::BELOW,
|
||||||
/// ## Category
|
args.named("below")?
|
||||||
/// layout
|
.map(VNode::block_around)
|
||||||
#[func]
|
.or_else(|| spacing.map(VNode::block_spacing)),
|
||||||
#[capable(Layout)]
|
);
|
||||||
#[derive(Debug, Hash)]
|
})]
|
||||||
pub struct BlockNode {
|
pub struct BlockNode {
|
||||||
/// The block's content.
|
/// The contents of the block.
|
||||||
|
#[positional]
|
||||||
|
#[default]
|
||||||
pub body: Content,
|
pub body: Content,
|
||||||
/// The box's width.
|
|
||||||
pub width: Smart<Rel<Length>>,
|
|
||||||
/// The box's height.
|
|
||||||
pub height: Smart<Rel<Length>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
/// The block's width.
|
||||||
impl BlockNode {
|
///
|
||||||
|
/// ```example
|
||||||
|
/// #set align(center)
|
||||||
|
/// #block(
|
||||||
|
/// width: 60%,
|
||||||
|
/// inset: 8pt,
|
||||||
|
/// fill: silver,
|
||||||
|
/// lorem(10),
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
|
#[named]
|
||||||
|
#[default]
|
||||||
|
pub width: Smart<Rel<Length>>,
|
||||||
|
|
||||||
|
/// The block's height. When the height is larger than the remaining space on
|
||||||
|
/// a page and [`breakable`]($func/block.breakable) is `{true}`, the block
|
||||||
|
/// will continue on the next page with the remaining height.
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// #set page(height: 80pt)
|
||||||
|
/// #set align(center)
|
||||||
|
/// #block(
|
||||||
|
/// width: 80%,
|
||||||
|
/// height: 150%,
|
||||||
|
/// fill: aqua,
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
|
#[named]
|
||||||
|
#[default]
|
||||||
|
pub height: Smart<Rel<Length>>,
|
||||||
|
|
||||||
/// Whether the block can be broken and continue on the next page.
|
/// Whether the block can be broken and continue on the next page.
|
||||||
///
|
///
|
||||||
/// Defaults to `{true}`.
|
/// Defaults to `{true}`.
|
||||||
|
|
@ -286,64 +282,74 @@ impl BlockNode {
|
||||||
/// lorem(15),
|
/// lorem(15),
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
pub const BREAKABLE: bool = true;
|
#[settable]
|
||||||
|
#[default(true)]
|
||||||
|
pub breakable: bool,
|
||||||
|
|
||||||
/// The block's background color. See the
|
/// The block's background color. See the
|
||||||
/// [rectangle's documentation]($func/rect.fill) for more details.
|
/// [rectangle's documentation]($func/rect.fill) for more details.
|
||||||
pub const FILL: Option<Paint> = None;
|
#[settable]
|
||||||
|
#[default]
|
||||||
|
pub fill: Option<Paint>,
|
||||||
|
|
||||||
/// The block's border color. See the
|
/// The block's border color. See the
|
||||||
/// [rectangle's documentation]($func/rect.stroke) for more details.
|
/// [rectangle's documentation]($func/rect.stroke) for more details.
|
||||||
#[property(resolve, fold)]
|
#[settable]
|
||||||
pub const STROKE: Sides<Option<Option<PartialStroke>>> = Sides::splat(None);
|
#[resolve]
|
||||||
|
#[fold]
|
||||||
|
#[default]
|
||||||
|
pub stroke: Sides<Option<Option<PartialStroke>>>,
|
||||||
|
|
||||||
/// How much to round the block's corners. See the [rectangle's
|
/// How much to round the block's corners. See the [rectangle's
|
||||||
/// documentation]($func/rect.radius) for more details.
|
/// documentation]($func/rect.radius) for more details.
|
||||||
#[property(resolve, fold)]
|
#[settable]
|
||||||
pub const RADIUS: Corners<Option<Rel<Length>>> = Corners::splat(Rel::zero());
|
#[resolve]
|
||||||
|
#[fold]
|
||||||
|
#[default]
|
||||||
|
pub radius: Corners<Option<Rel<Length>>>,
|
||||||
|
|
||||||
/// How much to pad the block's content. See the [rectangle's
|
/// How much to pad the block's content. See the [rectangle's
|
||||||
/// documentation]($func/rect.inset) for more details.
|
/// documentation]($func/rect.inset) for more details.
|
||||||
#[property(resolve, fold)]
|
#[settable]
|
||||||
pub const INSET: Sides<Option<Rel<Length>>> = Sides::splat(Rel::zero());
|
#[resolve]
|
||||||
|
#[fold]
|
||||||
|
#[default]
|
||||||
|
pub inset: Sides<Option<Rel<Length>>>,
|
||||||
|
|
||||||
/// How much to expand the block's size without affecting the layout. See
|
/// How much to expand the block's size without affecting the layout. See
|
||||||
/// the [rectangle's documentation]($func/rect.outset) for more details.
|
/// the [rectangle's documentation]($func/rect.outset) for more details.
|
||||||
#[property(resolve, fold)]
|
#[settable]
|
||||||
pub const OUTSET: Sides<Option<Rel<Length>>> = Sides::splat(Rel::zero());
|
#[resolve]
|
||||||
|
#[fold]
|
||||||
|
#[default]
|
||||||
|
pub outset: Sides<Option<Rel<Length>>>,
|
||||||
|
|
||||||
/// The spacing between the previous and this block.
|
/// The spacing between this block and its predecessor. Takes precedence over
|
||||||
#[property(skip)]
|
/// `spacing`. Can be used in combination with a show rule to adjust the
|
||||||
pub const ABOVE: VNode = VNode::block_spacing(Em::new(1.2).into());
|
/// spacing around arbitrary block-level elements.
|
||||||
|
///
|
||||||
|
/// The default value is `{1.2em}`.
|
||||||
|
#[settable]
|
||||||
|
#[skip]
|
||||||
|
#[default(VNode::block_spacing(Em::new(1.2).into()))]
|
||||||
|
pub above: VNode,
|
||||||
|
|
||||||
/// The spacing between this and the following block.
|
/// The spacing between this block and its successor. Takes precedence
|
||||||
#[property(skip)]
|
/// over `spacing`.
|
||||||
pub const BELOW: VNode = VNode::block_spacing(Em::new(1.2).into());
|
///
|
||||||
|
/// The default value is `{1.2em}`.
|
||||||
|
#[settable]
|
||||||
|
#[skip]
|
||||||
|
#[default(VNode::block_spacing(Em::new(1.2).into()))]
|
||||||
|
pub below: VNode,
|
||||||
|
|
||||||
/// Whether this block must stick to the following one.
|
/// Whether this block must stick to the following one.
|
||||||
///
|
///
|
||||||
/// Use this to prevent page breaks between e.g. a heading and its body.
|
/// Use this to prevent page breaks between e.g. a heading and its body.
|
||||||
#[property(skip)]
|
#[settable]
|
||||||
pub const STICKY: bool = false;
|
#[skip]
|
||||||
|
#[default(false)]
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
pub sticky: bool,
|
||||||
let body = args.eat()?.unwrap_or_default();
|
|
||||||
let width = args.named("width")?.unwrap_or_default();
|
|
||||||
let height = args.named("height")?.unwrap_or_default();
|
|
||||||
Ok(Self { body, width, height }.pack())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set(...) {
|
|
||||||
let spacing = args.named("spacing")?.map(VNode::block_spacing);
|
|
||||||
styles.set_opt(
|
|
||||||
Self::ABOVE,
|
|
||||||
args.named("above")?.map(VNode::block_around).or(spacing),
|
|
||||||
);
|
|
||||||
styles.set_opt(
|
|
||||||
Self::BELOW,
|
|
||||||
args.named("below")?.map(VNode::block_around).or(spacing),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for BlockNode {
|
impl Layout for BlockNode {
|
||||||
|
|
@ -354,14 +360,14 @@ impl Layout for BlockNode {
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
// Apply inset.
|
// Apply inset.
|
||||||
let mut child = self.body.clone();
|
let mut child = self.body();
|
||||||
let inset = styles.get(Self::INSET);
|
let inset = styles.get(Self::INSET);
|
||||||
if inset.iter().any(|v| !v.is_zero()) {
|
if inset.iter().any(|v| !v.is_zero()) {
|
||||||
child = child.clone().padded(inset.map(|side| side.map(Length::from)));
|
child = child.clone().padded(inset.map(|side| side.map(Length::from)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve the sizing to a concrete size.
|
// Resolve the sizing to a concrete size.
|
||||||
let sizing = Axes::new(self.width, self.height);
|
let sizing = Axes::new(self.width(), self.height());
|
||||||
let mut expand = sizing.as_ref().map(Smart::is_custom);
|
let mut expand = sizing.as_ref().map(Smart::is_custom);
|
||||||
let mut size = sizing
|
let mut size = sizing
|
||||||
.resolve(styles)
|
.resolve(styles)
|
||||||
|
|
@ -372,7 +378,7 @@ impl Layout for BlockNode {
|
||||||
// Layout the child.
|
// Layout the child.
|
||||||
let mut frames = if styles.get(Self::BREAKABLE) {
|
let mut frames = if styles.get(Self::BREAKABLE) {
|
||||||
// Measure to ensure frames for all regions have the same width.
|
// Measure to ensure frames for all regions have the same width.
|
||||||
if self.width == Smart::Auto {
|
if sizing.x == Smart::Auto {
|
||||||
let pod = Regions::one(size, Axes::splat(false));
|
let pod = Regions::one(size, Axes::splat(false));
|
||||||
let frame = child.layout(vt, styles, pod)?.into_frame();
|
let frame = child.layout(vt, styles, pod)?.into_frame();
|
||||||
size.x = frame.width();
|
size.x = frame.width();
|
||||||
|
|
@ -385,7 +391,7 @@ impl Layout for BlockNode {
|
||||||
|
|
||||||
// Generate backlog for fixed height.
|
// Generate backlog for fixed height.
|
||||||
let mut heights = vec![];
|
let mut heights = vec![];
|
||||||
if self.height.is_custom() {
|
if sizing.y.is_custom() {
|
||||||
let mut remaining = size.y;
|
let mut remaining = size.y;
|
||||||
for region in regions.iter() {
|
for region in regions.iter() {
|
||||||
let limited = region.y.min(remaining);
|
let limited = region.y.min(remaining);
|
||||||
|
|
@ -454,18 +460,6 @@ impl Sizing {
|
||||||
pub fn is_fractional(self) -> bool {
|
pub fn is_fractional(self) -> bool {
|
||||||
matches!(self, Self::Fr(_))
|
matches!(self, Self::Fr(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn encode(self) -> Value {
|
|
||||||
match self {
|
|
||||||
Self::Auto => Value::Auto,
|
|
||||||
Self::Rel(rel) => Spacing::Rel(rel).encode(),
|
|
||||||
Self::Fr(fr) => Spacing::Fr(fr).encode(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn encode_slice(vec: &[Sizing]) -> Value {
|
|
||||||
Value::Array(vec.iter().copied().map(Self::encode).collect())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Sizing {
|
impl Default for Sizing {
|
||||||
|
|
@ -474,11 +468,26 @@ impl Default for Sizing {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Spacing> for Sizing {
|
impl<T: Into<Spacing>> From<T> for Sizing {
|
||||||
fn from(spacing: Spacing) -> Self {
|
fn from(spacing: T) -> Self {
|
||||||
match spacing {
|
match spacing.into() {
|
||||||
Spacing::Rel(rel) => Self::Rel(rel),
|
Spacing::Rel(rel) => Self::Rel(rel),
|
||||||
Spacing::Fr(fr) => Self::Fr(fr),
|
Spacing::Fr(fr) => Self::Fr(fr),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
Sizing,
|
||||||
|
_: Smart<Never> => Self::Auto,
|
||||||
|
v: Rel<Length> => Self::Rel(v),
|
||||||
|
v: Fr => Self::Fr(v),
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: Sizing => match v {
|
||||||
|
Sizing::Auto => Value::Auto,
|
||||||
|
Sizing::Rel(rel) => Value::Relative(rel),
|
||||||
|
Sizing::Fr(fr) => Value::Fraction(fr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use crate::layout::{BlockNode, GridNode, ParNode, Sizing, Spacing};
|
use crate::layout::{BlockNode, ParNode, Sizing, Spacing};
|
||||||
use crate::meta::{Numbering, NumberingPattern};
|
use crate::meta::{Numbering, NumberingPattern};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::text::TextNode;
|
use crate::text::TextNode;
|
||||||
|
|
||||||
/// # Numbered List
|
use super::GridLayouter;
|
||||||
|
|
||||||
/// A numbered list.
|
/// A numbered list.
|
||||||
///
|
///
|
||||||
/// Displays a sequence of items vertically and numbers them consecutively.
|
/// Displays a sequence of items vertically and numbers them consecutively.
|
||||||
|
|
@ -89,20 +90,19 @@ use crate::text::TextNode;
|
||||||
/// items.
|
/// items.
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Numbered List
|
||||||
/// layout
|
/// Category: layout
|
||||||
#[func]
|
#[node(Construct, Layout)]
|
||||||
#[capable(Layout)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct EnumNode {
|
pub struct EnumNode {
|
||||||
/// If true, the items are separated by leading instead of list spacing.
|
/// The numbered list's items.
|
||||||
pub tight: bool,
|
#[variadic]
|
||||||
/// The individual numbered items.
|
pub items: Vec<EnumItem>,
|
||||||
pub items: StyleVec<(Option<NonZeroUsize>, Content)>,
|
|
||||||
}
|
/// If true, the items are separated by leading instead of list spacing.
|
||||||
|
#[named]
|
||||||
|
#[default(true)]
|
||||||
|
pub tight: bool,
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl EnumNode {
|
|
||||||
/// How to number the enumeration. Accepts a
|
/// How to number the enumeration. Accepts a
|
||||||
/// [numbering pattern or function]($func/numbering).
|
/// [numbering pattern or function]($func/numbering).
|
||||||
///
|
///
|
||||||
|
|
@ -122,9 +122,9 @@ impl EnumNode {
|
||||||
/// + Superscript
|
/// + Superscript
|
||||||
/// + Numbering!
|
/// + Numbering!
|
||||||
/// ```
|
/// ```
|
||||||
#[property(referenced)]
|
#[settable]
|
||||||
pub const NUMBERING: Numbering =
|
#[default(Numbering::Pattern(NumberingPattern::from_str("1.").unwrap()))]
|
||||||
Numbering::Pattern(NumberingPattern::from_str("1.").unwrap());
|
pub numbering: Numbering,
|
||||||
|
|
||||||
/// Whether to display the full numbering, including the numbers of
|
/// Whether to display the full numbering, including the numbers of
|
||||||
/// all parent enumerations.
|
/// all parent enumerations.
|
||||||
|
|
@ -138,63 +138,51 @@ impl EnumNode {
|
||||||
/// + Add integredients
|
/// + Add integredients
|
||||||
/// + Eat
|
/// + Eat
|
||||||
/// ```
|
/// ```
|
||||||
pub const FULL: bool = false;
|
#[settable]
|
||||||
|
#[default(false)]
|
||||||
|
pub full: bool,
|
||||||
|
|
||||||
/// The indentation of each item's label.
|
/// The indentation of each item's label.
|
||||||
#[property(resolve)]
|
#[settable]
|
||||||
pub const INDENT: Length = Length::zero();
|
#[resolve]
|
||||||
|
#[default]
|
||||||
|
pub indent: Length,
|
||||||
|
|
||||||
/// The space between the numbering and the body of each item.
|
/// The space between the numbering and the body of each item.
|
||||||
#[property(resolve)]
|
#[settable]
|
||||||
pub const BODY_INDENT: Length = Em::new(0.5).into();
|
#[resolve]
|
||||||
|
#[default(Em::new(0.5).into())]
|
||||||
|
pub body_indent: Length,
|
||||||
|
|
||||||
/// The spacing between the items of a wide (non-tight) enumeration.
|
/// The spacing between the items of a wide (non-tight) enumeration.
|
||||||
///
|
///
|
||||||
/// If set to `{auto}`, uses the spacing [below blocks]($func/block.below).
|
/// If set to `{auto}`, uses the spacing [below blocks]($func/block.below).
|
||||||
pub const SPACING: Smart<Spacing> = Smart::Auto;
|
#[settable]
|
||||||
|
#[default]
|
||||||
|
pub spacing: Smart<Spacing>,
|
||||||
|
|
||||||
/// The numbers of parent items.
|
/// The numbers of parent items.
|
||||||
#[property(skip, fold)]
|
#[settable]
|
||||||
const PARENTS: Parent = vec![];
|
#[fold]
|
||||||
|
#[skip]
|
||||||
|
#[default]
|
||||||
|
parents: Parent,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Construct for EnumNode {
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
let mut number: NonZeroUsize =
|
let mut items = args.all::<EnumItem>()?;
|
||||||
args.named("start")?.unwrap_or(NonZeroUsize::new(1).unwrap());
|
if let Some(number) = args.named::<NonZeroUsize>("start")? {
|
||||||
|
if let Some(first) = items.first_mut() {
|
||||||
Ok(Self {
|
if first.number().is_none() {
|
||||||
tight: args.named("tight")?.unwrap_or(true),
|
*first = EnumItem::new(first.body()).with_number(Some(number));
|
||||||
items: args
|
}
|
||||||
.all()?
|
}
|
||||||
.into_iter()
|
|
||||||
.map(|body| {
|
|
||||||
let item = (Some(number), body);
|
|
||||||
number = number.saturating_add(1);
|
|
||||||
item
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
}
|
}
|
||||||
.pack())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field(&self, name: &str) -> Option<Value> {
|
Ok(Self::new(items)
|
||||||
match name {
|
.with_tight(args.named("tight")?.unwrap_or(true))
|
||||||
"tight" => Some(Value::Bool(self.tight)),
|
.pack())
|
||||||
"items" => Some(Value::Array(
|
|
||||||
self.items
|
|
||||||
.items()
|
|
||||||
.map(|(number, body)| {
|
|
||||||
Value::Dict(dict! {
|
|
||||||
"number" => match *number {
|
|
||||||
Some(n) => Value::Int(n.get() as i64),
|
|
||||||
None => Value::None,
|
|
||||||
},
|
|
||||||
"body" => Value::Content(body.clone()),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
)),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -208,12 +196,12 @@ impl Layout for EnumNode {
|
||||||
let numbering = styles.get(Self::NUMBERING);
|
let numbering = styles.get(Self::NUMBERING);
|
||||||
let indent = styles.get(Self::INDENT);
|
let indent = styles.get(Self::INDENT);
|
||||||
let body_indent = styles.get(Self::BODY_INDENT);
|
let body_indent = styles.get(Self::BODY_INDENT);
|
||||||
let gutter = if self.tight {
|
let gutter = if self.tight() {
|
||||||
styles.get(ParNode::LEADING).into()
|
styles.get(ParNode::LEADING).into()
|
||||||
} else {
|
} else {
|
||||||
styles
|
styles
|
||||||
.get(Self::SPACING)
|
.get(Self::SPACING)
|
||||||
.unwrap_or_else(|| styles.get(BlockNode::BELOW).amount)
|
.unwrap_or_else(|| styles.get(BlockNode::BELOW).amount())
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut cells = vec![];
|
let mut cells = vec![];
|
||||||
|
|
@ -221,8 +209,8 @@ impl Layout for EnumNode {
|
||||||
let mut parents = styles.get(Self::PARENTS);
|
let mut parents = styles.get(Self::PARENTS);
|
||||||
let full = styles.get(Self::FULL);
|
let full = styles.get(Self::FULL);
|
||||||
|
|
||||||
for ((n, item), map) in self.items.iter() {
|
for item in self.items() {
|
||||||
number = n.unwrap_or(number);
|
number = item.number().unwrap_or(number);
|
||||||
|
|
||||||
let resolved = if full {
|
let resolved = if full {
|
||||||
parents.push(number);
|
parents.push(number);
|
||||||
|
|
@ -230,7 +218,7 @@ impl Layout for EnumNode {
|
||||||
parents.pop();
|
parents.pop();
|
||||||
content
|
content
|
||||||
} else {
|
} else {
|
||||||
match numbering {
|
match &numbering {
|
||||||
Numbering::Pattern(pattern) => {
|
Numbering::Pattern(pattern) => {
|
||||||
TextNode::packed(pattern.apply_kth(parents.len(), number))
|
TextNode::packed(pattern.apply_kth(parents.len(), number))
|
||||||
}
|
}
|
||||||
|
|
@ -239,33 +227,68 @@ impl Layout for EnumNode {
|
||||||
};
|
};
|
||||||
|
|
||||||
cells.push(Content::empty());
|
cells.push(Content::empty());
|
||||||
cells.push(resolved.styled_with_map(map.clone()));
|
cells.push(resolved);
|
||||||
cells.push(Content::empty());
|
cells.push(Content::empty());
|
||||||
cells.push(
|
cells.push(item.body().styled(Self::PARENTS, Parent(number)));
|
||||||
item.clone()
|
|
||||||
.styled_with_map(map.clone())
|
|
||||||
.styled(Self::PARENTS, Parent(number)),
|
|
||||||
);
|
|
||||||
number = number.saturating_add(1);
|
number = number.saturating_add(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
GridNode {
|
let layouter = GridLayouter::new(
|
||||||
tracks: Axes::with_x(vec![
|
vt,
|
||||||
|
Axes::with_x(&[
|
||||||
Sizing::Rel(indent.into()),
|
Sizing::Rel(indent.into()),
|
||||||
Sizing::Auto,
|
Sizing::Auto,
|
||||||
Sizing::Rel(body_indent.into()),
|
Sizing::Rel(body_indent.into()),
|
||||||
Sizing::Auto,
|
Sizing::Auto,
|
||||||
]),
|
]),
|
||||||
gutter: Axes::with_y(vec![gutter.into()]),
|
Axes::with_y(&[gutter.into()]),
|
||||||
cells,
|
&cells,
|
||||||
}
|
regions,
|
||||||
.layout(vt, styles, regions)
|
styles,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(layouter.layout()?.fragment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Hash)]
|
/// An enumeration item.
|
||||||
|
#[node]
|
||||||
|
pub struct EnumItem {
|
||||||
|
/// The item's number.
|
||||||
|
#[positional]
|
||||||
|
#[default]
|
||||||
|
pub number: Option<NonZeroUsize>,
|
||||||
|
|
||||||
|
/// The item's body.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
|
pub body: Content,
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
EnumItem,
|
||||||
|
array: Array => {
|
||||||
|
let mut iter = array.into_iter();
|
||||||
|
let (number, body) = match (iter.next(), iter.next(), iter.next()) {
|
||||||
|
(Some(a), Some(b), None) => (a.cast()?, b.cast()?),
|
||||||
|
_ => Err("array must contain exactly two entries")?,
|
||||||
|
};
|
||||||
|
Self::new(body).with_number(number)
|
||||||
|
},
|
||||||
|
v: Content => v.to::<Self>().cloned().unwrap_or_else(|| Self::new(v.clone())),
|
||||||
|
}
|
||||||
|
|
||||||
struct Parent(NonZeroUsize);
|
struct Parent(NonZeroUsize);
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
Parent,
|
||||||
|
v: NonZeroUsize => Self(v),
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: Parent => v.0.into()
|
||||||
|
}
|
||||||
|
|
||||||
impl Fold for Parent {
|
impl Fold for Parent {
|
||||||
type Output = Vec<NonZeroUsize>;
|
type Output = Vec<NonZeroUsize>;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use typst::model::Style;
|
use typst::model::{Style, StyledNode};
|
||||||
|
|
||||||
use super::{AlignNode, BlockNode, ColbreakNode, ParNode, PlaceNode, Spacing, VNode};
|
use super::{AlignNode, BlockNode, ColbreakNode, ParNode, PlaceNode, Spacing, VNode};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
@ -8,12 +8,12 @@ use crate::visualize::{CircleNode, EllipseNode, ImageNode, RectNode, SquareNode}
|
||||||
///
|
///
|
||||||
/// This node is responsible for layouting both the top-level content flow and
|
/// This node is responsible for layouting both the top-level content flow and
|
||||||
/// the contents of boxes.
|
/// the contents of boxes.
|
||||||
#[capable(Layout)]
|
#[node(Layout)]
|
||||||
#[derive(Hash)]
|
pub struct FlowNode {
|
||||||
pub struct FlowNode(pub StyleVec<Content>);
|
/// The children that will be arranges into a flow.
|
||||||
|
#[variadic]
|
||||||
#[node]
|
pub children: Vec<Content>,
|
||||||
impl FlowNode {}
|
}
|
||||||
|
|
||||||
impl Layout for FlowNode {
|
impl Layout for FlowNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
|
|
@ -24,9 +24,17 @@ impl Layout for FlowNode {
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
let mut layouter = FlowLayouter::new(regions);
|
let mut layouter = FlowLayouter::new(regions);
|
||||||
|
|
||||||
for (child, map) in self.0.iter() {
|
for mut child in self.children() {
|
||||||
let styles = styles.chain(&map);
|
let map;
|
||||||
if let Some(&node) = child.to::<VNode>() {
|
let outer = styles;
|
||||||
|
let mut styles = outer;
|
||||||
|
if let Some(node) = child.to::<StyledNode>() {
|
||||||
|
map = node.map();
|
||||||
|
styles = outer.chain(&map);
|
||||||
|
child = node.sub();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(node) = child.to::<VNode>() {
|
||||||
layouter.layout_spacing(node, styles);
|
layouter.layout_spacing(node, styles);
|
||||||
} else if let Some(node) = child.to::<ParNode>() {
|
} else if let Some(node) = child.to::<ParNode>() {
|
||||||
let barrier = Style::Barrier(child.id());
|
let barrier = Style::Barrier(child.id());
|
||||||
|
|
@ -40,16 +48,16 @@ impl Layout for FlowNode {
|
||||||
{
|
{
|
||||||
let barrier = Style::Barrier(child.id());
|
let barrier = Style::Barrier(child.id());
|
||||||
let styles = styles.chain_one(&barrier);
|
let styles = styles.chain_one(&barrier);
|
||||||
layouter.layout_single(vt, child, styles)?;
|
layouter.layout_single(vt, &child, styles)?;
|
||||||
} else if child.has::<dyn Layout>() {
|
} else if child.has::<dyn Layout>() {
|
||||||
layouter.layout_multiple(vt, child, styles)?;
|
layouter.layout_multiple(vt, &child, styles)?;
|
||||||
} else if child.is::<ColbreakNode>() {
|
} else if child.is::<ColbreakNode>() {
|
||||||
if !layouter.regions.backlog.is_empty() || layouter.regions.last.is_some()
|
if !layouter.regions.backlog.is_empty() || layouter.regions.last.is_some()
|
||||||
{
|
{
|
||||||
layouter.finish_region();
|
layouter.finish_region();
|
||||||
}
|
}
|
||||||
} else {
|
} else if let Some(span) = child.span() {
|
||||||
panic!("unexpected flow child: {child:?}");
|
bail!(span, "unexpected flow child");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,13 +65,6 @@ impl Layout for FlowNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for FlowNode {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
f.write_str("Flow ")?;
|
|
||||||
self.0.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Performs flow layout.
|
/// Performs flow layout.
|
||||||
struct FlowLayouter<'a> {
|
struct FlowLayouter<'a> {
|
||||||
/// The regions to layout children into.
|
/// The regions to layout children into.
|
||||||
|
|
@ -113,11 +114,11 @@ impl<'a> FlowLayouter<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout vertical spacing.
|
/// Layout vertical spacing.
|
||||||
fn layout_spacing(&mut self, node: VNode, styles: StyleChain) {
|
fn layout_spacing(&mut self, node: &VNode, styles: StyleChain) {
|
||||||
self.layout_item(match node.amount {
|
self.layout_item(match node.amount() {
|
||||||
Spacing::Rel(v) => FlowItem::Absolute(
|
Spacing::Rel(v) => FlowItem::Absolute(
|
||||||
v.resolve(styles).relative_to(self.initial.y),
|
v.resolve(styles).relative_to(self.initial.y),
|
||||||
node.weakness > 0,
|
node.weakness() > 0,
|
||||||
),
|
),
|
||||||
Spacing::Fr(v) => FlowItem::Fractional(v),
|
Spacing::Fr(v) => FlowItem::Fractional(v),
|
||||||
});
|
});
|
||||||
|
|
@ -130,7 +131,7 @@ impl<'a> FlowLayouter<'a> {
|
||||||
par: &ParNode,
|
par: &ParNode,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
let aligns = styles.get(AlignNode::ALIGNS).resolve(styles);
|
let aligns = styles.get(AlignNode::ALIGNMENT).resolve(styles);
|
||||||
let leading = styles.get(ParNode::LEADING);
|
let leading = styles.get(ParNode::LEADING);
|
||||||
let consecutive = self.last_was_par;
|
let consecutive = self.last_was_par;
|
||||||
let frames = par
|
let frames = par
|
||||||
|
|
@ -176,7 +177,7 @@ impl<'a> FlowLayouter<'a> {
|
||||||
content: &Content,
|
content: &Content,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
let aligns = styles.get(AlignNode::ALIGNS).resolve(styles);
|
let aligns = styles.get(AlignNode::ALIGNMENT).resolve(styles);
|
||||||
let sticky = styles.get(BlockNode::STICKY);
|
let sticky = styles.get(BlockNode::STICKY);
|
||||||
let pod = Regions::one(self.regions.base(), Axes::splat(false));
|
let pod = Regions::one(self.regions.base(), Axes::splat(false));
|
||||||
let layoutable = content.with::<dyn Layout>().unwrap();
|
let layoutable = content.with::<dyn Layout>().unwrap();
|
||||||
|
|
@ -204,7 +205,7 @@ impl<'a> FlowLayouter<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// How to align the block.
|
// How to align the block.
|
||||||
let aligns = styles.get(AlignNode::ALIGNS).resolve(styles);
|
let aligns = styles.get(AlignNode::ALIGNMENT).resolve(styles);
|
||||||
|
|
||||||
// Layout the block itself.
|
// Layout the block itself.
|
||||||
let sticky = styles.get(BlockNode::STICKY);
|
let sticky = styles.get(BlockNode::STICKY);
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ use crate::text::TextNode;
|
||||||
|
|
||||||
use super::Sizing;
|
use super::Sizing;
|
||||||
|
|
||||||
/// # Grid
|
|
||||||
/// Arrange content in a grid.
|
/// Arrange content in a grid.
|
||||||
///
|
///
|
||||||
/// The grid element allows you to arrange content in a grid. You can define the
|
/// The grid element allows you to arrange content in a grid. You can define the
|
||||||
|
|
@ -61,64 +60,50 @@ use super::Sizing;
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// ## Parameters
|
||||||
/// - cells: `Content` (positional, variadic) The contents of the table cells.
|
/// - gutter: `TrackSizings` (named)
|
||||||
///
|
/// Defines the gaps between rows & columns.
|
||||||
/// The cells are populated in row-major order.
|
|
||||||
///
|
|
||||||
/// - rows: `TrackSizings` (named) Defines the row sizes.
|
|
||||||
///
|
|
||||||
/// If there are more cells than fit the defined rows, the last row is
|
|
||||||
/// repeated until there are no more cells.
|
|
||||||
///
|
|
||||||
/// - columns: `TrackSizings` (named) Defines the column sizes.
|
|
||||||
///
|
|
||||||
/// Either specify a track size array or provide an integer to create a grid
|
|
||||||
/// with that many `{auto}`-sized columns. Note that opposed to rows and
|
|
||||||
/// gutters, providing a single track size will only ever create a single
|
|
||||||
/// column.
|
|
||||||
///
|
|
||||||
/// - gutter: `TrackSizings` (named) Defines the gaps between rows & columns.
|
|
||||||
///
|
///
|
||||||
/// If there are more gutters than defined sizes, the last gutter is repeated.
|
/// If there are more gutters than defined sizes, the last gutter is repeated.
|
||||||
///
|
///
|
||||||
/// - column-gutter: `TrackSizings` (named) Defines the gaps between columns.
|
/// Display: Grid
|
||||||
/// Takes precedence over `gutter`.
|
/// Category: layout
|
||||||
///
|
#[node(Layout)]
|
||||||
/// - row-gutter: `TrackSizings` (named) Defines the gaps between rows. Takes
|
|
||||||
/// precedence over `gutter`.
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// layout
|
|
||||||
#[func]
|
|
||||||
#[capable(Layout)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct GridNode {
|
pub struct GridNode {
|
||||||
/// Defines sizing for content rows and columns.
|
/// The contents of the table cells.
|
||||||
pub tracks: Axes<Vec<Sizing>>,
|
///
|
||||||
/// Defines sizing of gutter rows and columns between content.
|
/// The cells are populated in row-major order.
|
||||||
pub gutter: Axes<Vec<Sizing>>,
|
#[variadic]
|
||||||
/// The content to be arranged in a grid.
|
|
||||||
pub cells: Vec<Content>,
|
pub cells: Vec<Content>,
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
/// Defines the column sizes.
|
||||||
impl GridNode {
|
///
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
/// Either specify a track size array or provide an integer to create a grid
|
||||||
let TrackSizings(columns) = args.named("columns")?.unwrap_or_default();
|
/// with that many `{auto}`-sized columns. Note that opposed to rows and
|
||||||
let TrackSizings(rows) = args.named("rows")?.unwrap_or_default();
|
/// gutters, providing a single track size will only ever create a single
|
||||||
let TrackSizings(base_gutter) = args.named("gutter")?.unwrap_or_default();
|
/// column.
|
||||||
let column_gutter = args.named("column-gutter")?.map(|TrackSizings(v)| v);
|
#[named]
|
||||||
let row_gutter = args.named("row-gutter")?.map(|TrackSizings(v)| v);
|
#[default]
|
||||||
Ok(Self {
|
pub columns: TrackSizings,
|
||||||
tracks: Axes::new(columns, rows),
|
|
||||||
gutter: Axes::new(
|
/// Defines the row sizes.
|
||||||
column_gutter.unwrap_or_else(|| base_gutter.clone()),
|
///
|
||||||
row_gutter.unwrap_or(base_gutter),
|
/// If there are more cells than fit the defined rows, the last row is
|
||||||
),
|
/// repeated until there are no more cells.
|
||||||
cells: args.all()?,
|
#[named]
|
||||||
}
|
#[default]
|
||||||
.pack())
|
pub rows: TrackSizings,
|
||||||
}
|
|
||||||
|
/// Defines the gaps between columns. Takes precedence over `gutter`.
|
||||||
|
#[named]
|
||||||
|
#[shorthand(gutter)]
|
||||||
|
#[default]
|
||||||
|
pub column_gutter: TrackSizings,
|
||||||
|
|
||||||
|
/// Defines the gaps between rows. Takes precedence over `gutter`.
|
||||||
|
#[named]
|
||||||
|
#[shorthand(gutter)]
|
||||||
|
#[default]
|
||||||
|
pub row_gutter: TrackSizings,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for GridNode {
|
impl Layout for GridNode {
|
||||||
|
|
@ -129,11 +114,12 @@ impl Layout for GridNode {
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
// Prepare grid layout by unifying content and gutter tracks.
|
// Prepare grid layout by unifying content and gutter tracks.
|
||||||
|
let cells = self.cells();
|
||||||
let layouter = GridLayouter::new(
|
let layouter = GridLayouter::new(
|
||||||
vt,
|
vt,
|
||||||
self.tracks.as_deref(),
|
Axes::new(&self.columns().0, &self.rows().0),
|
||||||
self.gutter.as_deref(),
|
Axes::new(&self.column_gutter().0, &self.row_gutter().0),
|
||||||
&self.cells,
|
&cells,
|
||||||
regions,
|
regions,
|
||||||
styles,
|
styles,
|
||||||
);
|
);
|
||||||
|
|
@ -147,18 +133,15 @@ impl Layout for GridNode {
|
||||||
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct TrackSizings(pub Vec<Sizing>);
|
pub struct TrackSizings(pub Vec<Sizing>);
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
TrackSizings,
|
TrackSizings,
|
||||||
sizing: Sizing => Self(vec![sizing]),
|
sizing: Sizing => Self(vec![sizing]),
|
||||||
count: NonZeroUsize => Self(vec![Sizing::Auto; count.get()]),
|
count: NonZeroUsize => Self(vec![Sizing::Auto; count.get()]),
|
||||||
values: Array => Self(values.into_iter().map(Value::cast).collect::<StrResult<_>>()?),
|
values: Array => Self(values.into_iter().map(Value::cast).collect::<StrResult<_>>()?),
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
cast_to_value! {
|
||||||
Sizing,
|
v: TrackSizings => v.0.into()
|
||||||
_: AutoValue => Self::Auto,
|
|
||||||
v: Rel<Length> => Self::Rel(v),
|
|
||||||
v: Fr => Self::Fr(v),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs grid layout.
|
/// Performs grid layout.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// # Hide
|
|
||||||
/// Hide content without affecting layout.
|
/// Hide content without affecting layout.
|
||||||
///
|
///
|
||||||
/// The `hide` function allows you to hide content while the layout still 'sees'
|
/// The `hide` function allows you to hide content while the layout still 'sees'
|
||||||
|
|
@ -14,26 +13,18 @@ use crate::prelude::*;
|
||||||
/// #hide[Hello] Joe
|
/// #hide[Hello] Joe
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Hide
|
||||||
/// - body: `Content` (positional, required)
|
/// Category: layout
|
||||||
/// The content to hide.
|
#[node(Show)]
|
||||||
///
|
pub struct HideNode {
|
||||||
/// ## Category
|
/// The content to hide.
|
||||||
/// layout
|
#[positional]
|
||||||
#[func]
|
#[required]
|
||||||
#[capable(Show)]
|
pub body: Content,
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct HideNode(pub Content);
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl HideNode {
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
Ok(Self(args.expect("body")?).pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for HideNode {
|
impl Show for HideNode {
|
||||||
fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(self.0.clone().styled(Meta::DATA, vec![Meta::Hidden]))
|
Ok(self.body().styled(MetaNode::DATA, vec![Meta::Hidden]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
use crate::layout::{BlockNode, GridNode, ParNode, Sizing, Spacing};
|
use crate::layout::{BlockNode, ParNode, Sizing, Spacing};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::text::TextNode;
|
use crate::text::TextNode;
|
||||||
|
|
||||||
/// # Bullet List
|
use super::GridLayouter;
|
||||||
|
|
||||||
/// A bullet list.
|
/// A bullet list.
|
||||||
///
|
///
|
||||||
/// Displays a sequence of items vertically, with each item introduced by a
|
/// Displays a sequence of items vertically, with each item introduced by a
|
||||||
|
|
@ -33,48 +34,40 @@ use crate::text::TextNode;
|
||||||
/// paragraphs and other block-level content. All content that is indented
|
/// paragraphs and other block-level content. All content that is indented
|
||||||
/// more than an item's hyphen becomes part of that item.
|
/// more than an item's hyphen becomes part of that item.
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Bullet List
|
||||||
/// - items: `Content` (positional, variadic)
|
/// Category: layout
|
||||||
/// The list's children.
|
#[node(Layout)]
|
||||||
///
|
|
||||||
/// When using the list syntax, adjacent items are automatically collected
|
|
||||||
/// into lists, even through constructs like for loops.
|
|
||||||
///
|
|
||||||
/// ```example
|
|
||||||
/// #for letter in "ABC" [
|
|
||||||
/// - Letter #letter
|
|
||||||
/// ]
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// - tight: `bool` (named)
|
|
||||||
/// If this is `{false}`, the items are spaced apart with [list
|
|
||||||
/// spacing]($func/list.spacing). If it is `{true}`, they use normal
|
|
||||||
/// [leading]($func/par.leading) instead. This makes the list more compact,
|
|
||||||
/// which can look better if the items are short.
|
|
||||||
///
|
|
||||||
/// ```example
|
|
||||||
/// - If a list has a lot of text, and
|
|
||||||
/// maybe other inline content, it
|
|
||||||
/// should not be tight anymore.
|
|
||||||
///
|
|
||||||
/// - To make a list wide, simply insert
|
|
||||||
/// a blank line between the items.
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// layout
|
|
||||||
#[func]
|
|
||||||
#[capable(Layout)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct ListNode {
|
pub struct ListNode {
|
||||||
/// If true, the items are separated by leading instead of list spacing.
|
/// The bullet list's children.
|
||||||
pub tight: bool,
|
///
|
||||||
/// The individual bulleted or numbered items.
|
/// When using the list syntax, adjacent items are automatically collected
|
||||||
pub items: StyleVec<Content>,
|
/// into lists, even through constructs like for loops.
|
||||||
}
|
///
|
||||||
|
/// ```example
|
||||||
|
/// #for letter in "ABC" [
|
||||||
|
/// - Letter #letter
|
||||||
|
/// ]
|
||||||
|
/// ```
|
||||||
|
#[variadic]
|
||||||
|
pub items: Vec<ListItem>,
|
||||||
|
|
||||||
|
/// If this is `{false}`, the items are spaced apart with [list
|
||||||
|
/// spacing]($func/list.spacing). If it is `{true}`, they use normal
|
||||||
|
/// [leading]($func/par.leading) instead. This makes the list more compact,
|
||||||
|
/// which can look better if the items are short.
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// - If a list has a lot of text, and
|
||||||
|
/// maybe other inline content, it
|
||||||
|
/// should not be tight anymore.
|
||||||
|
///
|
||||||
|
/// - To make a list wide, simply insert
|
||||||
|
/// a blank line between the items.
|
||||||
|
/// ```
|
||||||
|
#[named]
|
||||||
|
#[default(true)]
|
||||||
|
pub tight: bool,
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl ListNode {
|
|
||||||
/// The marker which introduces each item.
|
/// The marker which introduces each item.
|
||||||
///
|
///
|
||||||
/// Instead of plain content, you can also pass an array with multiple
|
/// Instead of plain content, you can also pass an array with multiple
|
||||||
|
|
@ -96,43 +89,35 @@ impl ListNode {
|
||||||
/// - Items
|
/// - Items
|
||||||
/// - Items
|
/// - Items
|
||||||
/// ```
|
/// ```
|
||||||
#[property(referenced)]
|
#[settable]
|
||||||
pub const MARKER: Marker = Marker::Content(vec![]);
|
#[default(ListMarker::Content(vec![]))]
|
||||||
|
pub marker: ListMarker,
|
||||||
|
|
||||||
/// The indent of each item's marker.
|
/// The indent of each item's marker.
|
||||||
#[property(resolve)]
|
#[settable]
|
||||||
pub const INDENT: Length = Length::zero();
|
#[resolve]
|
||||||
|
#[default]
|
||||||
|
pub indent: Length,
|
||||||
|
|
||||||
/// The spacing between the marker and the body of each item.
|
/// The spacing between the marker and the body of each item.
|
||||||
#[property(resolve)]
|
#[settable]
|
||||||
pub const BODY_INDENT: Length = Em::new(0.5).into();
|
#[resolve]
|
||||||
|
#[default(Em::new(0.5).into())]
|
||||||
|
pub body_indent: Length,
|
||||||
|
|
||||||
/// The spacing between the items of a wide (non-tight) list.
|
/// The spacing between the items of a wide (non-tight) list.
|
||||||
///
|
///
|
||||||
/// If set to `{auto}`, uses the spacing [below blocks]($func/block.below).
|
/// If set to `{auto}`, uses the spacing [below blocks]($func/block.below).
|
||||||
pub const SPACING: Smart<Spacing> = Smart::Auto;
|
#[settable]
|
||||||
|
#[default]
|
||||||
|
pub spacing: Smart<Spacing>,
|
||||||
|
|
||||||
/// The nesting depth.
|
/// The nesting depth.
|
||||||
#[property(skip, fold)]
|
#[settable]
|
||||||
const DEPTH: Depth = 0;
|
#[fold]
|
||||||
|
#[skip]
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
#[default]
|
||||||
Ok(Self {
|
depth: Depth,
|
||||||
tight: args.named("tight")?.unwrap_or(true),
|
|
||||||
items: args.all()?.into_iter().collect(),
|
|
||||||
}
|
|
||||||
.pack())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field(&self, name: &str) -> Option<Value> {
|
|
||||||
match name {
|
|
||||||
"tight" => Some(Value::Bool(self.tight)),
|
|
||||||
"items" => Some(Value::Array(
|
|
||||||
self.items.items().cloned().map(Value::Content).collect(),
|
|
||||||
)),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for ListNode {
|
impl Layout for ListNode {
|
||||||
|
|
@ -144,49 +129,65 @@ impl Layout for ListNode {
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
let indent = styles.get(Self::INDENT);
|
let indent = styles.get(Self::INDENT);
|
||||||
let body_indent = styles.get(Self::BODY_INDENT);
|
let body_indent = styles.get(Self::BODY_INDENT);
|
||||||
let gutter = if self.tight {
|
let gutter = if self.tight() {
|
||||||
styles.get(ParNode::LEADING).into()
|
styles.get(ParNode::LEADING).into()
|
||||||
} else {
|
} else {
|
||||||
styles
|
styles
|
||||||
.get(Self::SPACING)
|
.get(Self::SPACING)
|
||||||
.unwrap_or_else(|| styles.get(BlockNode::BELOW).amount)
|
.unwrap_or_else(|| styles.get(BlockNode::BELOW).amount())
|
||||||
};
|
};
|
||||||
|
|
||||||
let depth = styles.get(Self::DEPTH);
|
let depth = styles.get(Self::DEPTH);
|
||||||
let marker = styles.get(Self::MARKER).resolve(vt.world(), depth)?;
|
let marker = styles.get(Self::MARKER).resolve(vt.world(), depth)?;
|
||||||
|
|
||||||
let mut cells = vec![];
|
let mut cells = vec![];
|
||||||
for (item, map) in self.items.iter() {
|
for item in self.items() {
|
||||||
cells.push(Content::empty());
|
cells.push(Content::empty());
|
||||||
cells.push(marker.clone());
|
cells.push(marker.clone());
|
||||||
cells.push(Content::empty());
|
cells.push(Content::empty());
|
||||||
cells.push(
|
cells.push(item.body().styled(Self::DEPTH, Depth));
|
||||||
item.clone().styled_with_map(map.clone()).styled(Self::DEPTH, Depth),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GridNode {
|
let layouter = GridLayouter::new(
|
||||||
tracks: Axes::with_x(vec![
|
vt,
|
||||||
|
Axes::with_x(&[
|
||||||
Sizing::Rel(indent.into()),
|
Sizing::Rel(indent.into()),
|
||||||
Sizing::Auto,
|
Sizing::Auto,
|
||||||
Sizing::Rel(body_indent.into()),
|
Sizing::Rel(body_indent.into()),
|
||||||
Sizing::Auto,
|
Sizing::Auto,
|
||||||
]),
|
]),
|
||||||
gutter: Axes::with_y(vec![gutter.into()]),
|
Axes::with_y(&[gutter.into()]),
|
||||||
cells,
|
&cells,
|
||||||
}
|
regions,
|
||||||
.layout(vt, styles, regions)
|
styles,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(layouter.layout()?.fragment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A bullet list item.
|
||||||
|
#[node]
|
||||||
|
pub struct ListItem {
|
||||||
|
/// The item's body.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
|
pub body: Content,
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
ListItem,
|
||||||
|
v: Content => v.to::<Self>().cloned().unwrap_or_else(|| Self::new(v.clone())),
|
||||||
|
}
|
||||||
|
|
||||||
/// A list's marker.
|
/// A list's marker.
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub enum Marker {
|
pub enum ListMarker {
|
||||||
Content(Vec<Content>),
|
Content(Vec<Content>),
|
||||||
Func(Func),
|
Func(Func),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Marker {
|
impl ListMarker {
|
||||||
/// Resolve the marker for the given depth.
|
/// Resolve the marker for the given depth.
|
||||||
fn resolve(&self, world: Tracked<dyn World>, depth: usize) -> SourceResult<Content> {
|
fn resolve(&self, world: Tracked<dyn World>, depth: usize) -> SourceResult<Content> {
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
|
|
@ -203,8 +204,8 @@ impl Marker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
Marker,
|
ListMarker,
|
||||||
v: Content => Self::Content(vec![v]),
|
v: Content => Self::Content(vec![v]),
|
||||||
array: Array => {
|
array: Array => {
|
||||||
if array.len() == 0 {
|
if array.len() == 0 {
|
||||||
|
|
@ -215,14 +216,28 @@ castable! {
|
||||||
v: Func => Self::Func(v),
|
v: Func => Self::Func(v),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Hash)]
|
cast_to_value! {
|
||||||
|
v: ListMarker => match v {
|
||||||
|
ListMarker::Content(vec) => vec.into(),
|
||||||
|
ListMarker::Func(func) => func.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct Depth;
|
struct Depth;
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
Depth,
|
||||||
|
_: Value => Self,
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
_: Depth => Value::None
|
||||||
|
}
|
||||||
|
|
||||||
impl Fold for Depth {
|
impl Fold for Depth {
|
||||||
type Output = usize;
|
type Output = usize;
|
||||||
|
|
||||||
fn fold(self, mut outer: Self::Output) -> Self::Output {
|
fn fold(self, outer: Self::Output) -> Self::Output {
|
||||||
outer += 1;
|
outer + 1
|
||||||
outer
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,8 @@ use std::mem;
|
||||||
use typed_arena::Arena;
|
use typed_arena::Arena;
|
||||||
use typst::diag::SourceResult;
|
use typst::diag::SourceResult;
|
||||||
use typst::model::{
|
use typst::model::{
|
||||||
applicable, capability, realize, Content, Node, SequenceNode, Style, StyleChain,
|
applicable, realize, Content, Node, SequenceNode, Style, StyleChain, StyleVecBuilder,
|
||||||
StyleVecBuilder, StyledNode,
|
StyledNode,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::math::{FormulaNode, LayoutMath};
|
use crate::math::{FormulaNode, LayoutMath};
|
||||||
|
|
@ -60,7 +60,6 @@ use crate::text::{LinebreakNode, SmartQuoteNode, SpaceNode, TextNode};
|
||||||
use crate::visualize::{CircleNode, EllipseNode, ImageNode, RectNode, SquareNode};
|
use crate::visualize::{CircleNode, EllipseNode, ImageNode, RectNode, SquareNode};
|
||||||
|
|
||||||
/// Root-level layout.
|
/// Root-level layout.
|
||||||
#[capability]
|
|
||||||
pub trait LayoutRoot {
|
pub trait LayoutRoot {
|
||||||
/// Layout into one frame per page.
|
/// Layout into one frame per page.
|
||||||
fn layout_root(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Document>;
|
fn layout_root(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Document>;
|
||||||
|
|
@ -96,7 +95,6 @@ impl LayoutRoot for Content {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout into regions.
|
/// Layout into regions.
|
||||||
#[capability]
|
|
||||||
pub trait Layout {
|
pub trait Layout {
|
||||||
/// Layout into one frame per region.
|
/// Layout into one frame per region.
|
||||||
fn layout(
|
fn layout(
|
||||||
|
|
@ -160,7 +158,7 @@ fn realize_root<'a>(
|
||||||
builder.accept(content, styles)?;
|
builder.accept(content, styles)?;
|
||||||
builder.interrupt_page(Some(styles))?;
|
builder.interrupt_page(Some(styles))?;
|
||||||
let (pages, shared) = builder.doc.unwrap().pages.finish();
|
let (pages, shared) = builder.doc.unwrap().pages.finish();
|
||||||
Ok((DocumentNode(pages).pack(), shared))
|
Ok((DocumentNode::new(pages.to_vec()).pack(), shared))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Realize into a node that is capable of block-level layout.
|
/// Realize into a node that is capable of block-level layout.
|
||||||
|
|
@ -185,7 +183,7 @@ fn realize_block<'a>(
|
||||||
builder.accept(content, styles)?;
|
builder.accept(content, styles)?;
|
||||||
builder.interrupt_par()?;
|
builder.interrupt_par()?;
|
||||||
let (children, shared) = builder.flow.0.finish();
|
let (children, shared) = builder.flow.0.finish();
|
||||||
Ok((FlowNode(children).pack(), shared))
|
Ok((FlowNode::new(children.to_vec()).pack(), shared))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds a document or a flow node from content.
|
/// Builds a document or a flow node from content.
|
||||||
|
|
@ -211,6 +209,7 @@ struct Scratch<'a> {
|
||||||
styles: Arena<StyleChain<'a>>,
|
styles: Arena<StyleChain<'a>>,
|
||||||
/// An arena where intermediate content resulting from show rules is stored.
|
/// An arena where intermediate content resulting from show rules is stored.
|
||||||
content: Arena<Content>,
|
content: Arena<Content>,
|
||||||
|
maps: Arena<StyleMap>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'v, 't> Builder<'a, 'v, 't> {
|
impl<'a, 'v, 't> Builder<'a, 'v, 't> {
|
||||||
|
|
@ -231,10 +230,8 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
if content.has::<dyn LayoutMath>() && !content.is::<FormulaNode>() {
|
if content.has::<dyn LayoutMath>() && !content.is::<FormulaNode>() {
|
||||||
content = self
|
content =
|
||||||
.scratch
|
self.scratch.content.alloc(FormulaNode::new(content.clone()).pack());
|
||||||
.content
|
|
||||||
.alloc(FormulaNode { body: content.clone(), block: false }.pack());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare only if this is the first application for this node.
|
// Prepare only if this is the first application for this node.
|
||||||
|
|
@ -252,8 +249,9 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(seq) = content.to::<SequenceNode>() {
|
if let Some(seq) = content.to::<SequenceNode>() {
|
||||||
for sub in &seq.0 {
|
for sub in seq.children() {
|
||||||
self.accept(sub, styles)?;
|
let stored = self.scratch.content.alloc(sub);
|
||||||
|
self.accept(stored, styles)?;
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
@ -269,8 +267,7 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
|
||||||
|
|
||||||
self.interrupt_list()?;
|
self.interrupt_list()?;
|
||||||
|
|
||||||
if content.is::<ListItem>() {
|
if self.list.accept(content, styles) {
|
||||||
self.list.accept(content, styles);
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -286,7 +283,7 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
|
||||||
|
|
||||||
let keep = content
|
let keep = content
|
||||||
.to::<PagebreakNode>()
|
.to::<PagebreakNode>()
|
||||||
.map_or(false, |pagebreak| !pagebreak.weak);
|
.map_or(false, |pagebreak| !pagebreak.weak());
|
||||||
|
|
||||||
self.interrupt_page(keep.then(|| styles))?;
|
self.interrupt_page(keep.then(|| styles))?;
|
||||||
|
|
||||||
|
|
@ -308,11 +305,13 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
|
||||||
styled: &'a StyledNode,
|
styled: &'a StyledNode,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
|
let map = self.scratch.maps.alloc(styled.map());
|
||||||
let stored = self.scratch.styles.alloc(styles);
|
let stored = self.scratch.styles.alloc(styles);
|
||||||
let styles = stored.chain(&styled.map);
|
let content = self.scratch.content.alloc(styled.sub());
|
||||||
self.interrupt_style(&styled.map, None)?;
|
let styles = stored.chain(map);
|
||||||
self.accept(&styled.sub, styles)?;
|
self.interrupt_style(&map, None)?;
|
||||||
self.interrupt_style(&styled.map, Some(styles))?;
|
self.accept(content, styles)?;
|
||||||
|
self.interrupt_style(map, Some(styles))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -381,7 +380,7 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
|
||||||
let (flow, shared) = mem::take(&mut self.flow).0.finish();
|
let (flow, shared) = mem::take(&mut self.flow).0.finish();
|
||||||
let styles =
|
let styles =
|
||||||
if shared == StyleChain::default() { styles.unwrap() } else { shared };
|
if shared == StyleChain::default() { styles.unwrap() } else { shared };
|
||||||
let page = PageNode(FlowNode(flow).pack()).pack();
|
let page = PageNode::new(FlowNode::new(flow.to_vec()).pack()).pack();
|
||||||
let stored = self.scratch.content.alloc(page);
|
let stored = self.scratch.content.alloc(page);
|
||||||
self.accept(stored, styles)?;
|
self.accept(stored, styles)?;
|
||||||
}
|
}
|
||||||
|
|
@ -392,7 +391,7 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
|
||||||
/// Accepts pagebreaks and pages.
|
/// Accepts pagebreaks and pages.
|
||||||
struct DocBuilder<'a> {
|
struct DocBuilder<'a> {
|
||||||
/// The page runs built so far.
|
/// The page runs built so far.
|
||||||
pages: StyleVecBuilder<'a, PageNode>,
|
pages: StyleVecBuilder<'a, Content>,
|
||||||
/// Whether to keep a following page even if it is empty.
|
/// Whether to keep a following page even if it is empty.
|
||||||
keep_next: bool,
|
keep_next: bool,
|
||||||
}
|
}
|
||||||
|
|
@ -400,12 +399,12 @@ struct DocBuilder<'a> {
|
||||||
impl<'a> DocBuilder<'a> {
|
impl<'a> DocBuilder<'a> {
|
||||||
fn accept(&mut self, content: &Content, styles: StyleChain<'a>) -> bool {
|
fn accept(&mut self, content: &Content, styles: StyleChain<'a>) -> bool {
|
||||||
if let Some(pagebreak) = content.to::<PagebreakNode>() {
|
if let Some(pagebreak) = content.to::<PagebreakNode>() {
|
||||||
self.keep_next = !pagebreak.weak;
|
self.keep_next = !pagebreak.weak();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(page) = content.to::<PageNode>() {
|
if content.is::<PageNode>() {
|
||||||
self.pages.push(page.clone(), styles);
|
self.pages.push(content.clone(), styles);
|
||||||
self.keep_next = false;
|
self.keep_next = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -441,11 +440,11 @@ impl<'a> FlowBuilder<'a> {
|
||||||
|
|
||||||
if content.has::<dyn Layout>() || content.is::<ParNode>() {
|
if content.has::<dyn Layout>() || content.is::<ParNode>() {
|
||||||
let is_tight_list = if let Some(node) = content.to::<ListNode>() {
|
let is_tight_list = if let Some(node) = content.to::<ListNode>() {
|
||||||
node.tight
|
node.tight()
|
||||||
} else if let Some(node) = content.to::<EnumNode>() {
|
} else if let Some(node) = content.to::<EnumNode>() {
|
||||||
node.tight
|
node.tight()
|
||||||
} else if let Some(node) = content.to::<TermsNode>() {
|
} else if let Some(node) = content.to::<TermsNode>() {
|
||||||
node.tight
|
node.tight()
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
|
@ -458,9 +457,9 @@ impl<'a> FlowBuilder<'a> {
|
||||||
|
|
||||||
let above = styles.get(BlockNode::ABOVE);
|
let above = styles.get(BlockNode::ABOVE);
|
||||||
let below = styles.get(BlockNode::BELOW);
|
let below = styles.get(BlockNode::BELOW);
|
||||||
self.0.push(above.pack(), styles);
|
self.0.push(above.clone().pack(), styles);
|
||||||
self.0.push(content.clone(), styles);
|
self.0.push(content.clone(), styles);
|
||||||
self.0.push(below.pack(), styles);
|
self.0.push(below.clone().pack(), styles);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -479,7 +478,7 @@ impl<'a> ParBuilder<'a> {
|
||||||
|| content.is::<HNode>()
|
|| content.is::<HNode>()
|
||||||
|| content.is::<LinebreakNode>()
|
|| content.is::<LinebreakNode>()
|
||||||
|| content.is::<SmartQuoteNode>()
|
|| content.is::<SmartQuoteNode>()
|
||||||
|| content.to::<FormulaNode>().map_or(false, |node| !node.block)
|
|| content.to::<FormulaNode>().map_or(false, |node| !node.block())
|
||||||
|| content.is::<BoxNode>()
|
|| content.is::<BoxNode>()
|
||||||
{
|
{
|
||||||
self.0.push(content.clone(), styles);
|
self.0.push(content.clone(), styles);
|
||||||
|
|
@ -491,14 +490,14 @@ impl<'a> ParBuilder<'a> {
|
||||||
|
|
||||||
fn finish(self) -> (Content, StyleChain<'a>) {
|
fn finish(self) -> (Content, StyleChain<'a>) {
|
||||||
let (children, shared) = self.0.finish();
|
let (children, shared) = self.0.finish();
|
||||||
(ParNode(children).pack(), shared)
|
(ParNode::new(children.to_vec()).pack(), shared)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Accepts list / enum items, spaces, paragraph breaks.
|
/// Accepts list / enum items, spaces, paragraph breaks.
|
||||||
struct ListBuilder<'a> {
|
struct ListBuilder<'a> {
|
||||||
/// The list items collected so far.
|
/// The list items collected so far.
|
||||||
items: StyleVecBuilder<'a, ListItem>,
|
items: StyleVecBuilder<'a, Content>,
|
||||||
/// Whether the list contains no paragraph breaks.
|
/// Whether the list contains no paragraph breaks.
|
||||||
tight: bool,
|
tight: bool,
|
||||||
/// Trailing content for which it is unclear whether it is part of the list.
|
/// Trailing content for which it is unclear whether it is part of the list.
|
||||||
|
|
@ -514,14 +513,18 @@ impl<'a> ListBuilder<'a> {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(item) = content.to::<ListItem>() {
|
if (content.is::<ListItem>()
|
||||||
if self.items.items().next().map_or(true, |first| {
|
|| content.is::<EnumItem>()
|
||||||
std::mem::discriminant(item) == std::mem::discriminant(first)
|
|| content.is::<TermItem>())
|
||||||
}) {
|
&& self
|
||||||
self.items.push(item.clone(), styles);
|
.items
|
||||||
self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::<ParbreakNode>());
|
.items()
|
||||||
return true;
|
.next()
|
||||||
}
|
.map_or(true, |first| first.id() == content.id())
|
||||||
|
{
|
||||||
|
self.items.push(content.clone(), styles);
|
||||||
|
self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::<ParbreakNode>());
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
false
|
||||||
|
|
@ -530,31 +533,48 @@ impl<'a> ListBuilder<'a> {
|
||||||
fn finish(self) -> (Content, StyleChain<'a>) {
|
fn finish(self) -> (Content, StyleChain<'a>) {
|
||||||
let (items, shared) = self.items.finish();
|
let (items, shared) = self.items.finish();
|
||||||
let item = items.items().next().unwrap();
|
let item = items.items().next().unwrap();
|
||||||
let output = match item {
|
let output = if item.is::<ListItem>() {
|
||||||
ListItem::List(_) => ListNode {
|
ListNode::new(
|
||||||
tight: self.tight,
|
items
|
||||||
items: items.map(|item| match item {
|
.iter()
|
||||||
ListItem::List(item) => item.clone(),
|
.map(|(item, map)| {
|
||||||
_ => panic!("wrong list item"),
|
let item = item.to::<ListItem>().unwrap();
|
||||||
}),
|
ListItem::new(item.body().styled_with_map(map.clone()))
|
||||||
}
|
})
|
||||||
.pack(),
|
.collect::<Vec<_>>(),
|
||||||
ListItem::Enum(..) => EnumNode {
|
)
|
||||||
tight: self.tight,
|
.with_tight(self.tight)
|
||||||
items: items.map(|item| match item {
|
.pack()
|
||||||
ListItem::Enum(number, body) => (*number, body.clone()),
|
} else if item.is::<EnumItem>() {
|
||||||
_ => panic!("wrong list item"),
|
EnumNode::new(
|
||||||
}),
|
items
|
||||||
}
|
.iter()
|
||||||
.pack(),
|
.map(|(item, map)| {
|
||||||
ListItem::Term(_) => TermsNode {
|
let item = item.to::<EnumItem>().unwrap();
|
||||||
tight: self.tight,
|
EnumItem::new(item.body().styled_with_map(map.clone()))
|
||||||
items: items.map(|item| match item {
|
.with_number(item.number())
|
||||||
ListItem::Term(item) => item.clone(),
|
})
|
||||||
_ => panic!("wrong list item"),
|
.collect::<Vec<_>>(),
|
||||||
}),
|
)
|
||||||
}
|
.with_tight(self.tight)
|
||||||
.pack(),
|
.pack()
|
||||||
|
} else if item.is::<TermItem>() {
|
||||||
|
TermsNode::new(
|
||||||
|
items
|
||||||
|
.iter()
|
||||||
|
.map(|(item, map)| {
|
||||||
|
let item = item.to::<TermItem>().unwrap();
|
||||||
|
TermItem::new(
|
||||||
|
item.term().styled_with_map(map.clone()),
|
||||||
|
item.description().styled_with_map(map.clone()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
.with_tight(self.tight)
|
||||||
|
.pack()
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
};
|
};
|
||||||
(output, shared)
|
(output, shared)
|
||||||
}
|
}
|
||||||
|
|
@ -569,18 +589,3 @@ impl Default for ListBuilder<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An item in a list.
|
|
||||||
#[capable]
|
|
||||||
#[derive(Debug, Clone, Hash)]
|
|
||||||
pub enum ListItem {
|
|
||||||
/// An item of a bullet list.
|
|
||||||
List(Content),
|
|
||||||
/// An item of a numbered list.
|
|
||||||
Enum(Option<NonZeroUsize>, Content),
|
|
||||||
/// An item of a term list.
|
|
||||||
Term(TermItem),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl ListItem {}
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// # Padding
|
|
||||||
/// Add spacing around content.
|
/// Add spacing around content.
|
||||||
///
|
///
|
||||||
/// The `pad` function adds spacing around content. The spacing can be specified
|
/// The `pad` function adds spacing around content. The spacing can be specified
|
||||||
|
|
@ -17,21 +16,6 @@ use crate::prelude::*;
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// ## Parameters
|
||||||
/// - body: `Content` (positional, required)
|
|
||||||
/// The content to pad at the sides.
|
|
||||||
///
|
|
||||||
/// - left: `Rel<Length>` (named)
|
|
||||||
/// The padding at the left side.
|
|
||||||
///
|
|
||||||
/// - right: `Rel<Length>` (named)
|
|
||||||
/// The padding at the right side.
|
|
||||||
///
|
|
||||||
/// - top: `Rel<Length>` (named)
|
|
||||||
/// The padding at the top side.
|
|
||||||
///
|
|
||||||
/// - bottom: `Rel<Length>` (named)
|
|
||||||
/// The padding at the bottom side.
|
|
||||||
///
|
|
||||||
/// - x: `Rel<Length>` (named)
|
/// - x: `Rel<Length>` (named)
|
||||||
/// The horizontal padding. Both `left` and `right` take precedence over this.
|
/// The horizontal padding. Both `left` and `right` take precedence over this.
|
||||||
///
|
///
|
||||||
|
|
@ -41,20 +25,37 @@ use crate::prelude::*;
|
||||||
/// - rest: `Rel<Length>` (named)
|
/// - rest: `Rel<Length>` (named)
|
||||||
/// The padding for all sides. All other parameters take precedence over this.
|
/// The padding for all sides. All other parameters take precedence over this.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Padding
|
||||||
/// layout
|
/// Category: layout
|
||||||
#[func]
|
#[node(Construct, Layout)]
|
||||||
#[capable(Layout)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct PadNode {
|
pub struct PadNode {
|
||||||
/// The amount of padding.
|
/// The content to pad at the sides.
|
||||||
pub padding: Sides<Rel<Length>>,
|
#[positional]
|
||||||
/// The content whose sides to pad.
|
#[required]
|
||||||
pub body: Content,
|
pub body: Content,
|
||||||
|
|
||||||
|
/// The padding at the left side.
|
||||||
|
#[named]
|
||||||
|
#[default]
|
||||||
|
pub left: Rel<Length>,
|
||||||
|
|
||||||
|
/// The padding at the right side.
|
||||||
|
#[named]
|
||||||
|
#[default]
|
||||||
|
pub right: Rel<Length>,
|
||||||
|
|
||||||
|
/// The padding at the top side.
|
||||||
|
#[named]
|
||||||
|
#[default]
|
||||||
|
pub top: Rel<Length>,
|
||||||
|
|
||||||
|
/// The padding at the bottom side.
|
||||||
|
#[named]
|
||||||
|
#[default]
|
||||||
|
pub bottom: Rel<Length>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node]
|
impl Construct for PadNode {
|
||||||
impl PadNode {
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
let all = args.named("rest")?.or(args.find()?);
|
let all = args.named("rest")?.or(args.find()?);
|
||||||
let x = args.named("x")?;
|
let x = args.named("x")?;
|
||||||
|
|
@ -64,8 +65,12 @@ impl PadNode {
|
||||||
let right = args.named("right")?.or(x).or(all).unwrap_or_default();
|
let right = args.named("right")?.or(x).or(all).unwrap_or_default();
|
||||||
let bottom = args.named("bottom")?.or(y).or(all).unwrap_or_default();
|
let bottom = args.named("bottom")?.or(y).or(all).unwrap_or_default();
|
||||||
let body = args.expect::<Content>("body")?;
|
let body = args.expect::<Content>("body")?;
|
||||||
let padding = Sides::new(left, top, right, bottom);
|
Ok(Self::new(body)
|
||||||
Ok(Self { padding, body }.pack())
|
.with_left(left)
|
||||||
|
.with_top(top)
|
||||||
|
.with_bottom(bottom)
|
||||||
|
.with_right(right)
|
||||||
|
.pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,9 +84,10 @@ impl Layout for PadNode {
|
||||||
let mut backlog = vec![];
|
let mut backlog = vec![];
|
||||||
|
|
||||||
// Layout child into padded regions.
|
// Layout child into padded regions.
|
||||||
let padding = self.padding.resolve(styles);
|
let sides = Sides::new(self.left(), self.top(), self.right(), self.bottom());
|
||||||
|
let padding = sides.resolve(styles);
|
||||||
let pod = regions.map(&mut backlog, |size| shrink(size, padding));
|
let pod = regions.map(&mut backlog, |size| shrink(size, padding));
|
||||||
let mut fragment = self.body.layout(vt, styles, pod)?;
|
let mut fragment = self.body().layout(vt, styles, pod)?;
|
||||||
|
|
||||||
for frame in &mut fragment {
|
for frame in &mut fragment {
|
||||||
// Apply the padding inversely such that the grown size padded
|
// Apply the padding inversely such that the grown size padded
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ use std::str::FromStr;
|
||||||
use super::ColumnsNode;
|
use super::ColumnsNode;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// # Page
|
|
||||||
/// Layouts its child onto one or multiple pages.
|
/// Layouts its child onto one or multiple pages.
|
||||||
///
|
///
|
||||||
/// Although this function is primarily used in set rules to affect page
|
/// Although this function is primarily used in set rules to affect page
|
||||||
|
|
@ -14,13 +13,6 @@ use crate::prelude::*;
|
||||||
/// the pages will grow to fit their content on the respective axis.
|
/// the pages will grow to fit their content on the respective axis.
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// ## Parameters
|
||||||
/// - body: `Content` (positional, required)
|
|
||||||
/// The contents of the page(s).
|
|
||||||
///
|
|
||||||
/// Multiple pages will be created if the content does not fit on a single
|
|
||||||
/// page. A new page with the page properties prior to the function invocation
|
|
||||||
/// will be created after the body has been typeset.
|
|
||||||
///
|
|
||||||
/// - paper: `Paper` (positional, settable)
|
/// - paper: `Paper` (positional, settable)
|
||||||
/// A standard paper size to set width and height. When this is not specified,
|
/// A standard paper size to set width and height. When this is not specified,
|
||||||
/// Typst defaults to `{"a4"}` paper.
|
/// Typst defaults to `{"a4"}` paper.
|
||||||
|
|
@ -33,15 +25,25 @@ use crate::prelude::*;
|
||||||
/// There you go, US friends!
|
/// There you go, US friends!
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Page
|
||||||
/// layout
|
/// Category: layout
|
||||||
#[func]
|
|
||||||
#[capable]
|
|
||||||
#[derive(Clone, Hash)]
|
|
||||||
pub struct PageNode(pub Content);
|
|
||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
impl PageNode {
|
#[set({
|
||||||
|
if let Some(paper) = args.named_or_find::<Paper>("paper")? {
|
||||||
|
styles.set(Self::WIDTH, Smart::Custom(paper.width().into()));
|
||||||
|
styles.set(Self::HEIGHT, Smart::Custom(paper.height().into()));
|
||||||
|
}
|
||||||
|
})]
|
||||||
|
pub struct PageNode {
|
||||||
|
/// The contents of the page(s).
|
||||||
|
///
|
||||||
|
/// Multiple pages will be created if the content does not fit on a single
|
||||||
|
/// page. A new page with the page properties prior to the function invocation
|
||||||
|
/// will be created after the body has been typeset.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
|
pub body: Content,
|
||||||
|
|
||||||
/// The width of the page.
|
/// The width of the page.
|
||||||
///
|
///
|
||||||
/// ```example
|
/// ```example
|
||||||
|
|
@ -54,8 +56,10 @@ impl PageNode {
|
||||||
/// box(square(width: 1cm))
|
/// box(square(width: 1cm))
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[property(resolve)]
|
#[settable]
|
||||||
pub const WIDTH: Smart<Length> = Smart::Custom(Paper::A4.width().into());
|
#[resolve]
|
||||||
|
#[default(Smart::Custom(Paper::A4.width().into()))]
|
||||||
|
pub width: Smart<Length>,
|
||||||
|
|
||||||
/// The height of the page.
|
/// The height of the page.
|
||||||
///
|
///
|
||||||
|
|
@ -63,8 +67,10 @@ impl PageNode {
|
||||||
/// by inserting a [page break]($func/pagebreak). Most examples throughout
|
/// by inserting a [page break]($func/pagebreak). Most examples throughout
|
||||||
/// this documentation use `{auto}` for the height of the page to
|
/// this documentation use `{auto}` for the height of the page to
|
||||||
/// dynamically grow and shrink to fit their content.
|
/// dynamically grow and shrink to fit their content.
|
||||||
#[property(resolve)]
|
#[settable]
|
||||||
pub const HEIGHT: Smart<Length> = Smart::Custom(Paper::A4.height().into());
|
#[resolve]
|
||||||
|
#[default(Smart::Custom(Paper::A4.height().into()))]
|
||||||
|
pub height: Smart<Length>,
|
||||||
|
|
||||||
/// Whether the page is flipped into landscape orientation.
|
/// Whether the page is flipped into landscape orientation.
|
||||||
///
|
///
|
||||||
|
|
@ -84,7 +90,9 @@ impl PageNode {
|
||||||
/// New York, NY 10001 \
|
/// New York, NY 10001 \
|
||||||
/// +1 555 555 5555
|
/// +1 555 555 5555
|
||||||
/// ```
|
/// ```
|
||||||
pub const FLIPPED: bool = false;
|
#[settable]
|
||||||
|
#[default(false)]
|
||||||
|
pub flipped: bool,
|
||||||
|
|
||||||
/// The page's margins.
|
/// The page's margins.
|
||||||
///
|
///
|
||||||
|
|
@ -114,8 +122,10 @@ impl PageNode {
|
||||||
/// fill: aqua,
|
/// fill: aqua,
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[property(fold)]
|
#[settable]
|
||||||
pub const MARGIN: Sides<Option<Smart<Rel<Length>>>> = Sides::splat(Smart::Auto);
|
#[fold]
|
||||||
|
#[default]
|
||||||
|
pub margin: Sides<Option<Smart<Rel<Length>>>>,
|
||||||
|
|
||||||
/// How many columns the page has.
|
/// How many columns the page has.
|
||||||
///
|
///
|
||||||
|
|
@ -131,7 +141,9 @@ impl PageNode {
|
||||||
/// emissions and mitigate the impacts
|
/// emissions and mitigate the impacts
|
||||||
/// of a rapidly changing climate.
|
/// of a rapidly changing climate.
|
||||||
/// ```
|
/// ```
|
||||||
pub const COLUMNS: NonZeroUsize = NonZeroUsize::new(1).unwrap();
|
#[settable]
|
||||||
|
#[default(NonZeroUsize::new(1).unwrap())]
|
||||||
|
pub columns: NonZeroUsize,
|
||||||
|
|
||||||
/// The page's background color.
|
/// The page's background color.
|
||||||
///
|
///
|
||||||
|
|
@ -145,7 +157,9 @@ impl PageNode {
|
||||||
/// #set text(fill: rgb("fdfdfd"))
|
/// #set text(fill: rgb("fdfdfd"))
|
||||||
/// *Dark mode enabled.*
|
/// *Dark mode enabled.*
|
||||||
/// ```
|
/// ```
|
||||||
pub const FILL: Option<Paint> = None;
|
#[settable]
|
||||||
|
#[default]
|
||||||
|
pub fill: Option<Paint>,
|
||||||
|
|
||||||
/// The page's header.
|
/// The page's header.
|
||||||
///
|
///
|
||||||
|
|
@ -166,8 +180,9 @@ impl PageNode {
|
||||||
///
|
///
|
||||||
/// #lorem(18)
|
/// #lorem(18)
|
||||||
/// ```
|
/// ```
|
||||||
#[property(referenced)]
|
#[settable]
|
||||||
pub const HEADER: Option<Marginal> = None;
|
#[default]
|
||||||
|
pub header: Option<Marginal>,
|
||||||
|
|
||||||
/// The page's footer.
|
/// The page's footer.
|
||||||
///
|
///
|
||||||
|
|
@ -190,8 +205,9 @@ impl PageNode {
|
||||||
///
|
///
|
||||||
/// #lorem(18)
|
/// #lorem(18)
|
||||||
/// ```
|
/// ```
|
||||||
#[property(referenced)]
|
#[settable]
|
||||||
pub const FOOTER: Option<Marginal> = None;
|
#[default]
|
||||||
|
pub footer: Option<Marginal>,
|
||||||
|
|
||||||
/// Content in the page's background.
|
/// Content in the page's background.
|
||||||
///
|
///
|
||||||
|
|
@ -211,8 +227,9 @@ impl PageNode {
|
||||||
/// In the year 2023, we plan to take over the world
|
/// In the year 2023, we plan to take over the world
|
||||||
/// (of typesetting).
|
/// (of typesetting).
|
||||||
/// ```
|
/// ```
|
||||||
#[property(referenced)]
|
#[settable]
|
||||||
pub const BACKGROUND: Option<Marginal> = None;
|
#[default]
|
||||||
|
pub background: Option<Marginal>,
|
||||||
|
|
||||||
/// Content in the page's foreground.
|
/// Content in the page's foreground.
|
||||||
///
|
///
|
||||||
|
|
@ -228,26 +245,9 @@ impl PageNode {
|
||||||
/// "Weak Reject" because they did
|
/// "Weak Reject" because they did
|
||||||
/// not understand our approach...
|
/// not understand our approach...
|
||||||
/// ```
|
/// ```
|
||||||
#[property(referenced)]
|
#[settable]
|
||||||
pub const FOREGROUND: Option<Marginal> = None;
|
#[default]
|
||||||
|
pub foreground: Option<Marginal>,
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
Ok(Self(args.expect("body")?).pack())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set(...) {
|
|
||||||
if let Some(paper) = args.named_or_find::<Paper>("paper")? {
|
|
||||||
styles.set(Self::WIDTH, Smart::Custom(paper.width().into()));
|
|
||||||
styles.set(Self::HEIGHT, Smart::Custom(paper.height().into()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field(&self, name: &str) -> Option<Value> {
|
|
||||||
match name {
|
|
||||||
"body" => Some(Value::Content(self.0.clone())),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PageNode {
|
impl PageNode {
|
||||||
|
|
@ -276,26 +276,22 @@ impl PageNode {
|
||||||
let default = Rel::from(0.1190 * min);
|
let default = Rel::from(0.1190 * min);
|
||||||
let padding = styles.get(Self::MARGIN).map(|side| side.unwrap_or(default));
|
let padding = styles.get(Self::MARGIN).map(|side| side.unwrap_or(default));
|
||||||
|
|
||||||
let mut child = self.0.clone();
|
let mut child = self.body();
|
||||||
|
|
||||||
// Realize columns.
|
// Realize columns.
|
||||||
let columns = styles.get(Self::COLUMNS);
|
let columns = styles.get(Self::COLUMNS);
|
||||||
if columns.get() > 1 {
|
if columns.get() > 1 {
|
||||||
child = ColumnsNode { count: columns, body: self.0.clone() }.pack();
|
child = ColumnsNode::new(columns, child).pack();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Realize margins.
|
// Realize margins.
|
||||||
child = child.padded(padding);
|
child = child.padded(padding);
|
||||||
|
|
||||||
// Realize background fill.
|
|
||||||
if let Some(fill) = styles.get(Self::FILL) {
|
|
||||||
child = child.filled(fill);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Layout the child.
|
// Layout the child.
|
||||||
let regions = Regions::repeat(size, size.map(Abs::is_finite));
|
let regions = Regions::repeat(size, size.map(Abs::is_finite));
|
||||||
let mut fragment = child.layout(vt, styles, regions)?;
|
let mut fragment = child.layout(vt, styles, regions)?;
|
||||||
|
|
||||||
|
let fill = styles.get(Self::FILL);
|
||||||
let header = styles.get(Self::HEADER);
|
let header = styles.get(Self::HEADER);
|
||||||
let footer = styles.get(Self::FOOTER);
|
let footer = styles.get(Self::FOOTER);
|
||||||
let foreground = styles.get(Self::FOREGROUND);
|
let foreground = styles.get(Self::FOREGROUND);
|
||||||
|
|
@ -303,17 +299,21 @@ impl PageNode {
|
||||||
|
|
||||||
// Realize overlays.
|
// Realize overlays.
|
||||||
for frame in &mut fragment {
|
for frame in &mut fragment {
|
||||||
|
if let Some(fill) = fill {
|
||||||
|
frame.fill(fill);
|
||||||
|
}
|
||||||
|
|
||||||
let size = frame.size();
|
let size = frame.size();
|
||||||
let pad = padding.resolve(styles).relative_to(size);
|
let pad = padding.resolve(styles).relative_to(size);
|
||||||
let pw = size.x - pad.left - pad.right;
|
let pw = size.x - pad.left - pad.right;
|
||||||
let py = size.y - pad.bottom;
|
let py = size.y - pad.bottom;
|
||||||
for (marginal, pos, area) in [
|
for (marginal, pos, area) in [
|
||||||
(header, Point::with_x(pad.left), Size::new(pw, pad.top)),
|
(&header, Point::with_x(pad.left), Size::new(pw, pad.top)),
|
||||||
(footer, Point::new(pad.left, py), Size::new(pw, pad.bottom)),
|
(&footer, Point::new(pad.left, py), Size::new(pw, pad.bottom)),
|
||||||
(foreground, Point::zero(), size),
|
(&foreground, Point::zero(), size),
|
||||||
(background, Point::zero(), size),
|
(&background, Point::zero(), size),
|
||||||
] {
|
] {
|
||||||
let in_background = std::ptr::eq(marginal, background);
|
let in_background = std::ptr::eq(marginal, &background);
|
||||||
let Some(marginal) = marginal else { continue };
|
let Some(marginal) = marginal else { continue };
|
||||||
let content = marginal.resolve(vt, page)?;
|
let content = marginal.resolve(vt, page)?;
|
||||||
let pod = Regions::one(area, Axes::splat(true));
|
let pod = Regions::one(area, Axes::splat(true));
|
||||||
|
|
@ -332,15 +332,6 @@ impl PageNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for PageNode {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
f.write_str("Page(")?;
|
|
||||||
self.0.fmt(f)?;
|
|
||||||
f.write_str(")")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # Page Break
|
|
||||||
/// A manual page break.
|
/// A manual page break.
|
||||||
///
|
///
|
||||||
/// Must not be used inside any containers.
|
/// Must not be used inside any containers.
|
||||||
|
|
@ -355,26 +346,15 @@ impl Debug for PageNode {
|
||||||
/// In 1984, the first ...
|
/// In 1984, the first ...
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Page Break
|
||||||
/// - weak: `bool` (named)
|
/// Category: layout
|
||||||
/// If `{true}`, the page break is skipped if the current page is already
|
|
||||||
/// empty.
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// layout
|
|
||||||
#[func]
|
|
||||||
#[capable]
|
|
||||||
#[derive(Debug, Copy, Clone, Hash)]
|
|
||||||
pub struct PagebreakNode {
|
|
||||||
pub weak: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
impl PagebreakNode {
|
pub struct PagebreakNode {
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
/// If `{true}`, the page break is skipped if the current page is already
|
||||||
let weak = args.named("weak")?.unwrap_or(false);
|
/// empty.
|
||||||
Ok(Self { weak }.pack())
|
#[named]
|
||||||
}
|
#[default(false)]
|
||||||
|
pub weak: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A header, footer, foreground or background definition.
|
/// A header, footer, foreground or background definition.
|
||||||
|
|
@ -399,12 +379,19 @@ impl Marginal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
Marginal,
|
Marginal,
|
||||||
v: Content => Self::Content(v),
|
v: Content => Self::Content(v),
|
||||||
v: Func => Self::Func(v),
|
v: Func => Self::Func(v),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: Marginal => match v {
|
||||||
|
Marginal::Content(v) => v.into(),
|
||||||
|
Marginal::Func(v) => v.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Specification of a paper.
|
/// Specification of a paper.
|
||||||
#[derive(Debug, Copy, Clone, Hash)]
|
#[derive(Debug, Copy, Clone, Hash)]
|
||||||
pub struct Paper {
|
pub struct Paper {
|
||||||
|
|
@ -450,7 +437,7 @@ macro_rules! papers {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
Paper,
|
Paper,
|
||||||
$(
|
$(
|
||||||
/// Produces a paper of the respective size.
|
/// Produces a paper of the respective size.
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use unicode_bidi::{BidiInfo, Level as BidiLevel};
|
||||||
use unicode_script::{Script, UnicodeScript};
|
use unicode_script::{Script, UnicodeScript};
|
||||||
use xi_unicode::LineBreakIterator;
|
use xi_unicode::LineBreakIterator;
|
||||||
|
|
||||||
use typst::model::Key;
|
use typst::model::{Key, StyledNode};
|
||||||
|
|
||||||
use super::{BoxNode, HNode, Sizing, Spacing};
|
use super::{BoxNode, HNode, Sizing, Spacing};
|
||||||
use crate::layout::AlignNode;
|
use crate::layout::AlignNode;
|
||||||
|
|
@ -12,7 +12,6 @@ use crate::text::{
|
||||||
shape, LinebreakNode, Quoter, Quotes, ShapedText, SmartQuoteNode, SpaceNode, TextNode,
|
shape, LinebreakNode, Quoter, Quotes, ShapedText, SmartQuoteNode, SpaceNode, TextNode,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// # Paragraph
|
|
||||||
/// Arrange text, spacing and inline-level nodes into a paragraph.
|
/// Arrange text, spacing and inline-level nodes into a paragraph.
|
||||||
///
|
///
|
||||||
/// Although this function is primarily used in set rules to affect paragraph
|
/// Although this function is primarily used in set rules to affect paragraph
|
||||||
|
|
@ -40,15 +39,14 @@ use crate::text::{
|
||||||
/// - body: `Content` (positional, required)
|
/// - body: `Content` (positional, required)
|
||||||
/// The contents of the paragraph.
|
/// The contents of the paragraph.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Paragraph
|
||||||
/// layout
|
/// Category: layout
|
||||||
#[func]
|
#[node(Construct)]
|
||||||
#[capable]
|
pub struct ParNode {
|
||||||
#[derive(Hash)]
|
/// The paragraph's children.
|
||||||
pub struct ParNode(pub StyleVec<Content>);
|
#[variadic]
|
||||||
|
pub children: Vec<Content>,
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl ParNode {
|
|
||||||
/// The indent the first line of a consecutive paragraph should have.
|
/// The indent the first line of a consecutive paragraph should have.
|
||||||
///
|
///
|
||||||
/// The first paragraph on a page will never be indented.
|
/// The first paragraph on a page will never be indented.
|
||||||
|
|
@ -57,14 +55,18 @@ impl ParNode {
|
||||||
/// space between paragraphs or indented first lines. Consider turning the
|
/// space between paragraphs or indented first lines. Consider turning the
|
||||||
/// [paragraph spacing]($func/block.spacing) off when using this property
|
/// [paragraph spacing]($func/block.spacing) off when using this property
|
||||||
/// (e.g. using `[#show par: set block(spacing: 0pt)]`).
|
/// (e.g. using `[#show par: set block(spacing: 0pt)]`).
|
||||||
#[property(resolve)]
|
#[settable]
|
||||||
pub const INDENT: Length = Length::zero();
|
#[resolve]
|
||||||
|
#[default]
|
||||||
|
pub indent: Length,
|
||||||
|
|
||||||
/// The spacing between lines.
|
/// The spacing between lines.
|
||||||
///
|
///
|
||||||
/// The default value is `{0.65em}`.
|
/// The default value is `{0.65em}`.
|
||||||
#[property(resolve)]
|
#[settable]
|
||||||
pub const LEADING: Length = Em::new(0.65).into();
|
#[resolve]
|
||||||
|
#[default(Em::new(0.65).into())]
|
||||||
|
pub leading: Length,
|
||||||
|
|
||||||
/// Whether to justify text in its line.
|
/// Whether to justify text in its line.
|
||||||
///
|
///
|
||||||
|
|
@ -75,7 +77,9 @@ impl ParNode {
|
||||||
/// Note that the current [alignment]($func/align) still has an effect on
|
/// Note that the current [alignment]($func/align) still has an effect on
|
||||||
/// the placement of the last line except if it ends with a [justified line
|
/// the placement of the last line except if it ends with a [justified line
|
||||||
/// break]($func/linebreak.justify).
|
/// break]($func/linebreak.justify).
|
||||||
pub const JUSTIFY: bool = false;
|
#[settable]
|
||||||
|
#[default(false)]
|
||||||
|
pub justify: bool,
|
||||||
|
|
||||||
/// How to determine line breaks.
|
/// How to determine line breaks.
|
||||||
///
|
///
|
||||||
|
|
@ -100,16 +104,20 @@ impl ParNode {
|
||||||
/// very aesthetic example is one
|
/// very aesthetic example is one
|
||||||
/// of them.
|
/// of them.
|
||||||
/// ```
|
/// ```
|
||||||
pub const LINEBREAKS: Smart<Linebreaks> = Smart::Auto;
|
#[settable]
|
||||||
|
#[default]
|
||||||
|
pub linebreaks: Smart<Linebreaks>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Construct for ParNode {
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
// The paragraph constructor is special: It doesn't create a paragraph
|
// The paragraph constructor is special: It doesn't create a paragraph
|
||||||
// node. Instead, it just ensures that the passed content lives in a
|
// node. Instead, it just ensures that the passed content lives in a
|
||||||
// separate paragraph and styles it.
|
// separate paragraph and styles it.
|
||||||
Ok(Content::sequence(vec![
|
Ok(Content::sequence(vec![
|
||||||
ParbreakNode.pack(),
|
ParbreakNode::new().pack(),
|
||||||
args.expect("body")?,
|
args.expect("body")?,
|
||||||
ParbreakNode.pack(),
|
ParbreakNode::new().pack(),
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -136,14 +144,15 @@ impl ParNode {
|
||||||
expand: bool,
|
expand: bool,
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
let mut vt = Vt { world, provider, introspector };
|
let mut vt = Vt { world, provider, introspector };
|
||||||
|
let children = par.children();
|
||||||
|
|
||||||
// Collect all text into one string for BiDi analysis.
|
// Collect all text into one string for BiDi analysis.
|
||||||
let (text, segments) = collect(par, &styles, consecutive);
|
let (text, segments) = collect(&children, &styles, consecutive)?;
|
||||||
|
|
||||||
// Perform BiDi analysis and then prepare paragraph layout by building a
|
// Perform BiDi analysis and then prepare paragraph layout by building a
|
||||||
// representation on which we can do line breaking without layouting
|
// representation on which we can do line breaking without layouting
|
||||||
// each and every line from scratch.
|
// each and every line from scratch.
|
||||||
let p = prepare(&mut vt, par, &text, segments, styles, region)?;
|
let p = prepare(&mut vt, &children, &text, segments, styles, region)?;
|
||||||
|
|
||||||
// Break the paragraph into lines.
|
// Break the paragraph into lines.
|
||||||
let lines = linebreak(&vt, &p, region.x);
|
let lines = linebreak(&vt, &p, region.x);
|
||||||
|
|
@ -165,18 +174,11 @@ impl ParNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for ParNode {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
f.write_str("Par ")?;
|
|
||||||
self.0.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A horizontal alignment.
|
/// A horizontal alignment.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct HorizontalAlign(pub GenAlign);
|
pub struct HorizontalAlign(pub GenAlign);
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
HorizontalAlign,
|
HorizontalAlign,
|
||||||
align: GenAlign => match align.axis() {
|
align: GenAlign => match align.axis() {
|
||||||
Axis::X => Self(align),
|
Axis::X => Self(align),
|
||||||
|
|
@ -201,7 +203,7 @@ pub enum Linebreaks {
|
||||||
Optimized,
|
Optimized,
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
Linebreaks,
|
Linebreaks,
|
||||||
/// Determine the line breaks in a simple first-fit style.
|
/// Determine the line breaks in a simple first-fit style.
|
||||||
"simple" => Self::Simple,
|
"simple" => Self::Simple,
|
||||||
|
|
@ -212,7 +214,13 @@ castable! {
|
||||||
"optimized" => Self::Optimized,
|
"optimized" => Self::Optimized,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Paragraph Break
|
cast_to_value! {
|
||||||
|
v: Linebreaks => Value::from(match v {
|
||||||
|
Linebreaks::Simple => "simple",
|
||||||
|
Linebreaks::Optimized => "optimized",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// A paragraph break.
|
/// A paragraph break.
|
||||||
///
|
///
|
||||||
/// This starts a new paragraph. Especially useful when used within code like
|
/// This starts a new paragraph. Especially useful when used within code like
|
||||||
|
|
@ -232,19 +240,10 @@ castable! {
|
||||||
/// Instead of calling this function, you can insert a blank line into your
|
/// Instead of calling this function, you can insert a blank line into your
|
||||||
/// markup to create a paragraph break.
|
/// markup to create a paragraph break.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Paragraph Break
|
||||||
/// layout
|
/// Category: layout
|
||||||
#[func]
|
#[node(Unlabellable)]
|
||||||
#[capable(Unlabellable)]
|
pub struct ParbreakNode {}
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct ParbreakNode;
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl ParbreakNode {
|
|
||||||
fn construct(_: &Vm, _: &mut Args) -> SourceResult<Content> {
|
|
||||||
Ok(Self.pack())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Unlabellable for ParbreakNode {}
|
impl Unlabellable for ParbreakNode {}
|
||||||
|
|
||||||
|
|
@ -343,7 +342,7 @@ impl Segment<'_> {
|
||||||
match *self {
|
match *self {
|
||||||
Self::Text(len) => len,
|
Self::Text(len) => len,
|
||||||
Self::Spacing(_) => SPACING_REPLACE.len_utf8(),
|
Self::Spacing(_) => SPACING_REPLACE.len_utf8(),
|
||||||
Self::Box(node) if node.width.is_fractional() => SPACING_REPLACE.len_utf8(),
|
Self::Box(node) if node.width().is_fractional() => SPACING_REPLACE.len_utf8(),
|
||||||
Self::Formula(_) | Self::Box(_) => NODE_REPLACE.len_utf8(),
|
Self::Formula(_) | Self::Box(_) => NODE_REPLACE.len_utf8(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -485,21 +484,20 @@ impl<'a> Line<'a> {
|
||||||
/// Collect all text of the paragraph into one string. This also performs
|
/// Collect all text of the paragraph into one string. This also performs
|
||||||
/// string-level preprocessing like case transformations.
|
/// string-level preprocessing like case transformations.
|
||||||
fn collect<'a>(
|
fn collect<'a>(
|
||||||
par: &'a ParNode,
|
children: &'a [Content],
|
||||||
styles: &'a StyleChain<'a>,
|
styles: &'a StyleChain<'a>,
|
||||||
consecutive: bool,
|
consecutive: bool,
|
||||||
) -> (String, Vec<(Segment<'a>, StyleChain<'a>)>) {
|
) -> SourceResult<(String, Vec<(Segment<'a>, StyleChain<'a>)>)> {
|
||||||
let mut full = String::new();
|
let mut full = String::new();
|
||||||
let mut quoter = Quoter::new();
|
let mut quoter = Quoter::new();
|
||||||
let mut segments = vec![];
|
let mut segments = vec![];
|
||||||
let mut iter = par.0.iter().peekable();
|
let mut iter = children.iter().peekable();
|
||||||
|
|
||||||
if consecutive {
|
if consecutive {
|
||||||
let indent = styles.get(ParNode::INDENT);
|
let indent = styles.get(ParNode::INDENT);
|
||||||
if !indent.is_zero()
|
if !indent.is_zero()
|
||||||
&& par
|
&& children
|
||||||
.0
|
.iter()
|
||||||
.items()
|
|
||||||
.find_map(|child| {
|
.find_map(|child| {
|
||||||
if child.with::<dyn Behave>().map_or(false, |behaved| {
|
if child.with::<dyn Behave>().map_or(false, |behaved| {
|
||||||
behaved.behaviour() == Behaviour::Ignorant
|
behaved.behaviour() == Behaviour::Ignorant
|
||||||
|
|
@ -518,24 +516,30 @@ fn collect<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while let Some((child, map)) = iter.next() {
|
while let Some(mut child) = iter.next() {
|
||||||
let styles = styles.chain(map);
|
let outer = styles;
|
||||||
|
let mut styles = *styles;
|
||||||
|
if let Some(node) = child.to::<StyledNode>() {
|
||||||
|
child = Box::leak(Box::new(node.sub()));
|
||||||
|
styles = outer.chain(Box::leak(Box::new(node.map())));
|
||||||
|
}
|
||||||
|
|
||||||
let segment = if child.is::<SpaceNode>() {
|
let segment = if child.is::<SpaceNode>() {
|
||||||
full.push(' ');
|
full.push(' ');
|
||||||
Segment::Text(1)
|
Segment::Text(1)
|
||||||
} else if let Some(node) = child.to::<TextNode>() {
|
} else if let Some(node) = child.to::<TextNode>() {
|
||||||
let prev = full.len();
|
let prev = full.len();
|
||||||
if let Some(case) = styles.get(TextNode::CASE) {
|
if let Some(case) = styles.get(TextNode::CASE) {
|
||||||
full.push_str(&case.apply(&node.0));
|
full.push_str(&case.apply(&node.text()));
|
||||||
} else {
|
} else {
|
||||||
full.push_str(&node.0);
|
full.push_str(&node.text());
|
||||||
}
|
}
|
||||||
Segment::Text(full.len() - prev)
|
Segment::Text(full.len() - prev)
|
||||||
} else if let Some(&node) = child.to::<HNode>() {
|
} else if let Some(node) = child.to::<HNode>() {
|
||||||
full.push(SPACING_REPLACE);
|
full.push(SPACING_REPLACE);
|
||||||
Segment::Spacing(node.amount)
|
Segment::Spacing(node.amount())
|
||||||
} else if let Some(node) = child.to::<LinebreakNode>() {
|
} else if let Some(node) = child.to::<LinebreakNode>() {
|
||||||
let c = if node.justify { '\u{2028}' } else { '\n' };
|
let c = if node.justify() { '\u{2028}' } else { '\n' };
|
||||||
full.push(c);
|
full.push(c);
|
||||||
Segment::Text(c.len_utf8())
|
Segment::Text(c.len_utf8())
|
||||||
} else if let Some(node) = child.to::<SmartQuoteNode>() {
|
} else if let Some(node) = child.to::<SmartQuoteNode>() {
|
||||||
|
|
@ -544,9 +548,9 @@ fn collect<'a>(
|
||||||
let lang = styles.get(TextNode::LANG);
|
let lang = styles.get(TextNode::LANG);
|
||||||
let region = styles.get(TextNode::REGION);
|
let region = styles.get(TextNode::REGION);
|
||||||
let quotes = Quotes::from_lang(lang, region);
|
let quotes = Quotes::from_lang(lang, region);
|
||||||
let peeked = iter.peek().and_then(|(child, _)| {
|
let peeked = iter.peek().and_then(|child| {
|
||||||
if let Some(node) = child.to::<TextNode>() {
|
if let Some(node) = child.to::<TextNode>() {
|
||||||
node.0.chars().next()
|
node.text().chars().next()
|
||||||
} else if child.is::<SmartQuoteNode>() {
|
} else if child.is::<SmartQuoteNode>() {
|
||||||
Some('"')
|
Some('"')
|
||||||
} else if child.is::<SpaceNode>() || child.is::<HNode>() {
|
} else if child.is::<SpaceNode>() || child.is::<HNode>() {
|
||||||
|
|
@ -556,23 +560,25 @@ fn collect<'a>(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
full.push_str(quoter.quote("es, node.double, peeked));
|
full.push_str(quoter.quote("es, node.double(), peeked));
|
||||||
} else {
|
} else {
|
||||||
full.push(if node.double { '"' } else { '\'' });
|
full.push(if node.double() { '"' } else { '\'' });
|
||||||
}
|
}
|
||||||
Segment::Text(full.len() - prev)
|
Segment::Text(full.len() - prev)
|
||||||
} else if let Some(node) = child.to::<FormulaNode>() {
|
} else if let Some(node) = child.to::<FormulaNode>() {
|
||||||
full.push(NODE_REPLACE);
|
full.push(NODE_REPLACE);
|
||||||
Segment::Formula(node)
|
Segment::Formula(node)
|
||||||
} else if let Some(node) = child.to::<BoxNode>() {
|
} else if let Some(node) = child.to::<BoxNode>() {
|
||||||
full.push(if node.width.is_fractional() {
|
full.push(if node.width().is_fractional() {
|
||||||
SPACING_REPLACE
|
SPACING_REPLACE
|
||||||
} else {
|
} else {
|
||||||
NODE_REPLACE
|
NODE_REPLACE
|
||||||
});
|
});
|
||||||
Segment::Box(node)
|
Segment::Box(node)
|
||||||
|
} else if let Some(span) = child.span() {
|
||||||
|
bail!(span, "unexpected document child");
|
||||||
} else {
|
} else {
|
||||||
panic!("unexpected par child: {child:?}");
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(last) = full.chars().last() {
|
if let Some(last) = full.chars().last() {
|
||||||
|
|
@ -591,14 +597,14 @@ fn collect<'a>(
|
||||||
segments.push((segment, styles));
|
segments.push((segment, styles));
|
||||||
}
|
}
|
||||||
|
|
||||||
(full, segments)
|
Ok((full, segments))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prepare paragraph layout by shaping the whole paragraph and layouting all
|
/// Prepare paragraph layout by shaping the whole paragraph and layouting all
|
||||||
/// contained inline-level content.
|
/// contained inline-level content.
|
||||||
fn prepare<'a>(
|
fn prepare<'a>(
|
||||||
vt: &mut Vt,
|
vt: &mut Vt,
|
||||||
par: &'a ParNode,
|
children: &'a [Content],
|
||||||
text: &'a str,
|
text: &'a str,
|
||||||
segments: Vec<(Segment<'a>, StyleChain<'a>)>,
|
segments: Vec<(Segment<'a>, StyleChain<'a>)>,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
|
|
@ -639,7 +645,7 @@ fn prepare<'a>(
|
||||||
items.push(Item::Frame(frame));
|
items.push(Item::Frame(frame));
|
||||||
}
|
}
|
||||||
Segment::Box(node) => {
|
Segment::Box(node) => {
|
||||||
if let Sizing::Fr(v) = node.width {
|
if let Sizing::Fr(v) = node.width() {
|
||||||
items.push(Item::Fractional(v, Some((node, styles))));
|
items.push(Item::Fractional(v, Some((node, styles))));
|
||||||
} else {
|
} else {
|
||||||
let pod = Regions::one(region, Axes::splat(false));
|
let pod = Regions::one(region, Axes::splat(false));
|
||||||
|
|
@ -657,9 +663,9 @@ fn prepare<'a>(
|
||||||
bidi,
|
bidi,
|
||||||
items,
|
items,
|
||||||
styles,
|
styles,
|
||||||
hyphenate: shared_get(styles, &par.0, TextNode::HYPHENATE),
|
hyphenate: shared_get(styles, children, TextNode::HYPHENATE),
|
||||||
lang: shared_get(styles, &par.0, TextNode::LANG),
|
lang: shared_get(styles, children, TextNode::LANG),
|
||||||
align: styles.get(AlignNode::ALIGNS).x.resolve(styles),
|
align: styles.get(AlignNode::ALIGNMENT).x.resolve(styles),
|
||||||
justify: styles.get(ParNode::JUSTIFY),
|
justify: styles.get(ParNode::JUSTIFY),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -722,12 +728,13 @@ fn is_compatible(a: Script, b: Script) -> bool {
|
||||||
/// paragraph.
|
/// paragraph.
|
||||||
fn shared_get<'a, K: Key>(
|
fn shared_get<'a, K: Key>(
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
children: &StyleVec<Content>,
|
children: &[Content],
|
||||||
key: K,
|
key: K,
|
||||||
) -> Option<K::Output<'a>> {
|
) -> Option<K::Output> {
|
||||||
children
|
children
|
||||||
.styles()
|
.iter()
|
||||||
.all(|map| !map.contains(key))
|
.filter_map(|child| child.to::<StyledNode>())
|
||||||
|
.all(|node| !node.map().contains(key))
|
||||||
.then(|| styles.get(key))
|
.then(|| styles.get(key))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// # Place
|
|
||||||
/// Place content at an absolute position.
|
/// Place content at an absolute position.
|
||||||
///
|
///
|
||||||
/// Placed content will not affect the position of other content. Place is
|
/// Placed content will not affect the position of other content. Place is
|
||||||
|
|
@ -22,48 +21,41 @@ use crate::prelude::*;
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Place
|
||||||
/// - alignment: `Axes<Option<GenAlign>>` (positional)
|
/// Category: layout
|
||||||
/// Relative to which position in the parent container to place the content.
|
#[node(Layout, Behave)]
|
||||||
///
|
pub struct PlaceNode {
|
||||||
/// When an axis of the page is `{auto}` sized, all alignments relative to that
|
/// Relative to which position in the parent container to place the content.
|
||||||
/// axis will be ignored, instead, the item will be placed in the origin of the
|
///
|
||||||
/// axis.
|
/// When an axis of the page is `{auto}` sized, all alignments relative to that
|
||||||
///
|
/// axis will be ignored, instead, the item will be placed in the origin of the
|
||||||
/// - body: `Content` (positional, required)
|
/// axis.
|
||||||
/// The content to place.
|
#[positional]
|
||||||
///
|
#[default(Axes::with_x(Some(GenAlign::Start)))]
|
||||||
/// - dx: `Rel<Length>` (named)
|
pub alignment: Axes<Option<GenAlign>>,
|
||||||
/// The horizontal displacement of the placed content.
|
|
||||||
///
|
|
||||||
/// ```example
|
|
||||||
/// #set page(height: 100pt)
|
|
||||||
/// #for i in range(16) {
|
|
||||||
/// let amount = i * 4pt
|
|
||||||
/// place(center, dx: amount - 32pt, dy: amount)[A]
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// - dy: `Rel<Length>` (named)
|
|
||||||
/// The vertical displacement of the placed content.
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// layout
|
|
||||||
#[func]
|
|
||||||
#[capable(Layout, Behave)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct PlaceNode(pub Content, bool);
|
|
||||||
|
|
||||||
#[node]
|
/// The content to place.
|
||||||
impl PlaceNode {
|
#[positional]
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
#[required]
|
||||||
let aligns = args.find()?.unwrap_or(Axes::with_x(Some(GenAlign::Start)));
|
pub body: Content,
|
||||||
let dx = args.named("dx")?.unwrap_or_default();
|
|
||||||
let dy = args.named("dy")?.unwrap_or_default();
|
/// The horizontal displacement of the placed content.
|
||||||
let body = args.expect::<Content>("body")?;
|
///
|
||||||
let out_of_flow = aligns.y.is_some();
|
/// ```example
|
||||||
Ok(Self(body.moved(Axes::new(dx, dy)).aligned(aligns), out_of_flow).pack())
|
/// #set page(height: 100pt)
|
||||||
}
|
/// #for i in range(16) {
|
||||||
|
/// let amount = i * 4pt
|
||||||
|
/// place(center, dx: amount - 32pt, dy: amount)[A]
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[named]
|
||||||
|
#[default]
|
||||||
|
pub dx: Rel<Length>,
|
||||||
|
|
||||||
|
/// The vertical displacement of the placed content.
|
||||||
|
#[named]
|
||||||
|
#[default]
|
||||||
|
pub dy: Rel<Length>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for PlaceNode {
|
impl Layout for PlaceNode {
|
||||||
|
|
@ -83,7 +75,12 @@ impl Layout for PlaceNode {
|
||||||
Regions::one(regions.base(), expand)
|
Regions::one(regions.base(), expand)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut frame = self.0.layout(vt, styles, pod)?.into_frame();
|
let child = self
|
||||||
|
.body()
|
||||||
|
.moved(Axes::new(self.dx(), self.dy()))
|
||||||
|
.aligned(self.alignment());
|
||||||
|
|
||||||
|
let mut frame = child.layout(vt, styles, pod)?.into_frame();
|
||||||
|
|
||||||
// If expansion is off, zero all sizes so that we don't take up any
|
// If expansion is off, zero all sizes so that we don't take up any
|
||||||
// space in our parent. Otherwise, respect the expand settings.
|
// space in our parent. Otherwise, respect the expand settings.
|
||||||
|
|
@ -99,7 +96,7 @@ impl PlaceNode {
|
||||||
/// origin. Instead of relative to the parent's current flow/cursor
|
/// origin. Instead of relative to the parent's current flow/cursor
|
||||||
/// position.
|
/// position.
|
||||||
pub fn out_of_flow(&self) -> bool {
|
pub fn out_of_flow(&self) -> bool {
|
||||||
self.1
|
self.alignment().y.is_some()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ use crate::prelude::*;
|
||||||
|
|
||||||
use super::AlignNode;
|
use super::AlignNode;
|
||||||
|
|
||||||
/// # Repeat
|
|
||||||
/// Repeats content to the available space.
|
/// Repeats content to the available space.
|
||||||
///
|
///
|
||||||
/// This can be useful when implementing a custom index, reference, or outline.
|
/// This can be useful when implementing a custom index, reference, or outline.
|
||||||
|
|
@ -22,22 +21,14 @@ use super::AlignNode;
|
||||||
/// ]
|
/// ]
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Repeat
|
||||||
/// - body: `Content` (positional, required)
|
/// Category: layout
|
||||||
/// The content to repeat.
|
#[node(Layout)]
|
||||||
///
|
pub struct RepeatNode {
|
||||||
/// ## Category
|
/// The content to repeat.
|
||||||
/// layout
|
#[positional]
|
||||||
#[func]
|
#[required]
|
||||||
#[capable(Layout)]
|
pub body: Content,
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct RepeatNode(pub Content);
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl RepeatNode {
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
Ok(Self(args.expect("body")?).pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for RepeatNode {
|
impl Layout for RepeatNode {
|
||||||
|
|
@ -48,8 +39,8 @@ impl Layout for RepeatNode {
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
let pod = Regions::one(regions.size, Axes::new(false, false));
|
let pod = Regions::one(regions.size, Axes::new(false, false));
|
||||||
let piece = self.0.layout(vt, styles, pod)?.into_frame();
|
let piece = self.body().layout(vt, styles, pod)?.into_frame();
|
||||||
let align = styles.get(AlignNode::ALIGNS).x.resolve(styles);
|
let align = styles.get(AlignNode::ALIGNMENT).x.resolve(styles);
|
||||||
|
|
||||||
let fill = regions.size.x;
|
let fill = regions.size.x;
|
||||||
let width = piece.width();
|
let width = piece.width();
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ use std::cmp::Ordering;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// # Spacing (H)
|
|
||||||
/// Insert horizontal spacing into a paragraph.
|
/// Insert horizontal spacing into a paragraph.
|
||||||
///
|
///
|
||||||
/// The spacing can be absolute, relative, or fractional. In the last case, the
|
/// The spacing can be absolute, relative, or fractional. In the last case, the
|
||||||
|
|
@ -20,64 +19,39 @@ use crate::prelude::*;
|
||||||
/// In [mathematical formulas]($category/math), you can additionally use these
|
/// In [mathematical formulas]($category/math), you can additionally use these
|
||||||
/// constants to add spacing between elements: `thin`, `med`, `thick`, `quad`.
|
/// constants to add spacing between elements: `thin`, `med`, `thick`, `quad`.
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Spacing (H)
|
||||||
/// - amount: `Spacing` (positional, required)
|
/// Category: layout
|
||||||
/// How much spacing to insert.
|
#[node(Behave)]
|
||||||
///
|
|
||||||
/// - weak: `bool` (named)
|
|
||||||
/// If true, the spacing collapses at the start or end of a paragraph.
|
|
||||||
/// Moreover, from multiple adjacent weak spacings all but the largest one
|
|
||||||
/// collapse.
|
|
||||||
///
|
|
||||||
/// ```example
|
|
||||||
/// #h(1cm, weak: true)
|
|
||||||
/// We identified a group of
|
|
||||||
/// _weak_ specimens that fail to
|
|
||||||
/// manifest in most cases. However,
|
|
||||||
/// when #h(8pt, weak: true)
|
|
||||||
/// supported
|
|
||||||
/// #h(8pt, weak: true) on both
|
|
||||||
/// sides, they do show up.
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// layout
|
|
||||||
#[func]
|
|
||||||
#[capable(Behave)]
|
|
||||||
#[derive(Debug, Copy, Clone, Hash)]
|
|
||||||
pub struct HNode {
|
pub struct HNode {
|
||||||
/// The amount of horizontal spacing.
|
/// How much spacing to insert.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
pub amount: Spacing,
|
pub amount: Spacing,
|
||||||
/// Whether the node is weak, see also [`Behaviour`].
|
|
||||||
|
/// If true, the spacing collapses at the start or end of a paragraph.
|
||||||
|
/// Moreover, from multiple adjacent weak spacings all but the largest one
|
||||||
|
/// collapse.
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// #h(1cm, weak: true)
|
||||||
|
/// We identified a group of
|
||||||
|
/// _weak_ specimens that fail to
|
||||||
|
/// manifest in most cases. However,
|
||||||
|
/// when #h(8pt, weak: true)
|
||||||
|
/// supported
|
||||||
|
/// #h(8pt, weak: true) on both
|
||||||
|
/// sides, they do show up.
|
||||||
|
/// ```
|
||||||
|
#[named]
|
||||||
|
#[default(false)]
|
||||||
pub weak: bool,
|
pub weak: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl HNode {
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
let amount = args.expect("amount")?;
|
|
||||||
let weak = args.named("weak")?.unwrap_or(false);
|
|
||||||
Ok(Self { amount, weak }.pack())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HNode {
|
|
||||||
/// Normal strong spacing.
|
|
||||||
pub fn strong(amount: impl Into<Spacing>) -> Self {
|
|
||||||
Self { amount: amount.into(), weak: false }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// User-created weak spacing.
|
|
||||||
pub fn weak(amount: impl Into<Spacing>) -> Self {
|
|
||||||
Self { amount: amount.into(), weak: true }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Behave for HNode {
|
impl Behave for HNode {
|
||||||
fn behaviour(&self) -> Behaviour {
|
fn behaviour(&self) -> Behaviour {
|
||||||
if self.amount.is_fractional() {
|
if self.amount().is_fractional() {
|
||||||
Behaviour::Destructive
|
Behaviour::Destructive
|
||||||
} else if self.weak {
|
} else if self.weak() {
|
||||||
Behaviour::Weak(1)
|
Behaviour::Weak(1)
|
||||||
} else {
|
} else {
|
||||||
Behaviour::Ignorant
|
Behaviour::Ignorant
|
||||||
|
|
@ -86,11 +60,10 @@ impl Behave for HNode {
|
||||||
|
|
||||||
fn larger(&self, prev: &Content) -> bool {
|
fn larger(&self, prev: &Content) -> bool {
|
||||||
let Some(prev) = prev.to::<Self>() else { return false };
|
let Some(prev) = prev.to::<Self>() else { return false };
|
||||||
self.amount > prev.amount
|
self.amount() > prev.amount()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Spacing (V)
|
|
||||||
/// Insert vertical spacing into a flow of blocks.
|
/// Insert vertical spacing into a flow of blocks.
|
||||||
///
|
///
|
||||||
/// The spacing can be absolute, relative, or fractional. In the last case,
|
/// The spacing can be absolute, relative, or fractional. In the last case,
|
||||||
|
|
@ -130,20 +103,24 @@ impl Behave for HNode {
|
||||||
/// #v(4pt, weak: true)
|
/// #v(4pt, weak: true)
|
||||||
/// The proof is simple:
|
/// The proof is simple:
|
||||||
/// ```
|
/// ```
|
||||||
/// ## Category
|
///
|
||||||
/// layout
|
/// Display: Spacing (V)
|
||||||
#[func]
|
/// Category: layout
|
||||||
#[capable(Behave)]
|
#[node(Construct, Behave)]
|
||||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd)]
|
|
||||||
pub struct VNode {
|
pub struct VNode {
|
||||||
/// The amount of vertical spacing.
|
/// The amount of vertical spacing.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
pub amount: Spacing,
|
pub amount: Spacing,
|
||||||
|
|
||||||
/// The node's weakness level, see also [`Behaviour`].
|
/// The node's weakness level, see also [`Behaviour`].
|
||||||
pub weakness: u8,
|
#[named]
|
||||||
|
#[skip]
|
||||||
|
#[default]
|
||||||
|
pub weakness: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node]
|
impl Construct for VNode {
|
||||||
impl VNode {
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
let amount = args.expect("spacing")?;
|
let amount = args.expect("spacing")?;
|
||||||
let node = if args.named("weak")?.unwrap_or(false) {
|
let node = if args.named("weak")?.unwrap_or(false) {
|
||||||
|
|
@ -158,36 +135,36 @@ impl VNode {
|
||||||
impl VNode {
|
impl VNode {
|
||||||
/// Normal strong spacing.
|
/// Normal strong spacing.
|
||||||
pub fn strong(amount: Spacing) -> Self {
|
pub fn strong(amount: Spacing) -> Self {
|
||||||
Self { amount, weakness: 0 }
|
Self::new(amount).with_weakness(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// User-created weak spacing.
|
/// User-created weak spacing.
|
||||||
pub fn weak(amount: Spacing) -> Self {
|
pub fn weak(amount: Spacing) -> Self {
|
||||||
Self { amount, weakness: 1 }
|
Self::new(amount).with_weakness(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Weak spacing with list attach weakness.
|
/// Weak spacing with list attach weakness.
|
||||||
pub fn list_attach(amount: Spacing) -> Self {
|
pub fn list_attach(amount: Spacing) -> Self {
|
||||||
Self { amount, weakness: 2 }
|
Self::new(amount).with_weakness(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Weak spacing with BlockNode::ABOVE/BELOW weakness.
|
/// Weak spacing with BlockNode::ABOVE/BELOW weakness.
|
||||||
pub fn block_around(amount: Spacing) -> Self {
|
pub fn block_around(amount: Spacing) -> Self {
|
||||||
Self { amount, weakness: 3 }
|
Self::new(amount).with_weakness(3)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Weak spacing with BlockNode::SPACING weakness.
|
/// Weak spacing with BlockNode::SPACING weakness.
|
||||||
pub fn block_spacing(amount: Spacing) -> Self {
|
pub fn block_spacing(amount: Spacing) -> Self {
|
||||||
Self { amount, weakness: 4 }
|
Self::new(amount).with_weakness(4)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Behave for VNode {
|
impl Behave for VNode {
|
||||||
fn behaviour(&self) -> Behaviour {
|
fn behaviour(&self) -> Behaviour {
|
||||||
if self.amount.is_fractional() {
|
if self.amount().is_fractional() {
|
||||||
Behaviour::Destructive
|
Behaviour::Destructive
|
||||||
} else if self.weakness > 0 {
|
} else if self.weakness() > 0 {
|
||||||
Behaviour::Weak(self.weakness)
|
Behaviour::Weak(self.weakness())
|
||||||
} else {
|
} else {
|
||||||
Behaviour::Ignorant
|
Behaviour::Ignorant
|
||||||
}
|
}
|
||||||
|
|
@ -195,10 +172,15 @@ impl Behave for VNode {
|
||||||
|
|
||||||
fn larger(&self, prev: &Content) -> bool {
|
fn larger(&self, prev: &Content) -> bool {
|
||||||
let Some(prev) = prev.to::<Self>() else { return false };
|
let Some(prev) = prev.to::<Self>() else { return false };
|
||||||
self.amount > prev.amount
|
self.amount() > prev.amount()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
VNode,
|
||||||
|
v: Content => v.to::<Self>().cloned().ok_or("expected vnode")?,
|
||||||
|
}
|
||||||
|
|
||||||
/// 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 {
|
||||||
|
|
@ -214,22 +196,6 @@ impl Spacing {
|
||||||
pub fn is_fractional(self) -> bool {
|
pub fn is_fractional(self) -> bool {
|
||||||
matches!(self, Self::Fr(_))
|
matches!(self, Self::Fr(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encode into a value.
|
|
||||||
pub fn encode(self) -> Value {
|
|
||||||
match self {
|
|
||||||
Self::Rel(rel) => {
|
|
||||||
if rel.rel.is_zero() {
|
|
||||||
Value::Length(rel.abs)
|
|
||||||
} else if rel.abs.is_zero() {
|
|
||||||
Value::Ratio(rel.rel)
|
|
||||||
} else {
|
|
||||||
Value::Relative(rel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::Fr(fr) => Value::Fraction(fr),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Abs> for Spacing {
|
impl From<Abs> for Spacing {
|
||||||
|
|
@ -244,6 +210,12 @@ impl From<Em> for Spacing {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Fr> for Spacing {
|
||||||
|
fn from(fr: Fr) -> Self {
|
||||||
|
Self::Fr(fr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PartialOrd for Spacing {
|
impl PartialOrd for Spacing {
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
|
|
@ -254,8 +226,23 @@ impl PartialOrd for Spacing {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
Spacing,
|
Spacing,
|
||||||
v: Rel<Length> => Self::Rel(v),
|
v: Rel<Length> => Self::Rel(v),
|
||||||
v: Fr => Self::Fr(v),
|
v: Fr => Self::Fr(v),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: Spacing => match v {
|
||||||
|
Spacing::Rel(rel) => {
|
||||||
|
if rel.rel.is_zero() {
|
||||||
|
Value::Length(rel.abs)
|
||||||
|
} else if rel.abs.is_zero() {
|
||||||
|
Value::Ratio(rel.rel)
|
||||||
|
} else {
|
||||||
|
Value::Relative(rel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacing::Fr(fr) => Value::Fraction(fr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ use typst::model::StyledNode;
|
||||||
use super::{AlignNode, Spacing};
|
use super::{AlignNode, Spacing};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// # Stack
|
|
||||||
/// Arrange content and spacing horizontally or vertically.
|
/// Arrange content and spacing horizontally or vertically.
|
||||||
///
|
///
|
||||||
/// The stack places a list of items along an axis, with optional spacing
|
/// The stack places a list of items along an axis, with optional spacing
|
||||||
|
|
@ -19,45 +18,28 @@ use crate::prelude::*;
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Stack
|
||||||
/// - items: `StackChild` (positional, variadic)
|
/// Category: layout
|
||||||
/// The items to stack along an axis.
|
#[node(Layout)]
|
||||||
///
|
|
||||||
/// - dir: `Dir` (named)
|
|
||||||
/// The direction along which the items are stacked. Possible values are:
|
|
||||||
///
|
|
||||||
/// - `{ltr}`: Left to right.
|
|
||||||
/// - `{rtl}`: Right to left.
|
|
||||||
/// - `{ttb}`: Top to bottom.
|
|
||||||
/// - `{btt}`: Bottom to top.
|
|
||||||
///
|
|
||||||
/// - spacing: `Spacing` (named)
|
|
||||||
/// Spacing to insert between items where no explicit spacing was provided.
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// layout
|
|
||||||
#[func]
|
|
||||||
#[capable(Layout)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct StackNode {
|
pub struct StackNode {
|
||||||
/// The stacking direction.
|
/// The childfren to stack along the axis.
|
||||||
pub dir: Dir,
|
#[variadic]
|
||||||
/// The spacing between non-spacing children.
|
|
||||||
pub spacing: Option<Spacing>,
|
|
||||||
/// The children to be stacked.
|
|
||||||
pub children: Vec<StackChild>,
|
pub children: Vec<StackChild>,
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
/// The direction along which the items are stacked. Possible values are:
|
||||||
impl StackNode {
|
///
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
/// - `{ltr}`: Left to right.
|
||||||
Ok(Self {
|
/// - `{rtl}`: Right to left.
|
||||||
dir: args.named("dir")?.unwrap_or(Dir::TTB),
|
/// - `{ttb}`: Top to bottom.
|
||||||
spacing: args.named("spacing")?,
|
/// - `{btt}`: Bottom to top.
|
||||||
children: args.all()?,
|
#[named]
|
||||||
}
|
#[default(Dir::TTB)]
|
||||||
.pack())
|
pub dir: Dir,
|
||||||
}
|
|
||||||
|
/// Spacing to insert between items where no explicit spacing was provided.
|
||||||
|
#[named]
|
||||||
|
#[default]
|
||||||
|
pub spacing: Option<Spacing>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for StackNode {
|
impl Layout for StackNode {
|
||||||
|
|
@ -67,15 +49,16 @@ impl Layout for StackNode {
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
let mut layouter = StackLayouter::new(self.dir, regions, styles);
|
let mut layouter = StackLayouter::new(self.dir(), regions, styles);
|
||||||
|
|
||||||
// Spacing to insert before the next block.
|
// Spacing to insert before the next block.
|
||||||
|
let spacing = self.spacing();
|
||||||
let mut deferred = None;
|
let mut deferred = None;
|
||||||
|
|
||||||
for child in &self.children {
|
for child in self.children() {
|
||||||
match child {
|
match child {
|
||||||
StackChild::Spacing(kind) => {
|
StackChild::Spacing(kind) => {
|
||||||
layouter.layout_spacing(*kind);
|
layouter.layout_spacing(kind);
|
||||||
deferred = None;
|
deferred = None;
|
||||||
}
|
}
|
||||||
StackChild::Block(block) => {
|
StackChild::Block(block) => {
|
||||||
|
|
@ -83,8 +66,8 @@ impl Layout for StackNode {
|
||||||
layouter.layout_spacing(kind);
|
layouter.layout_spacing(kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
layouter.layout_block(vt, block, styles)?;
|
layouter.layout_block(vt, &block, styles)?;
|
||||||
deferred = self.spacing;
|
deferred = spacing;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -111,10 +94,17 @@ impl Debug for StackChild {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
StackChild,
|
StackChild,
|
||||||
spacing: Spacing => Self::Spacing(spacing),
|
v: Spacing => Self::Spacing(v),
|
||||||
content: Content => Self::Block(content),
|
v: Content => Self::Block(v),
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: StackChild => match v {
|
||||||
|
StackChild::Spacing(spacing) => spacing.into(),
|
||||||
|
StackChild::Block(content) => content.into(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs stack layout.
|
/// Performs stack layout.
|
||||||
|
|
@ -212,9 +202,9 @@ impl<'a> StackLayouter<'a> {
|
||||||
// Block-axis alignment of the `AlignNode` is respected
|
// Block-axis alignment of the `AlignNode` is respected
|
||||||
// by the stack node.
|
// by the stack node.
|
||||||
let aligns = if let Some(styled) = block.to::<StyledNode>() {
|
let aligns = if let Some(styled) = block.to::<StyledNode>() {
|
||||||
styles.chain(&styled.map).get(AlignNode::ALIGNS)
|
styles.chain(&styled.map()).get(AlignNode::ALIGNMENT)
|
||||||
} else {
|
} else {
|
||||||
styles.get(AlignNode::ALIGNS)
|
styles.get(AlignNode::ALIGNMENT)
|
||||||
};
|
};
|
||||||
|
|
||||||
let aligns = aligns.resolve(styles);
|
let aligns = aligns.resolve(styles);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use crate::layout::{AlignNode, GridLayouter, Sizing, TrackSizings};
|
use crate::layout::{AlignNode, GridLayouter, TrackSizings};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// # Table
|
|
||||||
/// A table of items.
|
/// A table of items.
|
||||||
///
|
///
|
||||||
/// Tables are used to arrange content in cells. Cells can contain arbitrary
|
/// Tables are used to arrange content in cells. Cells can contain arbitrary
|
||||||
|
|
@ -31,47 +30,46 @@ use crate::prelude::*;
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// ## Parameters
|
||||||
/// - cells: `Content` (positional, variadic)
|
|
||||||
/// The contents of the table cells.
|
|
||||||
///
|
|
||||||
/// - rows: `TrackSizings` (named)
|
|
||||||
/// Defines the row sizes.
|
|
||||||
/// See the [grid documentation]($func/grid) for more information on track
|
|
||||||
/// sizing.
|
|
||||||
///
|
|
||||||
/// - columns: `TrackSizings` (named)
|
|
||||||
/// Defines the column sizes.
|
|
||||||
/// See the [grid documentation]($func/grid) for more information on track
|
|
||||||
/// sizing.
|
|
||||||
///
|
|
||||||
/// - gutter: `TrackSizings` (named)
|
/// - gutter: `TrackSizings` (named)
|
||||||
/// Defines the gaps between rows & columns.
|
/// Defines the gaps between rows & columns.
|
||||||
/// See the [grid documentation]($func/grid) for more information on gutters.
|
/// See the [grid documentation]($func/grid) for more information on gutters.
|
||||||
///
|
///
|
||||||
/// - column-gutter: `TrackSizings` (named)
|
/// Display: Table
|
||||||
/// Defines the gaps between columns. Takes precedence over `gutter`.
|
/// Category: layout
|
||||||
/// See the [grid documentation]($func/grid) for more information on gutters.
|
#[node(Layout)]
|
||||||
///
|
|
||||||
/// - row-gutter: `TrackSizings` (named)
|
|
||||||
/// Defines the gaps between rows. Takes precedence over `gutter`.
|
|
||||||
/// See the [grid documentation]($func/grid) for more information on gutters.
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// layout
|
|
||||||
#[func]
|
|
||||||
#[capable(Layout)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct TableNode {
|
pub struct TableNode {
|
||||||
/// Defines sizing for content rows and columns.
|
/// The contents of the table cells.
|
||||||
pub tracks: Axes<Vec<Sizing>>,
|
#[variadic]
|
||||||
/// Defines sizing of gutter rows and columns between content.
|
|
||||||
pub gutter: Axes<Vec<Sizing>>,
|
|
||||||
/// The content to be arranged in the table.
|
|
||||||
pub cells: Vec<Content>,
|
pub cells: Vec<Content>,
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
/// Defines the column sizes.
|
||||||
impl TableNode {
|
/// See the [grid documentation]($func/grid) for more information on track
|
||||||
|
/// sizing.
|
||||||
|
#[named]
|
||||||
|
#[default]
|
||||||
|
pub columns: TrackSizings,
|
||||||
|
|
||||||
|
/// Defines the row sizes.
|
||||||
|
/// See the [grid documentation]($func/grid) for more information on track
|
||||||
|
/// sizing.
|
||||||
|
#[named]
|
||||||
|
#[default]
|
||||||
|
pub rows: TrackSizings,
|
||||||
|
|
||||||
|
/// Defines the gaps between columns. Takes precedence over `gutter`.
|
||||||
|
/// See the [grid documentation]($func/grid) for more information on gutters.
|
||||||
|
#[named]
|
||||||
|
#[shorthand(gutter)]
|
||||||
|
#[default]
|
||||||
|
pub column_gutter: TrackSizings,
|
||||||
|
|
||||||
|
/// Defines the gaps between rows. Takes precedence over `gutter`.
|
||||||
|
/// See the [grid documentation]($func/grid) for more information on gutters.
|
||||||
|
#[named]
|
||||||
|
#[shorthand(gutter)]
|
||||||
|
#[default]
|
||||||
|
pub row_gutter: TrackSizings,
|
||||||
|
|
||||||
/// How to fill the cells.
|
/// How to fill the cells.
|
||||||
///
|
///
|
||||||
/// This can be a color or a function that returns a color. The function is
|
/// This can be a color or a function that returns a color. The function is
|
||||||
|
|
@ -92,58 +90,35 @@ impl TableNode {
|
||||||
/// [Profit:], [500 €], [1000 €], [1500 €],
|
/// [Profit:], [500 €], [1000 €], [1500 €],
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[property(referenced)]
|
#[settable]
|
||||||
pub const FILL: Celled<Option<Paint>> = Celled::Value(None);
|
#[default]
|
||||||
|
pub fill: Celled<Option<Paint>>,
|
||||||
|
|
||||||
/// How to align the cell's content.
|
/// How to align the cell's content.
|
||||||
///
|
///
|
||||||
/// This can either be a single alignment or a function that returns an
|
/// This can either be a single alignment or a function that returns an
|
||||||
/// alignment. The function is passed the cell's column and row index,
|
/// alignment. The function is passed the cell's column and row index,
|
||||||
/// starting at zero. If set to `{auto}`, the outer alignment is used.
|
/// starting at zero. If set to `{auto}`, the outer alignment is used.
|
||||||
#[property(referenced)]
|
#[settable]
|
||||||
pub const ALIGN: Celled<Smart<Axes<Option<GenAlign>>>> = Celled::Value(Smart::Auto);
|
#[default]
|
||||||
|
pub align: Celled<Smart<Axes<Option<GenAlign>>>>,
|
||||||
|
|
||||||
/// How to stroke the cells.
|
/// How to stroke the cells.
|
||||||
///
|
///
|
||||||
/// This can be a color, a stroke width, both, or `{none}` to disable
|
/// This can be a color, a stroke width, both, or `{none}` to disable
|
||||||
/// the stroke.
|
/// the stroke.
|
||||||
#[property(resolve, fold)]
|
#[settable]
|
||||||
pub const STROKE: Option<PartialStroke> = Some(PartialStroke::default());
|
#[resolve]
|
||||||
|
#[fold]
|
||||||
|
#[default(Some(PartialStroke::default()))]
|
||||||
|
pub stroke: Option<PartialStroke>,
|
||||||
|
|
||||||
/// How much to pad the cells's content.
|
/// How much to pad the cells's content.
|
||||||
///
|
///
|
||||||
/// The default value is `{5pt}`.
|
/// The default value is `{5pt}`.
|
||||||
pub const INSET: Rel<Length> = Abs::pt(5.0).into();
|
#[settable]
|
||||||
|
#[default(Abs::pt(5.0).into())]
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
pub inset: Rel<Length>,
|
||||||
let TrackSizings(columns) = args.named("columns")?.unwrap_or_default();
|
|
||||||
let TrackSizings(rows) = args.named("rows")?.unwrap_or_default();
|
|
||||||
let TrackSizings(base_gutter) = args.named("gutter")?.unwrap_or_default();
|
|
||||||
let column_gutter = args.named("column-gutter")?.map(|TrackSizings(v)| v);
|
|
||||||
let row_gutter = args.named("row-gutter")?.map(|TrackSizings(v)| v);
|
|
||||||
Ok(Self {
|
|
||||||
tracks: Axes::new(columns, rows),
|
|
||||||
gutter: Axes::new(
|
|
||||||
column_gutter.unwrap_or_else(|| base_gutter.clone()),
|
|
||||||
row_gutter.unwrap_or(base_gutter),
|
|
||||||
),
|
|
||||||
cells: args.all()?,
|
|
||||||
}
|
|
||||||
.pack())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field(&self, name: &str) -> Option<Value> {
|
|
||||||
match name {
|
|
||||||
"columns" => Some(Sizing::encode_slice(&self.tracks.x)),
|
|
||||||
"rows" => Some(Sizing::encode_slice(&self.tracks.y)),
|
|
||||||
"column-gutter" => Some(Sizing::encode_slice(&self.gutter.x)),
|
|
||||||
"row-gutter" => Some(Sizing::encode_slice(&self.gutter.y)),
|
|
||||||
"cells" => Some(Value::Array(
|
|
||||||
self.cells.iter().cloned().map(Value::Content).collect(),
|
|
||||||
)),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for TableNode {
|
impl Layout for TableNode {
|
||||||
|
|
@ -156,11 +131,12 @@ impl Layout for TableNode {
|
||||||
let inset = styles.get(Self::INSET);
|
let inset = styles.get(Self::INSET);
|
||||||
let align = styles.get(Self::ALIGN);
|
let align = styles.get(Self::ALIGN);
|
||||||
|
|
||||||
let cols = self.tracks.x.len().max(1);
|
let tracks = Axes::new(self.columns().0, self.rows().0);
|
||||||
|
let gutter = Axes::new(self.column_gutter().0, self.row_gutter().0);
|
||||||
|
let cols = tracks.x.len().max(1);
|
||||||
let cells: Vec<_> = self
|
let cells: Vec<_> = self
|
||||||
.cells
|
.cells()
|
||||||
.iter()
|
.into_iter()
|
||||||
.cloned()
|
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, child)| {
|
.map(|(i, child)| {
|
||||||
let mut child = child.padded(Sides::splat(inset));
|
let mut child = child.padded(Sides::splat(inset));
|
||||||
|
|
@ -168,7 +144,7 @@ impl Layout for TableNode {
|
||||||
let x = i % cols;
|
let x = i % cols;
|
||||||
let y = i / cols;
|
let y = i / cols;
|
||||||
if let Smart::Custom(alignment) = align.resolve(vt, x, y)? {
|
if let Smart::Custom(alignment) = align.resolve(vt, x, y)? {
|
||||||
child = child.styled(AlignNode::ALIGNS, alignment)
|
child = child.styled(AlignNode::ALIGNMENT, alignment)
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(child)
|
Ok(child)
|
||||||
|
|
@ -181,8 +157,8 @@ impl Layout for TableNode {
|
||||||
// Prepare grid layout by unifying content and gutter tracks.
|
// Prepare grid layout by unifying content and gutter tracks.
|
||||||
let layouter = GridLayouter::new(
|
let layouter = GridLayouter::new(
|
||||||
vt,
|
vt,
|
||||||
self.tracks.as_deref(),
|
tracks.as_deref(),
|
||||||
self.gutter.as_deref(),
|
gutter.as_deref(),
|
||||||
&cells,
|
&cells,
|
||||||
regions,
|
regions,
|
||||||
styles,
|
styles,
|
||||||
|
|
@ -269,6 +245,12 @@ impl<T: Cast + Clone> Celled<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: Default> Default for Celled<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Value(T::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Cast> Cast for Celled<T> {
|
impl<T: Cast> Cast for Celled<T> {
|
||||||
fn is(value: &Value) -> bool {
|
fn is(value: &Value) -> bool {
|
||||||
matches!(value, Value::Func(_)) || T::is(value)
|
matches!(value, Value::Func(_)) || T::is(value)
|
||||||
|
|
@ -286,3 +268,12 @@ impl<T: Cast> Cast for Celled<T> {
|
||||||
T::describe() + CastInfo::Type("function")
|
T::describe() + CastInfo::Type("function")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: Into<Value>> From<Celled<T>> for Value {
|
||||||
|
fn from(celled: Celled<T>) -> Self {
|
||||||
|
match celled {
|
||||||
|
Celled::Value(value) => value.into(),
|
||||||
|
Celled::Func(func) => func.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
use crate::layout::{BlockNode, GridNode, HNode, ParNode, Sizing, Spacing};
|
use crate::layout::{BlockNode, GridLayouter, HNode, ParNode, Sizing, Spacing};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::text::{SpaceNode, TextNode};
|
use crate::text::{SpaceNode, TextNode};
|
||||||
|
|
||||||
/// # Term List
|
|
||||||
/// A list of terms and their descriptions.
|
/// A list of terms and their descriptions.
|
||||||
///
|
///
|
||||||
/// Displays a sequence of terms and their descriptions vertically. When the
|
/// Displays a sequence of terms and their descriptions vertically. When the
|
||||||
|
|
@ -20,55 +19,49 @@ use crate::text::{SpaceNode, TextNode};
|
||||||
/// between two adjacent letters.
|
/// between two adjacent letters.
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Term List
|
||||||
/// - items: `Content` (positional, variadic)
|
/// Category: layout
|
||||||
/// The term list's children.
|
#[node(Layout)]
|
||||||
///
|
|
||||||
/// When using the term list syntax, adjacent items are automatically
|
|
||||||
/// collected into term lists, even through constructs like for loops.
|
|
||||||
///
|
|
||||||
/// ```example
|
|
||||||
/// #for year, product in (
|
|
||||||
/// "1978": "TeX",
|
|
||||||
/// "1984": "LaTeX",
|
|
||||||
/// "2019": "Typst",
|
|
||||||
/// ) [/ #product: Born in #year.]
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// - tight: `bool` (named)
|
|
||||||
/// If this is `{false}`, the items are spaced apart with [term list
|
|
||||||
/// spacing]($func/terms.spacing). If it is `{true}`, they use normal
|
|
||||||
/// [leading]($func/par.leading) instead. This makes the term list more
|
|
||||||
/// compact, which can look better if the items are short.
|
|
||||||
///
|
|
||||||
/// ```example
|
|
||||||
/// / Fact: If a term list has a lot
|
|
||||||
/// of text, and maybe other inline
|
|
||||||
/// content, it should not be tight
|
|
||||||
/// anymore.
|
|
||||||
///
|
|
||||||
/// / Tip: To make it wide, simply
|
|
||||||
/// insert a blank line between the
|
|
||||||
/// items.
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// layout
|
|
||||||
#[func]
|
|
||||||
#[capable(Layout)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct TermsNode {
|
pub struct TermsNode {
|
||||||
/// If true, the items are separated by leading instead of list spacing.
|
/// The term list's children.
|
||||||
pub tight: bool,
|
///
|
||||||
/// The individual bulleted or numbered items.
|
/// When using the term list syntax, adjacent items are automatically
|
||||||
pub items: StyleVec<TermItem>,
|
/// collected into term lists, even through constructs like for loops.
|
||||||
}
|
///
|
||||||
|
/// ```example
|
||||||
|
/// #for year, product in (
|
||||||
|
/// "1978": "TeX",
|
||||||
|
/// "1984": "LaTeX",
|
||||||
|
/// "2019": "Typst",
|
||||||
|
/// ) [/ #product: Born in #year.]
|
||||||
|
/// ```
|
||||||
|
#[variadic]
|
||||||
|
pub items: Vec<TermItem>,
|
||||||
|
|
||||||
|
/// If this is `{false}`, the items are spaced apart with [term list
|
||||||
|
/// spacing]($func/terms.spacing). If it is `{true}`, they use normal
|
||||||
|
/// [leading]($func/par.leading) instead. This makes the term list more
|
||||||
|
/// compact, which can look better if the items are short.
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// / Fact: If a term list has a lot
|
||||||
|
/// of text, and maybe other inline
|
||||||
|
/// content, it should not be tight
|
||||||
|
/// anymore.
|
||||||
|
///
|
||||||
|
/// / Tip: To make it wide, simply
|
||||||
|
/// insert a blank line between the
|
||||||
|
/// items.
|
||||||
|
/// ```
|
||||||
|
#[named]
|
||||||
|
#[default(true)]
|
||||||
|
pub tight: bool,
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl TermsNode {
|
|
||||||
/// The indentation of each item's term.
|
/// The indentation of each item's term.
|
||||||
#[property(resolve)]
|
#[settable]
|
||||||
pub const INDENT: Length = Length::zero();
|
#[resolve]
|
||||||
|
#[default]
|
||||||
|
pub indent: Length,
|
||||||
|
|
||||||
/// The hanging indent of the description.
|
/// The hanging indent of the description.
|
||||||
///
|
///
|
||||||
|
|
@ -77,31 +70,17 @@ impl TermsNode {
|
||||||
/// / Term: This term list does not
|
/// / Term: This term list does not
|
||||||
/// make use of hanging indents.
|
/// make use of hanging indents.
|
||||||
/// ```
|
/// ```
|
||||||
#[property(resolve)]
|
#[settable]
|
||||||
pub const HANGING_INDENT: Length = Em::new(1.0).into();
|
#[resolve]
|
||||||
|
#[default(Em::new(1.0).into())]
|
||||||
|
pub hanging_indent: Length,
|
||||||
|
|
||||||
/// The spacing between the items of a wide (non-tight) term list.
|
/// The spacing between the items of a wide (non-tight) term list.
|
||||||
///
|
///
|
||||||
/// If set to `{auto}`, uses the spacing [below blocks]($func/block.below).
|
/// If set to `{auto}`, uses the spacing [below blocks]($func/block.below).
|
||||||
pub const SPACING: Smart<Spacing> = Smart::Auto;
|
#[settable]
|
||||||
|
#[default]
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
pub spacing: Smart<Spacing>,
|
||||||
Ok(Self {
|
|
||||||
tight: args.named("tight")?.unwrap_or(true),
|
|
||||||
items: args.all()?.into_iter().collect(),
|
|
||||||
}
|
|
||||||
.pack())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field(&self, name: &str) -> Option<Value> {
|
|
||||||
match name {
|
|
||||||
"tight" => Some(Value::Bool(self.tight)),
|
|
||||||
"items" => {
|
|
||||||
Some(Value::Array(self.items.items().map(|item| item.encode()).collect()))
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for TermsNode {
|
impl Layout for TermsNode {
|
||||||
|
|
@ -113,66 +92,63 @@ impl Layout for TermsNode {
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
let indent = styles.get(Self::INDENT);
|
let indent = styles.get(Self::INDENT);
|
||||||
let body_indent = styles.get(Self::HANGING_INDENT);
|
let body_indent = styles.get(Self::HANGING_INDENT);
|
||||||
let gutter = if self.tight {
|
let gutter = if self.tight() {
|
||||||
styles.get(ParNode::LEADING).into()
|
styles.get(ParNode::LEADING).into()
|
||||||
} else {
|
} else {
|
||||||
styles
|
styles
|
||||||
.get(Self::SPACING)
|
.get(Self::SPACING)
|
||||||
.unwrap_or_else(|| styles.get(BlockNode::BELOW).amount)
|
.unwrap_or_else(|| styles.get(BlockNode::BELOW).amount())
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut cells = vec![];
|
let mut cells = vec![];
|
||||||
for (item, map) in self.items.iter() {
|
for item in self.items() {
|
||||||
let body = Content::sequence(vec![
|
let body = Content::sequence(vec![
|
||||||
HNode { amount: (-body_indent).into(), weak: false }.pack(),
|
HNode::new((-body_indent).into()).pack(),
|
||||||
(item.term.clone() + TextNode::packed(':')).strong(),
|
(item.term() + TextNode::packed(':')).strong(),
|
||||||
SpaceNode.pack(),
|
SpaceNode::new().pack(),
|
||||||
item.description.clone(),
|
item.description(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
cells.push(Content::empty());
|
cells.push(Content::empty());
|
||||||
cells.push(body.styled_with_map(map.clone()));
|
cells.push(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
GridNode {
|
let layouter = GridLayouter::new(
|
||||||
tracks: Axes::with_x(vec![
|
vt,
|
||||||
Sizing::Rel((indent + body_indent).into()),
|
Axes::with_x(&[Sizing::Rel((indent + body_indent).into()), Sizing::Auto]),
|
||||||
Sizing::Auto,
|
Axes::with_y(&[gutter.into()]),
|
||||||
]),
|
&cells,
|
||||||
gutter: Axes::with_y(vec![gutter.into()]),
|
regions,
|
||||||
cells,
|
styles,
|
||||||
}
|
);
|
||||||
.layout(vt, styles, regions)
|
|
||||||
|
Ok(layouter.layout()?.fragment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A term list item.
|
/// A term list item.
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[node]
|
||||||
pub struct TermItem {
|
pub struct TermItem {
|
||||||
/// The term described by the list item.
|
/// The term described by the list item.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
pub term: Content,
|
pub term: Content,
|
||||||
|
|
||||||
/// The description of the term.
|
/// The description of the term.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
pub description: Content,
|
pub description: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TermItem {
|
cast_from_value! {
|
||||||
/// Encode the item into a value.
|
|
||||||
fn encode(&self) -> Value {
|
|
||||||
Value::Array(array![
|
|
||||||
Value::Content(self.term.clone()),
|
|
||||||
Value::Content(self.description.clone()),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
castable! {
|
|
||||||
TermItem,
|
TermItem,
|
||||||
array: Array => {
|
array: Array => {
|
||||||
let mut iter = array.into_iter();
|
let mut iter = array.into_iter();
|
||||||
let (term, description) = match (iter.next(), iter.next(), iter.next()) {
|
let (term, description) = match (iter.next(), iter.next(), iter.next()) {
|
||||||
(Some(a), Some(b), None) => (a.cast()?, b.cast()?),
|
(Some(a), Some(b), None) => (a.cast()?, b.cast()?),
|
||||||
_ => Err("term array must contain exactly two entries")?,
|
_ => Err("array must contain exactly two entries")?,
|
||||||
};
|
};
|
||||||
Self { term, description }
|
Self::new(term, description)
|
||||||
},
|
},
|
||||||
|
v: Content => v.to::<Self>().cloned().ok_or("expected term item or array")?,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ use typst::geom::Transform;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// # Move
|
|
||||||
/// Move content without affecting layout.
|
/// Move content without affecting layout.
|
||||||
///
|
///
|
||||||
/// The `move` function allows you to move content while the layout still 'sees'
|
/// The `move` function allows you to move content while the layout still 'sees'
|
||||||
|
|
@ -22,39 +21,24 @@ use crate::prelude::*;
|
||||||
/// ))
|
/// ))
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Move
|
||||||
/// - body: `Content` (positional, required)
|
/// Category: layout
|
||||||
/// The content to move.
|
#[node(Layout)]
|
||||||
///
|
|
||||||
/// - dx: `Rel<Length>` (named)
|
|
||||||
/// The horizontal displacement of the content.
|
|
||||||
///
|
|
||||||
/// - dy: `Rel<Length>` (named)
|
|
||||||
/// The vertical displacement of the content.
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// layout
|
|
||||||
#[func]
|
|
||||||
#[capable(Layout)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct MoveNode {
|
pub struct MoveNode {
|
||||||
/// The offset by which to move the content.
|
/// The content to move.
|
||||||
pub delta: Axes<Rel<Length>>,
|
#[positional]
|
||||||
/// The content that should be moved.
|
#[required]
|
||||||
pub body: Content,
|
pub body: Content,
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
/// The horizontal displacement of the content.
|
||||||
impl MoveNode {
|
#[named]
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
#[default]
|
||||||
let dx = args.named("dx")?.unwrap_or_default();
|
pub dx: Rel<Length>,
|
||||||
let dy = args.named("dy")?.unwrap_or_default();
|
|
||||||
Ok(Self {
|
/// The vertical displacement of the content.
|
||||||
delta: Axes::new(dx, dy),
|
#[named]
|
||||||
body: args.expect("body")?,
|
#[default]
|
||||||
}
|
pub dy: Rel<Length>,
|
||||||
.pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for MoveNode {
|
impl Layout for MoveNode {
|
||||||
|
|
@ -65,15 +49,14 @@ impl Layout for MoveNode {
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
let pod = Regions::one(regions.base(), Axes::splat(false));
|
let pod = Regions::one(regions.base(), Axes::splat(false));
|
||||||
let mut frame = self.body.layout(vt, styles, pod)?.into_frame();
|
let mut frame = self.body().layout(vt, styles, pod)?.into_frame();
|
||||||
let delta = self.delta.resolve(styles);
|
let delta = Axes::new(self.dx(), self.dy()).resolve(styles);
|
||||||
let delta = delta.zip(regions.base()).map(|(d, s)| d.relative_to(s));
|
let delta = delta.zip(regions.base()).map(|(d, s)| d.relative_to(s));
|
||||||
frame.translate(delta.to_point());
|
frame.translate(delta.to_point());
|
||||||
Ok(Fragment::frame(frame))
|
Ok(Fragment::frame(frame))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Rotate
|
|
||||||
/// Rotate content with affecting layout.
|
/// Rotate content with affecting layout.
|
||||||
///
|
///
|
||||||
/// Rotate an element by a given angle. The layout will act as if the element
|
/// Rotate an element by a given angle. The layout will act as if the element
|
||||||
|
|
@ -89,31 +72,26 @@ impl Layout for MoveNode {
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Rotate
|
||||||
/// - body: `Content` (positional, required)
|
/// Category: layout
|
||||||
/// The content to rotate.
|
#[node(Layout)]
|
||||||
///
|
|
||||||
/// - angle: `Angle` (named)
|
|
||||||
/// The amount of rotation.
|
|
||||||
///
|
|
||||||
/// ```example
|
|
||||||
/// #rotate(angle: -1.571rad)[Space!]
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// layout
|
|
||||||
#[func]
|
|
||||||
#[capable(Layout)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct RotateNode {
|
pub struct RotateNode {
|
||||||
/// The angle by which to rotate the node.
|
/// The amount of rotation.
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// #rotate(angle: -1.571rad)[Space!]
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
#[named]
|
||||||
|
#[shorthand]
|
||||||
|
#[default]
|
||||||
pub angle: Angle,
|
pub angle: Angle,
|
||||||
/// The content that should be rotated.
|
|
||||||
pub body: Content,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
/// The content to rotate.
|
||||||
impl RotateNode {
|
#[positional]
|
||||||
|
#[required]
|
||||||
|
pub body: Content,
|
||||||
|
|
||||||
/// The origin of the rotation.
|
/// The origin of the rotation.
|
||||||
///
|
///
|
||||||
/// By default, the origin is the center of the rotated element. If,
|
/// By default, the origin is the center of the rotated element. If,
|
||||||
|
|
@ -130,16 +108,10 @@ impl RotateNode {
|
||||||
/// #box(rotate(angle: 30deg, origin: top + left, square()))
|
/// #box(rotate(angle: 30deg, origin: top + left, square()))
|
||||||
/// #box(rotate(angle: 30deg, origin: bottom + right, square()))
|
/// #box(rotate(angle: 30deg, origin: bottom + right, square()))
|
||||||
/// ```
|
/// ```
|
||||||
#[property(resolve)]
|
#[settable]
|
||||||
pub const ORIGIN: Axes<Option<GenAlign>> = Axes::default();
|
#[resolve]
|
||||||
|
#[default]
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
pub origin: Axes<Option<GenAlign>>,
|
||||||
Ok(Self {
|
|
||||||
angle: args.named_or_find("angle")?.unwrap_or_default(),
|
|
||||||
body: args.expect("body")?,
|
|
||||||
}
|
|
||||||
.pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for RotateNode {
|
impl Layout for RotateNode {
|
||||||
|
|
@ -150,18 +122,17 @@ impl Layout for RotateNode {
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
let pod = Regions::one(regions.base(), Axes::splat(false));
|
let pod = Regions::one(regions.base(), Axes::splat(false));
|
||||||
let mut frame = self.body.layout(vt, styles, pod)?.into_frame();
|
let mut frame = self.body().layout(vt, styles, pod)?.into_frame();
|
||||||
let origin = styles.get(Self::ORIGIN).unwrap_or(Align::CENTER_HORIZON);
|
let origin = styles.get(Self::ORIGIN).unwrap_or(Align::CENTER_HORIZON);
|
||||||
let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s));
|
let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s));
|
||||||
let ts = Transform::translate(x, y)
|
let ts = Transform::translate(x, y)
|
||||||
.pre_concat(Transform::rotate(self.angle))
|
.pre_concat(Transform::rotate(self.angle()))
|
||||||
.pre_concat(Transform::translate(-x, -y));
|
.pre_concat(Transform::translate(-x, -y));
|
||||||
frame.transform(ts);
|
frame.transform(ts);
|
||||||
Ok(Fragment::frame(frame))
|
Ok(Fragment::frame(frame))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Scale
|
|
||||||
/// Scale content without affecting layout.
|
/// Scale content without affecting layout.
|
||||||
///
|
///
|
||||||
/// The `scale` function allows you to scale and mirror content without
|
/// The `scale` function allows you to scale and mirror content without
|
||||||
|
|
@ -174,34 +145,29 @@ impl Layout for RotateNode {
|
||||||
/// #scale(x: -100%)[This is mirrored.]
|
/// #scale(x: -100%)[This is mirrored.]
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Scale
|
||||||
/// - body: `Content` (positional, required)
|
/// Category: layout
|
||||||
/// The content to scale.
|
#[node(Construct, Layout)]
|
||||||
///
|
|
||||||
/// - x: `Ratio` (named)
|
|
||||||
/// The horizontal scaling factor.
|
|
||||||
///
|
|
||||||
/// The body will be mirrored horizontally if the parameter is negative.
|
|
||||||
///
|
|
||||||
/// - y: `Ratio` (named)
|
|
||||||
/// The vertical scaling factor.
|
|
||||||
///
|
|
||||||
/// The body will be mirrored vertically if the parameter is negative.
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// layout
|
|
||||||
#[func]
|
|
||||||
#[capable(Layout)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct ScaleNode {
|
pub struct ScaleNode {
|
||||||
/// Scaling factor.
|
/// The content to scale.
|
||||||
pub factor: Axes<Ratio>,
|
#[positional]
|
||||||
/// The content that should be scaled.
|
#[required]
|
||||||
pub body: Content,
|
pub body: Content,
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
/// The horizontal scaling factor.
|
||||||
impl ScaleNode {
|
///
|
||||||
|
/// The body will be mirrored horizontally if the parameter is negative.
|
||||||
|
#[named]
|
||||||
|
#[default(Ratio::one())]
|
||||||
|
pub x: Ratio,
|
||||||
|
|
||||||
|
/// The vertical scaling factor.
|
||||||
|
///
|
||||||
|
/// The body will be mirrored vertically if the parameter is negative.
|
||||||
|
#[named]
|
||||||
|
#[default(Ratio::one())]
|
||||||
|
pub y: Ratio,
|
||||||
|
|
||||||
/// The origin of the transformation.
|
/// The origin of the transformation.
|
||||||
///
|
///
|
||||||
/// By default, the origin is the center of the scaled element.
|
/// By default, the origin is the center of the scaled element.
|
||||||
|
|
@ -210,18 +176,18 @@ impl ScaleNode {
|
||||||
/// A#box(scale(75%)[A])A \
|
/// A#box(scale(75%)[A])A \
|
||||||
/// B#box(scale(75%, origin: bottom + left)[B])B
|
/// B#box(scale(75%, origin: bottom + left)[B])B
|
||||||
/// ```
|
/// ```
|
||||||
#[property(resolve)]
|
#[settable]
|
||||||
pub const ORIGIN: Axes<Option<GenAlign>> = Axes::default();
|
#[resolve]
|
||||||
|
#[default]
|
||||||
|
pub origin: Axes<Option<GenAlign>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Construct for ScaleNode {
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
let all = args.find()?;
|
let all = args.find()?;
|
||||||
let x = args.named("x")?.or(all).unwrap_or(Ratio::one());
|
let x = args.named("x")?.or(all).unwrap_or(Ratio::one());
|
||||||
let y = args.named("y")?.or(all).unwrap_or(Ratio::one());
|
let y = args.named("y")?.or(all).unwrap_or(Ratio::one());
|
||||||
Ok(Self {
|
Ok(Self::new(args.expect::<Content>("body")?).with_x(x).with_y(y).pack())
|
||||||
factor: Axes::new(x, y),
|
|
||||||
body: args.expect("body")?,
|
|
||||||
}
|
|
||||||
.pack())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -233,11 +199,11 @@ impl Layout for ScaleNode {
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
let pod = Regions::one(regions.base(), Axes::splat(false));
|
let pod = Regions::one(regions.base(), Axes::splat(false));
|
||||||
let mut frame = self.body.layout(vt, styles, pod)?.into_frame();
|
let mut frame = self.body().layout(vt, styles, pod)?.into_frame();
|
||||||
let origin = styles.get(Self::ORIGIN).unwrap_or(Align::CENTER_HORIZON);
|
let origin = styles.get(Self::ORIGIN).unwrap_or(Align::CENTER_HORIZON);
|
||||||
let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s));
|
let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s));
|
||||||
let transform = Transform::translate(x, y)
|
let transform = Transform::translate(x, y)
|
||||||
.pre_concat(Transform::scale(self.factor.x, self.factor.y))
|
.pre_concat(Transform::scale(self.x(), self.y()))
|
||||||
.pre_concat(Transform::translate(-x, -y));
|
.pre_concat(Transform::translate(-x, -y));
|
||||||
frame.transform(transform);
|
frame.transform(transform);
|
||||||
Ok(Fragment::frame(frame))
|
Ok(Fragment::frame(frame))
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ use self::layout::LayoutRoot;
|
||||||
/// Construct the standard library.
|
/// Construct the standard library.
|
||||||
pub fn build() -> Library {
|
pub fn build() -> Library {
|
||||||
let math = math::module();
|
let math = math::module();
|
||||||
let calc = compute::calc();
|
let calc = compute::calc::module();
|
||||||
let global = global(math.clone(), calc);
|
let global = global(math.clone(), calc);
|
||||||
Library { global, math, styles: styles(), items: items() }
|
Library { global, math, styles: styles(), items: items() }
|
||||||
}
|
}
|
||||||
|
|
@ -166,37 +166,37 @@ fn items() -> LangItems {
|
||||||
layout: |world, content, styles| content.layout_root(world, styles),
|
layout: |world, content, styles| content.layout_root(world, styles),
|
||||||
em: |styles| styles.get(text::TextNode::SIZE),
|
em: |styles| styles.get(text::TextNode::SIZE),
|
||||||
dir: |styles| styles.get(text::TextNode::DIR),
|
dir: |styles| styles.get(text::TextNode::DIR),
|
||||||
space: || text::SpaceNode.pack(),
|
space: || text::SpaceNode::new().pack(),
|
||||||
linebreak: || text::LinebreakNode { justify: false }.pack(),
|
linebreak: || text::LinebreakNode::new().pack(),
|
||||||
text: |text| text::TextNode(text).pack(),
|
text: |text| text::TextNode::new(text).pack(),
|
||||||
text_id: NodeId::of::<text::TextNode>(),
|
text_id: NodeId::of::<text::TextNode>(),
|
||||||
text_str: |content| Some(&content.to::<text::TextNode>()?.0),
|
text_str: |content| Some(content.to::<text::TextNode>()?.text()),
|
||||||
smart_quote: |double| text::SmartQuoteNode { double }.pack(),
|
smart_quote: |double| text::SmartQuoteNode::new().with_double(double).pack(),
|
||||||
parbreak: || layout::ParbreakNode.pack(),
|
parbreak: || layout::ParbreakNode::new().pack(),
|
||||||
strong: |body| text::StrongNode(body).pack(),
|
strong: |body| text::StrongNode::new(body).pack(),
|
||||||
emph: |body| text::EmphNode(body).pack(),
|
emph: |body| text::EmphNode::new(body).pack(),
|
||||||
raw: |text, lang, block| {
|
raw: |text, lang, block| {
|
||||||
let content = text::RawNode { text, block }.pack();
|
let content = text::RawNode::new(text).with_block(block).pack();
|
||||||
match lang {
|
match lang {
|
||||||
Some(_) => content.styled(text::RawNode::LANG, lang),
|
Some(_) => content.styled(text::RawNode::LANG, lang),
|
||||||
None => content,
|
None => content,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
link: |url| meta::LinkNode::from_url(url).pack(),
|
link: |url| meta::LinkNode::from_url(url).pack(),
|
||||||
ref_: |target| meta::RefNode(target).pack(),
|
ref_: |target| meta::RefNode::new(target).pack(),
|
||||||
heading: |level, body| meta::HeadingNode { level, title: body }.pack(),
|
heading: |level, title| meta::HeadingNode::new(title).with_level(level).pack(),
|
||||||
list_item: |body| layout::ListItem::List(body).pack(),
|
list_item: |body| layout::ListItem::new(body).pack(),
|
||||||
enum_item: |number, body| layout::ListItem::Enum(number, body).pack(),
|
enum_item: |number, body| layout::EnumItem::new(body).with_number(number).pack(),
|
||||||
term_item: |term, description| {
|
term_item: |term, description| layout::TermItem::new(term, description).pack(),
|
||||||
layout::ListItem::Term(layout::TermItem { term, description }).pack()
|
formula: |body, block| math::FormulaNode::new(body).with_block(block).pack(),
|
||||||
|
math_align_point: || math::AlignPointNode::new().pack(),
|
||||||
|
math_delimited: |open, body, close| math::LrNode::new(open + body + close).pack(),
|
||||||
|
math_attach: |base, bottom, top| {
|
||||||
|
math::AttachNode::new(base).with_bottom(bottom).with_top(top).pack()
|
||||||
},
|
},
|
||||||
formula: |body, block| math::FormulaNode { body, block }.pack(),
|
math_accent: |base, accent| {
|
||||||
math_align_point: || math::AlignPointNode.pack(),
|
math::AccentNode::new(base, math::Accent::new(accent)).pack()
|
||||||
math_delimited: |open, body, close| {
|
|
||||||
math::LrNode { body: open + body + close, size: None }.pack()
|
|
||||||
},
|
},
|
||||||
math_attach: |base, bottom, top| math::AttachNode { base, bottom, top }.pack(),
|
math_frac: |num, denom| math::FracNode::new(num, denom).pack(),
|
||||||
math_accent: |base, accent| math::AccentNode { base, accent }.pack(),
|
|
||||||
math_frac: |num, denom| math::FracNode { num, denom }.pack(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ use super::*;
|
||||||
/// How much the accent can be shorter than the base.
|
/// How much the accent can be shorter than the base.
|
||||||
const ACCENT_SHORT_FALL: Em = Em::new(0.5);
|
const ACCENT_SHORT_FALL: Em = Em::new(0.5);
|
||||||
|
|
||||||
/// # Accent
|
|
||||||
/// Attach an accent to a base.
|
/// Attach an accent to a base.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -15,72 +14,48 @@ const ACCENT_SHORT_FALL: Em = Em::new(0.5);
|
||||||
/// $tilde(a) = accent(a, \u{0303})$
|
/// $tilde(a) = accent(a, \u{0303})$
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Accent
|
||||||
/// - base: `Content` (positional, required)
|
/// Category: math
|
||||||
/// The base to which the accent is applied.
|
#[node(LayoutMath)]
|
||||||
/// May consist of multiple letters.
|
|
||||||
///
|
|
||||||
/// ```example
|
|
||||||
/// $arrow(A B C)$
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// - accent: `char` (positional, required)
|
|
||||||
/// The accent to apply to the base.
|
|
||||||
///
|
|
||||||
/// Supported accents include:
|
|
||||||
///
|
|
||||||
/// | Accent | Name | Codepoint |
|
|
||||||
/// | ------------ | --------------- | --------- |
|
|
||||||
/// | Grave | `grave` | <code>`</code> |
|
|
||||||
/// | Acute | `acute` | `´` |
|
|
||||||
/// | Circumflex | `hat` | `^` |
|
|
||||||
/// | Tilde | `tilde` | `~` |
|
|
||||||
/// | Macron | `macron` | `¯` |
|
|
||||||
/// | Breve | `breve` | `˘` |
|
|
||||||
/// | Dot | `dot` | `.` |
|
|
||||||
/// | Diaeresis | `diaer` | `¨` |
|
|
||||||
/// | Circle | `circle` | `∘` |
|
|
||||||
/// | Double acute | `acute.double` | `˝` |
|
|
||||||
/// | Caron | `caron` | `ˇ` |
|
|
||||||
/// | Right arrow | `arrow`, `->` | `→` |
|
|
||||||
/// | Left arrow | `arrow.l`, `<-` | `←` |
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// math
|
|
||||||
#[func]
|
|
||||||
#[capable(LayoutMath)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct AccentNode {
|
pub struct AccentNode {
|
||||||
/// The accent base.
|
/// The base to which the accent is applied.
|
||||||
|
/// May consist of multiple letters.
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// $arrow(A B C)$
|
||||||
|
/// ```
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
pub base: Content,
|
pub base: Content,
|
||||||
/// The accent.
|
|
||||||
pub accent: char,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
/// The accent to apply to the base.
|
||||||
impl AccentNode {
|
///
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
/// Supported accents include:
|
||||||
let base = args.expect("base")?;
|
///
|
||||||
let accent = args.expect::<Accent>("accent")?.0;
|
/// | Accent | Name | Codepoint |
|
||||||
Ok(Self { base, accent }.pack())
|
/// | ------------ | --------------- | --------- |
|
||||||
}
|
/// | Grave | `grave` | <code>`</code> |
|
||||||
}
|
/// | Acute | `acute` | `´` |
|
||||||
|
/// | Circumflex | `hat` | `^` |
|
||||||
struct Accent(char);
|
/// | Tilde | `tilde` | `~` |
|
||||||
|
/// | Macron | `macron` | `¯` |
|
||||||
castable! {
|
/// | Breve | `breve` | `˘` |
|
||||||
Accent,
|
/// | Dot | `dot` | `.` |
|
||||||
v: char => Self(v),
|
/// | Diaeresis | `diaer` | `¨` |
|
||||||
v: Content => match v.to::<TextNode>() {
|
/// | Circle | `circle` | `∘` |
|
||||||
Some(text) => Self(Value::Str(text.0.clone().into()).cast()?),
|
/// | Double acute | `acute.double` | `˝` |
|
||||||
None => Err("expected text")?,
|
/// | Caron | `caron` | `ˇ` |
|
||||||
},
|
/// | Right arrow | `arrow`, `->` | `→` |
|
||||||
|
/// | Left arrow | `arrow.l`, `<-` | `←` |
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
|
pub accent: Accent,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for AccentNode {
|
impl LayoutMath for AccentNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
ctx.style(ctx.style.with_cramped(true));
|
ctx.style(ctx.style.with_cramped(true));
|
||||||
let base = ctx.layout_fragment(&self.base)?;
|
let base = ctx.layout_fragment(&self.base())?;
|
||||||
ctx.unstyle();
|
ctx.unstyle();
|
||||||
|
|
||||||
let base_attach = match &base {
|
let base_attach = match &base {
|
||||||
|
|
@ -92,7 +67,7 @@ impl LayoutMath for AccentNode {
|
||||||
|
|
||||||
// Forcing the accent to be at least as large as the base makes it too
|
// Forcing the accent to be at least as large as the base makes it too
|
||||||
// wide in many case.
|
// wide in many case.
|
||||||
let c = combining_accent(self.accent).unwrap_or(self.accent);
|
let Accent(c) = self.accent();
|
||||||
let glyph = GlyphFragment::new(ctx, c);
|
let glyph = GlyphFragment::new(ctx, c);
|
||||||
let short_fall = ACCENT_SHORT_FALL.scaled(ctx);
|
let short_fall = ACCENT_SHORT_FALL.scaled(ctx);
|
||||||
let variant = glyph.stretch_horizontal(ctx, base.width(), short_fall);
|
let variant = glyph.stretch_horizontal(ctx, base.width(), short_fall);
|
||||||
|
|
@ -136,3 +111,26 @@ fn attachment(ctx: &MathContext, id: GlyphId, italics_correction: Abs) -> Abs {
|
||||||
(advance.scaled(ctx) + italics_correction) / 2.0
|
(advance.scaled(ctx) + italics_correction) / 2.0
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An accent character.
|
||||||
|
pub struct Accent(char);
|
||||||
|
|
||||||
|
impl Accent {
|
||||||
|
/// Normalize a character into an accent.
|
||||||
|
pub fn new(c: char) -> Self {
|
||||||
|
Self(combining_accent(c).unwrap_or(c))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
Accent,
|
||||||
|
v: char => Self::new(v),
|
||||||
|
v: Content => match v.to::<TextNode>() {
|
||||||
|
Some(node) => Value::Str(node.text().into()).cast()?,
|
||||||
|
None => Err("expected text")?,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: Accent => v.0.into()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,11 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// # Alignment Point
|
|
||||||
/// A math alignment point: `&`, `&&`.
|
/// A math alignment point: `&`, `&&`.
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Alignment Point
|
||||||
/// - index: `usize` (positional, required)
|
/// Category: math
|
||||||
/// The alignment point's index.
|
#[node(LayoutMath)]
|
||||||
///
|
pub struct AlignPointNode {}
|
||||||
/// ## Category
|
|
||||||
/// math
|
|
||||||
#[func]
|
|
||||||
#[capable(LayoutMath)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct AlignPointNode;
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl AlignPointNode {}
|
|
||||||
|
|
||||||
impl LayoutMath for AlignPointNode {
|
impl LayoutMath for AlignPointNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// # Attachment
|
|
||||||
/// A base with optional attachments.
|
/// A base with optional attachments.
|
||||||
///
|
///
|
||||||
/// ## Syntax
|
/// ## Syntax
|
||||||
|
|
@ -12,58 +11,44 @@ use super::*;
|
||||||
/// $ sum_(i=0)^n a_i = 2^(1+i) $
|
/// $ sum_(i=0)^n a_i = 2^(1+i) $
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Attachment
|
||||||
/// - base: `Content` (positional, required)
|
/// Category: math
|
||||||
/// The base to which things are attached.
|
#[node(LayoutMath)]
|
||||||
///
|
|
||||||
/// - top: `Content` (named)
|
|
||||||
/// The top attachment.
|
|
||||||
///
|
|
||||||
/// - bottom: `Content` (named)
|
|
||||||
/// The bottom attachment.
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// math
|
|
||||||
#[func]
|
|
||||||
#[capable(LayoutMath)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct AttachNode {
|
pub struct AttachNode {
|
||||||
/// The base.
|
/// The base to which things are attached.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
pub base: Content,
|
pub base: Content,
|
||||||
/// The top attachment.
|
|
||||||
pub top: Option<Content>,
|
|
||||||
/// The bottom attachment.
|
|
||||||
pub bottom: Option<Content>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
/// The top attachment.
|
||||||
impl AttachNode {
|
#[named]
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
#[default]
|
||||||
let base = args.expect("base")?;
|
pub top: Option<Content>,
|
||||||
let top = args.named("top")?;
|
|
||||||
let bottom = args.named("bottom")?;
|
/// The bottom attachment.
|
||||||
Ok(Self { base, top, bottom }.pack())
|
#[named]
|
||||||
}
|
#[default]
|
||||||
|
pub bottom: Option<Content>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for AttachNode {
|
impl LayoutMath for AttachNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
let base = ctx.layout_fragment(&self.base)?;
|
let base = self.base();
|
||||||
|
let display_limits = base.is::<LimitsNode>();
|
||||||
|
let display_scripts = base.is::<ScriptsNode>();
|
||||||
|
|
||||||
|
let base = ctx.layout_fragment(&base)?;
|
||||||
|
|
||||||
ctx.style(ctx.style.for_subscript());
|
ctx.style(ctx.style.for_subscript());
|
||||||
let top = self.top.as_ref().map(|node| ctx.layout_fragment(node)).transpose()?;
|
let top = self.top().map(|node| ctx.layout_fragment(&node)).transpose()?;
|
||||||
ctx.unstyle();
|
ctx.unstyle();
|
||||||
|
|
||||||
ctx.style(ctx.style.for_superscript());
|
ctx.style(ctx.style.for_superscript());
|
||||||
let bottom = self
|
let bottom = self.bottom().map(|node| ctx.layout_fragment(&node)).transpose()?;
|
||||||
.bottom
|
|
||||||
.as_ref()
|
|
||||||
.map(|node| ctx.layout_fragment(node))
|
|
||||||
.transpose()?;
|
|
||||||
ctx.unstyle();
|
ctx.unstyle();
|
||||||
|
|
||||||
let render_limits = self.base.is::<LimitsNode>()
|
let display_limits = display_limits
|
||||||
|| (!self.base.is::<ScriptsNode>()
|
|| (!display_scripts
|
||||||
&& ctx.style.size == MathSize::Display
|
&& ctx.style.size == MathSize::Display
|
||||||
&& base.class() == Some(MathClass::Large)
|
&& base.class() == Some(MathClass::Large)
|
||||||
&& match &base {
|
&& match &base {
|
||||||
|
|
@ -72,7 +57,7 @@ impl LayoutMath for AttachNode {
|
||||||
_ => false,
|
_ => false,
|
||||||
});
|
});
|
||||||
|
|
||||||
if render_limits {
|
if display_limits {
|
||||||
limits(ctx, base, top, bottom)
|
limits(ctx, base, top, bottom)
|
||||||
} else {
|
} else {
|
||||||
scripts(ctx, base, top, bottom)
|
scripts(ctx, base, top, bottom)
|
||||||
|
|
@ -80,7 +65,6 @@ impl LayoutMath for AttachNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Scripts
|
|
||||||
/// Force a base to display attachments as scripts.
|
/// Force a base to display attachments as scripts.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -88,31 +72,22 @@ impl LayoutMath for AttachNode {
|
||||||
/// $ scripts(sum)_1^2 != sum_1^2 $
|
/// $ scripts(sum)_1^2 != sum_1^2 $
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Scripts
|
||||||
/// - base: `Content` (positional, required)
|
/// Category: math
|
||||||
/// The base to attach the scripts to.
|
#[node(LayoutMath)]
|
||||||
///
|
pub struct ScriptsNode {
|
||||||
/// ## Category
|
/// The base to attach the scripts to.
|
||||||
/// math
|
#[positional]
|
||||||
#[func]
|
#[required]
|
||||||
#[capable(LayoutMath)]
|
pub base: Content,
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct ScriptsNode(Content);
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl ScriptsNode {
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
Ok(Self(args.expect("base")?).pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for ScriptsNode {
|
impl LayoutMath for ScriptsNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
self.0.layout_math(ctx)
|
self.base().layout_math(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Limits
|
|
||||||
/// Force a base to display attachments as limits.
|
/// Force a base to display attachments as limits.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -120,27 +95,19 @@ impl LayoutMath for ScriptsNode {
|
||||||
/// $ limits(A)_1^2 != A_1^2 $
|
/// $ limits(A)_1^2 != A_1^2 $
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Limits
|
||||||
/// - base: `Content` (positional, required)
|
/// Category: math
|
||||||
/// The base to attach the limits to.
|
#[node(LayoutMath)]
|
||||||
///
|
pub struct LimitsNode {
|
||||||
/// ## Category
|
/// The base to attach the limits to.
|
||||||
/// math
|
#[positional]
|
||||||
#[func]
|
#[required]
|
||||||
#[capable(LayoutMath)]
|
pub base: Content,
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct LimitsNode(Content);
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl LimitsNode {
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
Ok(Self(args.expect("base")?).pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for LimitsNode {
|
impl LayoutMath for LimitsNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
self.0.layout_math(ctx)
|
self.base().layout_math(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ use super::*;
|
||||||
/// How much less high scaled delimiters can be than what they wrap.
|
/// How much less high scaled delimiters can be than what they wrap.
|
||||||
pub(super) const DELIM_SHORT_FALL: Em = Em::new(0.1);
|
pub(super) const DELIM_SHORT_FALL: Em = Em::new(0.1);
|
||||||
|
|
||||||
/// # Left/Right
|
|
||||||
/// Scales delimiters.
|
/// Scales delimiters.
|
||||||
///
|
///
|
||||||
/// While matched delimiters scale by default, this can be used to scale
|
/// While matched delimiters scale by default, this can be used to scale
|
||||||
|
|
@ -24,20 +23,22 @@ pub(super) const DELIM_SHORT_FALL: Em = Em::new(0.1);
|
||||||
///
|
///
|
||||||
/// Defaults to `{100%}`.
|
/// Defaults to `{100%}`.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Left/Right
|
||||||
/// math
|
/// Category: math
|
||||||
#[func]
|
#[node(Construct, LayoutMath)]
|
||||||
#[capable(LayoutMath)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct LrNode {
|
pub struct LrNode {
|
||||||
/// The delimited content, including the delimiters.
|
/// The delimited content, including the delimiters.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
pub body: Content,
|
pub body: Content,
|
||||||
|
|
||||||
/// The size of the brackets.
|
/// The size of the brackets.
|
||||||
pub size: Option<Rel<Length>>,
|
#[named]
|
||||||
|
#[default]
|
||||||
|
pub size: Smart<Rel<Length>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node]
|
impl Construct for LrNode {
|
||||||
impl LrNode {
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
let mut body = Content::empty();
|
let mut body = Content::empty();
|
||||||
for (i, arg) in args.all::<Content>()?.into_iter().enumerate() {
|
for (i, arg) in args.all::<Content>()?.into_iter().enumerate() {
|
||||||
|
|
@ -46,21 +47,21 @@ impl LrNode {
|
||||||
}
|
}
|
||||||
body += arg;
|
body += arg;
|
||||||
}
|
}
|
||||||
let size = args.named("size")?;
|
let size = args.named::<Smart<Rel<Length>>>("size")?.unwrap_or_default();
|
||||||
Ok(Self { body, size }.pack())
|
Ok(Self::new(body).with_size(size).pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for LrNode {
|
impl LayoutMath for LrNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
let mut body = &self.body;
|
let mut body = self.body();
|
||||||
if let Some(node) = self.body.to::<LrNode>() {
|
if let Some(node) = body.to::<LrNode>() {
|
||||||
if node.size.is_none() {
|
if node.size().is_auto() {
|
||||||
body = &node.body;
|
body = node.body();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut fragments = ctx.layout_fragments(body)?;
|
let mut fragments = ctx.layout_fragments(&body)?;
|
||||||
let axis = scaled!(ctx, axis_height);
|
let axis = scaled!(ctx, axis_height);
|
||||||
let max_extent = fragments
|
let max_extent = fragments
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -69,7 +70,7 @@ impl LayoutMath for LrNode {
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let height = self
|
let height = self
|
||||||
.size
|
.size()
|
||||||
.unwrap_or(Rel::one())
|
.unwrap_or(Rel::one())
|
||||||
.resolve(ctx.styles())
|
.resolve(ctx.styles())
|
||||||
.relative_to(2.0 * max_extent);
|
.relative_to(2.0 * max_extent);
|
||||||
|
|
@ -116,7 +117,6 @@ fn scale(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Floor
|
|
||||||
/// Floor an expression.
|
/// Floor an expression.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -128,14 +128,13 @@ fn scale(
|
||||||
/// - body: `Content` (positional, required)
|
/// - body: `Content` (positional, required)
|
||||||
/// The expression to floor.
|
/// The expression to floor.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Floor
|
||||||
/// math
|
/// Category: math
|
||||||
#[func]
|
#[func]
|
||||||
pub fn floor(args: &mut Args) -> SourceResult<Value> {
|
pub fn floor(args: &mut Args) -> SourceResult<Value> {
|
||||||
delimited(args, '⌊', '⌋')
|
delimited(args, '⌊', '⌋')
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Ceil
|
|
||||||
/// Ceil an expression.
|
/// Ceil an expression.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -147,14 +146,13 @@ pub fn floor(args: &mut Args) -> SourceResult<Value> {
|
||||||
/// - body: `Content` (positional, required)
|
/// - body: `Content` (positional, required)
|
||||||
/// The expression to ceil.
|
/// The expression to ceil.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Ceil
|
||||||
/// math
|
/// Category: math
|
||||||
#[func]
|
#[func]
|
||||||
pub fn ceil(args: &mut Args) -> SourceResult<Value> {
|
pub fn ceil(args: &mut Args) -> SourceResult<Value> {
|
||||||
delimited(args, '⌈', '⌉')
|
delimited(args, '⌈', '⌉')
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Abs
|
|
||||||
/// Take the absolute value of an expression.
|
/// Take the absolute value of an expression.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -166,14 +164,13 @@ pub fn ceil(args: &mut Args) -> SourceResult<Value> {
|
||||||
/// - body: `Content` (positional, required)
|
/// - body: `Content` (positional, required)
|
||||||
/// The expression to take the absolute value of.
|
/// The expression to take the absolute value of.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Abs
|
||||||
/// math
|
/// Category: math
|
||||||
#[func]
|
#[func]
|
||||||
pub fn abs(args: &mut Args) -> SourceResult<Value> {
|
pub fn abs(args: &mut Args) -> SourceResult<Value> {
|
||||||
delimited(args, '|', '|')
|
delimited(args, '|', '|')
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Norm
|
|
||||||
/// Take the norm of an expression.
|
/// Take the norm of an expression.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -185,8 +182,8 @@ pub fn abs(args: &mut Args) -> SourceResult<Value> {
|
||||||
/// - body: `Content` (positional, required)
|
/// - body: `Content` (positional, required)
|
||||||
/// The expression to take the norm of.
|
/// The expression to take the norm of.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Norm
|
||||||
/// math
|
/// Category: math
|
||||||
#[func]
|
#[func]
|
||||||
pub fn norm(args: &mut Args) -> SourceResult<Value> {
|
pub fn norm(args: &mut Args) -> SourceResult<Value> {
|
||||||
delimited(args, '‖', '‖')
|
delimited(args, '‖', '‖')
|
||||||
|
|
@ -194,14 +191,11 @@ pub fn norm(args: &mut Args) -> SourceResult<Value> {
|
||||||
|
|
||||||
fn delimited(args: &mut Args, left: char, right: char) -> SourceResult<Value> {
|
fn delimited(args: &mut Args, left: char, right: char) -> SourceResult<Value> {
|
||||||
Ok(Value::Content(
|
Ok(Value::Content(
|
||||||
LrNode {
|
LrNode::new(Content::sequence(vec![
|
||||||
body: Content::sequence(vec![
|
TextNode::packed(left),
|
||||||
TextNode::packed(left),
|
args.expect::<Content>("body")?,
|
||||||
args.expect::<Content>("body")?,
|
TextNode::packed(right),
|
||||||
TextNode::packed(right),
|
]))
|
||||||
]),
|
|
||||||
size: None,
|
|
||||||
}
|
|
||||||
.pack(),
|
.pack(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,41 +17,27 @@ const FRAC_AROUND: Em = Em::new(0.1);
|
||||||
/// expression using round grouping parenthesis. Such parentheses are removed
|
/// expression using round grouping parenthesis. Such parentheses are removed
|
||||||
/// from the output, but you can nest multiple to force them.
|
/// from the output, but you can nest multiple to force them.
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Fraction
|
||||||
/// - num: `Content` (positional, required)
|
/// Category: math
|
||||||
/// The fraction's numerator.
|
#[node(LayoutMath)]
|
||||||
///
|
|
||||||
/// - denom: `Content` (positional, required)
|
|
||||||
/// The fraction's denominator.
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// math
|
|
||||||
#[func]
|
|
||||||
#[capable(LayoutMath)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct FracNode {
|
pub struct FracNode {
|
||||||
/// The numerator.
|
/// The fraction's numerator.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
pub num: Content,
|
pub num: Content,
|
||||||
/// The denominator.
|
|
||||||
pub denom: Content,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
/// The fraction's denominator.
|
||||||
impl FracNode {
|
#[positional]
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
#[required]
|
||||||
let num = args.expect("numerator")?;
|
pub denom: Content,
|
||||||
let denom = args.expect("denominator")?;
|
|
||||||
Ok(Self { num, denom }.pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for FracNode {
|
impl LayoutMath for FracNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
layout(ctx, &self.num, &self.denom, false)
|
layout(ctx, &self.num(), &self.denom(), false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Binomial
|
|
||||||
/// A binomial expression.
|
/// A binomial expression.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -59,37 +45,24 @@ impl LayoutMath for FracNode {
|
||||||
/// $ binom(n, k) $
|
/// $ binom(n, k) $
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Binomial
|
||||||
/// - upper: `Content` (positional, required)
|
/// Category: math
|
||||||
/// The binomial's upper index.
|
#[node(LayoutMath)]
|
||||||
///
|
|
||||||
/// - lower: `Content` (positional, required)
|
|
||||||
/// The binomial's lower index.
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// math
|
|
||||||
#[func]
|
|
||||||
#[capable(LayoutMath)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct BinomNode {
|
pub struct BinomNode {
|
||||||
/// The upper index.
|
/// The binomial's upper index.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
pub upper: Content,
|
pub upper: Content,
|
||||||
/// The lower index.
|
|
||||||
pub lower: Content,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
/// The binomial's lower index.
|
||||||
impl BinomNode {
|
#[positional]
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
#[required]
|
||||||
let upper = args.expect("upper index")?;
|
pub lower: Content,
|
||||||
let lower = args.expect("lower index")?;
|
|
||||||
Ok(Self { upper, lower }.pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for BinomNode {
|
impl LayoutMath for BinomNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
layout(ctx, &self.upper, &self.lower, true)
|
layout(ctx, &self.upper(), &self.lower(), true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ const ROW_GAP: Em = Em::new(0.5);
|
||||||
const COL_GAP: Em = Em::new(0.5);
|
const COL_GAP: Em = Em::new(0.5);
|
||||||
const VERTICAL_PADDING: Ratio = Ratio::new(0.1);
|
const VERTICAL_PADDING: Ratio = Ratio::new(0.1);
|
||||||
|
|
||||||
/// # Vector
|
|
||||||
/// A column vector.
|
/// A column vector.
|
||||||
///
|
///
|
||||||
/// Content in the vector's elements can be aligned with the `&` symbol.
|
/// Content in the vector's elements can be aligned with the `&` symbol.
|
||||||
|
|
@ -15,41 +14,33 @@ const VERTICAL_PADDING: Ratio = Ratio::new(0.1);
|
||||||
/// = a + 2b + 3c $
|
/// = a + 2b + 3c $
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Vector
|
||||||
/// - elements: `Content` (positional, variadic)
|
/// Category: math
|
||||||
/// The elements of the vector.
|
#[node(LayoutMath)]
|
||||||
///
|
pub struct VecNode {
|
||||||
/// ## Category
|
/// The elements of the vector.
|
||||||
/// math
|
#[variadic]
|
||||||
#[func]
|
pub elements: Vec<Content>,
|
||||||
#[capable(LayoutMath)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct VecNode(Vec<Content>);
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl VecNode {
|
|
||||||
/// The delimiter to use.
|
/// The delimiter to use.
|
||||||
///
|
///
|
||||||
/// ```example
|
/// ```example
|
||||||
/// #set math.vec(delim: "[")
|
/// #set math.vec(delim: "[")
|
||||||
/// $ vec(1, 2) $
|
/// $ vec(1, 2) $
|
||||||
/// ```
|
/// ```
|
||||||
pub const DELIM: Delimiter = Delimiter::Paren;
|
#[settable]
|
||||||
|
#[default(Delimiter::Paren)]
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
pub delim: Delimiter,
|
||||||
Ok(Self(args.all()?).pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for VecNode {
|
impl LayoutMath for VecNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
let delim = ctx.styles().get(Self::DELIM);
|
let delim = ctx.styles().get(Self::DELIM);
|
||||||
let frame = layout_vec_body(ctx, &self.0, Align::Center)?;
|
let frame = layout_vec_body(ctx, &self.elements(), Align::Center)?;
|
||||||
layout_delimiters(ctx, frame, Some(delim.open()), Some(delim.close()))
|
layout_delimiters(ctx, frame, Some(delim.open()), Some(delim.close()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Matrix
|
|
||||||
/// A matrix.
|
/// A matrix.
|
||||||
///
|
///
|
||||||
/// The elements of a row should be separated by commas, while the rows
|
/// The elements of a row should be separated by commas, while the rows
|
||||||
|
|
@ -70,33 +61,32 @@ impl LayoutMath for VecNode {
|
||||||
/// ) $
|
/// ) $
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Matrix
|
||||||
/// - rows: `Array` (positional, variadic)
|
/// Category: math
|
||||||
/// An array of arrays with the rows of the matrix.
|
#[node(Construct, LayoutMath)]
|
||||||
///
|
pub struct MatNode {
|
||||||
/// ```example
|
/// An array of arrays with the rows of the matrix.
|
||||||
/// #let data = ((1, 2, 3), (4, 5, 6))
|
///
|
||||||
/// #let matrix = math.mat(..data)
|
/// ```example
|
||||||
/// $ v := matrix $
|
/// #let data = ((1, 2, 3), (4, 5, 6))
|
||||||
/// ```
|
/// #let matrix = math.mat(..data)
|
||||||
///
|
/// $ v := matrix $
|
||||||
/// ## Category
|
/// ```
|
||||||
/// math
|
#[variadic]
|
||||||
#[func]
|
pub rows: Vec<Vec<Content>>,
|
||||||
#[capable(LayoutMath)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct MatNode(Vec<Vec<Content>>);
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl MatNode {
|
|
||||||
/// The delimiter to use.
|
/// The delimiter to use.
|
||||||
///
|
///
|
||||||
/// ```example
|
/// ```example
|
||||||
/// #set math.mat(delim: "[")
|
/// #set math.mat(delim: "[")
|
||||||
/// $ mat(1, 2; 3, 4) $
|
/// $ mat(1, 2; 3, 4) $
|
||||||
/// ```
|
/// ```
|
||||||
pub const DELIM: Delimiter = Delimiter::Paren;
|
#[settable]
|
||||||
|
#[default(Delimiter::Paren)]
|
||||||
|
pub delim: Delimiter,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Construct for MatNode {
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
let mut rows = vec![];
|
let mut rows = vec![];
|
||||||
let mut width = 0;
|
let mut width = 0;
|
||||||
|
|
@ -119,19 +109,18 @@ impl MatNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self(rows).pack())
|
Ok(Self::new(rows).pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for MatNode {
|
impl LayoutMath for MatNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
let delim = ctx.styles().get(Self::DELIM);
|
let delim = ctx.styles().get(Self::DELIM);
|
||||||
let frame = layout_mat_body(ctx, &self.0)?;
|
let frame = layout_mat_body(ctx, &self.rows())?;
|
||||||
layout_delimiters(ctx, frame, Some(delim.open()), Some(delim.close()))
|
layout_delimiters(ctx, frame, Some(delim.open()), Some(delim.close()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Cases
|
|
||||||
/// A case distinction.
|
/// A case distinction.
|
||||||
///
|
///
|
||||||
/// Content across different branches can be aligned with the `&` symbol.
|
/// Content across different branches can be aligned with the `&` symbol.
|
||||||
|
|
@ -146,36 +135,29 @@ impl LayoutMath for MatNode {
|
||||||
/// ) $
|
/// ) $
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Cases
|
||||||
/// - branches: `Content` (positional, variadic)
|
/// Category: math
|
||||||
/// The branches of the case distinction.
|
#[node(LayoutMath)]
|
||||||
///
|
pub struct CasesNode {
|
||||||
/// ## Category
|
/// The branches of the case distinction.
|
||||||
/// math
|
#[variadic]
|
||||||
#[func]
|
pub branches: Vec<Content>,
|
||||||
#[capable(LayoutMath)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct CasesNode(Vec<Content>);
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl CasesNode {
|
|
||||||
/// The delimiter to use.
|
/// The delimiter to use.
|
||||||
///
|
///
|
||||||
/// ```example
|
/// ```example
|
||||||
/// #set math.cases(delim: "[")
|
/// #set math.cases(delim: "[")
|
||||||
/// $ x = cases(1, 2) $
|
/// $ x = cases(1, 2) $
|
||||||
/// ```
|
/// ```
|
||||||
pub const DELIM: Delimiter = Delimiter::Brace;
|
#[settable]
|
||||||
|
#[default(Delimiter::Brace)]
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
pub delim: Delimiter,
|
||||||
Ok(Self(args.all()?).pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for CasesNode {
|
impl LayoutMath for CasesNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
let delim = ctx.styles().get(Self::DELIM);
|
let delim = ctx.styles().get(Self::DELIM);
|
||||||
let frame = layout_vec_body(ctx, &self.0, Align::Left)?;
|
let frame = layout_vec_body(ctx, &self.branches(), Align::Left)?;
|
||||||
layout_delimiters(ctx, frame, Some(delim.open()), None)
|
layout_delimiters(ctx, frame, Some(delim.open()), None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -214,7 +196,7 @@ impl Delimiter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
Delimiter,
|
Delimiter,
|
||||||
/// Delimit with parentheses.
|
/// Delimit with parentheses.
|
||||||
"(" => Self::Paren,
|
"(" => Self::Paren,
|
||||||
|
|
@ -228,6 +210,16 @@ castable! {
|
||||||
"||" => Self::DoubleBar,
|
"||" => Self::DoubleBar,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: Delimiter => Value::from(match v {
|
||||||
|
Delimiter::Paren => "(",
|
||||||
|
Delimiter::Bracket => "[",
|
||||||
|
Delimiter::Brace => "{",
|
||||||
|
Delimiter::Bar => "|",
|
||||||
|
Delimiter::DoubleBar => "||",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Layout the inner contents of a vector.
|
/// Layout the inner contents of a vector.
|
||||||
fn layout_vec_body(
|
fn layout_vec_body(
|
||||||
ctx: &mut MathContext,
|
ctx: &mut MathContext,
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,6 @@ pub fn module() -> Module {
|
||||||
Module::new("math").with_scope(math)
|
Module::new("math").with_scope(math)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Formula
|
|
||||||
/// A mathematical formula.
|
/// A mathematical formula.
|
||||||
///
|
///
|
||||||
/// Can be displayed inline with text or as a separate block.
|
/// Can be displayed inline with text or as a separate block.
|
||||||
|
|
@ -132,46 +131,25 @@ pub fn module() -> Module {
|
||||||
/// horizontally. For more details about math syntax, see the
|
/// horizontally. For more details about math syntax, see the
|
||||||
/// [main math page]($category/math).
|
/// [main math page]($category/math).
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Formula
|
||||||
/// - body: `Content` (positional, required)
|
/// Category: math
|
||||||
/// The contents of the formula.
|
#[node(Show, Finalize, Layout, LayoutMath)]
|
||||||
///
|
|
||||||
/// - block: `bool` (named)
|
|
||||||
/// Whether the formula is displayed as a separate block.
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// math
|
|
||||||
#[func]
|
|
||||||
#[capable(Show, Finalize, Layout, LayoutMath)]
|
|
||||||
#[derive(Debug, Clone, Hash)]
|
|
||||||
pub struct FormulaNode {
|
pub struct FormulaNode {
|
||||||
/// Whether the formula is displayed as a separate block.
|
|
||||||
pub block: bool,
|
|
||||||
/// The content of the formula.
|
/// The content of the formula.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
pub body: Content,
|
pub body: Content,
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
/// Whether the formula is displayed as a separate block.
|
||||||
impl FormulaNode {
|
#[named]
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
#[default(false)]
|
||||||
let body = args.expect("body")?;
|
pub block: bool,
|
||||||
let block = args.named("block")?.unwrap_or(false);
|
|
||||||
Ok(Self { block, body }.pack())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field(&self, name: &str) -> Option<Value> {
|
|
||||||
match name {
|
|
||||||
"body" => Some(Value::Content(self.body.clone())),
|
|
||||||
"block" => Some(Value::Bool(self.block)),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for FormulaNode {
|
impl Show for FormulaNode {
|
||||||
fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> SourceResult<Content> {
|
||||||
let mut realized = self.clone().pack().guarded(Guard::Base(NodeId::of::<Self>()));
|
let mut realized = self.clone().pack().guarded(Guard::Base(NodeId::of::<Self>()));
|
||||||
if self.block {
|
if self.block() {
|
||||||
realized = realized.aligned(Axes::with_x(Some(Align::Center.into())))
|
realized = realized.aligned(Axes::with_x(Some(Align::Center.into())))
|
||||||
}
|
}
|
||||||
Ok(realized)
|
Ok(realized)
|
||||||
|
|
@ -196,27 +174,29 @@ impl Layout for FormulaNode {
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
|
let block = self.block();
|
||||||
|
|
||||||
// Find a math font.
|
// Find a math font.
|
||||||
let variant = variant(styles);
|
let variant = variant(styles);
|
||||||
let world = vt.world();
|
let world = vt.world();
|
||||||
let Some(font) = families(styles)
|
let Some(font) = families(styles)
|
||||||
.find_map(|family| {
|
.find_map(|family| {
|
||||||
let id = world.book().select(family, variant)?;
|
let id = world.book().select(family.as_str(), variant)?;
|
||||||
let font = world.font(id)?;
|
let font = world.font(id)?;
|
||||||
let _ = font.ttf().tables().math?.constants?;
|
let _ = font.ttf().tables().math?.constants?;
|
||||||
Some(font)
|
Some(font)
|
||||||
})
|
})
|
||||||
else {
|
else {
|
||||||
if let Some(span) = self.body.span() {
|
if let Some(span) = self.span() {
|
||||||
bail!(span, "current font does not support math");
|
bail!(span, "current font does not support math");
|
||||||
}
|
}
|
||||||
return Ok(Fragment::frame(Frame::new(Size::zero())))
|
return Ok(Fragment::frame(Frame::new(Size::zero())))
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut ctx = MathContext::new(vt, styles, regions, &font, self.block);
|
let mut ctx = MathContext::new(vt, styles, regions, &font, block);
|
||||||
let mut frame = ctx.layout_frame(self)?;
|
let mut frame = ctx.layout_frame(self)?;
|
||||||
|
|
||||||
if !self.block {
|
if !block {
|
||||||
let slack = styles.get(ParNode::LEADING) * 0.7;
|
let slack = styles.get(ParNode::LEADING) * 0.7;
|
||||||
let top_edge = styles.get(TextNode::TOP_EDGE).resolve(styles, font.metrics());
|
let top_edge = styles.get(TextNode::TOP_EDGE).resolve(styles, font.metrics());
|
||||||
let bottom_edge =
|
let bottom_edge =
|
||||||
|
|
@ -232,38 +212,38 @@ impl Layout for FormulaNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[capability]
|
|
||||||
pub trait LayoutMath {
|
pub trait LayoutMath {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()>;
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for FormulaNode {
|
impl LayoutMath for FormulaNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
self.body.layout_math(ctx)
|
self.body().layout_math(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for Content {
|
impl LayoutMath for Content {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
if let Some(node) = self.to::<SequenceNode>() {
|
if let Some(node) = self.to::<SequenceNode>() {
|
||||||
for child in &node.0 {
|
for child in node.children() {
|
||||||
child.layout_math(ctx)?;
|
child.layout_math(ctx)?;
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(styled) = self.to::<StyledNode>() {
|
if let Some(styled) = self.to::<StyledNode>() {
|
||||||
if styled.map.contains(TextNode::FAMILY) {
|
let map = styled.map();
|
||||||
|
if map.contains(TextNode::FAMILY) {
|
||||||
let frame = ctx.layout_content(self)?;
|
let frame = ctx.layout_content(self)?;
|
||||||
ctx.push(FrameFragment::new(ctx, frame).with_spaced(true));
|
ctx.push(FrameFragment::new(ctx, frame).with_spaced(true));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let prev_map = std::mem::replace(&mut ctx.map, styled.map.clone());
|
let prev_map = std::mem::replace(&mut ctx.map, map);
|
||||||
let prev_size = ctx.size;
|
let prev_size = ctx.size;
|
||||||
ctx.map.apply(prev_map.clone());
|
ctx.map.apply(prev_map.clone());
|
||||||
ctx.size = ctx.styles().get(TextNode::SIZE);
|
ctx.size = ctx.styles().get(TextNode::SIZE);
|
||||||
styled.sub.layout_math(ctx)?;
|
styled.sub().layout_math(ctx)?;
|
||||||
ctx.size = prev_size;
|
ctx.size = prev_size;
|
||||||
ctx.map = prev_map;
|
ctx.map = prev_map;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
@ -280,7 +260,7 @@ impl LayoutMath for Content {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(node) = self.to::<HNode>() {
|
if let Some(node) = self.to::<HNode>() {
|
||||||
if let Spacing::Rel(rel) = node.amount {
|
if let Spacing::Rel(rel) = node.amount() {
|
||||||
if rel.rel.is_zero() {
|
if rel.rel.is_zero() {
|
||||||
ctx.push(MathFragment::Spacing(rel.abs.resolve(ctx.styles())));
|
ctx.push(MathFragment::Spacing(rel.abs.resolve(ctx.styles())));
|
||||||
}
|
}
|
||||||
|
|
@ -289,7 +269,7 @@ impl LayoutMath for Content {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(node) = self.to::<TextNode>() {
|
if let Some(node) = self.to::<TextNode>() {
|
||||||
ctx.layout_text(&node.0)?;
|
ctx.layout_text(&node.text())?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ use typst::eval::Scope;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// # Text Operator
|
|
||||||
/// A text operator in a math formula.
|
/// A text operator in a math formula.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -19,45 +18,30 @@ use super::*;
|
||||||
/// `max`, `min`, `Pr`, `sec`, `sin`, `sinh`, `sup`, `tan`, `tg`, `tanh`,
|
/// `max`, `min`, `Pr`, `sec`, `sin`, `sinh`, `sup`, `tan`, `tg`, `tanh`,
|
||||||
/// `liminf`, and `limsup`.
|
/// `liminf`, and `limsup`.
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Text Operator
|
||||||
/// - text: `EcoString` (positional, required)
|
/// Category: math
|
||||||
/// The operator's text.
|
#[node(LayoutMath)]
|
||||||
///
|
|
||||||
/// - limits: `bool` (named)
|
|
||||||
/// Whether the operator should force attachments to display as limits.
|
|
||||||
///
|
|
||||||
/// Defaults to `{false}`.
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// math
|
|
||||||
#[func]
|
|
||||||
#[capable(LayoutMath)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct OpNode {
|
pub struct OpNode {
|
||||||
/// The operator's text.
|
/// The operator's text.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
pub text: EcoString,
|
pub text: EcoString,
|
||||||
/// Whether the operator should force attachments to display as limits.
|
|
||||||
pub limits: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
/// Whether the operator should force attachments to display as limits.
|
||||||
impl OpNode {
|
///
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
/// Defaults to `{false}`.
|
||||||
Ok(Self {
|
#[named]
|
||||||
text: args.expect("text")?,
|
#[default(false)]
|
||||||
limits: args.named("limits")?.unwrap_or(false),
|
pub limits: bool,
|
||||||
}
|
|
||||||
.pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for OpNode {
|
impl LayoutMath for OpNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
let frame = ctx.layout_content(&TextNode(self.text.clone()).pack())?;
|
let frame = ctx.layout_content(&TextNode::packed(self.text()))?;
|
||||||
ctx.push(
|
ctx.push(
|
||||||
FrameFragment::new(ctx, frame)
|
FrameFragment::new(ctx, frame)
|
||||||
.with_class(MathClass::Large)
|
.with_class(MathClass::Large)
|
||||||
.with_limits(self.limits),
|
.with_limits(self.limits()),
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -68,13 +52,15 @@ macro_rules! ops {
|
||||||
pub(super) fn define(math: &mut Scope) {
|
pub(super) fn define(math: &mut Scope) {
|
||||||
$(math.define(
|
$(math.define(
|
||||||
stringify!($name),
|
stringify!($name),
|
||||||
OpNode {
|
OpNode::new(ops!(@name $name $(: $value)?).into())
|
||||||
text: ops!(@name $name $(: $value)?).into(),
|
.with_limits(ops!(@limit $($tts)*))
|
||||||
limits: ops!(@limit $($tts)*),
|
.pack()
|
||||||
}.pack()
|
|
||||||
);)*
|
);)*
|
||||||
|
|
||||||
let dif = |d| HNode::strong(THIN).pack() + UprightNode(TextNode::packed(d)).pack();
|
let dif = |d| {
|
||||||
|
HNode::new(THIN.into()).pack()
|
||||||
|
+ UprightNode::new(TextNode::packed(d)).pack()
|
||||||
|
};
|
||||||
math.define("dif", dif('d'));
|
math.define("dif", dif('d'));
|
||||||
math.define("Dif", dif('D'));
|
math.define("Dif", dif('D'));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// # Square Root
|
|
||||||
/// A square root.
|
/// A square root.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -8,31 +7,22 @@ use super::*;
|
||||||
/// $ sqrt(x^2) = x = sqrt(x)^2 $
|
/// $ sqrt(x^2) = x = sqrt(x)^2 $
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Square Root
|
||||||
/// - radicand: `Content` (positional, required)
|
/// Category: math
|
||||||
/// The expression to take the square root of.
|
#[node(LayoutMath)]
|
||||||
///
|
pub struct SqrtNode {
|
||||||
/// ## Category
|
/// The expression to take the square root of.
|
||||||
/// math
|
#[positional]
|
||||||
#[func]
|
#[required]
|
||||||
#[capable(LayoutMath)]
|
pub radicand: Content,
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct SqrtNode(pub Content);
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl SqrtNode {
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
Ok(Self(args.expect("radicand")?).pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for SqrtNode {
|
impl LayoutMath for SqrtNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
layout(ctx, None, &self.0)
|
layout(ctx, None, &self.radicand())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Root
|
|
||||||
/// A general root.
|
/// A general root.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -40,37 +30,24 @@ impl LayoutMath for SqrtNode {
|
||||||
/// $ root(3, x) $
|
/// $ root(3, x) $
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Root
|
||||||
/// - index: `Content` (positional, required)
|
/// Category: math
|
||||||
/// Which root of the radicand to take.
|
#[node(LayoutMath)]
|
||||||
///
|
|
||||||
/// - radicand: `Content` (positional, required)
|
|
||||||
/// The expression to take the root of.
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// math
|
|
||||||
#[func]
|
|
||||||
#[capable(LayoutMath)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct RootNode {
|
pub struct RootNode {
|
||||||
|
/// Which root of the radicand to take.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
index: Content,
|
index: Content,
|
||||||
radicand: Content,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
/// The expression to take the root of.
|
||||||
impl RootNode {
|
#[positional]
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
#[required]
|
||||||
Ok(Self {
|
radicand: Content,
|
||||||
index: args.expect("index")?,
|
|
||||||
radicand: args.expect("radicand")?,
|
|
||||||
}
|
|
||||||
.pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for RootNode {
|
impl LayoutMath for RootNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
layout(ctx, Some(&self.index), &self.radicand)
|
layout(ctx, Some(&self.index()), &self.radicand())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -164,7 +141,7 @@ fn layout(
|
||||||
/// Select a precomposed radical, if the font has it.
|
/// Select a precomposed radical, if the font has it.
|
||||||
fn precomposed(ctx: &MathContext, index: Option<&Content>, target: Abs) -> Option<Frame> {
|
fn precomposed(ctx: &MathContext, index: Option<&Content>, target: Abs) -> Option<Frame> {
|
||||||
let node = index?.to::<TextNode>()?;
|
let node = index?.to::<TextNode>()?;
|
||||||
let c = match node.0.as_str() {
|
let c = match node.text().as_str() {
|
||||||
"3" => '∛',
|
"3" => '∛',
|
||||||
"4" => '∜',
|
"4" => '∜',
|
||||||
_ => return None,
|
_ => return None,
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ impl MathRow {
|
||||||
|
|
||||||
pub fn to_frame(self, ctx: &MathContext) -> Frame {
|
pub fn to_frame(self, ctx: &MathContext) -> Frame {
|
||||||
let styles = ctx.styles();
|
let styles = ctx.styles();
|
||||||
let align = styles.get(AlignNode::ALIGNS).x.resolve(styles);
|
let align = styles.get(AlignNode::ALIGNMENT).x.resolve(styles);
|
||||||
self.to_aligned_frame(ctx, &[], align)
|
self.to_aligned_frame(ctx, &[], align)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -200,10 +200,7 @@ impl MathRow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> From<T> for MathRow
|
impl<T: Into<MathFragment>> From<T> for MathRow {
|
||||||
where
|
|
||||||
T: Into<MathFragment>,
|
|
||||||
{
|
|
||||||
fn from(fragment: T) -> Self {
|
fn from(fragment: T) -> Self {
|
||||||
Self(vec![fragment.into()])
|
Self(vec![fragment.into()])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,10 @@ pub(super) const QUAD: Em = Em::new(1.0);
|
||||||
|
|
||||||
/// Hook up all spacings.
|
/// Hook up all spacings.
|
||||||
pub(super) fn define(math: &mut Scope) {
|
pub(super) fn define(math: &mut Scope) {
|
||||||
math.define("thin", HNode::strong(THIN).pack());
|
math.define("thin", HNode::new(THIN.into()).pack());
|
||||||
math.define("med", HNode::strong(MEDIUM).pack());
|
math.define("med", HNode::new(MEDIUM.into()).pack());
|
||||||
math.define("thick", HNode::strong(THICK).pack());
|
math.define("thick", HNode::new(THICK.into()).pack());
|
||||||
math.define("quad", HNode::strong(QUAD).pack());
|
math.define("quad", HNode::new(QUAD.into()).pack());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create the spacing between two fragments in a given style.
|
/// Create the spacing between two fragments in a given style.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// # Bold
|
|
||||||
/// Bold font style in math.
|
/// Bold font style in math.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -8,34 +7,25 @@ use super::*;
|
||||||
/// $ bold(A) := B^+ $
|
/// $ bold(A) := B^+ $
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Bold
|
||||||
/// - body: `Content` (positional, required)
|
/// Category: math
|
||||||
/// The piece of formula to style.
|
#[node(LayoutMath)]
|
||||||
///
|
pub struct BoldNode {
|
||||||
/// ## Category
|
/// The piece of formula to style.
|
||||||
/// math
|
#[positional]
|
||||||
#[func]
|
#[required]
|
||||||
#[capable(LayoutMath)]
|
pub body: Content,
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct BoldNode(pub Content);
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl BoldNode {
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
Ok(Self(args.expect("body")?).pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for BoldNode {
|
impl LayoutMath for BoldNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
ctx.style(ctx.style.with_bold(true));
|
ctx.style(ctx.style.with_bold(true));
|
||||||
self.0.layout_math(ctx)?;
|
self.body().layout_math(ctx)?;
|
||||||
ctx.unstyle();
|
ctx.unstyle();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Upright
|
|
||||||
/// Upright (non-italic) font style in math.
|
/// Upright (non-italic) font style in math.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -43,98 +33,71 @@ impl LayoutMath for BoldNode {
|
||||||
/// $ upright(A) != A $
|
/// $ upright(A) != A $
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Upright
|
||||||
/// - body: `Content` (positional, required)
|
/// Category: math
|
||||||
/// The piece of formula to style.
|
#[node(LayoutMath)]
|
||||||
///
|
pub struct UprightNode {
|
||||||
/// ## Category
|
/// The piece of formula to style.
|
||||||
/// math
|
#[positional]
|
||||||
#[func]
|
#[required]
|
||||||
#[capable(LayoutMath)]
|
pub body: Content,
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct UprightNode(pub Content);
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl UprightNode {
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
Ok(Self(args.expect("body")?).pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for UprightNode {
|
impl LayoutMath for UprightNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
ctx.style(ctx.style.with_italic(false));
|
ctx.style(ctx.style.with_italic(false));
|
||||||
self.0.layout_math(ctx)?;
|
self.body().layout_math(ctx)?;
|
||||||
ctx.unstyle();
|
ctx.unstyle();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Italic
|
|
||||||
/// Italic font style in math.
|
/// Italic font style in math.
|
||||||
///
|
///
|
||||||
/// For roman letters and greek lowercase letters, this is already the default.
|
/// For roman letters and greek lowercase letters, this is already the default.
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Italic
|
||||||
/// - body: `Content` (positional, required)
|
/// Category: math
|
||||||
/// The piece of formula to style.
|
#[node(LayoutMath)]
|
||||||
///
|
pub struct ItalicNode {
|
||||||
/// ## Category
|
/// The piece of formula to style.
|
||||||
/// math
|
#[positional]
|
||||||
#[func]
|
#[required]
|
||||||
#[capable(LayoutMath)]
|
pub body: Content,
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct ItalicNode(pub Content);
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl ItalicNode {
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
Ok(Self(args.expect("body")?).pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for ItalicNode {
|
impl LayoutMath for ItalicNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
ctx.style(ctx.style.with_italic(true));
|
ctx.style(ctx.style.with_italic(true));
|
||||||
self.0.layout_math(ctx)?;
|
self.body().layout_math(ctx)?;
|
||||||
ctx.unstyle();
|
ctx.unstyle();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Serif
|
|
||||||
/// Serif (roman) font style in math.
|
/// Serif (roman) font style in math.
|
||||||
///
|
///
|
||||||
/// This is already the default.
|
/// This is already the default.
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Serif
|
||||||
/// - body: `Content` (positional, required)
|
/// Category: math
|
||||||
/// The piece of formula to style.
|
#[node(LayoutMath)]
|
||||||
///
|
pub struct SerifNode {
|
||||||
/// ## Category
|
/// The piece of formula to style.
|
||||||
/// math
|
#[positional]
|
||||||
#[func]
|
#[required]
|
||||||
#[capable(LayoutMath)]
|
pub body: Content,
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct SerifNode(pub Content);
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl SerifNode {
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
Ok(Self(args.expect("body")?).pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for SerifNode {
|
impl LayoutMath for SerifNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
ctx.style(ctx.style.with_variant(MathVariant::Serif));
|
ctx.style(ctx.style.with_variant(MathVariant::Serif));
|
||||||
self.0.layout_math(ctx)?;
|
self.body().layout_math(ctx)?;
|
||||||
ctx.unstyle();
|
ctx.unstyle();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Sans-serif
|
|
||||||
/// Sans-serif font style in math.
|
/// Sans-serif font style in math.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -142,34 +105,25 @@ impl LayoutMath for SerifNode {
|
||||||
/// $ sans(A B C) $
|
/// $ sans(A B C) $
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Sans-serif
|
||||||
/// - body: `Content` (positional, required)
|
/// Category: math
|
||||||
/// The piece of formula to style.
|
#[node(LayoutMath)]
|
||||||
///
|
pub struct SansNode {
|
||||||
/// ## Category
|
/// The piece of formula to style.
|
||||||
/// math
|
#[positional]
|
||||||
#[func]
|
#[required]
|
||||||
#[capable(LayoutMath)]
|
pub body: Content,
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct SansNode(pub Content);
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl SansNode {
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
Ok(Self(args.expect("body")?).pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for SansNode {
|
impl LayoutMath for SansNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
ctx.style(ctx.style.with_variant(MathVariant::Sans));
|
ctx.style(ctx.style.with_variant(MathVariant::Sans));
|
||||||
self.0.layout_math(ctx)?;
|
self.body().layout_math(ctx)?;
|
||||||
ctx.unstyle();
|
ctx.unstyle();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Calligraphic
|
|
||||||
/// Calligraphic font style in math.
|
/// Calligraphic font style in math.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -177,34 +131,25 @@ impl LayoutMath for SansNode {
|
||||||
/// Let $cal(P)$ be the set of ...
|
/// Let $cal(P)$ be the set of ...
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Calligraphic
|
||||||
/// - body: `Content` (positional, required)
|
/// Category: math
|
||||||
/// The piece of formula to style.
|
#[node(LayoutMath)]
|
||||||
///
|
pub struct CalNode {
|
||||||
/// ## Category
|
/// The piece of formula to style.
|
||||||
/// math
|
#[positional]
|
||||||
#[func]
|
#[required]
|
||||||
#[capable(LayoutMath)]
|
pub body: Content,
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct CalNode(pub Content);
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl CalNode {
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
Ok(Self(args.expect("body")?).pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for CalNode {
|
impl LayoutMath for CalNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
ctx.style(ctx.style.with_variant(MathVariant::Cal));
|
ctx.style(ctx.style.with_variant(MathVariant::Cal));
|
||||||
self.0.layout_math(ctx)?;
|
self.body().layout_math(ctx)?;
|
||||||
ctx.unstyle();
|
ctx.unstyle();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Fraktur
|
|
||||||
/// Fraktur font style in math.
|
/// Fraktur font style in math.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -212,34 +157,25 @@ impl LayoutMath for CalNode {
|
||||||
/// $ frak(P) $
|
/// $ frak(P) $
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Fraktur
|
||||||
/// - body: `Content` (positional, required)
|
/// Category: math
|
||||||
/// The piece of formula to style.
|
#[node(LayoutMath)]
|
||||||
///
|
pub struct FrakNode {
|
||||||
/// ## Category
|
/// The piece of formula to style.
|
||||||
/// math
|
#[positional]
|
||||||
#[func]
|
#[required]
|
||||||
#[capable(LayoutMath)]
|
pub body: Content,
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct FrakNode(pub Content);
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl FrakNode {
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
Ok(Self(args.expect("body")?).pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for FrakNode {
|
impl LayoutMath for FrakNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
ctx.style(ctx.style.with_variant(MathVariant::Frak));
|
ctx.style(ctx.style.with_variant(MathVariant::Frak));
|
||||||
self.0.layout_math(ctx)?;
|
self.body().layout_math(ctx)?;
|
||||||
ctx.unstyle();
|
ctx.unstyle();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Monospace
|
|
||||||
/// Monospace font style in math.
|
/// Monospace font style in math.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -247,34 +183,25 @@ impl LayoutMath for FrakNode {
|
||||||
/// $ mono(x + y = z) $
|
/// $ mono(x + y = z) $
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Monospace
|
||||||
/// - body: `Content` (positional, required)
|
/// Category: math
|
||||||
/// The piece of formula to style.
|
#[node(LayoutMath)]
|
||||||
///
|
pub struct MonoNode {
|
||||||
/// ## Category
|
/// The piece of formula to style.
|
||||||
/// math
|
#[positional]
|
||||||
#[func]
|
#[required]
|
||||||
#[capable(LayoutMath)]
|
pub body: Content,
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct MonoNode(pub Content);
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl MonoNode {
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
Ok(Self(args.expect("body")?).pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for MonoNode {
|
impl LayoutMath for MonoNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
ctx.style(ctx.style.with_variant(MathVariant::Mono));
|
ctx.style(ctx.style.with_variant(MathVariant::Mono));
|
||||||
self.0.layout_math(ctx)?;
|
self.body().layout_math(ctx)?;
|
||||||
ctx.unstyle();
|
ctx.unstyle();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Blackboard Bold
|
|
||||||
/// Blackboard bold (double-struck) font style in math.
|
/// Blackboard bold (double-struck) font style in math.
|
||||||
///
|
///
|
||||||
/// For uppercase latin letters, blackboard bold is additionally available
|
/// For uppercase latin letters, blackboard bold is additionally available
|
||||||
|
|
@ -287,28 +214,20 @@ impl LayoutMath for MonoNode {
|
||||||
/// $ f: NN -> RR $
|
/// $ f: NN -> RR $
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Blackboard Bold
|
||||||
/// - body: `Content` (positional, required)
|
/// Category: math
|
||||||
/// The piece of formula to style.
|
#[node(LayoutMath)]
|
||||||
///
|
pub struct BbNode {
|
||||||
/// ## Category
|
/// The piece of formula to style.
|
||||||
/// math
|
#[positional]
|
||||||
#[func]
|
#[required]
|
||||||
#[capable(LayoutMath)]
|
pub body: Content,
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct BbNode(pub Content);
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl BbNode {
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
Ok(Self(args.expect("body")?).pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for BbNode {
|
impl LayoutMath for BbNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
ctx.style(ctx.style.with_variant(MathVariant::Bb));
|
ctx.style(ctx.style.with_variant(MathVariant::Bb));
|
||||||
self.0.layout_math(ctx)?;
|
self.body().layout_math(ctx)?;
|
||||||
ctx.unstyle();
|
ctx.unstyle();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ const LINE_GAP: Em = Em::new(0.15);
|
||||||
const BRACE_GAP: Em = Em::new(0.25);
|
const BRACE_GAP: Em = Em::new(0.25);
|
||||||
const BRACKET_GAP: Em = Em::new(0.25);
|
const BRACKET_GAP: Em = Em::new(0.25);
|
||||||
|
|
||||||
/// # Underline
|
|
||||||
/// A horizontal line under content.
|
/// A horizontal line under content.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -12,31 +11,22 @@ const BRACKET_GAP: Em = Em::new(0.25);
|
||||||
/// $ underline(1 + 2 + ... + 5) $
|
/// $ underline(1 + 2 + ... + 5) $
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Underline
|
||||||
/// - body: `Content` (positional, required)
|
/// Category: math
|
||||||
/// The content above the line.
|
#[node(LayoutMath)]
|
||||||
///
|
pub struct UnderlineNode {
|
||||||
/// ## Category
|
/// The content above the line.
|
||||||
/// math
|
#[positional]
|
||||||
#[func]
|
#[required]
|
||||||
#[capable(LayoutMath)]
|
pub body: Content,
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct UnderlineNode(Content);
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl UnderlineNode {
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
Ok(Self(args.expect("body")?).pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for UnderlineNode {
|
impl LayoutMath for UnderlineNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
layout(ctx, &self.0, &None, '\u{305}', LINE_GAP, false)
|
layout(ctx, &self.body(), &None, '\u{305}', LINE_GAP, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Overline
|
|
||||||
/// A horizontal line over content.
|
/// A horizontal line over content.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -44,31 +34,22 @@ impl LayoutMath for UnderlineNode {
|
||||||
/// $ overline(1 + 2 + ... + 5) $
|
/// $ overline(1 + 2 + ... + 5) $
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Overline
|
||||||
/// - body: `Content` (positional, required)
|
/// Category: math
|
||||||
/// The content below the line.
|
#[node(LayoutMath)]
|
||||||
///
|
pub struct OverlineNode {
|
||||||
/// ## Category
|
/// The content below the line.
|
||||||
/// math
|
#[positional]
|
||||||
#[func]
|
#[required]
|
||||||
#[capable(LayoutMath)]
|
pub body: Content,
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct OverlineNode(Content);
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl OverlineNode {
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
Ok(Self(args.expect("body")?).pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for OverlineNode {
|
impl LayoutMath for OverlineNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
layout(ctx, &self.0, &None, '\u{332}', LINE_GAP, true)
|
layout(ctx, &self.body(), &None, '\u{332}', LINE_GAP, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Underbrace
|
|
||||||
/// A horizontal brace under content, with an optional annotation below.
|
/// A horizontal brace under content, with an optional annotation below.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -76,41 +57,27 @@ impl LayoutMath for OverlineNode {
|
||||||
/// $ underbrace(1 + 2 + ... + 5, "numbers") $
|
/// $ underbrace(1 + 2 + ... + 5, "numbers") $
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Underbrace
|
||||||
/// - body: `Content` (positional, required)
|
/// Category: math
|
||||||
/// The content above the brace.
|
#[node(LayoutMath)]
|
||||||
///
|
|
||||||
/// - annotation: `Content` (positional)
|
|
||||||
/// The optional content below the brace.
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// math
|
|
||||||
#[func]
|
|
||||||
#[capable(LayoutMath)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct UnderbraceNode {
|
pub struct UnderbraceNode {
|
||||||
/// The content above the brace.
|
/// The content above the brace.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
pub body: Content,
|
pub body: Content,
|
||||||
/// The optional content below the brace.
|
|
||||||
pub annotation: Option<Content>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
/// The optional content below the brace.
|
||||||
impl UnderbraceNode {
|
#[positional]
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
#[default]
|
||||||
let body = args.expect("body")?;
|
pub annotation: Option<Content>,
|
||||||
let annotation = args.eat()?;
|
|
||||||
Ok(Self { body, annotation }.pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for UnderbraceNode {
|
impl LayoutMath for UnderbraceNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
layout(ctx, &self.body, &self.annotation, '⏟', BRACE_GAP, false)
|
layout(ctx, &self.body(), &self.annotation(), '⏟', BRACE_GAP, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Overbrace
|
|
||||||
/// A horizontal brace over content, with an optional annotation above.
|
/// A horizontal brace over content, with an optional annotation above.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -118,41 +85,27 @@ impl LayoutMath for UnderbraceNode {
|
||||||
/// $ overbrace(1 + 2 + ... + 5, "numbers") $
|
/// $ overbrace(1 + 2 + ... + 5, "numbers") $
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Overbrace
|
||||||
/// - body: `Content` (positional, required)
|
/// Category: math
|
||||||
/// The content below the brace.
|
#[node(LayoutMath)]
|
||||||
///
|
|
||||||
/// - annotation: `Content` (positional)
|
|
||||||
/// The optional content above the brace.
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// math
|
|
||||||
#[func]
|
|
||||||
#[capable(LayoutMath)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct OverbraceNode {
|
pub struct OverbraceNode {
|
||||||
/// The content below the brace.
|
/// The content below the brace.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
pub body: Content,
|
pub body: Content,
|
||||||
/// The optional content above the brace.
|
|
||||||
pub annotation: Option<Content>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
/// The optional content above the brace.
|
||||||
impl OverbraceNode {
|
#[positional]
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
#[default]
|
||||||
let body = args.expect("body")?;
|
pub annotation: Option<Content>,
|
||||||
let annotation = args.eat()?;
|
|
||||||
Ok(Self { body, annotation }.pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for OverbraceNode {
|
impl LayoutMath for OverbraceNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
layout(ctx, &self.body, &self.annotation, '⏞', BRACE_GAP, true)
|
layout(ctx, &self.body(), &self.annotation(), '⏞', BRACE_GAP, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Underbracket
|
|
||||||
/// A horizontal bracket under content, with an optional annotation below.
|
/// A horizontal bracket under content, with an optional annotation below.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -160,41 +113,27 @@ impl LayoutMath for OverbraceNode {
|
||||||
/// $ underbracket(1 + 2 + ... + 5, "numbers") $
|
/// $ underbracket(1 + 2 + ... + 5, "numbers") $
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Underbracket
|
||||||
/// - body: `Content` (positional, required)
|
/// Category: math
|
||||||
/// The content above the bracket.
|
#[node(LayoutMath)]
|
||||||
///
|
|
||||||
/// - annotation: `Content` (positional)
|
|
||||||
/// The optional content below the bracket.
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// math
|
|
||||||
#[func]
|
|
||||||
#[capable(LayoutMath)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct UnderbracketNode {
|
pub struct UnderbracketNode {
|
||||||
/// The content above the bracket.
|
/// The content above the bracket.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
pub body: Content,
|
pub body: Content,
|
||||||
/// The optional content below the bracket.
|
|
||||||
pub annotation: Option<Content>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
/// The optional content below the bracket.
|
||||||
impl UnderbracketNode {
|
#[positional]
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
#[default]
|
||||||
let body = args.expect("body")?;
|
pub annotation: Option<Content>,
|
||||||
let annotation = args.eat()?;
|
|
||||||
Ok(Self { body, annotation }.pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for UnderbracketNode {
|
impl LayoutMath for UnderbracketNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
layout(ctx, &self.body, &self.annotation, '⎵', BRACKET_GAP, false)
|
layout(ctx, &self.body(), &self.annotation(), '⎵', BRACKET_GAP, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Overbracket
|
|
||||||
/// A horizontal bracket over content, with an optional annotation above.
|
/// A horizontal bracket over content, with an optional annotation above.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -202,37 +141,24 @@ impl LayoutMath for UnderbracketNode {
|
||||||
/// $ overbracket(1 + 2 + ... + 5, "numbers") $
|
/// $ overbracket(1 + 2 + ... + 5, "numbers") $
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Overbracket
|
||||||
/// - body: `Content` (positional, required)
|
/// Category: math
|
||||||
/// The content below the bracket.
|
#[node(LayoutMath)]
|
||||||
///
|
|
||||||
/// - annotation: `Content` (positional)
|
|
||||||
/// The optional content above the bracket.
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// math
|
|
||||||
#[func]
|
|
||||||
#[capable(LayoutMath)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct OverbracketNode {
|
pub struct OverbracketNode {
|
||||||
/// The content below the bracket.
|
/// The content below the bracket.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
pub body: Content,
|
pub body: Content,
|
||||||
/// The optional content above the bracket.
|
|
||||||
pub annotation: Option<Content>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
/// The optional content above the bracket.
|
||||||
impl OverbracketNode {
|
#[positional]
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
#[default]
|
||||||
let body = args.expect("body")?;
|
pub annotation: Option<Content>,
|
||||||
let annotation = args.eat()?;
|
|
||||||
Ok(Self { body, annotation }.pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for OverbracketNode {
|
impl LayoutMath for OverbracketNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
layout(ctx, &self.body, &self.annotation, '⎴', BRACKET_GAP, true)
|
layout(ctx, &self.body(), &self.annotation(), '⎴', BRACKET_GAP, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
|
use typst::model::StyledNode;
|
||||||
|
|
||||||
use crate::layout::{LayoutRoot, PageNode};
|
use crate::layout::{LayoutRoot, PageNode};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// # Document
|
|
||||||
/// The root element of a document and its metadata.
|
/// The root element of a document and its metadata.
|
||||||
///
|
///
|
||||||
/// All documents are automatically wrapped in a `document` element. The main
|
/// All documents are automatically wrapped in a `document` element. The main
|
||||||
|
|
@ -11,33 +12,48 @@ use crate::prelude::*;
|
||||||
/// The metadata set with this function is not rendered within the document.
|
/// The metadata set with this function is not rendered within the document.
|
||||||
/// Instead, it is embedded in the compiled PDF file.
|
/// Instead, it is embedded in the compiled PDF file.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Document
|
||||||
/// meta
|
/// Category: meta
|
||||||
#[func]
|
#[node(LayoutRoot)]
|
||||||
#[capable(LayoutRoot)]
|
pub struct DocumentNode {
|
||||||
#[derive(Hash)]
|
/// The page runs.
|
||||||
pub struct DocumentNode(pub StyleVec<PageNode>);
|
#[variadic]
|
||||||
|
pub children: Vec<Content>,
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl DocumentNode {
|
|
||||||
/// The document's title. This is often rendered as the title of the
|
/// The document's title. This is often rendered as the title of the
|
||||||
/// PDF viewer window.
|
/// PDF viewer window.
|
||||||
#[property(referenced)]
|
#[settable]
|
||||||
pub const TITLE: Option<EcoString> = None;
|
#[default]
|
||||||
|
pub title: Option<EcoString>,
|
||||||
|
|
||||||
/// The document's authors.
|
/// The document's authors.
|
||||||
#[property(referenced)]
|
#[settable]
|
||||||
pub const AUTHOR: Author = Author(vec![]);
|
#[default]
|
||||||
|
pub author: Author,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutRoot for DocumentNode {
|
impl LayoutRoot for DocumentNode {
|
||||||
/// Layout the document into a sequence of frames, one per page.
|
/// Layout the document into a sequence of frames, one per page.
|
||||||
fn layout_root(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Document> {
|
fn layout_root(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Document> {
|
||||||
let mut pages = vec![];
|
let mut pages = vec![];
|
||||||
for (page, map) in self.0.iter() {
|
|
||||||
let number = 1 + pages.len();
|
for mut child in self.children() {
|
||||||
let fragment = page.layout(vt, number, styles.chain(map))?;
|
let map;
|
||||||
pages.extend(fragment);
|
let outer = styles;
|
||||||
|
let mut styles = outer;
|
||||||
|
if let Some(node) = child.to::<StyledNode>() {
|
||||||
|
map = node.map();
|
||||||
|
styles = outer.chain(&map);
|
||||||
|
child = node.sub();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(page) = child.to::<PageNode>() {
|
||||||
|
let number = 1 + pages.len();
|
||||||
|
let fragment = page.layout(vt, number, styles)?;
|
||||||
|
pages.extend(fragment);
|
||||||
|
} else if let Some(span) = child.span() {
|
||||||
|
bail!(span, "unexpected document child");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Document {
|
Ok(Document {
|
||||||
|
|
@ -48,19 +64,16 @@ impl LayoutRoot for DocumentNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for DocumentNode {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
f.write_str("Document ")?;
|
|
||||||
self.0.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A list of authors.
|
/// A list of authors.
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Default, Clone, Hash)]
|
||||||
pub struct Author(Vec<EcoString>);
|
pub struct Author(Vec<EcoString>);
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
Author,
|
Author,
|
||||||
v: EcoString => Self(vec![v]),
|
v: EcoString => Self(vec![v]),
|
||||||
v: Array => Self(v.into_iter().map(Value::cast).collect::<StrResult<_>>()?),
|
v: Array => Self(v.into_iter().map(Value::cast).collect::<StrResult<_>>()?),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: Author => v.0.into()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ use crate::layout::{BlockNode, HNode, VNode};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::text::{TextNode, TextSize};
|
use crate::text::{TextNode, TextSize};
|
||||||
|
|
||||||
/// # Heading
|
|
||||||
/// A section heading.
|
/// A section heading.
|
||||||
///
|
///
|
||||||
/// With headings, you can structure your document into sections. Each heading
|
/// With headings, you can structure your document into sections. Each heading
|
||||||
|
|
@ -39,28 +38,20 @@ use crate::text::{TextNode, TextSize};
|
||||||
/// one or multiple equals signs, followed by a space. The number of equals
|
/// one or multiple equals signs, followed by a space. The number of equals
|
||||||
/// signs determines the heading's logical nesting depth.
|
/// signs determines the heading's logical nesting depth.
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Heading
|
||||||
/// - title: `Content` (positional, required)
|
/// Category: meta
|
||||||
/// The heading's title.
|
#[node(Prepare, Show, Finalize)]
|
||||||
///
|
|
||||||
/// - level: `NonZeroUsize` (named)
|
|
||||||
/// The logical nesting depth of the heading, starting from one.
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// meta
|
|
||||||
#[func]
|
|
||||||
#[capable(Prepare, Show, Finalize)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct HeadingNode {
|
pub struct HeadingNode {
|
||||||
/// The logical nesting depth of the section, starting from one. In the
|
/// The heading's title.
|
||||||
/// default style, this controls the text size of the heading.
|
#[positional]
|
||||||
pub level: NonZeroUsize,
|
#[required]
|
||||||
/// The heading's contents.
|
|
||||||
pub title: Content,
|
pub title: Content,
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
/// The logical nesting depth of the heading, starting from one.
|
||||||
impl HeadingNode {
|
#[named]
|
||||||
|
#[default(NonZeroUsize::new(1).unwrap())]
|
||||||
|
pub level: NonZeroUsize,
|
||||||
|
|
||||||
/// How to number the heading. Accepts a
|
/// How to number the heading. Accepts a
|
||||||
/// [numbering pattern or function]($func/numbering).
|
/// [numbering pattern or function]($func/numbering).
|
||||||
///
|
///
|
||||||
|
|
@ -71,8 +62,9 @@ impl HeadingNode {
|
||||||
/// == A subsection
|
/// == A subsection
|
||||||
/// === A sub-subsection
|
/// === A sub-subsection
|
||||||
/// ```
|
/// ```
|
||||||
#[property(referenced)]
|
#[settable]
|
||||||
pub const NUMBERING: Option<Numbering> = None;
|
#[default]
|
||||||
|
pub numbering: Option<Numbering>,
|
||||||
|
|
||||||
/// Whether the heading should appear in the outline.
|
/// Whether the heading should appear in the outline.
|
||||||
///
|
///
|
||||||
|
|
@ -86,23 +78,9 @@ impl HeadingNode {
|
||||||
/// This heading does not appear
|
/// This heading does not appear
|
||||||
/// in the outline.
|
/// in the outline.
|
||||||
/// ```
|
/// ```
|
||||||
pub const OUTLINED: bool = true;
|
#[settable]
|
||||||
|
#[default(true)]
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
pub outlined: bool,
|
||||||
Ok(Self {
|
|
||||||
title: args.expect("title")?,
|
|
||||||
level: args.named("level")?.unwrap_or(NonZeroUsize::new(1).unwrap()),
|
|
||||||
}
|
|
||||||
.pack())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field(&self, name: &str) -> Option<Value> {
|
|
||||||
match name {
|
|
||||||
"level" => Some(Value::Int(self.level.get() as i64)),
|
|
||||||
"title" => Some(Value::Content(self.title.clone())),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Prepare for HeadingNode {
|
impl Prepare for HeadingNode {
|
||||||
|
|
@ -121,7 +99,7 @@ impl Prepare for HeadingNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
let numbers = node.field("numbers").unwrap();
|
let numbers = node.field("numbers").unwrap();
|
||||||
if numbers != Value::None {
|
if *numbers != Value::None {
|
||||||
let heading = node.to::<Self>().unwrap();
|
let heading = node.to::<Self>().unwrap();
|
||||||
counter.advance(heading);
|
counter.advance(heading);
|
||||||
}
|
}
|
||||||
|
|
@ -136,38 +114,34 @@ impl Prepare for HeadingNode {
|
||||||
this.push_field("numbers", numbers);
|
this.push_field("numbers", numbers);
|
||||||
|
|
||||||
let meta = Meta::Node(my_id, this.clone());
|
let meta = Meta::Node(my_id, this.clone());
|
||||||
Ok(this.styled(Meta::DATA, vec![meta]))
|
Ok(this.styled(MetaNode::DATA, vec![meta]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for HeadingNode {
|
impl Show for HeadingNode {
|
||||||
fn show(&self, _: &mut Vt, this: &Content, _: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, this: &Content, _: StyleChain) -> SourceResult<Content> {
|
||||||
let mut realized = self.title.clone();
|
let mut realized = self.title();
|
||||||
let numbers = this.field("numbers").unwrap();
|
let numbers = this.field("numbers").unwrap();
|
||||||
if numbers != Value::None {
|
if *numbers != Value::None {
|
||||||
realized = numbers.display()
|
realized = numbers.clone().display()
|
||||||
+ HNode { amount: Em::new(0.3).into(), weak: true }.pack()
|
+ HNode::new(Em::new(0.3).into()).with_weak(true).pack()
|
||||||
+ realized;
|
+ realized;
|
||||||
}
|
}
|
||||||
Ok(BlockNode {
|
Ok(BlockNode::new().with_body(realized).pack())
|
||||||
body: realized,
|
|
||||||
width: Smart::Auto,
|
|
||||||
height: Smart::Auto,
|
|
||||||
}
|
|
||||||
.pack())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Finalize for HeadingNode {
|
impl Finalize for HeadingNode {
|
||||||
fn finalize(&self, realized: Content) -> Content {
|
fn finalize(&self, realized: Content) -> Content {
|
||||||
let scale = match self.level.get() {
|
let level = self.level().get();
|
||||||
|
let scale = match level {
|
||||||
1 => 1.4,
|
1 => 1.4,
|
||||||
2 => 1.2,
|
2 => 1.2,
|
||||||
_ => 1.0,
|
_ => 1.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let size = Em::new(scale);
|
let size = Em::new(scale);
|
||||||
let above = Em::new(if self.level.get() == 1 { 1.8 } else { 1.44 }) / scale;
|
let above = Em::new(if level == 1 { 1.8 } else { 1.44 }) / scale;
|
||||||
let below = Em::new(0.75) / scale;
|
let below = Em::new(0.75) / scale;
|
||||||
|
|
||||||
let mut map = StyleMap::new();
|
let mut map = StyleMap::new();
|
||||||
|
|
@ -191,7 +165,7 @@ impl HeadingCounter {
|
||||||
|
|
||||||
/// Advance the counter and return the numbers for the given heading.
|
/// Advance the counter and return the numbers for the given heading.
|
||||||
pub fn advance(&mut self, heading: &HeadingNode) -> &[NonZeroUsize] {
|
pub fn advance(&mut self, heading: &HeadingNode) -> &[NonZeroUsize] {
|
||||||
let level = heading.level.get();
|
let level = heading.level().get();
|
||||||
|
|
||||||
if self.0.len() >= level {
|
if self.0.len() >= level {
|
||||||
self.0[level - 1] = self.0[level - 1].saturating_add(1);
|
self.0[level - 1] = self.0[level - 1].saturating_add(1);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::text::{Hyphenate, TextNode};
|
use crate::text::{Hyphenate, TextNode};
|
||||||
|
|
||||||
/// # Link
|
|
||||||
/// Link to a URL or another location in the document.
|
/// Link to a URL or another location in the document.
|
||||||
///
|
///
|
||||||
/// The link function makes its positional `body` argument clickable and links
|
/// The link function makes its positional `body` argument clickable and links
|
||||||
|
|
@ -50,67 +49,62 @@ use crate::text::{Hyphenate, TextNode};
|
||||||
/// The content that should become a link. If `dest` is an URL string, the
|
/// The content that should become a link. If `dest` is an URL string, the
|
||||||
/// parameter can be omitted. In this case, the URL will be shown as the link.
|
/// parameter can be omitted. In this case, the URL will be shown as the link.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Link
|
||||||
/// meta
|
/// Category: meta
|
||||||
#[func]
|
#[node(Construct, Show, Finalize)]
|
||||||
#[capable(Show, Finalize)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct LinkNode {
|
pub struct LinkNode {
|
||||||
/// The destination the link points to.
|
/// The destination the link points to.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
pub dest: Destination,
|
pub dest: Destination,
|
||||||
|
|
||||||
/// How the link is represented.
|
/// How the link is represented.
|
||||||
|
#[positional]
|
||||||
|
#[default]
|
||||||
pub body: Content,
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LinkNode {
|
impl LinkNode {
|
||||||
/// Create a link node from a URL with its bare text.
|
/// Create a link node from a URL with its bare text.
|
||||||
pub fn from_url(url: EcoString) -> Self {
|
pub fn from_url(url: EcoString) -> Self {
|
||||||
let mut text = url.as_str();
|
let body = body_from_url(&url);
|
||||||
for prefix in ["mailto:", "tel:"] {
|
Self::new(Destination::Url(url)).with_body(body)
|
||||||
text = text.trim_start_matches(prefix);
|
|
||||||
}
|
|
||||||
let shorter = text.len() < url.len();
|
|
||||||
let body = TextNode::packed(if shorter { text.into() } else { url.clone() });
|
|
||||||
Self { dest: Destination::Url(url), body }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node]
|
impl Construct for LinkNode {
|
||||||
impl LinkNode {
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
let dest = args.expect::<Destination>("destination")?;
|
let dest = args.expect::<Destination>("destination")?;
|
||||||
Ok(match dest {
|
let body = match &dest {
|
||||||
Destination::Url(url) => match args.eat()? {
|
Destination::Url(url) => match args.eat()? {
|
||||||
Some(body) => Self { dest: Destination::Url(url), body },
|
Some(body) => body,
|
||||||
None => Self::from_url(url),
|
None => body_from_url(url),
|
||||||
},
|
},
|
||||||
Destination::Internal(_) => Self { dest, body: args.expect("body")? },
|
Destination::Internal(_) => args.expect("body")?,
|
||||||
}
|
};
|
||||||
.pack())
|
Ok(Self::new(dest).with_body(body).pack())
|
||||||
}
|
|
||||||
|
|
||||||
fn field(&self, name: &str) -> Option<Value> {
|
|
||||||
match name {
|
|
||||||
"dest" => Some(match &self.dest {
|
|
||||||
Destination::Url(url) => Value::Str(url.clone().into()),
|
|
||||||
Destination::Internal(loc) => Value::Dict(loc.encode()),
|
|
||||||
}),
|
|
||||||
"body" => Some(Value::Content(self.body.clone())),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for LinkNode {
|
impl Show for LinkNode {
|
||||||
fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(self.body.clone())
|
Ok(self.body())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Finalize for LinkNode {
|
impl Finalize for LinkNode {
|
||||||
fn finalize(&self, realized: Content) -> Content {
|
fn finalize(&self, realized: Content) -> Content {
|
||||||
realized
|
realized
|
||||||
.styled(Meta::DATA, vec![Meta::Link(self.dest.clone())])
|
.styled(MetaNode::DATA, vec![Meta::Link(self.dest())])
|
||||||
.styled(TextNode::HYPHENATE, Hyphenate(Smart::Custom(false)))
|
.styled(TextNode::HYPHENATE, Hyphenate(Smart::Custom(false)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn body_from_url(url: &EcoString) -> Content {
|
||||||
|
let mut text = url.as_str();
|
||||||
|
for prefix in ["mailto:", "tel:"] {
|
||||||
|
text = text.trim_start_matches(prefix);
|
||||||
|
}
|
||||||
|
let shorter = text.len() < url.len();
|
||||||
|
TextNode::packed(if shorter { text.into() } else { url.clone() })
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ use std::str::FromStr;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::text::Case;
|
use crate::text::Case;
|
||||||
|
|
||||||
/// # Numbering
|
|
||||||
/// Apply a numbering to a sequence of numbers.
|
/// Apply a numbering to a sequence of numbers.
|
||||||
///
|
///
|
||||||
/// A numbering defines how a sequence of numbers should be displayed as
|
/// A numbering defines how a sequence of numbers should be displayed as
|
||||||
|
|
@ -61,8 +60,8 @@ use crate::text::Case;
|
||||||
///
|
///
|
||||||
/// - returns: any
|
/// - returns: any
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Numbering
|
||||||
/// meta
|
/// Category: meta
|
||||||
#[func]
|
#[func]
|
||||||
pub fn numbering(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
|
pub fn numbering(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
|
||||||
let numbering = args.expect::<Numbering>("pattern or function")?;
|
let numbering = args.expect::<Numbering>("pattern or function")?;
|
||||||
|
|
@ -99,12 +98,19 @@ impl Numbering {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
Numbering,
|
Numbering,
|
||||||
v: Str => Self::Pattern(v.parse()?),
|
v: NumberingPattern => Self::Pattern(v),
|
||||||
v: Func => Self::Func(v),
|
v: Func => Self::Func(v),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: Numbering => match v {
|
||||||
|
Numbering::Pattern(pattern) => pattern.into(),
|
||||||
|
Numbering::Func(func) => func.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// How to turn a number into text.
|
/// How to turn a number into text.
|
||||||
///
|
///
|
||||||
/// A pattern consists of a prefix, followed by one of `1`, `a`, `A`, `i`, `I`
|
/// A pattern consists of a prefix, followed by one of `1`, `a`, `A`, `i`, `I`
|
||||||
|
|
@ -173,12 +179,8 @@ impl FromStr for NumberingPattern {
|
||||||
let mut handled = 0;
|
let mut handled = 0;
|
||||||
|
|
||||||
for (i, c) in pattern.char_indices() {
|
for (i, c) in pattern.char_indices() {
|
||||||
let kind = match c.to_ascii_lowercase() {
|
let Some(kind) = NumberingKind::from_char(c.to_ascii_lowercase()) else {
|
||||||
'1' => NumberingKind::Arabic,
|
continue;
|
||||||
'a' => NumberingKind::Letter,
|
|
||||||
'i' => NumberingKind::Roman,
|
|
||||||
'*' => NumberingKind::Symbol,
|
|
||||||
_ => continue,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let prefix = pattern[handled..i].into();
|
let prefix = pattern[handled..i].into();
|
||||||
|
|
@ -196,6 +198,27 @@ impl FromStr for NumberingPattern {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
NumberingPattern,
|
||||||
|
v: Str => v.parse()?,
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: NumberingPattern => {
|
||||||
|
let mut pat = EcoString::new();
|
||||||
|
for (prefix, kind, case) in &v.pieces {
|
||||||
|
pat.push_str(prefix);
|
||||||
|
let mut c = kind.to_char();
|
||||||
|
if *case == Case::Upper {
|
||||||
|
c = c.to_ascii_uppercase();
|
||||||
|
}
|
||||||
|
pat.push(c);
|
||||||
|
}
|
||||||
|
pat.push_str(&v.suffix);
|
||||||
|
pat.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Different kinds of numberings.
|
/// Different kinds of numberings.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
enum NumberingKind {
|
enum NumberingKind {
|
||||||
|
|
@ -206,6 +229,27 @@ enum NumberingKind {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NumberingKind {
|
impl NumberingKind {
|
||||||
|
/// Create a numbering kind from a lowercase character.
|
||||||
|
pub fn from_char(c: char) -> Option<Self> {
|
||||||
|
Some(match c {
|
||||||
|
'1' => NumberingKind::Arabic,
|
||||||
|
'a' => NumberingKind::Letter,
|
||||||
|
'i' => NumberingKind::Roman,
|
||||||
|
'*' => NumberingKind::Symbol,
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The lowercase character for this numbering kind.
|
||||||
|
pub fn to_char(self) -> char {
|
||||||
|
match self {
|
||||||
|
Self::Arabic => '1',
|
||||||
|
Self::Letter => 'a',
|
||||||
|
Self::Roman => 'i',
|
||||||
|
Self::Symbol => '*',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Apply the numbering to the given number.
|
/// Apply the numbering to the given number.
|
||||||
pub fn apply(self, n: NonZeroUsize, case: Case) -> EcoString {
|
pub fn apply(self, n: NonZeroUsize, case: Case) -> EcoString {
|
||||||
let mut n = n.get();
|
let mut n = n.get();
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,8 @@
|
||||||
use super::HeadingNode;
|
use super::HeadingNode;
|
||||||
use crate::layout::{
|
use crate::layout::{BoxNode, HNode, HideNode, ParbreakNode, RepeatNode};
|
||||||
BoxNode, HNode, HideNode, ParbreakNode, RepeatNode, Sizing, Spacing,
|
|
||||||
};
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::text::{LinebreakNode, SpaceNode, TextNode};
|
use crate::text::{LinebreakNode, SpaceNode, TextNode};
|
||||||
|
|
||||||
/// # Outline
|
|
||||||
/// A section outline / table of contents.
|
/// A section outline / table of contents.
|
||||||
///
|
///
|
||||||
/// This function generates a list of all headings in the document, up to a
|
/// This function generates a list of all headings in the document, up to a
|
||||||
|
|
@ -23,27 +20,25 @@ use crate::text::{LinebreakNode, SpaceNode, TextNode};
|
||||||
/// #lorem(10)
|
/// #lorem(10)
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Outline
|
||||||
/// meta
|
/// Category: meta
|
||||||
#[func]
|
#[node(Prepare, Show)]
|
||||||
#[capable(Prepare, Show)]
|
pub struct OutlineNode {
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct OutlineNode;
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl OutlineNode {
|
|
||||||
/// The title of the outline.
|
/// The title of the outline.
|
||||||
///
|
///
|
||||||
/// - When set to `{auto}`, an appropriate title for the [text
|
/// - When set to `{auto}`, an appropriate title for the [text
|
||||||
/// language]($func/text.lang) will be used. This is the default.
|
/// language]($func/text.lang) will be used. This is the default.
|
||||||
/// - When set to `{none}`, the outline will not have a title.
|
/// - When set to `{none}`, the outline will not have a title.
|
||||||
/// - A custom title can be set by passing content.
|
/// - A custom title can be set by passing content.
|
||||||
#[property(referenced)]
|
#[settable]
|
||||||
pub const TITLE: Option<Smart<Content>> = Some(Smart::Auto);
|
#[default(Some(Smart::Auto))]
|
||||||
|
pub title: Option<Smart<Content>>,
|
||||||
|
|
||||||
/// The maximum depth up to which headings are included in the outline. When
|
/// The maximum depth up to which headings are included in the outline. When
|
||||||
/// this argument is `{none}`, all headings are included.
|
/// this argument is `{none}`, all headings are included.
|
||||||
pub const DEPTH: Option<NonZeroUsize> = None;
|
#[settable]
|
||||||
|
#[default]
|
||||||
|
pub depth: Option<NonZeroUsize>,
|
||||||
|
|
||||||
/// Whether to indent the subheadings to align the start of their numbering
|
/// Whether to indent the subheadings to align the start of their numbering
|
||||||
/// with the title of their parents. This will only have an effect if a
|
/// with the title of their parents. This will only have an effect if a
|
||||||
|
|
@ -62,7 +57,9 @@ impl OutlineNode {
|
||||||
/// == Products
|
/// == Products
|
||||||
/// #lorem(10)
|
/// #lorem(10)
|
||||||
/// ```
|
/// ```
|
||||||
pub const INDENT: bool = false;
|
#[settable]
|
||||||
|
#[default(false)]
|
||||||
|
pub indent: bool,
|
||||||
|
|
||||||
/// Content to fill the space between the title and the page number. Can be
|
/// Content to fill the space between the title and the page number. Can be
|
||||||
/// set to `none` to disable filling. The default is `{repeat[.]}`.
|
/// set to `none` to disable filling. The default is `{repeat[.]}`.
|
||||||
|
|
@ -72,12 +69,9 @@ impl OutlineNode {
|
||||||
///
|
///
|
||||||
/// = A New Beginning
|
/// = A New Beginning
|
||||||
/// ```
|
/// ```
|
||||||
#[property(referenced)]
|
#[settable]
|
||||||
pub const FILL: Option<Content> = Some(RepeatNode(TextNode::packed(".")).pack());
|
#[default(Some(RepeatNode::new(TextNode::packed(".")).pack()))]
|
||||||
|
pub fill: Option<Content>,
|
||||||
fn construct(_: &Vm, _: &mut Args) -> SourceResult<Content> {
|
|
||||||
Ok(Self.pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Prepare for OutlineNode {
|
impl Prepare for OutlineNode {
|
||||||
|
|
@ -91,7 +85,7 @@ impl Prepare for OutlineNode {
|
||||||
.locate(Selector::node::<HeadingNode>())
|
.locate(Selector::node::<HeadingNode>())
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(_, node)| node)
|
.map(|(_, node)| node)
|
||||||
.filter(|node| node.field("outlined").unwrap() == Value::Bool(true))
|
.filter(|node| *node.field("outlined").unwrap() == Value::Bool(true))
|
||||||
.map(|node| Value::Content(node.clone()))
|
.map(|node| Value::Content(node.clone()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|
@ -107,7 +101,7 @@ impl Show for OutlineNode {
|
||||||
_: &Content,
|
_: &Content,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Content> {
|
) -> SourceResult<Content> {
|
||||||
let mut seq = vec![ParbreakNode.pack()];
|
let mut seq = vec![ParbreakNode::new().pack()];
|
||||||
if let Some(title) = styles.get(Self::TITLE) {
|
if let Some(title) = styles.get(Self::TITLE) {
|
||||||
let body = title.clone().unwrap_or_else(|| {
|
let body = title.clone().unwrap_or_else(|| {
|
||||||
TextNode::packed(match styles.get(TextNode::LANG) {
|
TextNode::packed(match styles.get(TextNode::LANG) {
|
||||||
|
|
@ -117,7 +111,7 @@ impl Show for OutlineNode {
|
||||||
});
|
});
|
||||||
|
|
||||||
seq.push(
|
seq.push(
|
||||||
HeadingNode { title: body, level: NonZeroUsize::new(1).unwrap() }
|
HeadingNode::new(body)
|
||||||
.pack()
|
.pack()
|
||||||
.styled(HeadingNode::NUMBERING, None)
|
.styled(HeadingNode::NUMBERING, None)
|
||||||
.styled(HeadingNode::OUTLINED, false),
|
.styled(HeadingNode::OUTLINED, false),
|
||||||
|
|
@ -129,26 +123,26 @@ impl Show for OutlineNode {
|
||||||
|
|
||||||
let mut ancestors: Vec<&Content> = vec![];
|
let mut ancestors: Vec<&Content> = vec![];
|
||||||
for (_, node) in vt.locate(Selector::node::<HeadingNode>()) {
|
for (_, node) in vt.locate(Selector::node::<HeadingNode>()) {
|
||||||
if node.field("outlined").unwrap() != Value::Bool(true) {
|
if *node.field("outlined").unwrap() != Value::Bool(true) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let heading = node.to::<HeadingNode>().unwrap();
|
let heading = node.to::<HeadingNode>().unwrap();
|
||||||
if let Some(depth) = depth {
|
if let Some(depth) = depth {
|
||||||
if depth < heading.level {
|
if depth < heading.level() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while ancestors.last().map_or(false, |last| {
|
while ancestors.last().map_or(false, |last| {
|
||||||
last.to::<HeadingNode>().unwrap().level >= heading.level
|
last.to::<HeadingNode>().unwrap().level() >= heading.level()
|
||||||
}) {
|
}) {
|
||||||
ancestors.pop();
|
ancestors.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adjust the link destination a bit to the topleft so that the
|
// Adjust the link destination a bit to the topleft so that the
|
||||||
// heading is fully visible.
|
// heading is fully visible.
|
||||||
let mut loc = node.field("loc").unwrap().cast::<Location>().unwrap();
|
let mut loc = node.field("loc").unwrap().clone().cast::<Location>().unwrap();
|
||||||
loc.pos -= Point::splat(Abs::pt(10.0));
|
loc.pos -= Point::splat(Abs::pt(10.0));
|
||||||
|
|
||||||
// Add hidden ancestors numberings to realize the indent.
|
// Add hidden ancestors numberings to realize the indent.
|
||||||
|
|
@ -156,21 +150,21 @@ impl Show for OutlineNode {
|
||||||
let hidden: Vec<_> = ancestors
|
let hidden: Vec<_> = ancestors
|
||||||
.iter()
|
.iter()
|
||||||
.map(|node| node.field("numbers").unwrap())
|
.map(|node| node.field("numbers").unwrap())
|
||||||
.filter(|numbers| *numbers != Value::None)
|
.filter(|&numbers| *numbers != Value::None)
|
||||||
.map(|numbers| numbers.display() + SpaceNode.pack())
|
.map(|numbers| numbers.clone().display() + SpaceNode::new().pack())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if !hidden.is_empty() {
|
if !hidden.is_empty() {
|
||||||
seq.push(HideNode(Content::sequence(hidden)).pack());
|
seq.push(HideNode::new(Content::sequence(hidden)).pack());
|
||||||
seq.push(SpaceNode.pack());
|
seq.push(SpaceNode::new().pack());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format the numbering.
|
// Format the numbering.
|
||||||
let mut start = heading.title.clone();
|
let mut start = heading.title();
|
||||||
let numbers = node.field("numbers").unwrap();
|
let numbers = node.field("numbers").unwrap();
|
||||||
if numbers != Value::None {
|
if *numbers != Value::None {
|
||||||
start = numbers.display() + SpaceNode.pack() + start;
|
start = numbers.clone().display() + SpaceNode::new().pack() + start;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add the numbering and section name.
|
// Add the numbering and section name.
|
||||||
|
|
@ -178,30 +172,27 @@ impl Show for OutlineNode {
|
||||||
|
|
||||||
// Add filler symbols between the section name and page number.
|
// Add filler symbols between the section name and page number.
|
||||||
if let Some(filler) = styles.get(Self::FILL) {
|
if let Some(filler) = styles.get(Self::FILL) {
|
||||||
seq.push(SpaceNode.pack());
|
seq.push(SpaceNode::new().pack());
|
||||||
seq.push(
|
seq.push(
|
||||||
BoxNode {
|
BoxNode::new()
|
||||||
body: filler.clone(),
|
.with_body(filler.clone())
|
||||||
width: Sizing::Fr(Fr::one()),
|
.with_width(Fr::one().into())
|
||||||
height: Smart::Auto,
|
.pack(),
|
||||||
}
|
|
||||||
.pack(),
|
|
||||||
);
|
);
|
||||||
seq.push(SpaceNode.pack());
|
seq.push(SpaceNode::new().pack());
|
||||||
} else {
|
} else {
|
||||||
let amount = Spacing::Fr(Fr::one());
|
seq.push(HNode::new(Fr::one().into()).pack());
|
||||||
seq.push(HNode { amount, weak: false }.pack());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the page number and linebreak.
|
// Add the page number and linebreak.
|
||||||
let end = TextNode::packed(eco_format!("{}", loc.page));
|
let end = TextNode::packed(eco_format!("{}", loc.page));
|
||||||
seq.push(end.linked(Destination::Internal(loc)));
|
seq.push(end.linked(Destination::Internal(loc)));
|
||||||
seq.push(LinebreakNode { justify: false }.pack());
|
seq.push(LinebreakNode::new().pack());
|
||||||
|
|
||||||
ancestors.push(node);
|
ancestors.push(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
seq.push(ParbreakNode.pack());
|
seq.push(ParbreakNode::new().pack());
|
||||||
|
|
||||||
Ok(Content::sequence(seq))
|
Ok(Content::sequence(seq))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::text::TextNode;
|
use crate::text::TextNode;
|
||||||
|
|
||||||
/// # Reference
|
|
||||||
/// A reference to a label.
|
/// A reference to a label.
|
||||||
///
|
///
|
||||||
/// *Note: This function is currently unimplemented.*
|
/// *Note: This function is currently unimplemented.*
|
||||||
|
|
@ -16,33 +15,18 @@ use crate::text::TextNode;
|
||||||
/// created by typing an `@` followed by the name of the label (e.g. `[=
|
/// created by typing an `@` followed by the name of the label (e.g. `[=
|
||||||
/// Introduction <intro>]` can be referenced by typing `[@intro]`).
|
/// Introduction <intro>]` can be referenced by typing `[@intro]`).
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Reference
|
||||||
/// - target: `Label` (positional, required)
|
/// Category: meta
|
||||||
/// The label that should be referenced.
|
#[node(Show)]
|
||||||
///
|
pub struct RefNode {
|
||||||
/// ## Category
|
/// The label that should be referenced.
|
||||||
/// meta
|
#[positional]
|
||||||
#[func]
|
#[required]
|
||||||
#[capable(Show)]
|
pub target: EcoString,
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct RefNode(pub EcoString);
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl RefNode {
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
Ok(Self(args.expect("target")?).pack())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field(&self, name: &str) -> Option<Value> {
|
|
||||||
match name {
|
|
||||||
"target" => Some(Value::Str(self.0.clone().into())),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for RefNode {
|
impl Show for RefNode {
|
||||||
fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(TextNode::packed(eco_format!("@{}", self.0)))
|
Ok(TextNode::packed(eco_format!("@{}", self.target())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,16 +15,16 @@ pub use typst::diag::{bail, error, At, SourceResult, StrResult};
|
||||||
pub use typst::doc::*;
|
pub use typst::doc::*;
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use typst::eval::{
|
pub use typst::eval::{
|
||||||
array, castable, dict, format_str, func, Args, Array, AutoValue, Cast, CastInfo,
|
array, cast_from_value, cast_to_value, dict, format_str, func, Args, Array, Cast,
|
||||||
Dict, Func, NoneValue, Str, Symbol, Value, Vm,
|
CastInfo, Dict, Func, Never, Str, Symbol, Value, Vm,
|
||||||
};
|
};
|
||||||
#[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::{
|
||||||
capability, capable, node, Content, Finalize, Fold, Introspector, Label, Node,
|
node, Construct, Content, Finalize, Fold, Introspector, Label, Node, NodeId, Prepare,
|
||||||
NodeId, Prepare, Resolve, Selector, Show, StabilityProvider, StyleChain, StyleMap,
|
Resolve, Selector, Set, Show, StabilityProvider, StyleChain, StyleMap, StyleVec,
|
||||||
StyleVec, Unlabellable, Vt,
|
Unlabellable, Vt,
|
||||||
};
|
};
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use typst::syntax::{Span, Spanned};
|
pub use typst::syntax::{Span, Spanned};
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
//! Node interaction.
|
//! Node interaction.
|
||||||
|
|
||||||
use typst::model::{capability, Content, StyleChain, StyleVec, StyleVecBuilder};
|
use typst::model::{Content, StyleChain, StyleVec, StyleVecBuilder};
|
||||||
|
|
||||||
/// How a node interacts with other nodes.
|
/// How a node interacts with other nodes.
|
||||||
#[capability]
|
|
||||||
pub trait Behave {
|
pub trait Behave {
|
||||||
/// The node's interaction behaviour.
|
/// The node's interaction behaviour.
|
||||||
fn behaviour(&self) -> Behaviour;
|
fn behaviour(&self) -> Behaviour;
|
||||||
|
|
@ -23,7 +22,7 @@ pub enum Behaviour {
|
||||||
/// after it. Furthermore, per consecutive run of weak nodes, only one
|
/// after it. Furthermore, per consecutive run of weak nodes, only one
|
||||||
/// survives: The one with the lowest weakness level (or the larger one if
|
/// survives: The one with the lowest weakness level (or the larger one if
|
||||||
/// there is a tie).
|
/// there is a tie).
|
||||||
Weak(u8),
|
Weak(usize),
|
||||||
/// A node that enables adjacent weak nodes to exist. The default.
|
/// A node that enables adjacent weak nodes to exist. The default.
|
||||||
Supportive,
|
Supportive,
|
||||||
/// A node that destroys adjacent weak nodes.
|
/// A node that destroys adjacent weak nodes.
|
||||||
|
|
|
||||||
|
|
@ -24,49 +24,43 @@ pub trait ContentExt {
|
||||||
|
|
||||||
/// Transform this content's contents without affecting layout.
|
/// Transform this content's contents without affecting layout.
|
||||||
fn moved(self, delta: Axes<Rel<Length>>) -> Self;
|
fn moved(self, delta: Axes<Rel<Length>>) -> Self;
|
||||||
|
|
||||||
/// Fill the frames resulting from a content.
|
|
||||||
fn filled(self, fill: Paint) -> Self;
|
|
||||||
|
|
||||||
/// Stroke the frames resulting from a content.
|
|
||||||
fn stroked(self, stroke: Stroke) -> Self;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContentExt for Content {
|
impl ContentExt for Content {
|
||||||
fn strong(self) -> Self {
|
fn strong(self) -> Self {
|
||||||
crate::text::StrongNode(self).pack()
|
crate::text::StrongNode::new(self).pack()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emph(self) -> Self {
|
fn emph(self) -> Self {
|
||||||
crate::text::EmphNode(self).pack()
|
crate::text::EmphNode::new(self).pack()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn underlined(self) -> Self {
|
fn underlined(self) -> Self {
|
||||||
crate::text::UnderlineNode(self).pack()
|
crate::text::UnderlineNode::new(self).pack()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn linked(self, dest: Destination) -> Self {
|
fn linked(self, dest: Destination) -> Self {
|
||||||
self.styled(Meta::DATA, vec![Meta::Link(dest.clone())])
|
self.styled(MetaNode::DATA, vec![Meta::Link(dest.clone())])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn aligned(self, aligns: Axes<Option<GenAlign>>) -> Self {
|
fn aligned(self, aligns: Axes<Option<GenAlign>>) -> Self {
|
||||||
self.styled(crate::layout::AlignNode::ALIGNS, aligns)
|
self.styled(crate::layout::AlignNode::ALIGNMENT, aligns)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn padded(self, padding: Sides<Rel<Length>>) -> Self {
|
fn padded(self, padding: Sides<Rel<Length>>) -> Self {
|
||||||
crate::layout::PadNode { padding, body: self }.pack()
|
crate::layout::PadNode::new(self)
|
||||||
|
.with_left(padding.left)
|
||||||
|
.with_top(padding.top)
|
||||||
|
.with_right(padding.right)
|
||||||
|
.with_bottom(padding.bottom)
|
||||||
|
.pack()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn moved(self, delta: Axes<Rel<Length>>) -> Self {
|
fn moved(self, delta: Axes<Rel<Length>>) -> Self {
|
||||||
crate::layout::MoveNode { delta, body: self }.pack()
|
crate::layout::MoveNode::new(self)
|
||||||
}
|
.with_dx(delta.x)
|
||||||
|
.with_dy(delta.y)
|
||||||
fn filled(self, fill: Paint) -> Self {
|
.pack()
|
||||||
FillNode { fill, child: self }.pack()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stroked(self, stroke: Stroke) -> Self {
|
|
||||||
StrokeNode { stroke, child: self }.pack()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,61 +83,3 @@ impl StyleMapExt for StyleMap {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fill the frames resulting from content.
|
|
||||||
#[capable(Layout)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
struct FillNode {
|
|
||||||
/// How to fill the frames resulting from the `child`.
|
|
||||||
fill: Paint,
|
|
||||||
/// The content whose frames should be filled.
|
|
||||||
child: Content,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl FillNode {}
|
|
||||||
|
|
||||||
impl Layout for FillNode {
|
|
||||||
fn layout(
|
|
||||||
&self,
|
|
||||||
vt: &mut Vt,
|
|
||||||
styles: StyleChain,
|
|
||||||
regions: Regions,
|
|
||||||
) -> SourceResult<Fragment> {
|
|
||||||
let mut fragment = self.child.layout(vt, styles, regions)?;
|
|
||||||
for frame in &mut fragment {
|
|
||||||
let shape = Geometry::Rect(frame.size()).filled(self.fill);
|
|
||||||
frame.prepend(Point::zero(), Element::Shape(shape));
|
|
||||||
}
|
|
||||||
Ok(fragment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stroke the frames resulting from content.
|
|
||||||
#[capable(Layout)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
struct StrokeNode {
|
|
||||||
/// How to stroke the frames resulting from the `child`.
|
|
||||||
stroke: Stroke,
|
|
||||||
/// The content whose frames should be stroked.
|
|
||||||
child: Content,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl StrokeNode {}
|
|
||||||
|
|
||||||
impl Layout for StrokeNode {
|
|
||||||
fn layout(
|
|
||||||
&self,
|
|
||||||
vt: &mut Vt,
|
|
||||||
styles: StyleChain,
|
|
||||||
regions: Regions,
|
|
||||||
) -> SourceResult<Fragment> {
|
|
||||||
let mut fragment = self.child.layout(vt, styles, regions)?;
|
|
||||||
for frame in &mut fragment {
|
|
||||||
let shape = Geometry::Rect(frame.size()).stroked(self.stroke);
|
|
||||||
frame.prepend(Point::zero(), Element::Shape(shape));
|
|
||||||
}
|
|
||||||
Ok(fragment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ use ttf_parser::{GlyphId, OutlineBuilder};
|
||||||
use super::TextNode;
|
use super::TextNode;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// # Underline
|
|
||||||
/// Underline text.
|
/// Underline text.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -12,19 +11,15 @@ use crate::prelude::*;
|
||||||
/// This is #underline[important].
|
/// This is #underline[important].
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Underline
|
||||||
/// - body: `Content` (positional, required)
|
/// Category: text
|
||||||
/// The content to underline.
|
#[node(Show)]
|
||||||
///
|
pub struct UnderlineNode {
|
||||||
/// ## Category
|
/// The content to underline.
|
||||||
/// text
|
#[positional]
|
||||||
#[func]
|
#[required]
|
||||||
#[capable(Show)]
|
pub body: Content,
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct UnderlineNode(pub Content);
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl UnderlineNode {
|
|
||||||
/// How to stroke the line. The text color and thickness are read from the
|
/// How to stroke the line. The text color and thickness are read from the
|
||||||
/// font tables if `{auto}`.
|
/// font tables if `{auto}`.
|
||||||
///
|
///
|
||||||
|
|
@ -35,8 +30,12 @@ impl UnderlineNode {
|
||||||
/// [care],
|
/// [care],
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[property(shorthand, resolve, fold)]
|
#[settable]
|
||||||
pub const STROKE: Smart<PartialStroke> = Smart::Auto;
|
#[shorthand]
|
||||||
|
#[resolve]
|
||||||
|
#[fold]
|
||||||
|
#[default]
|
||||||
|
pub stroke: Smart<PartialStroke>,
|
||||||
|
|
||||||
/// Position of the line relative to the baseline, read from the font tables
|
/// Position of the line relative to the baseline, read from the font tables
|
||||||
/// if `{auto}`.
|
/// if `{auto}`.
|
||||||
|
|
@ -46,8 +45,10 @@ impl UnderlineNode {
|
||||||
/// The Tale Of A Faraway Line I
|
/// The Tale Of A Faraway Line I
|
||||||
/// ]
|
/// ]
|
||||||
/// ```
|
/// ```
|
||||||
#[property(resolve)]
|
#[settable]
|
||||||
pub const OFFSET: Smart<Length> = Smart::Auto;
|
#[resolve]
|
||||||
|
#[default]
|
||||||
|
pub offset: Smart<Length>,
|
||||||
|
|
||||||
/// Amount that the line will be longer or shorter than its associated text.
|
/// Amount that the line will be longer or shorter than its associated text.
|
||||||
///
|
///
|
||||||
|
|
@ -56,8 +57,10 @@ impl UnderlineNode {
|
||||||
/// underline(extent: 2pt)[Chapter 1]
|
/// underline(extent: 2pt)[Chapter 1]
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[property(resolve)]
|
#[settable]
|
||||||
pub const EXTENT: Length = Length::zero();
|
#[resolve]
|
||||||
|
#[default]
|
||||||
|
pub extent: Length,
|
||||||
|
|
||||||
/// Whether the line skips sections in which it would collide with the
|
/// Whether the line skips sections in which it would collide with the
|
||||||
/// glyphs.
|
/// glyphs.
|
||||||
|
|
@ -66,23 +69,14 @@ impl UnderlineNode {
|
||||||
/// This #underline(evade: true)[is great].
|
/// This #underline(evade: true)[is great].
|
||||||
/// This #underline(evade: false)[is less great].
|
/// This #underline(evade: false)[is less great].
|
||||||
/// ```
|
/// ```
|
||||||
pub const EVADE: bool = true;
|
#[settable]
|
||||||
|
#[default(true)]
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
pub evade: bool,
|
||||||
Ok(Self(args.expect("body")?).pack())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field(&self, name: &str) -> Option<Value> {
|
|
||||||
match name {
|
|
||||||
"body" => Some(Value::Content(self.0.clone())),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for UnderlineNode {
|
impl Show for UnderlineNode {
|
||||||
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(self.0.clone().styled(
|
Ok(self.body().styled(
|
||||||
TextNode::DECO,
|
TextNode::DECO,
|
||||||
Decoration {
|
Decoration {
|
||||||
line: DecoLine::Underline,
|
line: DecoLine::Underline,
|
||||||
|
|
@ -95,7 +89,6 @@ impl Show for UnderlineNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Overline
|
|
||||||
/// Add a line over text.
|
/// Add a line over text.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -103,19 +96,15 @@ impl Show for UnderlineNode {
|
||||||
/// #overline[A line over text.]
|
/// #overline[A line over text.]
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Overline
|
||||||
/// - body: `Content` (positional, required)
|
/// Category: text
|
||||||
/// The content to add a line over.
|
#[node(Show)]
|
||||||
///
|
pub struct OverlineNode {
|
||||||
/// ## Category
|
/// The content to add a line over.
|
||||||
/// text
|
#[positional]
|
||||||
#[func]
|
#[required]
|
||||||
#[capable(Show)]
|
pub body: Content,
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct OverlineNode(pub Content);
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl OverlineNode {
|
|
||||||
/// How to stroke the line. The text color and thickness are read from the
|
/// How to stroke the line. The text color and thickness are read from the
|
||||||
/// font tables if `{auto}`.
|
/// font tables if `{auto}`.
|
||||||
///
|
///
|
||||||
|
|
@ -127,8 +116,12 @@ impl OverlineNode {
|
||||||
/// [The Forest Theme],
|
/// [The Forest Theme],
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[property(shorthand, resolve, fold)]
|
#[settable]
|
||||||
pub const STROKE: Smart<PartialStroke> = Smart::Auto;
|
#[shorthand]
|
||||||
|
#[resolve]
|
||||||
|
#[fold]
|
||||||
|
#[default]
|
||||||
|
pub stroke: Smart<PartialStroke>,
|
||||||
|
|
||||||
/// Position of the line relative to the baseline, read from the font tables
|
/// Position of the line relative to the baseline, read from the font tables
|
||||||
/// if `{auto}`.
|
/// if `{auto}`.
|
||||||
|
|
@ -138,8 +131,10 @@ impl OverlineNode {
|
||||||
/// The Tale Of A Faraway Line II
|
/// The Tale Of A Faraway Line II
|
||||||
/// ]
|
/// ]
|
||||||
/// ```
|
/// ```
|
||||||
#[property(resolve)]
|
#[settable]
|
||||||
pub const OFFSET: Smart<Length> = Smart::Auto;
|
#[resolve]
|
||||||
|
#[default]
|
||||||
|
pub offset: Smart<Length>,
|
||||||
|
|
||||||
/// Amount that the line will be longer or shorter than its associated text.
|
/// Amount that the line will be longer or shorter than its associated text.
|
||||||
///
|
///
|
||||||
|
|
@ -148,8 +143,10 @@ impl OverlineNode {
|
||||||
/// #set underline(extent: 4pt)
|
/// #set underline(extent: 4pt)
|
||||||
/// #overline(underline[Typography Today])
|
/// #overline(underline[Typography Today])
|
||||||
/// ```
|
/// ```
|
||||||
#[property(resolve)]
|
#[settable]
|
||||||
pub const EXTENT: Length = Length::zero();
|
#[resolve]
|
||||||
|
#[default]
|
||||||
|
pub extent: Length,
|
||||||
|
|
||||||
/// Whether the line skips sections in which it would collide with the
|
/// Whether the line skips sections in which it would collide with the
|
||||||
/// glyphs.
|
/// glyphs.
|
||||||
|
|
@ -163,23 +160,14 @@ impl OverlineNode {
|
||||||
/// [Temple],
|
/// [Temple],
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
pub const EVADE: bool = true;
|
#[settable]
|
||||||
|
#[default(true)]
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
pub evade: bool,
|
||||||
Ok(Self(args.expect("body")?).pack())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field(&self, name: &str) -> Option<Value> {
|
|
||||||
match name {
|
|
||||||
"body" => Some(Value::Content(self.0.clone())),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for OverlineNode {
|
impl Show for OverlineNode {
|
||||||
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(self.0.clone().styled(
|
Ok(self.body().styled(
|
||||||
TextNode::DECO,
|
TextNode::DECO,
|
||||||
Decoration {
|
Decoration {
|
||||||
line: DecoLine::Overline,
|
line: DecoLine::Overline,
|
||||||
|
|
@ -192,7 +180,6 @@ impl Show for OverlineNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Strikethrough
|
|
||||||
/// Strike through text.
|
/// Strike through text.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -200,19 +187,15 @@ impl Show for OverlineNode {
|
||||||
/// This is #strike[not] relevant.
|
/// This is #strike[not] relevant.
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Strikethrough
|
||||||
/// - body: `Content` (positional, required)
|
/// Category: text
|
||||||
/// The content to strike through.
|
#[node(Show)]
|
||||||
///
|
pub struct StrikeNode {
|
||||||
/// ## Category
|
/// The content to strike through.
|
||||||
/// text
|
#[positional]
|
||||||
#[func]
|
#[required]
|
||||||
#[capable(Show)]
|
pub body: Content,
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct StrikeNode(pub Content);
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl StrikeNode {
|
|
||||||
/// How to stroke the line. The text color and thickness are read from the
|
/// How to stroke the line. The text color and thickness are read from the
|
||||||
/// font tables if `{auto}`.
|
/// font tables if `{auto}`.
|
||||||
///
|
///
|
||||||
|
|
@ -223,8 +206,12 @@ impl StrikeNode {
|
||||||
/// This is #strike(stroke: 1.5pt + red)[very stricken through]. \
|
/// This is #strike(stroke: 1.5pt + red)[very stricken through]. \
|
||||||
/// This is #strike(stroke: 10pt)[redacted].
|
/// This is #strike(stroke: 10pt)[redacted].
|
||||||
/// ```
|
/// ```
|
||||||
#[property(shorthand, resolve, fold)]
|
#[settable]
|
||||||
pub const STROKE: Smart<PartialStroke> = Smart::Auto;
|
#[shorthand]
|
||||||
|
#[resolve]
|
||||||
|
#[fold]
|
||||||
|
#[default]
|
||||||
|
pub stroke: Smart<PartialStroke>,
|
||||||
|
|
||||||
/// Position of the line relative to the baseline, read from the font tables
|
/// Position of the line relative to the baseline, read from the font tables
|
||||||
/// if `{auto}`.
|
/// if `{auto}`.
|
||||||
|
|
@ -236,8 +223,10 @@ impl StrikeNode {
|
||||||
/// This is #strike(offset: auto)[low-ish]. \
|
/// This is #strike(offset: auto)[low-ish]. \
|
||||||
/// This is #strike(offset: -3.5pt)[on-top].
|
/// This is #strike(offset: -3.5pt)[on-top].
|
||||||
/// ```
|
/// ```
|
||||||
#[property(resolve)]
|
#[settable]
|
||||||
pub const OFFSET: Smart<Length> = Smart::Auto;
|
#[resolve]
|
||||||
|
#[default]
|
||||||
|
pub offset: Smart<Length>,
|
||||||
|
|
||||||
/// Amount that the line will be longer or shorter than its associated text.
|
/// Amount that the line will be longer or shorter than its associated text.
|
||||||
///
|
///
|
||||||
|
|
@ -245,24 +234,15 @@ impl StrikeNode {
|
||||||
/// This #strike(extent: -2pt)[skips] parts of the word.
|
/// This #strike(extent: -2pt)[skips] parts of the word.
|
||||||
/// This #strike(extent: 2pt)[extends] beyond the word.
|
/// This #strike(extent: 2pt)[extends] beyond the word.
|
||||||
/// ```
|
/// ```
|
||||||
#[property(resolve)]
|
#[settable]
|
||||||
pub const EXTENT: Length = Length::zero();
|
#[resolve]
|
||||||
|
#[default]
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
pub extent: Length,
|
||||||
Ok(Self(args.expect("body")?).pack())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field(&self, name: &str) -> Option<Value> {
|
|
||||||
match name {
|
|
||||||
"body" => Some(Value::Content(self.0.clone())),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for StrikeNode {
|
impl Show for StrikeNode {
|
||||||
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(self.0.clone().styled(
|
Ok(self.body().styled(
|
||||||
TextNode::DECO,
|
TextNode::DECO,
|
||||||
Decoration {
|
Decoration {
|
||||||
line: DecoLine::Strikethrough,
|
line: DecoLine::Strikethrough,
|
||||||
|
|
@ -294,6 +274,10 @@ impl Fold for Decoration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
Decoration: "decoration",
|
||||||
|
}
|
||||||
|
|
||||||
/// A kind of decorative line.
|
/// A kind of decorative line.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum DecoLine {
|
pub enum DecoLine {
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,12 @@
|
||||||
use super::TextNode;
|
use super::TextNode;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// # Space
|
|
||||||
/// A text space.
|
/// A text space.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Space
|
||||||
/// text
|
/// Category: text
|
||||||
#[func]
|
#[node(Unlabellable, Behave)]
|
||||||
#[capable(Unlabellable, Behave)]
|
pub struct SpaceNode {}
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct SpaceNode;
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl SpaceNode {
|
|
||||||
fn construct(_: &Vm, _: &mut Args) -> SourceResult<Content> {
|
|
||||||
Ok(Self.pack())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Unlabellable for SpaceNode {}
|
|
||||||
|
|
||||||
impl Behave for SpaceNode {
|
impl Behave for SpaceNode {
|
||||||
fn behaviour(&self) -> Behaviour {
|
fn behaviour(&self) -> Behaviour {
|
||||||
|
|
@ -26,7 +14,8 @@ impl Behave for SpaceNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Line Break
|
impl Unlabellable for SpaceNode {}
|
||||||
|
|
||||||
/// Inserts a line break.
|
/// Inserts a line break.
|
||||||
///
|
///
|
||||||
/// Advances the paragraph to the next line. A single trailing line break at the
|
/// Advances the paragraph to the next line. A single trailing line break at the
|
||||||
|
|
@ -45,46 +34,34 @@ impl Behave for SpaceNode {
|
||||||
/// a backslash followed by whitespace. This always creates an unjustified
|
/// a backslash followed by whitespace. This always creates an unjustified
|
||||||
/// break.
|
/// break.
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Line Break
|
||||||
/// - justify: `bool` (named)
|
/// Category: text
|
||||||
/// Whether to justify the line before the break.
|
#[node(Behave)]
|
||||||
///
|
|
||||||
/// This is useful if you found a better line break opportunity in your
|
|
||||||
/// justified text than Typst did.
|
|
||||||
///
|
|
||||||
/// ```example
|
|
||||||
/// #set par(justify: true)
|
|
||||||
/// #let jb = linebreak(justify: true)
|
|
||||||
///
|
|
||||||
/// I have manually tuned the #jb
|
|
||||||
/// line breaks in this paragraph #jb
|
|
||||||
/// for an _interesting_ result. #jb
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// text
|
|
||||||
#[func]
|
|
||||||
#[capable(Behave)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct LinebreakNode {
|
pub struct LinebreakNode {
|
||||||
|
/// Whether to justify the line before the break.
|
||||||
|
///
|
||||||
|
/// This is useful if you found a better line break opportunity in your
|
||||||
|
/// justified text than Typst did.
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// #set par(justify: true)
|
||||||
|
/// #let jb = linebreak(justify: true)
|
||||||
|
///
|
||||||
|
/// I have manually tuned the #jb
|
||||||
|
/// line breaks in this paragraph #jb
|
||||||
|
/// for an _interesting_ result. #jb
|
||||||
|
/// ```
|
||||||
|
#[named]
|
||||||
|
#[default(false)]
|
||||||
pub justify: bool,
|
pub justify: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl LinebreakNode {
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
let justify = args.named("justify")?.unwrap_or(false);
|
|
||||||
Ok(Self { justify }.pack())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Behave for LinebreakNode {
|
impl Behave for LinebreakNode {
|
||||||
fn behaviour(&self) -> Behaviour {
|
fn behaviour(&self) -> Behaviour {
|
||||||
Behaviour::Destructive
|
Behaviour::Destructive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Strong Emphasis
|
|
||||||
/// Strongly emphasizes content by increasing the font weight.
|
/// Strongly emphasizes content by increasing the font weight.
|
||||||
///
|
///
|
||||||
/// Increases the current font weight by a given `delta`.
|
/// Increases the current font weight by a given `delta`.
|
||||||
|
|
@ -104,42 +81,29 @@ impl Behave for LinebreakNode {
|
||||||
/// word boundaries. To strongly emphasize part of a word, you have to use the
|
/// word boundaries. To strongly emphasize part of a word, you have to use the
|
||||||
/// function.
|
/// function.
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Strong Emphasis
|
||||||
/// - body: `Content` (positional, required)
|
/// Category: text
|
||||||
/// The content to strongly emphasize.
|
#[node(Show)]
|
||||||
///
|
pub struct StrongNode {
|
||||||
/// ## Category
|
/// The content to strongly emphasize.
|
||||||
/// text
|
#[positional]
|
||||||
#[func]
|
#[required]
|
||||||
#[capable(Show)]
|
pub body: Content,
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct StrongNode(pub Content);
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl StrongNode {
|
|
||||||
/// The delta to apply on the font weight.
|
/// The delta to apply on the font weight.
|
||||||
///
|
///
|
||||||
/// ```example
|
/// ```example
|
||||||
/// #set strong(delta: 0)
|
/// #set strong(delta: 0)
|
||||||
/// No *effect!*
|
/// No *effect!*
|
||||||
/// ```
|
/// ```
|
||||||
pub const DELTA: i64 = 300;
|
#[settable]
|
||||||
|
#[default(300)]
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
pub delta: i64,
|
||||||
Ok(Self(args.expect("body")?).pack())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field(&self, name: &str) -> Option<Value> {
|
|
||||||
match name {
|
|
||||||
"body" => Some(Value::Content(self.0.clone())),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for StrongNode {
|
impl Show for StrongNode {
|
||||||
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(self.0.clone().styled(TextNode::DELTA, Delta(styles.get(Self::DELTA))))
|
Ok(self.body().styled(TextNode::DELTA, Delta(styles.get(Self::DELTA))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,11 +111,15 @@ impl Show for StrongNode {
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct Delta(pub i64);
|
pub struct Delta(pub i64);
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
Delta,
|
Delta,
|
||||||
v: i64 => Self(v),
|
v: i64 => Self(v),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: Delta => v.0.into()
|
||||||
|
}
|
||||||
|
|
||||||
impl Fold for Delta {
|
impl Fold for Delta {
|
||||||
type Output = i64;
|
type Output = i64;
|
||||||
|
|
||||||
|
|
@ -160,7 +128,6 @@ impl Fold for Delta {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Emphasis
|
|
||||||
/// Emphasizes content by setting it in italics.
|
/// Emphasizes content by setting it in italics.
|
||||||
///
|
///
|
||||||
/// - If the current [text style]($func/text.style) is `{"normal"}`,
|
/// - If the current [text style]($func/text.style) is `{"normal"}`,
|
||||||
|
|
@ -185,34 +152,19 @@ impl Fold for Delta {
|
||||||
/// enclose it in underscores (`_`). Note that this only works at word
|
/// enclose it in underscores (`_`). Note that this only works at word
|
||||||
/// boundaries. To emphasize part of a word, you have to use the function.
|
/// boundaries. To emphasize part of a word, you have to use the function.
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Emphasis
|
||||||
/// - body: `Content` (positional, required)
|
/// Category: text
|
||||||
/// The content to emphasize.
|
#[node(Show)]
|
||||||
///
|
pub struct EmphNode {
|
||||||
/// ## Category
|
/// The content to emphasize.
|
||||||
/// text
|
#[positional]
|
||||||
#[func]
|
#[required]
|
||||||
#[capable(Show)]
|
pub body: Content,
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct EmphNode(pub Content);
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl EmphNode {
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
Ok(Self(args.expect("body")?).pack())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field(&self, name: &str) -> Option<Value> {
|
|
||||||
match name {
|
|
||||||
"body" => Some(Value::Content(self.0.clone())),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for EmphNode {
|
impl Show for EmphNode {
|
||||||
fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(self.0.clone().styled(TextNode::EMPH, Toggle))
|
Ok(self.body().styled(TextNode::EMPH, Toggle))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -220,6 +172,15 @@ impl Show for EmphNode {
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct Toggle;
|
pub struct Toggle;
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
Toggle,
|
||||||
|
_: Value => Self,
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
_: Toggle => Value::None
|
||||||
|
}
|
||||||
|
|
||||||
impl Fold for Toggle {
|
impl Fold for Toggle {
|
||||||
type Output = bool;
|
type Output = bool;
|
||||||
|
|
||||||
|
|
@ -228,7 +189,6 @@ impl Fold for Toggle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Lowercase
|
|
||||||
/// Convert text or content to lowercase.
|
/// Convert text or content to lowercase.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -242,14 +202,13 @@ impl Fold for Toggle {
|
||||||
/// - text: `ToCase` (positional, required)
|
/// - text: `ToCase` (positional, required)
|
||||||
/// The text to convert to lowercase.
|
/// The text to convert to lowercase.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Lowercase
|
||||||
/// text
|
/// Category: 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Uppercase
|
|
||||||
/// Convert text or content to uppercase.
|
/// Convert text or content to uppercase.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -263,8 +222,8 @@ pub fn lower(args: &mut Args) -> SourceResult<Value> {
|
||||||
/// - text: `ToCase` (positional, required)
|
/// - text: `ToCase` (positional, required)
|
||||||
/// The text to convert to uppercase.
|
/// The text to convert to uppercase.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Uppercase
|
||||||
/// text
|
/// Category: 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)
|
||||||
|
|
@ -272,21 +231,22 @@ pub fn upper(args: &mut Args) -> SourceResult<Value> {
|
||||||
|
|
||||||
/// Change the case of text.
|
/// Change the case of text.
|
||||||
fn case(case: Case, args: &mut Args) -> SourceResult<Value> {
|
fn case(case: Case, args: &mut Args) -> SourceResult<Value> {
|
||||||
let Spanned { v, span } = args.expect("string or content")?;
|
Ok(match args.expect("string or content")? {
|
||||||
Ok(match v {
|
ToCase::Str(v) => Value::Str(case.apply(&v).into()),
|
||||||
Value::Str(v) => Value::Str(case.apply(&v).into()),
|
ToCase::Content(v) => Value::Content(v.styled(TextNode::CASE, Some(case))),
|
||||||
Value::Content(v) => Value::Content(v.styled(TextNode::CASE, Some(case))),
|
|
||||||
v => bail!(span, "expected string or content, found {}", v.type_name()),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A value whose case can be changed.
|
/// A value whose case can be changed.
|
||||||
struct ToCase;
|
enum ToCase {
|
||||||
|
Str(Str),
|
||||||
|
Content(Content),
|
||||||
|
}
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
ToCase,
|
ToCase,
|
||||||
_: Str => Self,
|
v: Str => Self::Str(v),
|
||||||
_: Content => Self,
|
v: Content => Self::Content(v),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A case transformation on text.
|
/// A case transformation on text.
|
||||||
|
|
@ -308,7 +268,19 @@ impl Case {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Small Capitals
|
cast_from_value! {
|
||||||
|
Case,
|
||||||
|
"lower" => Self::Lower,
|
||||||
|
"upper" => Self::Upper,
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: Case => Value::from(match v {
|
||||||
|
Case::Lower => "lower",
|
||||||
|
Case::Upper => "upper",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Display text in small capitals.
|
/// Display text in small capitals.
|
||||||
///
|
///
|
||||||
/// _Note:_ This enables the OpenType `smcp` feature for the font. Not all fonts
|
/// _Note:_ This enables the OpenType `smcp` feature for the font. Not all fonts
|
||||||
|
|
@ -336,15 +308,14 @@ impl Case {
|
||||||
/// - text: `Content` (positional, required)
|
/// - text: `Content` (positional, required)
|
||||||
/// The text to display to small capitals.
|
/// The text to display to small capitals.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Small Capitals
|
||||||
/// text
|
/// Category: 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")?;
|
||||||
Ok(Value::Content(body.styled(TextNode::SMALLCAPS, true)))
|
Ok(Value::Content(body.styled(TextNode::SMALLCAPS, true)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Blind Text
|
|
||||||
/// Create blind text.
|
/// Create blind text.
|
||||||
///
|
///
|
||||||
/// This function yields a Latin-like _Lorem Ipsum_ blind text with the given
|
/// This function yields a Latin-like _Lorem Ipsum_ blind text with the given
|
||||||
|
|
@ -367,8 +338,8 @@ pub fn smallcaps(args: &mut Args) -> SourceResult<Value> {
|
||||||
///
|
///
|
||||||
/// - returns: string
|
/// - returns: string
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Blind Text
|
||||||
/// text
|
/// Category: text
|
||||||
#[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")?;
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ use typst::font::{FontMetrics, FontStretch, FontStyle, FontWeight, VerticalFontM
|
||||||
use crate::layout::ParNode;
|
use crate::layout::ParNode;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// # Text
|
|
||||||
/// Customize the look and layout of text in a variety of ways.
|
/// Customize the look and layout of text in a variety of ways.
|
||||||
///
|
///
|
||||||
/// This function is used often, both with set rules and directly. While the set
|
/// This function is used often, both with set rules and directly. While the set
|
||||||
|
|
@ -62,26 +61,50 @@ use crate::prelude::*;
|
||||||
/// - body: `Content` (positional, required)
|
/// - body: `Content` (positional, required)
|
||||||
/// Content in which all text is styled according to the other arguments.
|
/// Content in which all text is styled according to the other arguments.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Text
|
||||||
/// text
|
/// Category: text
|
||||||
#[func]
|
#[node(Construct)]
|
||||||
#[capable]
|
#[set({
|
||||||
#[derive(Clone, Hash)]
|
if let Some(family) = args.named("family")? {
|
||||||
pub struct TextNode(pub EcoString);
|
styles.set(Self::FAMILY, family);
|
||||||
|
} else {
|
||||||
|
let mut count = 0;
|
||||||
|
let mut content = false;
|
||||||
|
for item in args.items.iter().filter(|item| item.name.is_none()) {
|
||||||
|
if EcoString::is(&item.value) {
|
||||||
|
count += 1;
|
||||||
|
} else if <Content as Cast<Spanned<Value>>>::is(&item.value) {
|
||||||
|
content = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TextNode {
|
// Skip the final string if it's needed as the body.
|
||||||
/// Create a new packed text node.
|
if constructor && !content && count > 0 {
|
||||||
pub fn packed(text: impl Into<EcoString>) -> Content {
|
count -= 1;
|
||||||
Self(text.into()).pack()
|
}
|
||||||
|
|
||||||
|
if count > 0 {
|
||||||
|
let mut list = Vec::with_capacity(count);
|
||||||
|
for _ in 0..count {
|
||||||
|
list.push(args.find()?.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
styles.set(Self::FAMILY, FallbackList(list));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})]
|
||||||
|
pub struct TextNode {
|
||||||
|
/// The text.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
|
#[skip]
|
||||||
|
pub text: EcoString,
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl TextNode {
|
|
||||||
/// A prioritized sequence of font families.
|
/// A prioritized sequence of font families.
|
||||||
#[property(skip, referenced)]
|
#[settable]
|
||||||
pub const FAMILY: FallbackList =
|
#[skip]
|
||||||
FallbackList(vec![FontFamily::new("Linux Libertine")]);
|
#[default(FallbackList(vec![FontFamily::new("Linux Libertine")]))]
|
||||||
|
pub family: FallbackList,
|
||||||
|
|
||||||
/// Whether to allow last resort font fallback when the primary font list
|
/// Whether to allow last resort font fallback when the primary font list
|
||||||
/// contains no match. This lets Typst search through all available fonts
|
/// contains no match. This lets Typst search through all available fonts
|
||||||
|
|
@ -100,7 +123,9 @@ impl TextNode {
|
||||||
/// #set text(fallback: false)
|
/// #set text(fallback: false)
|
||||||
/// هذا عربي
|
/// هذا عربي
|
||||||
/// ```
|
/// ```
|
||||||
pub const FALLBACK: bool = true;
|
#[settable]
|
||||||
|
#[default(true)]
|
||||||
|
pub fallback: bool,
|
||||||
|
|
||||||
/// The desired font style.
|
/// The desired font style.
|
||||||
///
|
///
|
||||||
|
|
@ -119,7 +144,9 @@ impl TextNode {
|
||||||
/// #text("Linux Libertine", style: "italic")[Italic]
|
/// #text("Linux Libertine", style: "italic")[Italic]
|
||||||
/// #text("DejaVu Sans", style: "oblique")[Oblique]
|
/// #text("DejaVu Sans", style: "oblique")[Oblique]
|
||||||
/// ```
|
/// ```
|
||||||
pub const STYLE: FontStyle = FontStyle::Normal;
|
#[settable]
|
||||||
|
#[default(FontStyle::Normal)]
|
||||||
|
pub style: FontStyle,
|
||||||
|
|
||||||
/// The desired thickness of the font's glyphs. Accepts an integer between
|
/// The desired thickness of the font's glyphs. Accepts an integer between
|
||||||
/// `{100}` and `{900}` or one of the predefined weight names. When the
|
/// `{100}` and `{900}` or one of the predefined weight names. When the
|
||||||
|
|
@ -138,7 +165,9 @@ impl TextNode {
|
||||||
/// #text(weight: 500)[Medium] \
|
/// #text(weight: 500)[Medium] \
|
||||||
/// #text(weight: "bold")[Bold]
|
/// #text(weight: "bold")[Bold]
|
||||||
/// ```
|
/// ```
|
||||||
pub const WEIGHT: FontWeight = FontWeight::REGULAR;
|
#[settable]
|
||||||
|
#[default(FontWeight::REGULAR)]
|
||||||
|
pub weight: FontWeight,
|
||||||
|
|
||||||
/// The desired width of the glyphs. Accepts a ratio between `{50%}` and
|
/// The desired width of the glyphs. Accepts a ratio between `{50%}` and
|
||||||
/// `{200%}`. When the desired weight is not available, Typst selects the
|
/// `{200%}`. When the desired weight is not available, Typst selects the
|
||||||
|
|
@ -148,7 +177,9 @@ impl TextNode {
|
||||||
/// #text(stretch: 75%)[Condensed] \
|
/// #text(stretch: 75%)[Condensed] \
|
||||||
/// #text(stretch: 100%)[Normal]
|
/// #text(stretch: 100%)[Normal]
|
||||||
/// ```
|
/// ```
|
||||||
pub const STRETCH: FontStretch = FontStretch::NORMAL;
|
#[settable]
|
||||||
|
#[default(FontStretch::NORMAL)]
|
||||||
|
pub stretch: FontStretch,
|
||||||
|
|
||||||
/// The size of the glyphs. This value forms the basis of the `em` unit:
|
/// The size of the glyphs. This value forms the basis of the `em` unit:
|
||||||
/// `{1em}` is equivalent to the font size.
|
/// `{1em}` is equivalent to the font size.
|
||||||
|
|
@ -160,8 +191,11 @@ impl TextNode {
|
||||||
/// #set text(size: 20pt)
|
/// #set text(size: 20pt)
|
||||||
/// very #text(1.5em)[big] text
|
/// very #text(1.5em)[big] text
|
||||||
/// ```
|
/// ```
|
||||||
#[property(shorthand, fold)]
|
#[settable]
|
||||||
pub const SIZE: TextSize = Abs::pt(11.0);
|
#[shorthand]
|
||||||
|
#[fold]
|
||||||
|
#[default(Abs::pt(11.0))]
|
||||||
|
pub size: TextSize,
|
||||||
|
|
||||||
/// The glyph fill color.
|
/// The glyph fill color.
|
||||||
///
|
///
|
||||||
|
|
@ -169,8 +203,10 @@ impl TextNode {
|
||||||
/// #set text(fill: red)
|
/// #set text(fill: red)
|
||||||
/// This text is red.
|
/// This text is red.
|
||||||
/// ```
|
/// ```
|
||||||
#[property(shorthand)]
|
#[shorthand]
|
||||||
pub const FILL: Paint = Color::BLACK.into();
|
#[settable]
|
||||||
|
#[default(Color::BLACK.into())]
|
||||||
|
pub fill: Paint,
|
||||||
|
|
||||||
/// The amount of space that should be added between characters.
|
/// The amount of space that should be added between characters.
|
||||||
///
|
///
|
||||||
|
|
@ -178,8 +214,10 @@ impl TextNode {
|
||||||
/// #set text(tracking: 1.5pt)
|
/// #set text(tracking: 1.5pt)
|
||||||
/// Distant text.
|
/// Distant text.
|
||||||
/// ```
|
/// ```
|
||||||
#[property(resolve)]
|
#[settable]
|
||||||
pub const TRACKING: Length = Length::zero();
|
#[resolve]
|
||||||
|
#[default(Length::zero())]
|
||||||
|
pub tracking: Length,
|
||||||
|
|
||||||
/// The amount of space between words.
|
/// The amount of space between words.
|
||||||
///
|
///
|
||||||
|
|
@ -190,8 +228,10 @@ impl TextNode {
|
||||||
/// #set text(spacing: 200%)
|
/// #set text(spacing: 200%)
|
||||||
/// Text with distant words.
|
/// Text with distant words.
|
||||||
/// ```
|
/// ```
|
||||||
#[property(resolve)]
|
#[settable]
|
||||||
pub const SPACING: Rel<Length> = Rel::one();
|
#[resolve]
|
||||||
|
#[default(Rel::one())]
|
||||||
|
pub spacing: Rel<Length>,
|
||||||
|
|
||||||
/// An amount to shift the text baseline by.
|
/// An amount to shift the text baseline by.
|
||||||
///
|
///
|
||||||
|
|
@ -199,8 +239,10 @@ impl TextNode {
|
||||||
/// A #text(baseline: 3pt)[lowered]
|
/// A #text(baseline: 3pt)[lowered]
|
||||||
/// word.
|
/// word.
|
||||||
/// ```
|
/// ```
|
||||||
#[property(resolve)]
|
#[settable]
|
||||||
pub const BASELINE: Length = Length::zero();
|
#[resolve]
|
||||||
|
#[default(Length::zero())]
|
||||||
|
pub baseline: Length,
|
||||||
|
|
||||||
/// Whether certain glyphs can hang over into the margin in justified text.
|
/// Whether certain glyphs can hang over into the margin in justified text.
|
||||||
/// This can make justification visually more pleasing.
|
/// This can make justification visually more pleasing.
|
||||||
|
|
@ -222,7 +264,9 @@ impl TextNode {
|
||||||
/// margin, making the paragraph's
|
/// margin, making the paragraph's
|
||||||
/// edge less clear.
|
/// edge less clear.
|
||||||
/// ```
|
/// ```
|
||||||
pub const OVERHANG: bool = true;
|
#[settable]
|
||||||
|
#[default(true)]
|
||||||
|
pub overhang: bool,
|
||||||
|
|
||||||
/// The top end of the conceptual frame around the text used for layout and
|
/// The top end of the conceptual frame around the text used for layout and
|
||||||
/// positioning. This affects the size of containers that hold text.
|
/// positioning. This affects the size of containers that hold text.
|
||||||
|
|
@ -237,7 +281,9 @@ impl TextNode {
|
||||||
/// #set text(top-edge: "cap-height")
|
/// #set text(top-edge: "cap-height")
|
||||||
/// #rect(fill: aqua)[Typst]
|
/// #rect(fill: aqua)[Typst]
|
||||||
/// ```
|
/// ```
|
||||||
pub const TOP_EDGE: TextEdge = TextEdge::Metric(VerticalFontMetric::CapHeight);
|
#[settable]
|
||||||
|
#[default(TextEdge::Metric(VerticalFontMetric::CapHeight))]
|
||||||
|
pub top_edge: TextEdge,
|
||||||
|
|
||||||
/// The bottom end of the conceptual frame around the text used for layout
|
/// The bottom end of the conceptual frame around the text used for layout
|
||||||
/// and positioning. This affects the size of containers that hold text.
|
/// and positioning. This affects the size of containers that hold text.
|
||||||
|
|
@ -252,7 +298,9 @@ impl TextNode {
|
||||||
/// #set text(bottom-edge: "descender")
|
/// #set text(bottom-edge: "descender")
|
||||||
/// #rect(fill: aqua)[Typst]
|
/// #rect(fill: aqua)[Typst]
|
||||||
/// ```
|
/// ```
|
||||||
pub const BOTTOM_EDGE: TextEdge = TextEdge::Metric(VerticalFontMetric::Baseline);
|
#[settable]
|
||||||
|
#[default(TextEdge::Metric(VerticalFontMetric::Baseline))]
|
||||||
|
pub bottom_edge: TextEdge,
|
||||||
|
|
||||||
/// An [ISO 639-1/2/3 language code.](https://en.wikipedia.org/wiki/ISO_639)
|
/// An [ISO 639-1/2/3 language code.](https://en.wikipedia.org/wiki/ISO_639)
|
||||||
///
|
///
|
||||||
|
|
@ -271,12 +319,16 @@ impl TextNode {
|
||||||
/// = Einleitung
|
/// = Einleitung
|
||||||
/// In diesem Dokument, ...
|
/// In diesem Dokument, ...
|
||||||
/// ```
|
/// ```
|
||||||
pub const LANG: Lang = Lang::ENGLISH;
|
#[settable]
|
||||||
|
#[default(Lang::ENGLISH)]
|
||||||
|
pub lang: Lang,
|
||||||
|
|
||||||
/// An [ISO 3166-1 alpha-2 region code.](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)
|
/// An [ISO 3166-1 alpha-2 region code.](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)
|
||||||
///
|
///
|
||||||
/// This lets the text processing pipeline make more informed choices.
|
/// This lets the text processing pipeline make more informed choices.
|
||||||
pub const REGION: Option<Region> = None;
|
#[settable]
|
||||||
|
#[default(None)]
|
||||||
|
pub region: Option<Region>,
|
||||||
|
|
||||||
/// The dominant direction for text and inline objects. Possible values are:
|
/// The dominant direction for text and inline objects. Possible values are:
|
||||||
///
|
///
|
||||||
|
|
@ -302,8 +354,10 @@ impl TextNode {
|
||||||
/// #set text(dir: rtl)
|
/// #set text(dir: rtl)
|
||||||
/// هذا عربي.
|
/// هذا عربي.
|
||||||
/// ```
|
/// ```
|
||||||
#[property(resolve)]
|
#[settable]
|
||||||
pub const DIR: HorizontalDir = HorizontalDir(Smart::Auto);
|
#[resolve]
|
||||||
|
#[default(HorizontalDir(Smart::Auto))]
|
||||||
|
pub dir: HorizontalDir,
|
||||||
|
|
||||||
/// Whether to hyphenate text to improve line breaking. When `{auto}`, text
|
/// Whether to hyphenate text to improve line breaking. When `{auto}`, text
|
||||||
/// will be hyphenated if and only if justification is enabled.
|
/// will be hyphenated if and only if justification is enabled.
|
||||||
|
|
@ -322,8 +376,10 @@ impl TextNode {
|
||||||
/// enabling hyphenation can
|
/// enabling hyphenation can
|
||||||
/// improve justification.
|
/// improve justification.
|
||||||
/// ```
|
/// ```
|
||||||
#[property(resolve)]
|
#[settable]
|
||||||
pub const HYPHENATE: Hyphenate = Hyphenate(Smart::Auto);
|
#[resolve]
|
||||||
|
#[default(Hyphenate(Smart::Auto))]
|
||||||
|
pub hyphenate: Hyphenate,
|
||||||
|
|
||||||
/// Whether to apply kerning.
|
/// Whether to apply kerning.
|
||||||
///
|
///
|
||||||
|
|
@ -340,7 +396,9 @@ impl TextNode {
|
||||||
/// #set text(kerning: false)
|
/// #set text(kerning: false)
|
||||||
/// Totally
|
/// Totally
|
||||||
/// ```
|
/// ```
|
||||||
pub const KERNING: bool = true;
|
#[settable]
|
||||||
|
#[default(true)]
|
||||||
|
pub kerning: bool,
|
||||||
|
|
||||||
/// Whether to apply stylistic alternates.
|
/// Whether to apply stylistic alternates.
|
||||||
///
|
///
|
||||||
|
|
@ -355,14 +413,18 @@ impl TextNode {
|
||||||
/// #set text(alternates: true)
|
/// #set text(alternates: true)
|
||||||
/// 0, a, g, ß
|
/// 0, a, g, ß
|
||||||
/// ```
|
/// ```
|
||||||
pub const ALTERNATES: bool = false;
|
#[settable]
|
||||||
|
#[default(false)]
|
||||||
|
pub alternates: bool,
|
||||||
|
|
||||||
/// Which stylistic set to apply. Font designers can categorize alternative
|
/// Which stylistic set to apply. Font designers can categorize alternative
|
||||||
/// glyphs forms into stylistic sets. As this value is highly font-specific,
|
/// glyphs forms into stylistic sets. As this value is highly font-specific,
|
||||||
/// you need to consult your font to know which sets are available. When set
|
/// you need to consult your font to know which sets are available. When set
|
||||||
/// to an integer between `{1}` and `{20}`, enables the corresponding
|
/// to an integer between `{1}` and `{20}`, enables the corresponding
|
||||||
/// OpenType font feature from `ss01`, ..., `ss20`.
|
/// OpenType font feature from `ss01`, ..., `ss20`.
|
||||||
pub const STYLISTIC_SET: Option<StylisticSet> = None;
|
#[settable]
|
||||||
|
#[default(None)]
|
||||||
|
pub stylistic_set: Option<StylisticSet>,
|
||||||
|
|
||||||
/// Whether standard ligatures are active.
|
/// Whether standard ligatures are active.
|
||||||
///
|
///
|
||||||
|
|
@ -378,15 +440,21 @@ impl TextNode {
|
||||||
/// #set text(ligatures: false)
|
/// #set text(ligatures: false)
|
||||||
/// A fine ligature.
|
/// A fine ligature.
|
||||||
/// ```
|
/// ```
|
||||||
pub const LIGATURES: bool = true;
|
#[settable]
|
||||||
|
#[default(true)]
|
||||||
|
pub ligatures: bool,
|
||||||
|
|
||||||
/// Whether ligatures that should be used sparingly are active. Setting this
|
/// Whether ligatures that should be used sparingly are active. Setting this
|
||||||
/// to `{true}` enables the OpenType `dlig` font feature.
|
/// to `{true}` enables the OpenType `dlig` font feature.
|
||||||
pub const DISCRETIONARY_LIGATURES: bool = false;
|
#[settable]
|
||||||
|
#[default(false)]
|
||||||
|
pub discretionary_ligatures: bool,
|
||||||
|
|
||||||
/// Whether historical ligatures are active. Setting this to `{true}`
|
/// Whether historical ligatures are active. Setting this to `{true}`
|
||||||
/// enables the OpenType `hlig` font feature.
|
/// enables the OpenType `hlig` font feature.
|
||||||
pub const HISTORICAL_LIGATURES: bool = false;
|
#[settable]
|
||||||
|
#[default(false)]
|
||||||
|
pub historical_ligatures: bool,
|
||||||
|
|
||||||
/// Which kind of numbers / figures to select. When set to `{auto}`, the
|
/// Which kind of numbers / figures to select. When set to `{auto}`, the
|
||||||
/// default numbers for the font are used.
|
/// default numbers for the font are used.
|
||||||
|
|
@ -399,7 +467,9 @@ impl TextNode {
|
||||||
/// #set text(number-type: "old-style")
|
/// #set text(number-type: "old-style")
|
||||||
/// Number 9.
|
/// Number 9.
|
||||||
/// ```
|
/// ```
|
||||||
pub const NUMBER_TYPE: Smart<NumberType> = Smart::Auto;
|
#[settable]
|
||||||
|
#[default(Smart::Auto)]
|
||||||
|
pub number_type: Smart<NumberType>,
|
||||||
|
|
||||||
/// The width of numbers / figures. When set to `{auto}`, the default
|
/// The width of numbers / figures. When set to `{auto}`, the default
|
||||||
/// numbers for the font are used.
|
/// numbers for the font are used.
|
||||||
|
|
@ -414,7 +484,9 @@ impl TextNode {
|
||||||
/// A 12 B 34. \
|
/// A 12 B 34. \
|
||||||
/// A 56 B 78.
|
/// A 56 B 78.
|
||||||
/// ```
|
/// ```
|
||||||
pub const NUMBER_WIDTH: Smart<NumberWidth> = Smart::Auto;
|
#[settable]
|
||||||
|
#[default(Smart::Auto)]
|
||||||
|
pub number_width: Smart<NumberWidth>,
|
||||||
|
|
||||||
/// Whether to have a slash through the zero glyph. Setting this to `{true}`
|
/// Whether to have a slash through the zero glyph. Setting this to `{true}`
|
||||||
/// enables the OpenType `zero` font feature.
|
/// enables the OpenType `zero` font feature.
|
||||||
|
|
@ -422,7 +494,9 @@ impl TextNode {
|
||||||
/// ```example
|
/// ```example
|
||||||
/// 0, #text(slashed-zero: true)[0]
|
/// 0, #text(slashed-zero: true)[0]
|
||||||
/// ```
|
/// ```
|
||||||
pub const SLASHED_ZERO: bool = false;
|
#[settable]
|
||||||
|
#[default(false)]
|
||||||
|
pub slashed_zero: bool,
|
||||||
|
|
||||||
/// Whether to turns numbers into fractions. Setting this to `{true}`
|
/// Whether to turns numbers into fractions. Setting this to `{true}`
|
||||||
/// enables the OpenType `frac` font feature.
|
/// enables the OpenType `frac` font feature.
|
||||||
|
|
@ -431,7 +505,9 @@ impl TextNode {
|
||||||
/// 1/2 \
|
/// 1/2 \
|
||||||
/// #text(fractions: true)[1/2]
|
/// #text(fractions: true)[1/2]
|
||||||
/// ```
|
/// ```
|
||||||
pub const FRACTIONS: bool = false;
|
#[settable]
|
||||||
|
#[default(false)]
|
||||||
|
pub fractions: bool,
|
||||||
|
|
||||||
/// Raw OpenType features to apply.
|
/// Raw OpenType features to apply.
|
||||||
///
|
///
|
||||||
|
|
@ -445,74 +521,59 @@ impl TextNode {
|
||||||
/// #set text(features: ("frac",))
|
/// #set text(features: ("frac",))
|
||||||
/// 1/2
|
/// 1/2
|
||||||
/// ```
|
/// ```
|
||||||
#[property(fold)]
|
#[settable]
|
||||||
pub const FEATURES: FontFeatures = FontFeatures(vec![]);
|
#[fold]
|
||||||
|
#[default(FontFeatures(vec![]))]
|
||||||
|
pub features: FontFeatures,
|
||||||
|
|
||||||
/// A delta to apply on the font weight.
|
/// A delta to apply on the font weight.
|
||||||
#[property(skip, fold)]
|
#[settable]
|
||||||
pub const DELTA: Delta = 0;
|
#[fold]
|
||||||
/// Whether the font style should be inverted.
|
#[skip]
|
||||||
#[property(skip, fold)]
|
#[default(0)]
|
||||||
pub const EMPH: Toggle = false;
|
pub delta: Delta,
|
||||||
/// A case transformation that should be applied to the text.
|
|
||||||
#[property(skip)]
|
|
||||||
pub const CASE: Option<Case> = None;
|
|
||||||
/// Whether small capital glyphs should be used. ("smcp")
|
|
||||||
#[property(skip)]
|
|
||||||
pub const SMALLCAPS: bool = false;
|
|
||||||
/// Decorative lines.
|
|
||||||
#[property(skip, fold)]
|
|
||||||
pub const DECO: Decoration = vec![];
|
|
||||||
|
|
||||||
|
/// Whether the font style should be inverted.
|
||||||
|
#[settable]
|
||||||
|
#[fold]
|
||||||
|
#[skip]
|
||||||
|
#[default(false)]
|
||||||
|
pub emph: Toggle,
|
||||||
|
|
||||||
|
/// A case transformation that should be applied to the text.
|
||||||
|
#[settable]
|
||||||
|
#[skip]
|
||||||
|
#[default(None)]
|
||||||
|
pub case: Option<Case>,
|
||||||
|
|
||||||
|
/// Whether small capital glyphs should be used. ("smcp")
|
||||||
|
#[settable]
|
||||||
|
#[skip]
|
||||||
|
#[default(false)]
|
||||||
|
pub smallcaps: bool,
|
||||||
|
|
||||||
|
/// Decorative lines.
|
||||||
|
#[settable]
|
||||||
|
#[fold]
|
||||||
|
#[skip]
|
||||||
|
#[default(vec![])]
|
||||||
|
pub deco: Decoration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextNode {
|
||||||
|
/// Create a new packed text node.
|
||||||
|
pub fn packed(text: impl Into<EcoString>) -> Content {
|
||||||
|
Self::new(text.into()).pack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Construct for TextNode {
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
// The text constructor is special: It doesn't create a text node.
|
// The text constructor is special: It doesn't create a text node.
|
||||||
// Instead, it leaves the passed argument structurally unchanged, but
|
// Instead, it leaves the passed argument structurally unchanged, but
|
||||||
// styles all text in it.
|
// styles all text in it.
|
||||||
args.expect("body")
|
args.expect("body")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set(...) {
|
|
||||||
if let Some(family) = args.named("family")? {
|
|
||||||
styles.set(Self::FAMILY, family);
|
|
||||||
} else {
|
|
||||||
let mut count = 0;
|
|
||||||
let mut content = false;
|
|
||||||
for item in args.items.iter().filter(|item| item.name.is_none()) {
|
|
||||||
if EcoString::is(&item.value) {
|
|
||||||
count += 1;
|
|
||||||
} else if <Content as Cast<Spanned<Value>>>::is(&item.value) {
|
|
||||||
content = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip the final string if it's needed as the body.
|
|
||||||
if constructor && !content && count > 0 {
|
|
||||||
count -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if count > 0 {
|
|
||||||
let mut list = Vec::with_capacity(count);
|
|
||||||
for _ in 0..count {
|
|
||||||
list.push(args.find()?.unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
styles.set(Self::FAMILY, FallbackList(list));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field(&self, name: &str) -> Option<Value> {
|
|
||||||
match name {
|
|
||||||
"text" => Some(Value::Str(self.0.clone().into())),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for TextNode {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
write!(f, "Text({:?})", self.0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A lowercased font family like "arial".
|
/// A lowercased font family like "arial".
|
||||||
|
|
@ -537,21 +598,29 @@ impl Debug for FontFamily {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
FontFamily,
|
FontFamily,
|
||||||
string: EcoString => Self::new(&string),
|
string: EcoString => Self::new(&string),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: FontFamily => v.0.into()
|
||||||
|
}
|
||||||
|
|
||||||
/// Font family fallback list.
|
/// Font family fallback list.
|
||||||
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct FallbackList(pub Vec<FontFamily>);
|
pub struct FallbackList(pub Vec<FontFamily>);
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
FallbackList,
|
FallbackList,
|
||||||
family: FontFamily => Self(vec![family]),
|
family: FontFamily => Self(vec![family]),
|
||||||
values: Array => Self(values.into_iter().map(|v| v.cast()).collect::<StrResult<_>>()?),
|
values: Array => Self(values.into_iter().map(|v| v.cast()).collect::<StrResult<_>>()?),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: FallbackList => v.0.into()
|
||||||
|
}
|
||||||
|
|
||||||
/// The size of text.
|
/// The size of text.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct TextSize(pub Length);
|
pub struct TextSize(pub Length);
|
||||||
|
|
@ -564,11 +633,15 @@ impl Fold for TextSize {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
TextSize,
|
TextSize,
|
||||||
v: Length => Self(v),
|
v: Length => Self(v),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: TextSize => v.0.into()
|
||||||
|
}
|
||||||
|
|
||||||
/// 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)]
|
||||||
pub enum TextEdge {
|
pub enum TextEdge {
|
||||||
|
|
@ -588,34 +661,37 @@ impl TextEdge {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
TextEdge,
|
TextEdge,
|
||||||
|
v: VerticalFontMetric => Self::Metric(v),
|
||||||
v: Length => Self::Length(v),
|
v: Length => Self::Length(v),
|
||||||
/// The font's ascender, which typically exceeds the height of all glyphs.
|
}
|
||||||
"ascender" => Self::Metric(VerticalFontMetric::Ascender),
|
|
||||||
/// The approximate height of uppercase letters.
|
cast_to_value! {
|
||||||
"cap-height" => Self::Metric(VerticalFontMetric::CapHeight),
|
v: TextEdge => match v {
|
||||||
/// The approximate height of non-ascending lowercase letters.
|
TextEdge::Metric(metric) => metric.into(),
|
||||||
"x-height" => Self::Metric(VerticalFontMetric::XHeight),
|
TextEdge::Length(length) => length.into(),
|
||||||
/// The baseline on which the letters rest.
|
}
|
||||||
"baseline" => Self::Metric(VerticalFontMetric::Baseline),
|
|
||||||
/// The font's ascender, which typically exceeds the depth of all glyphs.
|
|
||||||
"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.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct HorizontalDir(pub Smart<Dir>);
|
pub struct HorizontalDir(pub Smart<Dir>);
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
HorizontalDir,
|
HorizontalDir,
|
||||||
_: AutoValue => Self(Smart::Auto),
|
v: Smart<Dir> => {
|
||||||
dir: Dir => match dir.axis() {
|
if v.map_or(false, |dir| dir.axis() == Axis::Y) {
|
||||||
Axis::X => Self(Smart::Custom(dir)),
|
Err("must be horizontal")?;
|
||||||
Axis::Y => Err("must be horizontal")?,
|
}
|
||||||
|
Self(v)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: HorizontalDir => v.0.into()
|
||||||
|
}
|
||||||
|
|
||||||
impl Resolve for HorizontalDir {
|
impl Resolve for HorizontalDir {
|
||||||
type Output = Dir;
|
type Output = Dir;
|
||||||
|
|
||||||
|
|
@ -631,10 +707,13 @@ impl Resolve for HorizontalDir {
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct Hyphenate(pub Smart<bool>);
|
pub struct Hyphenate(pub Smart<bool>);
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
Hyphenate,
|
Hyphenate,
|
||||||
_: AutoValue => Self(Smart::Auto),
|
v: Smart<bool> => Self(v),
|
||||||
v: bool => Self(Smart::Custom(v)),
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: Hyphenate => v.0.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Resolve for Hyphenate {
|
impl Resolve for Hyphenate {
|
||||||
|
|
@ -664,7 +743,7 @@ impl StylisticSet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
StylisticSet,
|
StylisticSet,
|
||||||
v: i64 => match v {
|
v: i64 => match v {
|
||||||
1 ..= 20 => Self::new(v as u8),
|
1 ..= 20 => Self::new(v as u8),
|
||||||
|
|
@ -672,6 +751,10 @@ castable! {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: StylisticSet => v.0.into()
|
||||||
|
}
|
||||||
|
|
||||||
/// Which kind of numbers / figures to select.
|
/// Which kind of numbers / figures to select.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum NumberType {
|
pub enum NumberType {
|
||||||
|
|
@ -681,16 +764,23 @@ pub enum NumberType {
|
||||||
OldStyle,
|
OldStyle,
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
NumberType,
|
NumberType,
|
||||||
/// Numbers that fit well with capital text (the OpenType `lnum`
|
/// Numbers that fit well with capital text (the OpenType `lnum`
|
||||||
/// font feature).
|
/// font feature).
|
||||||
"lining" => Self::Lining,
|
"lining" => Self::Lining,
|
||||||
/// Numbers that fit well into a flow of upper- and lowercase text (the
|
// Numbers that fit well into a flow of upper- and lowercase text (the
|
||||||
/// OpenType `onum` font feature).
|
/// OpenType `onum` font feature).
|
||||||
"old-style" => Self::OldStyle,
|
"old-style" => Self::OldStyle,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: NumberType => Value::from(match v {
|
||||||
|
NumberType::Lining => "lining",
|
||||||
|
NumberType::OldStyle => "old-style",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// The width of numbers / figures.
|
/// The width of numbers / figures.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum NumberWidth {
|
pub enum NumberWidth {
|
||||||
|
|
@ -700,7 +790,7 @@ pub enum NumberWidth {
|
||||||
Tabular,
|
Tabular,
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
NumberWidth,
|
NumberWidth,
|
||||||
/// Numbers with glyph-specific widths (the OpenType `pnum` font feature).
|
/// Numbers with glyph-specific widths (the OpenType `pnum` font feature).
|
||||||
"proportional" => Self::Proportional,
|
"proportional" => Self::Proportional,
|
||||||
|
|
@ -708,11 +798,18 @@ castable! {
|
||||||
"tabular" => Self::Tabular,
|
"tabular" => Self::Tabular,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: NumberWidth => Value::from(match v {
|
||||||
|
NumberWidth::Proportional => "proportional",
|
||||||
|
NumberWidth::Tabular => "tabular",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// OpenType font features settings.
|
/// OpenType font features settings.
|
||||||
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct FontFeatures(pub Vec<(Tag, u32)>);
|
pub struct FontFeatures(pub Vec<(Tag, u32)>);
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
FontFeatures,
|
FontFeatures,
|
||||||
values: Array => Self(values
|
values: Array => Self(values
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
@ -731,6 +828,18 @@ castable! {
|
||||||
.collect::<StrResult<_>>()?),
|
.collect::<StrResult<_>>()?),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: FontFeatures => Value::Dict(
|
||||||
|
v.0.into_iter()
|
||||||
|
.map(|(tag, num)| {
|
||||||
|
let bytes = tag.to_bytes();
|
||||||
|
let key = std::str::from_utf8(&bytes).unwrap_or_default();
|
||||||
|
(key.into(), num.into())
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
impl Fold for FontFeatures {
|
impl Fold for FontFeatures {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ use typst::syntax::is_newline;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// # Smart Quote
|
|
||||||
/// A language-aware quote that reacts to its context.
|
/// A language-aware quote that reacts to its context.
|
||||||
///
|
///
|
||||||
/// Automatically turns into an appropriate opening or closing quote based on
|
/// Automatically turns into an appropriate opening or closing quote based on
|
||||||
|
|
@ -23,21 +22,15 @@ use crate::prelude::*;
|
||||||
/// This function also has dedicated syntax: The normal quote characters
|
/// This function also has dedicated syntax: The normal quote characters
|
||||||
/// (`'` and `"`). Typst automatically makes your quotes smart.
|
/// (`'` and `"`). Typst automatically makes your quotes smart.
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Smart Quote
|
||||||
/// - double: `bool` (named)
|
/// Category: text
|
||||||
/// Whether this should be a double quote.
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// text
|
|
||||||
#[func]
|
|
||||||
#[capable]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct SmartQuoteNode {
|
|
||||||
pub double: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
impl SmartQuoteNode {
|
pub struct SmartQuoteNode {
|
||||||
|
/// Whether this should be a double quote.
|
||||||
|
#[named]
|
||||||
|
#[default(true)]
|
||||||
|
pub double: bool,
|
||||||
|
|
||||||
/// Whether smart quotes are enabled.
|
/// Whether smart quotes are enabled.
|
||||||
///
|
///
|
||||||
/// To disable smartness for a single quote, you can also escape it with a
|
/// To disable smartness for a single quote, you can also escape it with a
|
||||||
|
|
@ -48,19 +41,9 @@ impl SmartQuoteNode {
|
||||||
///
|
///
|
||||||
/// These are "dumb" quotes.
|
/// These are "dumb" quotes.
|
||||||
/// ```
|
/// ```
|
||||||
pub const ENABLED: bool = true;
|
#[settable]
|
||||||
|
#[default(true)]
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
pub enabled: bool,
|
||||||
let double = args.named("double")?.unwrap_or(true);
|
|
||||||
Ok(Self { double }.pack())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field(&self, name: &str) -> Option<Value> {
|
|
||||||
match name {
|
|
||||||
"double" => Some(Value::Bool(self.double)),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// State machine for smart quote substitution.
|
/// State machine for smart quote substitution.
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ use super::{
|
||||||
use crate::layout::BlockNode;
|
use crate::layout::BlockNode;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// # Raw Text / Code
|
|
||||||
/// Raw text with optional syntax highlighting.
|
/// Raw text with optional syntax highlighting.
|
||||||
///
|
///
|
||||||
/// Displays the text verbatim and in a monospace font. This is typically used
|
/// Displays the text verbatim and in a monospace font. This is typically used
|
||||||
|
|
@ -35,71 +34,64 @@ use crate::prelude::*;
|
||||||
/// ```
|
/// ```
|
||||||
/// ````
|
/// ````
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Raw Text / Code
|
||||||
/// - text: `EcoString` (positional, required)
|
/// Category: text
|
||||||
/// The raw text.
|
#[node(Prepare, Show, Finalize)]
|
||||||
///
|
|
||||||
/// You can also use raw blocks creatively to create custom syntaxes for
|
|
||||||
/// your automations.
|
|
||||||
///
|
|
||||||
/// ````example
|
|
||||||
/// // Parse numbers in raw blocks with the
|
|
||||||
/// // `mydsl` tag and sum them up.
|
|
||||||
/// #show raw.where(lang: "mydsl"): it => {
|
|
||||||
/// let sum = 0
|
|
||||||
/// for part in it.text.split("+") {
|
|
||||||
/// sum += int(part.trim())
|
|
||||||
/// }
|
|
||||||
/// sum
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// ```mydsl
|
|
||||||
/// 1 + 2 + 3 + 4 + 5
|
|
||||||
/// ```
|
|
||||||
/// ````
|
|
||||||
///
|
|
||||||
/// - block: `bool` (named)
|
|
||||||
/// Whether the raw text is displayed as a separate block.
|
|
||||||
///
|
|
||||||
/// ````example
|
|
||||||
/// // Display inline code in a small box
|
|
||||||
/// // that retains the correct baseline.
|
|
||||||
/// #show raw.where(block: false): box.with(
|
|
||||||
/// fill: luma(240),
|
|
||||||
/// inset: (x: 3pt, y: 0pt),
|
|
||||||
/// outset: (y: 3pt),
|
|
||||||
/// radius: 2pt,
|
|
||||||
/// )
|
|
||||||
///
|
|
||||||
/// // Display block code in a larger block
|
|
||||||
/// // with more padding.
|
|
||||||
/// #show raw.where(block: true): block.with(
|
|
||||||
/// fill: luma(240),
|
|
||||||
/// inset: 10pt,
|
|
||||||
/// radius: 4pt,
|
|
||||||
/// )
|
|
||||||
///
|
|
||||||
/// With `rg`, you can search through your files quickly.
|
|
||||||
///
|
|
||||||
/// ```bash
|
|
||||||
/// rg "Hello World"
|
|
||||||
/// ```
|
|
||||||
/// ````
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// text
|
|
||||||
#[func]
|
|
||||||
#[capable(Prepare, Show, Finalize)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct RawNode {
|
pub struct RawNode {
|
||||||
/// The raw text.
|
/// The raw text.
|
||||||
|
///
|
||||||
|
/// You can also use raw blocks creatively to create custom syntaxes for
|
||||||
|
/// your automations.
|
||||||
|
///
|
||||||
|
/// ````example
|
||||||
|
/// // Parse numbers in raw blocks with the
|
||||||
|
/// // `mydsl` tag and sum them up.
|
||||||
|
/// #show raw.where(lang: "mydsl"): it => {
|
||||||
|
/// let sum = 0
|
||||||
|
/// for part in it.text.split("+") {
|
||||||
|
/// sum += int(part.trim())
|
||||||
|
/// }
|
||||||
|
/// sum
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// ```mydsl
|
||||||
|
/// 1 + 2 + 3 + 4 + 5
|
||||||
|
/// ```
|
||||||
|
/// ````
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
pub text: EcoString,
|
pub text: EcoString,
|
||||||
/// Whether the raw text is displayed as a separate block.
|
|
||||||
pub block: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
/// Whether the raw text is displayed as a separate block.
|
||||||
impl RawNode {
|
///
|
||||||
|
/// ````example
|
||||||
|
/// // Display inline code in a small box
|
||||||
|
/// // that retains the correct baseline.
|
||||||
|
/// #show raw.where(block: false): box.with(
|
||||||
|
/// fill: luma(240),
|
||||||
|
/// inset: (x: 3pt, y: 0pt),
|
||||||
|
/// outset: (y: 3pt),
|
||||||
|
/// radius: 2pt,
|
||||||
|
/// )
|
||||||
|
///
|
||||||
|
/// // Display block code in a larger block
|
||||||
|
/// // with more padding.
|
||||||
|
/// #show raw.where(block: true): block.with(
|
||||||
|
/// fill: luma(240),
|
||||||
|
/// inset: 10pt,
|
||||||
|
/// radius: 4pt,
|
||||||
|
/// )
|
||||||
|
///
|
||||||
|
/// With `rg`, you can search through your files quickly.
|
||||||
|
///
|
||||||
|
/// ```bash
|
||||||
|
/// rg "Hello World"
|
||||||
|
/// ```
|
||||||
|
/// ````
|
||||||
|
#[named]
|
||||||
|
#[default(false)]
|
||||||
|
pub block: bool,
|
||||||
|
|
||||||
/// The language to syntax-highlight in.
|
/// The language to syntax-highlight in.
|
||||||
///
|
///
|
||||||
/// Apart from typical language tags known from Markdown, this supports the
|
/// Apart from typical language tags known from Markdown, this supports the
|
||||||
|
|
@ -111,24 +103,9 @@ impl RawNode {
|
||||||
/// This is *Typst!*
|
/// This is *Typst!*
|
||||||
/// ```
|
/// ```
|
||||||
/// ````
|
/// ````
|
||||||
#[property(referenced)]
|
#[settable]
|
||||||
pub const LANG: Option<EcoString> = None;
|
#[default]
|
||||||
|
pub lang: Option<EcoString>,
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
Ok(Self {
|
|
||||||
text: args.expect("text")?,
|
|
||||||
block: args.named("block")?.unwrap_or(false),
|
|
||||||
}
|
|
||||||
.pack())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field(&self, name: &str) -> Option<Value> {
|
|
||||||
match name {
|
|
||||||
"text" => Some(Value::Str(self.text.clone().into())),
|
|
||||||
"block" => Some(Value::Bool(self.block)),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Prepare for RawNode {
|
impl Prepare for RawNode {
|
||||||
|
|
@ -138,19 +115,14 @@ impl Prepare for RawNode {
|
||||||
mut this: Content,
|
mut this: Content,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Content> {
|
) -> SourceResult<Content> {
|
||||||
this.push_field(
|
this.push_field("lang", styles.get(Self::LANG).clone());
|
||||||
"lang",
|
|
||||||
match styles.get(Self::LANG) {
|
|
||||||
Some(lang) => Value::Str(lang.clone().into()),
|
|
||||||
None => Value::None,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
Ok(this)
|
Ok(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for RawNode {
|
impl Show for RawNode {
|
||||||
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
||||||
|
let text = self.text();
|
||||||
let lang = styles.get(Self::LANG).as_ref().map(|s| s.to_lowercase());
|
let lang = styles.get(Self::LANG).as_ref().map(|s| s.to_lowercase());
|
||||||
let foreground = THEME
|
let foreground = THEME
|
||||||
.settings
|
.settings
|
||||||
|
|
@ -161,8 +133,8 @@ impl Show for RawNode {
|
||||||
|
|
||||||
let mut realized = if matches!(lang.as_deref(), Some("typ" | "typst" | "typc")) {
|
let mut realized = if matches!(lang.as_deref(), Some("typ" | "typst" | "typc")) {
|
||||||
let root = match lang.as_deref() {
|
let root = match lang.as_deref() {
|
||||||
Some("typc") => syntax::parse_code(&self.text),
|
Some("typc") => syntax::parse_code(&text),
|
||||||
_ => syntax::parse(&self.text),
|
_ => syntax::parse(&text),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut seq = vec![];
|
let mut seq = vec![];
|
||||||
|
|
@ -172,7 +144,7 @@ impl Show for RawNode {
|
||||||
vec![],
|
vec![],
|
||||||
&highlighter,
|
&highlighter,
|
||||||
&mut |node, style| {
|
&mut |node, style| {
|
||||||
seq.push(styled(&self.text[node.range()], foreground, style));
|
seq.push(styled(&text[node.range()], foreground, style));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -182,9 +154,9 @@ impl Show for RawNode {
|
||||||
{
|
{
|
||||||
let mut seq = vec![];
|
let mut seq = vec![];
|
||||||
let mut highlighter = syntect::easy::HighlightLines::new(syntax, &THEME);
|
let mut highlighter = syntect::easy::HighlightLines::new(syntax, &THEME);
|
||||||
for (i, line) in self.text.lines().enumerate() {
|
for (i, line) in text.lines().enumerate() {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
seq.push(LinebreakNode { justify: false }.pack());
|
seq.push(LinebreakNode::new().pack());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (style, piece) in
|
for (style, piece) in
|
||||||
|
|
@ -196,16 +168,11 @@ impl Show for RawNode {
|
||||||
|
|
||||||
Content::sequence(seq)
|
Content::sequence(seq)
|
||||||
} else {
|
} else {
|
||||||
TextNode::packed(self.text.clone())
|
TextNode::packed(text)
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.block {
|
if self.block() {
|
||||||
realized = BlockNode {
|
realized = BlockNode::new().with_body(realized).pack();
|
||||||
body: realized,
|
|
||||||
width: Smart::Auto,
|
|
||||||
height: Smart::Auto,
|
|
||||||
}
|
|
||||||
.pack();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(realized)
|
Ok(realized)
|
||||||
|
|
|
||||||
|
|
@ -154,7 +154,7 @@ impl<'a> ShapedText<'a> {
|
||||||
for family in families(self.styles) {
|
for family in families(self.styles) {
|
||||||
if let Some(font) = world
|
if let Some(font) = world
|
||||||
.book()
|
.book()
|
||||||
.select(family, self.variant)
|
.select(family.as_str(), self.variant)
|
||||||
.and_then(|id| world.font(id))
|
.and_then(|id| world.font(id))
|
||||||
{
|
{
|
||||||
expand(&font);
|
expand(&font);
|
||||||
|
|
@ -209,7 +209,7 @@ impl<'a> ShapedText<'a> {
|
||||||
let world = vt.world();
|
let world = vt.world();
|
||||||
let font = world
|
let font = world
|
||||||
.book()
|
.book()
|
||||||
.select(family, self.variant)
|
.select(family.as_str(), self.variant)
|
||||||
.and_then(|id| world.font(id))?;
|
.and_then(|id| world.font(id))?;
|
||||||
let ttf = font.ttf();
|
let ttf = font.ttf();
|
||||||
let glyph_id = ttf.glyph_index('-')?;
|
let glyph_id = ttf.glyph_index('-')?;
|
||||||
|
|
@ -351,7 +351,7 @@ fn shape_segment<'a>(
|
||||||
ctx: &mut ShapingContext,
|
ctx: &mut ShapingContext,
|
||||||
base: usize,
|
base: usize,
|
||||||
text: &str,
|
text: &str,
|
||||||
mut families: impl Iterator<Item = &'a str> + Clone,
|
mut families: impl Iterator<Item = FontFamily> + Clone,
|
||||||
) {
|
) {
|
||||||
// Fonts dont have newlines and tabs.
|
// Fonts dont have newlines and tabs.
|
||||||
if text.chars().all(|c| c == '\n' || c == '\t') {
|
if text.chars().all(|c| c == '\n' || c == '\t') {
|
||||||
|
|
@ -362,7 +362,7 @@ fn shape_segment<'a>(
|
||||||
let world = ctx.vt.world();
|
let world = ctx.vt.world();
|
||||||
let book = world.book();
|
let book = world.book();
|
||||||
let mut selection = families.find_map(|family| {
|
let mut selection = families.find_map(|family| {
|
||||||
book.select(family, ctx.variant)
|
book.select(family.as_str(), ctx.variant)
|
||||||
.and_then(|id| world.font(id))
|
.and_then(|id| world.font(id))
|
||||||
.filter(|font| !ctx.used.contains(font))
|
.filter(|font| !ctx.used.contains(font))
|
||||||
});
|
});
|
||||||
|
|
@ -549,7 +549,7 @@ pub fn variant(styles: StyleChain) -> FontVariant {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve a prioritized iterator over the font families.
|
/// Resolve a prioritized iterator over the font families.
|
||||||
pub fn families(styles: StyleChain) -> impl Iterator<Item = &str> + Clone {
|
pub fn families(styles: StyleChain) -> impl Iterator<Item = FontFamily> + Clone {
|
||||||
const FALLBACKS: &[&str] = &[
|
const FALLBACKS: &[&str] = &[
|
||||||
"linux libertine",
|
"linux libertine",
|
||||||
"twitter color emoji",
|
"twitter color emoji",
|
||||||
|
|
@ -562,9 +562,8 @@ pub fn families(styles: StyleChain) -> impl Iterator<Item = &str> + Clone {
|
||||||
styles
|
styles
|
||||||
.get(TextNode::FAMILY)
|
.get(TextNode::FAMILY)
|
||||||
.0
|
.0
|
||||||
.iter()
|
.into_iter()
|
||||||
.map(|family| family.as_str())
|
.chain(tail.iter().copied().map(FontFamily::new))
|
||||||
.chain(tail.iter().copied())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Collect the tags of the OpenType features to apply.
|
/// Collect the tags of the OpenType features to apply.
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ use typst::model::SequenceNode;
|
||||||
use super::{variant, SpaceNode, TextNode, TextSize};
|
use super::{variant, SpaceNode, TextNode, TextSize};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// # Subscript
|
|
||||||
/// Set text in subscript.
|
/// Set text in subscript.
|
||||||
///
|
///
|
||||||
/// The text is rendered smaller and its baseline is lowered.
|
/// The text is rendered smaller and its baseline is lowered.
|
||||||
|
|
@ -13,19 +12,15 @@ use crate::prelude::*;
|
||||||
/// Revenue#sub[yearly]
|
/// Revenue#sub[yearly]
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Subscript
|
||||||
/// - body: `Content` (positional, required)
|
/// Category: text
|
||||||
/// The text to display in subscript.
|
#[node(Show)]
|
||||||
///
|
pub struct SubNode {
|
||||||
/// ## Category
|
/// The text to display in subscript.
|
||||||
/// text
|
#[positional]
|
||||||
#[func]
|
#[required]
|
||||||
#[capable(Show)]
|
pub body: Content,
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct SubNode(pub Content);
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl SubNode {
|
|
||||||
/// Whether to prefer the dedicated subscript characters of the font.
|
/// Whether to prefer the dedicated subscript characters of the font.
|
||||||
///
|
///
|
||||||
/// If this is enabled, Typst first tries to transform the text to subscript
|
/// If this is enabled, Typst first tries to transform the text to subscript
|
||||||
|
|
@ -36,19 +31,23 @@ impl SubNode {
|
||||||
/// N#sub(typographic: true)[1]
|
/// N#sub(typographic: true)[1]
|
||||||
/// N#sub(typographic: false)[1]
|
/// N#sub(typographic: false)[1]
|
||||||
/// ```
|
/// ```
|
||||||
pub const TYPOGRAPHIC: bool = true;
|
#[settable]
|
||||||
|
#[default(true)]
|
||||||
|
pub typographic: bool,
|
||||||
|
|
||||||
/// The baseline shift for synthetic subscripts. Does not apply if
|
/// The baseline shift for synthetic subscripts. Does not apply if
|
||||||
/// `typographic` is true and the font has subscript codepoints for the
|
/// `typographic` is true and the font has subscript codepoints for the
|
||||||
/// given `body`.
|
/// given `body`.
|
||||||
pub const BASELINE: Length = Em::new(0.2).into();
|
#[settable]
|
||||||
|
#[default(Em::new(0.2).into())]
|
||||||
|
pub baseline: Length,
|
||||||
|
|
||||||
/// The font size for synthetic subscripts. Does not apply if
|
/// The font size for synthetic subscripts. Does not apply if
|
||||||
/// `typographic` is true and the font has subscript codepoints for the
|
/// `typographic` is true and the font has subscript codepoints for the
|
||||||
/// given `body`.
|
/// given `body`.
|
||||||
pub const SIZE: TextSize = TextSize(Em::new(0.6).into());
|
#[settable]
|
||||||
|
#[default(TextSize(Em::new(0.6).into()))]
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
pub size: TextSize,
|
||||||
Ok(Self(args.expect("body")?).pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for SubNode {
|
impl Show for SubNode {
|
||||||
|
|
@ -58,9 +57,10 @@ impl Show for SubNode {
|
||||||
_: &Content,
|
_: &Content,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Content> {
|
) -> SourceResult<Content> {
|
||||||
|
let body = self.body();
|
||||||
let mut transformed = None;
|
let mut transformed = None;
|
||||||
if styles.get(Self::TYPOGRAPHIC) {
|
if styles.get(Self::TYPOGRAPHIC) {
|
||||||
if let Some(text) = search_text(&self.0, true) {
|
if let Some(text) = search_text(&body, true) {
|
||||||
if is_shapable(vt, &text, styles) {
|
if is_shapable(vt, &text, styles) {
|
||||||
transformed = Some(TextNode::packed(text));
|
transformed = Some(TextNode::packed(text));
|
||||||
}
|
}
|
||||||
|
|
@ -71,12 +71,11 @@ impl Show for SubNode {
|
||||||
let mut map = StyleMap::new();
|
let mut map = StyleMap::new();
|
||||||
map.set(TextNode::BASELINE, styles.get(Self::BASELINE));
|
map.set(TextNode::BASELINE, styles.get(Self::BASELINE));
|
||||||
map.set(TextNode::SIZE, styles.get(Self::SIZE));
|
map.set(TextNode::SIZE, styles.get(Self::SIZE));
|
||||||
self.0.clone().styled_with_map(map)
|
body.styled_with_map(map)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Superscript
|
|
||||||
/// Set text in superscript.
|
/// Set text in superscript.
|
||||||
///
|
///
|
||||||
/// The text is rendered smaller and its baseline is raised.
|
/// The text is rendered smaller and its baseline is raised.
|
||||||
|
|
@ -86,19 +85,15 @@ impl Show for SubNode {
|
||||||
/// 1#super[st] try!
|
/// 1#super[st] try!
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Superscript
|
||||||
/// - body: `Content` (positional, required)
|
/// Category: text
|
||||||
/// The text to display in superscript.
|
#[node(Show)]
|
||||||
///
|
pub struct SuperNode {
|
||||||
/// ## Category
|
/// The text to display in superscript.
|
||||||
/// text
|
#[positional]
|
||||||
#[func]
|
#[required]
|
||||||
#[capable(Show)]
|
pub body: Content,
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct SuperNode(pub Content);
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl SuperNode {
|
|
||||||
/// Whether to prefer the dedicated superscript characters of the font.
|
/// Whether to prefer the dedicated superscript characters of the font.
|
||||||
///
|
///
|
||||||
/// If this is enabled, Typst first tries to transform the text to
|
/// If this is enabled, Typst first tries to transform the text to
|
||||||
|
|
@ -109,19 +104,23 @@ impl SuperNode {
|
||||||
/// N#super(typographic: true)[1]
|
/// N#super(typographic: true)[1]
|
||||||
/// N#super(typographic: false)[1]
|
/// N#super(typographic: false)[1]
|
||||||
/// ```
|
/// ```
|
||||||
pub const TYPOGRAPHIC: bool = true;
|
#[settable]
|
||||||
|
#[default(true)]
|
||||||
|
pub typographic: bool,
|
||||||
|
|
||||||
/// The baseline shift for synthetic superscripts. Does not apply if
|
/// The baseline shift for synthetic superscripts. Does not apply if
|
||||||
/// `typographic` is true and the font has superscript codepoints for the
|
/// `typographic` is true and the font has superscript codepoints for the
|
||||||
/// given `body`.
|
/// given `body`.
|
||||||
pub const BASELINE: Length = Em::new(-0.5).into();
|
#[settable]
|
||||||
|
#[default(Em::new(-0.5).into())]
|
||||||
|
pub baseline: Length,
|
||||||
|
|
||||||
/// The font size for synthetic superscripts. Does not apply if
|
/// The font size for synthetic superscripts. Does not apply if
|
||||||
/// `typographic` is true and the font has superscript codepoints for the
|
/// `typographic` is true and the font has superscript codepoints for the
|
||||||
/// given `body`.
|
/// given `body`.
|
||||||
pub const SIZE: TextSize = TextSize(Em::new(0.6).into());
|
#[settable]
|
||||||
|
#[default(TextSize(Em::new(0.6).into()))]
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
pub size: TextSize,
|
||||||
Ok(Self(args.expect("body")?).pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for SuperNode {
|
impl Show for SuperNode {
|
||||||
|
|
@ -131,9 +130,10 @@ impl Show for SuperNode {
|
||||||
_: &Content,
|
_: &Content,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Content> {
|
) -> SourceResult<Content> {
|
||||||
|
let body = self.body();
|
||||||
let mut transformed = None;
|
let mut transformed = None;
|
||||||
if styles.get(Self::TYPOGRAPHIC) {
|
if styles.get(Self::TYPOGRAPHIC) {
|
||||||
if let Some(text) = search_text(&self.0, false) {
|
if let Some(text) = search_text(&body, false) {
|
||||||
if is_shapable(vt, &text, styles) {
|
if is_shapable(vt, &text, styles) {
|
||||||
transformed = Some(TextNode::packed(text));
|
transformed = Some(TextNode::packed(text));
|
||||||
}
|
}
|
||||||
|
|
@ -144,7 +144,7 @@ impl Show for SuperNode {
|
||||||
let mut map = StyleMap::new();
|
let mut map = StyleMap::new();
|
||||||
map.set(TextNode::BASELINE, styles.get(Self::BASELINE));
|
map.set(TextNode::BASELINE, styles.get(Self::BASELINE));
|
||||||
map.set(TextNode::SIZE, styles.get(Self::SIZE));
|
map.set(TextNode::SIZE, styles.get(Self::SIZE));
|
||||||
self.0.clone().styled_with_map(map)
|
body.styled_with_map(map)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -154,12 +154,12 @@ impl Show for SuperNode {
|
||||||
fn search_text(content: &Content, sub: bool) -> Option<EcoString> {
|
fn search_text(content: &Content, sub: bool) -> Option<EcoString> {
|
||||||
if content.is::<SpaceNode>() {
|
if content.is::<SpaceNode>() {
|
||||||
Some(' '.into())
|
Some(' '.into())
|
||||||
} else if let Some(text) = content.to::<TextNode>() {
|
} else if let Some(node) = content.to::<TextNode>() {
|
||||||
convert_script(&text.0, sub)
|
convert_script(&node.text(), sub)
|
||||||
} else if let Some(seq) = content.to::<SequenceNode>() {
|
} else if let Some(seq) = content.to::<SequenceNode>() {
|
||||||
let mut full = EcoString::new();
|
let mut full = EcoString::new();
|
||||||
for item in seq.0.iter() {
|
for item in seq.children() {
|
||||||
match search_text(item, sub) {
|
match search_text(&item, sub) {
|
||||||
Some(text) => full.push_str(&text),
|
Some(text) => full.push_str(&text),
|
||||||
None => return None,
|
None => return None,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use typst::image::{Image, ImageFormat, RasterFormat, VectorFormat};
|
use typst::image::{Image, ImageFormat, RasterFormat, VectorFormat};
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// # Image
|
|
||||||
/// A raster or vector graphic.
|
/// A raster or vector graphic.
|
||||||
///
|
///
|
||||||
/// Supported formats are PNG, JPEG, GIF and SVG.
|
/// Supported formats are PNG, JPEG, GIF and SVG.
|
||||||
|
|
@ -18,62 +18,52 @@ use crate::prelude::*;
|
||||||
/// ]
|
/// ]
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Image
|
||||||
/// - path: `EcoString` (positional, required)
|
/// Category: visualize
|
||||||
/// Path to an image file.
|
#[node(Construct, Layout)]
|
||||||
///
|
|
||||||
/// - width: `Rel<Length>` (named)
|
|
||||||
/// The width of the image.
|
|
||||||
///
|
|
||||||
/// - height: `Rel<Length>` (named)
|
|
||||||
/// The height of the image.
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// visualize
|
|
||||||
#[func]
|
|
||||||
#[capable(Layout)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct ImageNode {
|
pub struct ImageNode {
|
||||||
pub image: Image,
|
/// Path to an image file.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
|
pub path: EcoString,
|
||||||
|
|
||||||
|
/// The width of the image.
|
||||||
|
#[named]
|
||||||
|
#[default]
|
||||||
pub width: Smart<Rel<Length>>,
|
pub width: Smart<Rel<Length>>,
|
||||||
|
|
||||||
|
/// The height of the image.
|
||||||
|
#[named]
|
||||||
|
#[default]
|
||||||
pub height: Smart<Rel<Length>>,
|
pub height: Smart<Rel<Length>>,
|
||||||
|
|
||||||
|
/// How the image should adjust itself to a given area.
|
||||||
|
#[settable]
|
||||||
|
#[default(ImageFit::Cover)]
|
||||||
|
pub fit: ImageFit,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node]
|
impl Construct for ImageNode {
|
||||||
impl ImageNode {
|
|
||||||
/// How the image should adjust itself to a given area.
|
|
||||||
pub const FIT: ImageFit = ImageFit::Cover;
|
|
||||||
|
|
||||||
fn construct(vm: &Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(vm: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
let Spanned { v: path, span } =
|
let Spanned { v: path, span } =
|
||||||
args.expect::<Spanned<EcoString>>("path to image file")?;
|
args.expect::<Spanned<EcoString>>("path to image file")?;
|
||||||
|
let path: EcoString = vm.locate(&path).at(span)?.to_string_lossy().into();
|
||||||
let full = vm.locate(&path).at(span)?;
|
let _ = load(vm.world(), &path).at(span)?;
|
||||||
let buffer = vm.world().file(&full).at(span)?;
|
let width = args.named::<Smart<Rel<Length>>>("width")?.unwrap_or_default();
|
||||||
let ext = full.extension().and_then(OsStr::to_str).unwrap_or_default();
|
let height = args.named::<Smart<Rel<Length>>>("height")?.unwrap_or_default();
|
||||||
let format = match ext.to_lowercase().as_str() {
|
Ok(ImageNode::new(path).with_width(width).with_height(height).pack())
|
||||||
"png" => ImageFormat::Raster(RasterFormat::Png),
|
|
||||||
"jpg" | "jpeg" => ImageFormat::Raster(RasterFormat::Jpg),
|
|
||||||
"gif" => ImageFormat::Raster(RasterFormat::Gif),
|
|
||||||
"svg" | "svgz" => ImageFormat::Vector(VectorFormat::Svg),
|
|
||||||
_ => bail!(span, "unknown image format"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let image = Image::new(buffer, format).at(span)?;
|
|
||||||
let width = args.named("width")?.unwrap_or_default();
|
|
||||||
let height = args.named("height")?.unwrap_or_default();
|
|
||||||
Ok(ImageNode { image, width, height }.pack())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for ImageNode {
|
impl Layout for ImageNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
_: &mut Vt,
|
vt: &mut Vt,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
let sizing = Axes::new(self.width, self.height);
|
let image = load(vt.world(), &self.path()).unwrap();
|
||||||
|
let sizing = Axes::new(self.width(), self.height());
|
||||||
let region = sizing
|
let region = sizing
|
||||||
.zip(regions.base())
|
.zip(regions.base())
|
||||||
.map(|(s, r)| s.map(|v| v.resolve(styles).relative_to(r)))
|
.map(|(s, r)| s.map(|v| v.resolve(styles).relative_to(r)))
|
||||||
|
|
@ -83,8 +73,8 @@ impl Layout for ImageNode {
|
||||||
let region_ratio = region.x / region.y;
|
let region_ratio = region.x / region.y;
|
||||||
|
|
||||||
// Find out whether the image is wider or taller than the target size.
|
// Find out whether the image is wider or taller than the target size.
|
||||||
let pxw = self.image.width() as f64;
|
let pxw = image.width() as f64;
|
||||||
let pxh = self.image.height() as f64;
|
let pxh = image.height() as f64;
|
||||||
let px_ratio = pxw / pxh;
|
let px_ratio = pxw / pxh;
|
||||||
let wide = px_ratio > region_ratio;
|
let wide = px_ratio > region_ratio;
|
||||||
|
|
||||||
|
|
@ -116,7 +106,7 @@ impl Layout for ImageNode {
|
||||||
// the frame to the target size, center aligning the image in the
|
// the frame to the target size, center aligning the image in the
|
||||||
// process.
|
// process.
|
||||||
let mut frame = Frame::new(fitted);
|
let mut frame = Frame::new(fitted);
|
||||||
frame.push(Point::zero(), Element::Image(self.image.clone(), fitted));
|
frame.push(Point::zero(), Element::Image(image, fitted));
|
||||||
frame.resize(target, Align::CENTER_HORIZON);
|
frame.resize(target, Align::CENTER_HORIZON);
|
||||||
|
|
||||||
// Create a clipping group if only part of the image should be visible.
|
// Create a clipping group if only part of the image should be visible.
|
||||||
|
|
@ -142,7 +132,7 @@ pub enum ImageFit {
|
||||||
Stretch,
|
Stretch,
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
ImageFit,
|
ImageFit,
|
||||||
/// The image should completely cover the area. This is the default.
|
/// The image should completely cover the area. This is the default.
|
||||||
"cover" => Self::Cover,
|
"cover" => Self::Cover,
|
||||||
|
|
@ -152,3 +142,27 @@ castable! {
|
||||||
/// this means that the image will be distorted.
|
/// this means that the image will be distorted.
|
||||||
"stretch" => Self::Stretch,
|
"stretch" => Self::Stretch,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
fit: ImageFit => Value::from(match fit {
|
||||||
|
ImageFit::Cover => "cover",
|
||||||
|
ImageFit::Contain => "contain",
|
||||||
|
ImageFit::Stretch => "stretch",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load an image from a path.
|
||||||
|
#[comemo::memoize]
|
||||||
|
fn load(world: Tracked<dyn World>, full: &str) -> StrResult<Image> {
|
||||||
|
let full = Path::new(full);
|
||||||
|
let buffer = world.file(full)?;
|
||||||
|
let ext = full.extension().and_then(OsStr::to_str).unwrap_or_default();
|
||||||
|
let format = match ext.to_lowercase().as_str() {
|
||||||
|
"png" => ImageFormat::Raster(RasterFormat::Png),
|
||||||
|
"jpg" | "jpeg" => ImageFormat::Raster(RasterFormat::Jpg),
|
||||||
|
"gif" => ImageFormat::Raster(RasterFormat::Gif),
|
||||||
|
"svg" | "svgz" => ImageFormat::Vector(VectorFormat::Svg),
|
||||||
|
_ => return Err("unknown image format".into()),
|
||||||
|
};
|
||||||
|
Image::new(buffer, format)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// # Line
|
|
||||||
/// A line from one point to another.
|
/// A line from one point to another.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -26,20 +25,20 @@ use crate::prelude::*;
|
||||||
/// The angle at which the line points away from the origin. Mutually
|
/// The angle at which the line points away from the origin. Mutually
|
||||||
/// exclusive with `end`.
|
/// exclusive with `end`.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Line
|
||||||
/// visualize
|
/// Category: visualize
|
||||||
#[func]
|
#[node(Construct, Layout)]
|
||||||
#[capable(Layout)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct LineNode {
|
pub struct LineNode {
|
||||||
/// Where the line starts.
|
/// Where the line starts.
|
||||||
|
#[named]
|
||||||
|
#[default]
|
||||||
pub start: Axes<Rel<Length>>,
|
pub start: Axes<Rel<Length>>,
|
||||||
/// The offset from `start` where the line ends.
|
|
||||||
pub delta: Axes<Rel<Length>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
/// The offset from `start` where the line ends.
|
||||||
impl LineNode {
|
#[named]
|
||||||
|
#[default]
|
||||||
|
pub delta: Axes<Rel<Length>>,
|
||||||
|
|
||||||
/// How to stroke the line. This can be:
|
/// How to stroke the line. This can be:
|
||||||
///
|
///
|
||||||
/// - A length specifying the stroke's thickness. The color is inherited,
|
/// - A length specifying the stroke's thickness. The color is inherited,
|
||||||
|
|
@ -52,12 +51,16 @@ impl LineNode {
|
||||||
/// ```example
|
/// ```example
|
||||||
/// #line(length: 100%, stroke: 2pt + red)
|
/// #line(length: 100%, stroke: 2pt + red)
|
||||||
/// ```
|
/// ```
|
||||||
#[property(resolve, fold)]
|
#[settable]
|
||||||
pub const STROKE: PartialStroke = PartialStroke::default();
|
#[resolve]
|
||||||
|
#[fold]
|
||||||
|
#[default]
|
||||||
|
pub stroke: PartialStroke,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Construct for LineNode {
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
let start = args.named("start")?.unwrap_or_default();
|
let start = args.named("start")?.unwrap_or_default();
|
||||||
|
|
||||||
let delta = match args.named::<Axes<Rel<Length>>>("end")? {
|
let delta = match args.named::<Axes<Rel<Length>>>("end")? {
|
||||||
Some(end) => end.zip(start).map(|(to, from)| to - from),
|
Some(end) => end.zip(start).map(|(to, from)| to - from),
|
||||||
None => {
|
None => {
|
||||||
|
|
@ -71,8 +74,7 @@ impl LineNode {
|
||||||
Axes::new(x, y)
|
Axes::new(x, y)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Ok(Self::new().with_start(start).with_delta(delta).pack())
|
||||||
Ok(Self { start, delta }.pack())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,13 +88,13 @@ impl Layout for LineNode {
|
||||||
let stroke = styles.get(Self::STROKE).unwrap_or_default();
|
let stroke = styles.get(Self::STROKE).unwrap_or_default();
|
||||||
|
|
||||||
let origin = self
|
let origin = self
|
||||||
.start
|
.start()
|
||||||
.resolve(styles)
|
.resolve(styles)
|
||||||
.zip(regions.base())
|
.zip(regions.base())
|
||||||
.map(|(l, b)| l.relative_to(b));
|
.map(|(l, b)| l.relative_to(b));
|
||||||
|
|
||||||
let delta = self
|
let delta = self
|
||||||
.delta
|
.delta()
|
||||||
.resolve(styles)
|
.resolve(styles)
|
||||||
.zip(regions.base())
|
.zip(regions.base())
|
||||||
.map(|(l, b)| l.relative_to(b));
|
.map(|(l, b)| l.relative_to(b));
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ use std::f64::consts::SQRT_2;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// # Rectangle
|
|
||||||
/// A rectangle with optional content.
|
/// A rectangle with optional content.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -17,32 +16,28 @@ use crate::prelude::*;
|
||||||
/// ]
|
/// ]
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Rectangle
|
||||||
/// - body: `Content` (positional)
|
/// Category: visualize
|
||||||
/// The content to place into the rectangle.
|
#[node(Layout)]
|
||||||
///
|
|
||||||
/// When this is omitted, the rectangle takes on a default size of at most
|
|
||||||
/// `{45pt}` by `{30pt}`.
|
|
||||||
///
|
|
||||||
/// - width: `Rel<Length>` (named)
|
|
||||||
/// The rectangle's width, relative to its parent container.
|
|
||||||
///
|
|
||||||
/// - height: `Rel<Length>` (named)
|
|
||||||
/// The rectangle's height, relative to its parent container.
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// visualize
|
|
||||||
#[func]
|
|
||||||
#[capable(Layout)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct RectNode {
|
pub struct RectNode {
|
||||||
|
/// The content to place into the rectangle.
|
||||||
|
///
|
||||||
|
/// When this is omitted, the rectangle takes on a default size of at most
|
||||||
|
/// `{45pt}` by `{30pt}`.
|
||||||
|
#[positional]
|
||||||
|
#[default]
|
||||||
pub body: Option<Content>,
|
pub body: Option<Content>,
|
||||||
pub width: Smart<Rel<Length>>,
|
|
||||||
pub height: Smart<Rel<Length>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
/// The rectangle's width, relative to its parent container.
|
||||||
impl RectNode {
|
#[named]
|
||||||
|
#[default]
|
||||||
|
pub width: Smart<Rel<Length>>,
|
||||||
|
|
||||||
|
/// The rectangle's height, relative to its parent container.
|
||||||
|
#[named]
|
||||||
|
#[default]
|
||||||
|
pub height: Smart<Rel<Length>>,
|
||||||
|
|
||||||
/// How to fill the rectangle.
|
/// How to fill the rectangle.
|
||||||
///
|
///
|
||||||
/// When setting a fill, the default stroke disappears. To create a
|
/// When setting a fill, the default stroke disappears. To create a
|
||||||
|
|
@ -51,7 +46,9 @@ impl RectNode {
|
||||||
/// ```example
|
/// ```example
|
||||||
/// #rect(fill: blue)
|
/// #rect(fill: blue)
|
||||||
/// ```
|
/// ```
|
||||||
pub const FILL: Option<Paint> = None;
|
#[settable]
|
||||||
|
#[default]
|
||||||
|
pub fill: Option<Paint>,
|
||||||
|
|
||||||
/// How to stroke the rectangle. This can be:
|
/// How to stroke the rectangle. This can be:
|
||||||
///
|
///
|
||||||
|
|
@ -85,8 +82,11 @@ impl RectNode {
|
||||||
/// rect(stroke: 2pt + red),
|
/// rect(stroke: 2pt + red),
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[property(resolve, fold)]
|
#[settable]
|
||||||
pub const STROKE: Smart<Sides<Option<Option<PartialStroke>>>> = Smart::Auto;
|
#[resolve]
|
||||||
|
#[fold]
|
||||||
|
#[default]
|
||||||
|
pub stroke: Smart<Sides<Option<Option<PartialStroke>>>>,
|
||||||
|
|
||||||
/// How much to round the rectangle's corners, relative to the minimum of
|
/// How much to round the rectangle's corners, relative to the minimum of
|
||||||
/// the width and height divided by two. This can be:
|
/// the width and height divided by two. This can be:
|
||||||
|
|
@ -122,8 +122,11 @@ impl RectNode {
|
||||||
/// ),
|
/// ),
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[property(resolve, fold)]
|
#[settable]
|
||||||
pub const RADIUS: Corners<Option<Rel<Length>>> = Corners::splat(Rel::zero());
|
#[resolve]
|
||||||
|
#[fold]
|
||||||
|
#[default]
|
||||||
|
pub radius: Corners<Option<Rel<Length>>>,
|
||||||
|
|
||||||
/// How much to pad the rectangle's content.
|
/// How much to pad the rectangle's content.
|
||||||
///
|
///
|
||||||
|
|
@ -135,20 +138,19 @@ impl RectNode {
|
||||||
/// ```example
|
/// ```example
|
||||||
/// #rect(inset: 0pt)[Tight])
|
/// #rect(inset: 0pt)[Tight])
|
||||||
/// ```
|
/// ```
|
||||||
#[property(resolve, fold)]
|
#[settable]
|
||||||
pub const INSET: Sides<Option<Rel<Length>>> = Sides::splat(Abs::pt(5.0).into());
|
#[resolve]
|
||||||
|
#[fold]
|
||||||
|
#[default(Sides::splat(Abs::pt(5.0).into()))]
|
||||||
|
pub inset: Sides<Option<Rel<Length>>>,
|
||||||
|
|
||||||
/// How much to expand the rectangle's size without affecting the layout.
|
/// How much to expand the rectangle's size without affecting the layout.
|
||||||
/// See the [box's documentation]($func/box.outset) for more details.
|
/// See the [box's documentation]($func/box.outset) for more details.
|
||||||
#[property(resolve, fold)]
|
#[settable]
|
||||||
pub const OUTSET: Sides<Option<Rel<Length>>> = Sides::splat(Rel::zero());
|
#[resolve]
|
||||||
|
#[fold]
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
#[default]
|
||||||
let width = args.named("width")?.unwrap_or_default();
|
pub outset: Sides<Option<Rel<Length>>>,
|
||||||
let height = args.named("height")?.unwrap_or_default();
|
|
||||||
let body = args.eat()?;
|
|
||||||
Ok(Self { body, width, height }.pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for RectNode {
|
impl Layout for RectNode {
|
||||||
|
|
@ -163,8 +165,8 @@ impl Layout for RectNode {
|
||||||
styles,
|
styles,
|
||||||
regions,
|
regions,
|
||||||
ShapeKind::Rect,
|
ShapeKind::Rect,
|
||||||
&self.body,
|
&self.body(),
|
||||||
Axes::new(self.width, self.height),
|
Axes::new(self.width(), self.height()),
|
||||||
styles.get(Self::FILL),
|
styles.get(Self::FILL),
|
||||||
styles.get(Self::STROKE),
|
styles.get(Self::STROKE),
|
||||||
styles.get(Self::INSET),
|
styles.get(Self::INSET),
|
||||||
|
|
@ -174,7 +176,6 @@ impl Layout for RectNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Square
|
|
||||||
/// A square with optional content.
|
/// A square with optional content.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -189,69 +190,77 @@ impl Layout for RectNode {
|
||||||
/// ]
|
/// ]
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Square
|
||||||
/// - body: `Content` (positional)
|
/// Category: visualize
|
||||||
/// The content to place into the square. The square expands to fit this
|
#[node(Construct, Layout)]
|
||||||
/// content, keeping the 1-1 aspect ratio.
|
|
||||||
///
|
|
||||||
/// When this is omitted, the square takes on a default size of at most
|
|
||||||
/// `{30pt}`.
|
|
||||||
///
|
|
||||||
/// - size: `Length` (named)
|
|
||||||
/// The square's side length. This is mutually exclusive with `width` and
|
|
||||||
/// `height`.
|
|
||||||
///
|
|
||||||
/// - width: `Rel<Length>` (named)
|
|
||||||
/// The square's width. This is mutually exclusive with `size` and `height`.
|
|
||||||
///
|
|
||||||
/// In contrast to `size`, this can be relative to the parent container's
|
|
||||||
/// width.
|
|
||||||
///
|
|
||||||
/// - height: `Rel<Length>` (named)
|
|
||||||
/// The square's height. This is mutually exclusive with `size` and `width`.
|
|
||||||
///
|
|
||||||
/// In contrast to `size`, this can be relative to the parent container's
|
|
||||||
/// height.
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// visualize
|
|
||||||
#[func]
|
|
||||||
#[capable(Layout)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct SquareNode {
|
pub struct SquareNode {
|
||||||
|
/// The content to place into the square. The square expands to fit this
|
||||||
|
/// content, keeping the 1-1 aspect ratio.
|
||||||
|
///
|
||||||
|
/// When this is omitted, the square takes on a default size of at most
|
||||||
|
/// `{30pt}`.
|
||||||
|
#[positional]
|
||||||
|
#[default]
|
||||||
pub body: Option<Content>,
|
pub body: Option<Content>,
|
||||||
pub width: Smart<Rel<Length>>,
|
|
||||||
pub height: Smart<Rel<Length>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
/// The square's width. This is mutually exclusive with `size` and `height`.
|
||||||
impl SquareNode {
|
///
|
||||||
|
/// In contrast to `size`, this can be relative to the parent container's
|
||||||
|
/// width.
|
||||||
|
#[named]
|
||||||
|
#[default]
|
||||||
|
pub width: Smart<Rel<Length>>,
|
||||||
|
|
||||||
|
/// The square's height. This is mutually exclusive with `size` and `width`.
|
||||||
|
///
|
||||||
|
/// In contrast to `size`, this can be relative to the parent container's
|
||||||
|
/// height.
|
||||||
|
#[named]
|
||||||
|
#[default]
|
||||||
|
pub height: Smart<Rel<Length>>,
|
||||||
|
|
||||||
/// How to fill the square. See the
|
/// How to fill the square. See the
|
||||||
/// [rectangle's documentation]($func/rect.fill) for more details.
|
/// [rectangle's documentation]($func/rect.fill) for more details.
|
||||||
pub const FILL: Option<Paint> = None;
|
#[settable]
|
||||||
|
#[default]
|
||||||
|
pub fill: Option<Paint>,
|
||||||
|
|
||||||
/// How to stroke the square. See the [rectangle's
|
/// How to stroke the square. See the [rectangle's
|
||||||
/// documentation]($func/rect.stroke) for more details.
|
/// documentation]($func/rect.stroke) for more details.
|
||||||
#[property(resolve, fold)]
|
#[settable]
|
||||||
pub const STROKE: Smart<Sides<Option<Option<PartialStroke>>>> = Smart::Auto;
|
#[resolve]
|
||||||
|
#[fold]
|
||||||
|
#[default]
|
||||||
|
pub stroke: Smart<Sides<Option<Option<PartialStroke>>>>,
|
||||||
|
|
||||||
/// How much to round the square's corners. See the [rectangle's
|
/// How much to round the square's corners. See the [rectangle's
|
||||||
/// documentation]($func/rect.radius) for more details.
|
/// documentation]($func/rect.radius) for more details.
|
||||||
#[property(resolve, fold)]
|
#[settable]
|
||||||
pub const RADIUS: Corners<Option<Rel<Length>>> = Corners::splat(Rel::zero());
|
#[resolve]
|
||||||
|
#[fold]
|
||||||
|
#[default]
|
||||||
|
pub radius: Corners<Option<Rel<Length>>>,
|
||||||
|
|
||||||
/// How much to pad the square's content. See the [rectangle's
|
/// How much to pad the square's content. See the [rectangle's
|
||||||
/// documentation]($func/rect.inset) for more details.
|
/// documentation]($func/rect.inset) for more details.
|
||||||
///
|
///
|
||||||
/// The default value is `{5pt}`.
|
/// The default value is `{5pt}`.
|
||||||
#[property(resolve, fold)]
|
#[settable]
|
||||||
pub const INSET: Sides<Option<Rel<Length>>> = Sides::splat(Abs::pt(5.0).into());
|
#[resolve]
|
||||||
|
#[fold]
|
||||||
|
#[default(Sides::splat(Abs::pt(5.0).into()))]
|
||||||
|
pub inset: Sides<Option<Rel<Length>>>,
|
||||||
|
|
||||||
/// How much to expand the square's size without affecting the layout. See
|
/// How much to expand the square's size without affecting the layout. See
|
||||||
/// the [rectangle's documentation]($func/rect.outset) for more details.
|
/// the [rectangle's documentation]($func/rect.outset) for more details.
|
||||||
#[property(resolve, fold)]
|
#[settable]
|
||||||
pub const OUTSET: Sides<Option<Rel<Length>>> = Sides::splat(Rel::zero());
|
#[resolve]
|
||||||
|
#[fold]
|
||||||
|
#[default]
|
||||||
|
pub outset: Sides<Option<Rel<Length>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Construct for SquareNode {
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
let size = args.named::<Smart<Length>>("size")?.map(|s| s.map(Rel::from));
|
let size = args.named::<Smart<Length>>("size")?.map(|s| s.map(Rel::from));
|
||||||
let width = match size {
|
let width = match size {
|
||||||
|
|
@ -264,8 +273,12 @@ impl SquareNode {
|
||||||
size => size,
|
size => size,
|
||||||
}
|
}
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let body = args.eat()?;
|
let body = args.eat::<Content>()?;
|
||||||
Ok(Self { body, width, height }.pack())
|
Ok(Self::new()
|
||||||
|
.with_body(body)
|
||||||
|
.with_width(width)
|
||||||
|
.with_height(height)
|
||||||
|
.pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -281,8 +294,8 @@ impl Layout for SquareNode {
|
||||||
styles,
|
styles,
|
||||||
regions,
|
regions,
|
||||||
ShapeKind::Square,
|
ShapeKind::Square,
|
||||||
&self.body,
|
&self.body(),
|
||||||
Axes::new(self.width, self.height),
|
Axes::new(self.width(), self.height()),
|
||||||
styles.get(Self::FILL),
|
styles.get(Self::FILL),
|
||||||
styles.get(Self::STROKE),
|
styles.get(Self::STROKE),
|
||||||
styles.get(Self::INSET),
|
styles.get(Self::INSET),
|
||||||
|
|
@ -292,7 +305,6 @@ impl Layout for SquareNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Ellipse
|
|
||||||
/// An ellipse with optional content.
|
/// An ellipse with optional content.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -308,59 +320,59 @@ impl Layout for SquareNode {
|
||||||
/// ]
|
/// ]
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// Display: Ellipse
|
||||||
/// - body: `Content` (positional)
|
/// Category: visualize
|
||||||
/// The content to place into the ellipse.
|
#[node(Layout)]
|
||||||
///
|
|
||||||
/// When this is omitted, the ellipse takes on a default size of at most
|
|
||||||
/// `{45pt}` by `{30pt}`.
|
|
||||||
///
|
|
||||||
/// - width: `Rel<Length>` (named)
|
|
||||||
/// The ellipse's width, relative to its parent container.
|
|
||||||
///
|
|
||||||
/// - height: `Rel<Length>` (named)
|
|
||||||
/// The ellipse's height, relative to its parent container.
|
|
||||||
///
|
|
||||||
/// ## Category
|
|
||||||
/// visualize
|
|
||||||
#[func]
|
|
||||||
#[capable(Layout)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct EllipseNode {
|
pub struct EllipseNode {
|
||||||
|
/// The content to place into the ellipse.
|
||||||
|
///
|
||||||
|
/// When this is omitted, the ellipse takes on a default size of at most
|
||||||
|
/// `{45pt}` by `{30pt}`.
|
||||||
|
#[positional]
|
||||||
|
#[default]
|
||||||
pub body: Option<Content>,
|
pub body: Option<Content>,
|
||||||
pub width: Smart<Rel<Length>>,
|
|
||||||
pub height: Smart<Rel<Length>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
/// The ellipse's width, relative to its parent container.
|
||||||
impl EllipseNode {
|
#[named]
|
||||||
|
#[default]
|
||||||
|
pub width: Smart<Rel<Length>>,
|
||||||
|
|
||||||
|
/// The ellipse's height, relative to its parent container.
|
||||||
|
#[named]
|
||||||
|
#[default]
|
||||||
|
pub height: Smart<Rel<Length>>,
|
||||||
|
|
||||||
/// How to fill the ellipse. See the
|
/// How to fill the ellipse. See the
|
||||||
/// [rectangle's documentation]($func/rect.fill) for more details.
|
/// [rectangle's documentation]($func/rect.fill) for more details.
|
||||||
pub const FILL: Option<Paint> = None;
|
#[settable]
|
||||||
|
#[default]
|
||||||
|
pub fill: Option<Paint>,
|
||||||
|
|
||||||
/// How to stroke the ellipse. See the [rectangle's
|
/// How to stroke the ellipse. See the [rectangle's
|
||||||
/// documentation]($func/rect.stroke) for more details.
|
/// documentation]($func/rect.stroke) for more details.
|
||||||
#[property(resolve, fold)]
|
#[settable]
|
||||||
pub const STROKE: Smart<Option<PartialStroke>> = Smart::Auto;
|
#[resolve]
|
||||||
|
#[fold]
|
||||||
|
#[default]
|
||||||
|
pub stroke: Smart<Option<PartialStroke>>,
|
||||||
|
|
||||||
/// How much to pad the ellipse's content. See the [rectangle's
|
/// How much to pad the ellipse's content. See the [rectangle's
|
||||||
/// documentation]($func/rect.inset) for more details.
|
/// documentation]($func/rect.inset) for more details.
|
||||||
///
|
///
|
||||||
/// The default value is `{5pt}`.
|
/// The default value is `{5pt}`.
|
||||||
#[property(resolve, fold)]
|
#[settable]
|
||||||
pub const INSET: Sides<Option<Rel<Length>>> = Sides::splat(Abs::pt(5.0).into());
|
#[resolve]
|
||||||
|
#[fold]
|
||||||
|
#[default(Sides::splat(Abs::pt(5.0).into()))]
|
||||||
|
pub inset: Sides<Option<Rel<Length>>>,
|
||||||
|
|
||||||
/// How much to expand the ellipse's size without affecting the layout. See
|
/// How much to expand the ellipse's size without affecting the layout. See
|
||||||
/// the [rectangle's documentation]($func/rect.outset) for more details.
|
/// the [rectangle's documentation]($func/rect.outset) for more details.
|
||||||
#[property(resolve, fold)]
|
#[settable]
|
||||||
pub const OUTSET: Sides<Option<Rel<Length>>> = Sides::splat(Rel::zero());
|
#[resolve]
|
||||||
|
#[fold]
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
#[default]
|
||||||
let width = args.named("width")?.unwrap_or_default();
|
pub outset: Sides<Option<Rel<Length>>>,
|
||||||
let height = args.named("height")?.unwrap_or_default();
|
|
||||||
let body = args.eat()?;
|
|
||||||
Ok(Self { body, width, height }.pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for EllipseNode {
|
impl Layout for EllipseNode {
|
||||||
|
|
@ -375,8 +387,8 @@ impl Layout for EllipseNode {
|
||||||
styles,
|
styles,
|
||||||
regions,
|
regions,
|
||||||
ShapeKind::Ellipse,
|
ShapeKind::Ellipse,
|
||||||
&self.body,
|
&self.body(),
|
||||||
Axes::new(self.width, self.height),
|
Axes::new(self.width(), self.height()),
|
||||||
styles.get(Self::FILL),
|
styles.get(Self::FILL),
|
||||||
styles.get(Self::STROKE).map(Sides::splat),
|
styles.get(Self::STROKE).map(Sides::splat),
|
||||||
styles.get(Self::INSET),
|
styles.get(Self::INSET),
|
||||||
|
|
@ -386,7 +398,6 @@ impl Layout for EllipseNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Circle
|
|
||||||
/// A circle with optional content.
|
/// A circle with optional content.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
|
|
@ -423,40 +434,68 @@ impl Layout for EllipseNode {
|
||||||
/// In contrast to `size`, this can be relative to the parent container's
|
/// In contrast to `size`, this can be relative to the parent container's
|
||||||
/// height.
|
/// height.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// Display: Circle
|
||||||
/// visualize
|
/// Category: visualize
|
||||||
#[func]
|
#[node(Construct, Layout)]
|
||||||
#[capable(Layout)]
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct CircleNode {
|
pub struct CircleNode {
|
||||||
|
/// The content to place into the circle. The circle expands to fit this
|
||||||
|
/// content, keeping the 1-1 aspect ratio.
|
||||||
|
#[positional]
|
||||||
|
#[default]
|
||||||
pub body: Option<Content>,
|
pub body: Option<Content>,
|
||||||
pub width: Smart<Rel<Length>>,
|
|
||||||
pub height: Smart<Rel<Length>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node]
|
/// The circle's width. This is mutually exclusive with `radius` and
|
||||||
impl CircleNode {
|
/// `height`.
|
||||||
|
///
|
||||||
|
/// In contrast to `size`, this can be relative to the parent container's
|
||||||
|
/// width.
|
||||||
|
#[named]
|
||||||
|
#[default]
|
||||||
|
pub width: Smart<Rel<Length>>,
|
||||||
|
|
||||||
|
/// The circle's height.This is mutually exclusive with `radius` and
|
||||||
|
/// `width`.
|
||||||
|
///
|
||||||
|
/// In contrast to `size`, this can be relative to the parent container's
|
||||||
|
/// height.
|
||||||
|
#[named]
|
||||||
|
#[default]
|
||||||
|
pub height: Smart<Rel<Length>>,
|
||||||
|
|
||||||
/// How to fill the circle. See the
|
/// How to fill the circle. See the
|
||||||
/// [rectangle's documentation]($func/rect.fill) for more details.
|
/// [rectangle's documentation]($func/rect.fill) for more details.
|
||||||
pub const FILL: Option<Paint> = None;
|
#[settable]
|
||||||
|
#[default]
|
||||||
|
pub fill: Option<Paint>,
|
||||||
|
|
||||||
/// How to stroke the circle. See the [rectangle's
|
/// How to stroke the circle. See the [rectangle's
|
||||||
/// documentation]($func/rect.stroke) for more details.
|
/// documentation]($func/rect.stroke) for more details.
|
||||||
#[property(resolve, fold)]
|
#[settable]
|
||||||
pub const STROKE: Smart<Option<PartialStroke>> = Smart::Auto;
|
#[resolve]
|
||||||
|
#[fold]
|
||||||
|
#[default(Smart::Auto)]
|
||||||
|
pub stroke: Smart<Option<PartialStroke>>,
|
||||||
|
|
||||||
/// How much to pad the circle's content. See the [rectangle's
|
/// How much to pad the circle's content. See the [rectangle's
|
||||||
/// documentation]($func/rect.inset) for more details.
|
/// documentation]($func/rect.inset) for more details.
|
||||||
///
|
///
|
||||||
/// The default value is `{5pt}`.
|
/// The default value is `{5pt}`.
|
||||||
#[property(resolve, fold)]
|
#[settable]
|
||||||
pub const INSET: Sides<Option<Rel<Length>>> = Sides::splat(Abs::pt(5.0).into());
|
#[resolve]
|
||||||
|
#[fold]
|
||||||
|
#[default(Sides::splat(Abs::pt(5.0).into()))]
|
||||||
|
pub inset: Sides<Option<Rel<Length>>>,
|
||||||
|
|
||||||
/// How much to expand the circle's size without affecting the layout. See
|
/// How much to expand the circle's size without affecting the layout. See
|
||||||
/// the [rectangle's documentation]($func/rect.outset) for more details.
|
/// the [rectangle's documentation]($func/rect.outset) for more details.
|
||||||
#[property(resolve, fold)]
|
#[settable]
|
||||||
pub const OUTSET: Sides<Option<Rel<Length>>> = Sides::splat(Rel::zero());
|
#[resolve]
|
||||||
|
#[fold]
|
||||||
|
#[default]
|
||||||
|
pub outset: Sides<Option<Rel<Length>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Construct for CircleNode {
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
let size = args
|
let size = args
|
||||||
.named::<Smart<Length>>("radius")?
|
.named::<Smart<Length>>("radius")?
|
||||||
|
|
@ -471,8 +510,12 @@ impl CircleNode {
|
||||||
size => size,
|
size => size,
|
||||||
}
|
}
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let body = args.eat()?;
|
let body = args.eat::<Content>()?;
|
||||||
Ok(Self { body, width, height }.pack())
|
Ok(Self::new()
|
||||||
|
.with_body(body)
|
||||||
|
.with_width(width)
|
||||||
|
.with_height(height)
|
||||||
|
.pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -488,8 +531,8 @@ impl Layout for CircleNode {
|
||||||
styles,
|
styles,
|
||||||
regions,
|
regions,
|
||||||
ShapeKind::Circle,
|
ShapeKind::Circle,
|
||||||
&self.body,
|
&self.body(),
|
||||||
Axes::new(self.width, self.height),
|
Axes::new(self.width(), self.height()),
|
||||||
styles.get(Self::FILL),
|
styles.get(Self::FILL),
|
||||||
styles.get(Self::STROKE).map(Sides::splat),
|
styles.get(Self::STROKE).map(Sides::splat),
|
||||||
styles.get(Self::INSET),
|
styles.get(Self::INSET),
|
||||||
|
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
use syn::parse::Parser;
|
|
||||||
use syn::punctuated::Punctuated;
|
|
||||||
use syn::Token;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
/// Expand the `#[capability]` macro.
|
|
||||||
pub fn capability(item: syn::ItemTrait) -> Result<TokenStream> {
|
|
||||||
let ident = &item.ident;
|
|
||||||
Ok(quote! {
|
|
||||||
#item
|
|
||||||
impl ::typst::model::Capability for dyn #ident {}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Expand the `#[capable(..)]` macro.
|
|
||||||
pub fn capable(attr: TokenStream, item: syn::Item) -> Result<TokenStream> {
|
|
||||||
let (ident, generics) = match &item {
|
|
||||||
syn::Item::Struct(s) => (&s.ident, &s.generics),
|
|
||||||
syn::Item::Enum(s) => (&s.ident, &s.generics),
|
|
||||||
_ => bail!(item, "only structs and enums are supported"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let (params, args, clause) = generics.split_for_impl();
|
|
||||||
let checks = Punctuated::<Ident, Token![,]>::parse_terminated
|
|
||||||
.parse2(attr)?
|
|
||||||
.into_iter()
|
|
||||||
.map(|capability| {
|
|
||||||
quote! {
|
|
||||||
if id == ::std::any::TypeId::of::<dyn #capability>() {
|
|
||||||
return Some(unsafe {
|
|
||||||
::typst::util::fat::vtable(self as &dyn #capability)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(quote! {
|
|
||||||
#item
|
|
||||||
|
|
||||||
unsafe impl #params ::typst::model::Capable for #ident #args #clause {
|
|
||||||
fn vtable(&self, id: ::std::any::TypeId) -> ::std::option::Option<*const ()> {
|
|
||||||
#(#checks)*
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
use syn::parse::{Parse, ParseStream};
|
|
||||||
use syn::punctuated::Punctuated;
|
|
||||||
use syn::Token;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Expand the `castable!` macro.
|
/// Expand the `cast_from_value!` macro.
|
||||||
pub fn castable(stream: TokenStream) -> Result<TokenStream> {
|
pub fn cast_from_value(stream: TokenStream) -> Result<TokenStream> {
|
||||||
let castable: Castable = syn::parse2(stream)?;
|
let castable: Castable = syn::parse2(stream)?;
|
||||||
let ty = &castable.ty;
|
let ty = &castable.ty;
|
||||||
|
|
||||||
|
|
@ -41,6 +37,77 @@ pub fn castable(stream: TokenStream) -> Result<TokenStream> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Expand the `cast_to_value!` macro.
|
||||||
|
pub fn cast_to_value(stream: TokenStream) -> Result<TokenStream> {
|
||||||
|
let cast: Cast = syn::parse2(stream)?;
|
||||||
|
let Pattern::Ty(pat, ty) = &cast.pattern else {
|
||||||
|
bail!(callsite, "expected pattern");
|
||||||
|
};
|
||||||
|
|
||||||
|
let expr = &cast.expr;
|
||||||
|
Ok(quote! {
|
||||||
|
impl ::std::convert::From<#ty> for ::typst::eval::Value {
|
||||||
|
fn from(#pat: #ty) -> Self {
|
||||||
|
#expr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Castable {
|
||||||
|
ty: syn::Type,
|
||||||
|
name: Option<syn::LitStr>,
|
||||||
|
casts: Punctuated<Cast, Token![,]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Cast {
|
||||||
|
attrs: Vec<syn::Attribute>,
|
||||||
|
pattern: Pattern,
|
||||||
|
expr: syn::Expr,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Pattern {
|
||||||
|
Str(syn::LitStr),
|
||||||
|
Ty(syn::Pat, syn::Type),
|
||||||
|
}
|
||||||
|
|
||||||
|
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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Create the castable's `is` function.
|
/// Create the castable's `is` function.
|
||||||
fn create_is_func(castable: &Castable) -> TokenStream {
|
fn create_is_func(castable: &Castable) -> TokenStream {
|
||||||
let mut string_arms = vec![];
|
let mut string_arms = vec![];
|
||||||
|
|
@ -163,7 +230,7 @@ fn create_describe_func(castable: &Castable) -> TokenStream {
|
||||||
|
|
||||||
if let Some(name) = &castable.name {
|
if let Some(name) = &castable.name {
|
||||||
infos.push(quote! {
|
infos.push(quote! {
|
||||||
CastInfo::Type(#name)
|
::typst::eval::CastInfo::Type(#name)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,57 +240,3 @@ fn create_describe_func(castable: &Castable) -> TokenStream {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,22 @@
|
||||||
use unscanny::Scanner;
|
|
||||||
|
|
||||||
use super::*;
|
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 docs = match &item {
|
let mut docs = match &item {
|
||||||
syn::Item::Struct(item) => documentation(&item.attrs),
|
syn::Item::Struct(item) => documentation(&item.attrs),
|
||||||
syn::Item::Enum(item) => documentation(&item.attrs),
|
syn::Item::Enum(item) => documentation(&item.attrs),
|
||||||
syn::Item::Fn(item) => documentation(&item.attrs),
|
syn::Item::Fn(item) => documentation(&item.attrs),
|
||||||
_ => String::new(),
|
_ => String::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let first = docs.lines().next().unwrap();
|
|
||||||
let display = first.strip_prefix("# ").unwrap();
|
|
||||||
let display = display.trim();
|
|
||||||
|
|
||||||
let mut docs = docs[first.len()..].to_string();
|
|
||||||
let (params, returns) = params(&mut docs)?;
|
let (params, returns) = params(&mut docs)?;
|
||||||
let category = section(&mut docs, "Category", 2).expect("missing category");
|
|
||||||
let docs = docs.trim();
|
let docs = docs.trim();
|
||||||
|
|
||||||
let info = quote! {
|
let info = quote! {
|
||||||
::typst::eval::FuncInfo {
|
::typst::eval::FuncInfo {
|
||||||
name,
|
name,
|
||||||
display: #display,
|
display: "TODO",
|
||||||
category: #category,
|
category: "TODO",
|
||||||
docs: #docs,
|
docs: #docs,
|
||||||
params: ::std::vec![#(#params),*],
|
params: ::std::vec![#(#params),*],
|
||||||
returns: ::std::vec![#(#returns),*]
|
returns: ::std::vec![#(#returns),*]
|
||||||
|
|
@ -82,7 +74,7 @@ pub fn func(item: syn::Item) -> Result<TokenStream> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract a section.
|
/// Extract a section.
|
||||||
pub fn section(docs: &mut String, title: &str, level: usize) -> Option<String> {
|
fn section(docs: &mut String, title: &str, level: usize) -> Option<String> {
|
||||||
let hashtags = "#".repeat(level);
|
let hashtags = "#".repeat(level);
|
||||||
let needle = format!("\n{hashtags} {title}\n");
|
let needle = format!("\n{hashtags} {title}\n");
|
||||||
let start = docs.find(&needle)?;
|
let start = docs.find(&needle)?;
|
||||||
|
|
|
||||||
|
|
@ -2,32 +2,23 @@
|
||||||
|
|
||||||
extern crate proc_macro;
|
extern crate proc_macro;
|
||||||
|
|
||||||
/// Return an error at the given item.
|
#[macro_use]
|
||||||
macro_rules! bail {
|
mod util;
|
||||||
(callsite, $fmt:literal $($tts:tt)*) => {
|
|
||||||
return Err(syn::Error::new(
|
|
||||||
proc_macro2::Span::call_site(),
|
|
||||||
format!(concat!("typst: ", $fmt) $($tts)*)
|
|
||||||
))
|
|
||||||
};
|
|
||||||
($item:expr, $fmt:literal $($tts:tt)*) => {
|
|
||||||
return Err(syn::Error::new_spanned(
|
|
||||||
&$item,
|
|
||||||
format!(concat!("typst: ", $fmt) $($tts)*)
|
|
||||||
))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
mod capable;
|
|
||||||
mod castable;
|
mod castable;
|
||||||
mod func;
|
mod func;
|
||||||
mod node;
|
mod node;
|
||||||
mod symbols;
|
mod symbols;
|
||||||
|
|
||||||
use proc_macro::TokenStream as BoundaryStream;
|
use proc_macro::TokenStream as BoundaryStream;
|
||||||
use proc_macro2::{TokenStream, TokenTree};
|
use proc_macro2::TokenStream;
|
||||||
use quote::{quote, quote_spanned};
|
use quote::quote;
|
||||||
use syn::{parse_quote, Ident, Result};
|
use syn::ext::IdentExt;
|
||||||
|
use syn::parse::{Parse, ParseStream, Parser};
|
||||||
|
use syn::punctuated::Punctuated;
|
||||||
|
use syn::{parse_quote, Ident, Result, Token};
|
||||||
|
use unscanny::Scanner;
|
||||||
|
|
||||||
|
use self::util::*;
|
||||||
|
|
||||||
/// Implement `FuncType` for a type or function.
|
/// Implement `FuncType` for a type or function.
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
|
|
@ -38,33 +29,25 @@ pub fn func(_: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
|
||||||
|
|
||||||
/// Implement `Node` for a struct.
|
/// Implement `Node` for a struct.
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn node(_: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
|
pub fn node(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
|
||||||
let item = syn::parse_macro_input!(item as syn::ItemImpl);
|
let item = syn::parse_macro_input!(item as syn::ItemStruct);
|
||||||
node::node(item).unwrap_or_else(|err| err.to_compile_error()).into()
|
node::node(stream.into(), item)
|
||||||
}
|
|
||||||
|
|
||||||
/// Implement `Capability` for a trait.
|
|
||||||
#[proc_macro_attribute]
|
|
||||||
pub fn capability(_: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
|
|
||||||
let item = syn::parse_macro_input!(item as syn::ItemTrait);
|
|
||||||
capable::capability(item)
|
|
||||||
.unwrap_or_else(|err| err.to_compile_error())
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Implement `Capable` for a type.
|
|
||||||
#[proc_macro_attribute]
|
|
||||||
pub fn capable(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
|
|
||||||
let item = syn::parse_macro_input!(item as syn::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.
|
/// Implement `Cast` and optionally `Type` for a type.
|
||||||
#[proc_macro]
|
#[proc_macro]
|
||||||
pub fn castable(stream: BoundaryStream) -> BoundaryStream {
|
pub fn cast_from_value(stream: BoundaryStream) -> BoundaryStream {
|
||||||
castable::castable(stream.into())
|
castable::cast_from_value(stream.into())
|
||||||
|
.unwrap_or_else(|err| err.to_compile_error())
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implement `From<T> for Value` for a type `T`.
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn cast_to_value(stream: BoundaryStream) -> BoundaryStream {
|
||||||
|
castable::cast_to_value(stream.into())
|
||||||
.unwrap_or_else(|err| err.to_compile_error())
|
.unwrap_or_else(|err| err.to_compile_error())
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
@ -76,32 +59,3 @@ pub fn symbols(stream: BoundaryStream) -> BoundaryStream {
|
||||||
.unwrap_or_else(|err| err.to_compile_error())
|
.unwrap_or_else(|err| err.to_compile_error())
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract documentation comments from an attribute list.
|
|
||||||
fn documentation(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 {
|
|
||||||
let full = string.value();
|
|
||||||
let line = full.strip_prefix(' ').unwrap_or(&full);
|
|
||||||
doc.push_str(line);
|
|
||||||
doc.push('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
doc.trim().into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dedent documentation text.
|
|
||||||
fn dedent(text: &str) -> String {
|
|
||||||
text.lines()
|
|
||||||
.map(|s| s.strip_prefix(" ").unwrap_or(s))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join("\n")
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,309 +1,370 @@
|
||||||
use syn::punctuated::Punctuated;
|
|
||||||
use syn::spanned::Spanned;
|
|
||||||
use syn::Token;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Expand the `#[node]` macro.
|
/// Expand the `#[node]` macro.
|
||||||
pub fn node(body: syn::ItemImpl) -> Result<TokenStream> {
|
pub fn node(stream: TokenStream, body: syn::ItemStruct) -> Result<TokenStream> {
|
||||||
let node = prepare(body)?;
|
let node = prepare(stream, &body)?;
|
||||||
create(&node)
|
Ok(create(&node))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Details about a node.
|
|
||||||
struct Node {
|
struct Node {
|
||||||
body: syn::ItemImpl,
|
|
||||||
params: Punctuated<syn::GenericParam, Token![,]>,
|
|
||||||
self_ty: syn::Type,
|
|
||||||
self_name: String,
|
|
||||||
self_args: Punctuated<syn::GenericArgument, Token![,]>,
|
|
||||||
properties: Vec<Property>,
|
|
||||||
construct: Option<syn::ImplItemMethod>,
|
|
||||||
set: Option<syn::ImplItemMethod>,
|
|
||||||
field: Option<syn::ImplItemMethod>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A style property.
|
|
||||||
struct Property {
|
|
||||||
attrs: Vec<syn::Attribute>,
|
attrs: Vec<syn::Attribute>,
|
||||||
vis: syn::Visibility,
|
vis: syn::Visibility,
|
||||||
name: Ident,
|
ident: Ident,
|
||||||
value_ty: syn::Type,
|
name: String,
|
||||||
output_ty: syn::Type,
|
capable: Vec<Ident>,
|
||||||
default: syn::Expr,
|
set: Option<syn::Block>,
|
||||||
skip: bool,
|
fields: Vec<Field>,
|
||||||
referenced: bool,
|
}
|
||||||
shorthand: Option<Shorthand>,
|
|
||||||
resolve: bool,
|
struct Field {
|
||||||
fold: bool,
|
attrs: Vec<syn::Attribute>,
|
||||||
|
vis: syn::Visibility,
|
||||||
|
ident: Ident,
|
||||||
|
with_ident: Ident,
|
||||||
|
name: String,
|
||||||
|
|
||||||
|
positional: bool,
|
||||||
|
required: bool,
|
||||||
|
variadic: bool,
|
||||||
|
|
||||||
|
named: bool,
|
||||||
|
shorthand: Option<Shorthand>,
|
||||||
|
|
||||||
|
settable: bool,
|
||||||
|
fold: bool,
|
||||||
|
resolve: bool,
|
||||||
|
skip: bool,
|
||||||
|
|
||||||
|
ty: syn::Type,
|
||||||
|
default: Option<syn::Expr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The shorthand form of a style property.
|
|
||||||
enum Shorthand {
|
enum Shorthand {
|
||||||
Positional,
|
Positional,
|
||||||
Named(Ident),
|
Named(Ident),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Preprocess the impl block of a node.
|
impl Node {
|
||||||
fn prepare(body: syn::ItemImpl) -> Result<Node> {
|
fn inherent(&self) -> impl Iterator<Item = &Field> + Clone {
|
||||||
// Extract the generic type arguments.
|
self.fields.iter().filter(|field| !field.settable)
|
||||||
let params = body.generics.params.clone();
|
|
||||||
|
|
||||||
// Extract the node type for which we want to generate properties.
|
|
||||||
let self_ty = (*body.self_ty).clone();
|
|
||||||
let self_path = match &self_ty {
|
|
||||||
syn::Type::Path(path) => path,
|
|
||||||
ty => bail!(ty, "must be a path type"),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Split up the type into its name and its generic type arguments.
|
|
||||||
let last = self_path.path.segments.last().unwrap();
|
|
||||||
let self_name = last.ident.to_string();
|
|
||||||
let self_args = match &last.arguments {
|
|
||||||
syn::PathArguments::AngleBracketed(args) => args.args.clone(),
|
|
||||||
_ => Punctuated::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut properties = vec![];
|
|
||||||
let mut construct = None;
|
|
||||||
let mut set = None;
|
|
||||||
let mut field = None;
|
|
||||||
|
|
||||||
// Parse the properties and methods.
|
|
||||||
for item in &body.items {
|
|
||||||
match item {
|
|
||||||
syn::ImplItem::Const(item) => {
|
|
||||||
properties.push(prepare_property(item)?);
|
|
||||||
}
|
|
||||||
syn::ImplItem::Method(method) => {
|
|
||||||
match method.sig.ident.to_string().as_str() {
|
|
||||||
"construct" => construct = Some(method.clone()),
|
|
||||||
"set" => set = Some(method.clone()),
|
|
||||||
"field" => field = Some(method.clone()),
|
|
||||||
_ => bail!(method, "unexpected method"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => bail!(item, "unexpected item"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn settable(&self) -> impl Iterator<Item = &Field> + Clone {
|
||||||
|
self.fields.iter().filter(|field| field.settable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Preprocess the node's definition.
|
||||||
|
fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> {
|
||||||
|
let syn::Fields::Named(named) = &body.fields else {
|
||||||
|
bail!(body, "expected named fields");
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut fields = vec![];
|
||||||
|
for field in &named.named {
|
||||||
|
let Some(mut ident) = field.ident.clone() else {
|
||||||
|
bail!(field, "expected named field");
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut attrs = field.attrs.clone();
|
||||||
|
let settable = has_attr(&mut attrs, "settable");
|
||||||
|
if settable {
|
||||||
|
ident = Ident::new(&ident.to_string().to_uppercase(), ident.span());
|
||||||
|
}
|
||||||
|
|
||||||
|
let field = Field {
|
||||||
|
vis: field.vis.clone(),
|
||||||
|
ident: ident.clone(),
|
||||||
|
with_ident: Ident::new(&format!("with_{}", ident), ident.span()),
|
||||||
|
name: kebab_case(&ident),
|
||||||
|
|
||||||
|
positional: has_attr(&mut attrs, "positional"),
|
||||||
|
required: has_attr(&mut attrs, "required"),
|
||||||
|
variadic: has_attr(&mut attrs, "variadic"),
|
||||||
|
|
||||||
|
named: has_attr(&mut attrs, "named"),
|
||||||
|
shorthand: parse_attr(&mut attrs, "shorthand")?.map(|v| match v {
|
||||||
|
None => Shorthand::Positional,
|
||||||
|
Some(ident) => Shorthand::Named(ident),
|
||||||
|
}),
|
||||||
|
|
||||||
|
settable,
|
||||||
|
fold: has_attr(&mut attrs, "fold"),
|
||||||
|
resolve: has_attr(&mut attrs, "resolve"),
|
||||||
|
skip: has_attr(&mut attrs, "skip"),
|
||||||
|
|
||||||
|
ty: field.ty.clone(),
|
||||||
|
default: parse_attr(&mut attrs, "default")?.map(|opt| {
|
||||||
|
opt.unwrap_or_else(|| parse_quote! { ::std::default::Default::default() })
|
||||||
|
}),
|
||||||
|
|
||||||
|
attrs: {
|
||||||
|
validate_attrs(&attrs)?;
|
||||||
|
attrs
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if !field.positional && !field.named && !field.variadic && !field.settable {
|
||||||
|
bail!(ident, "expected positional, named, variadic, or settable");
|
||||||
|
}
|
||||||
|
|
||||||
|
if !field.required && !field.variadic && field.default.is_none() {
|
||||||
|
bail!(ident, "non-required fields must have a default value");
|
||||||
|
}
|
||||||
|
|
||||||
|
fields.push(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
let capable = Punctuated::<Ident, Token![,]>::parse_terminated
|
||||||
|
.parse2(stream)?
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut attrs = body.attrs.clone();
|
||||||
Ok(Node {
|
Ok(Node {
|
||||||
body,
|
vis: body.vis.clone(),
|
||||||
params,
|
ident: body.ident.clone(),
|
||||||
self_ty,
|
name: body.ident.to_string().trim_end_matches("Node").to_lowercase(),
|
||||||
self_name,
|
capable,
|
||||||
self_args,
|
fields,
|
||||||
properties,
|
set: parse_attr(&mut attrs, "set")?.flatten(),
|
||||||
construct,
|
attrs: {
|
||||||
set,
|
validate_attrs(&attrs)?;
|
||||||
field,
|
attrs
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Preprocess and validate a property constant.
|
/// Produce the node's definition.
|
||||||
fn prepare_property(item: &syn::ImplItemConst) -> Result<Property> {
|
fn create(node: &Node) -> TokenStream {
|
||||||
let mut attrs = item.attrs.clone();
|
let attrs = &node.attrs;
|
||||||
let tokens = match attrs
|
let vis = &node.vis;
|
||||||
|
let ident = &node.ident;
|
||||||
|
let name = &node.name;
|
||||||
|
let new = create_new_func(node);
|
||||||
|
let construct = node
|
||||||
|
.capable
|
||||||
.iter()
|
.iter()
|
||||||
.position(|attr| attr.path.is_ident("property"))
|
.all(|capability| capability != "Construct")
|
||||||
.map(|i| attrs.remove(i))
|
.then(|| create_construct_impl(node));
|
||||||
{
|
let set = create_set_impl(node);
|
||||||
Some(attr) => attr.parse_args::<TokenStream>()?,
|
let builders = node.inherent().map(create_builder_method);
|
||||||
None => TokenStream::default(),
|
let accessors = node.inherent().map(create_accessor_method);
|
||||||
};
|
let vtable = create_vtable(node);
|
||||||
|
|
||||||
let mut skip = false;
|
let mut modules = vec![];
|
||||||
let mut shorthand = None;
|
let mut items = vec![];
|
||||||
let mut referenced = false;
|
let scope = quote::format_ident!("__{}_keys", ident);
|
||||||
let mut resolve = false;
|
|
||||||
let mut fold = false;
|
|
||||||
|
|
||||||
// Parse the `#[property(..)]` attribute.
|
for field in node.settable() {
|
||||||
let mut stream = tokens.into_iter().peekable();
|
let ident = &field.ident;
|
||||||
while let Some(token) = stream.next() {
|
let attrs = &field.attrs;
|
||||||
let ident = match token {
|
let vis = &field.vis;
|
||||||
TokenTree::Ident(ident) => ident,
|
let ty = &field.ty;
|
||||||
TokenTree::Punct(_) => continue,
|
modules.push(create_field_module(node, field));
|
||||||
_ => bail!(token, "invalid token"),
|
items.push(quote! {
|
||||||
};
|
|
||||||
|
|
||||||
let mut arg = None;
|
|
||||||
if let Some(TokenTree::Group(group)) = stream.peek() {
|
|
||||||
let span = group.span();
|
|
||||||
let string = group.to_string();
|
|
||||||
let ident = string.trim_start_matches('(').trim_end_matches(')');
|
|
||||||
if !ident.chars().all(|c| c.is_ascii_alphabetic()) {
|
|
||||||
bail!(group, "invalid arguments");
|
|
||||||
}
|
|
||||||
arg = Some(Ident::new(ident, span));
|
|
||||||
stream.next();
|
|
||||||
};
|
|
||||||
|
|
||||||
match ident.to_string().as_str() {
|
|
||||||
"skip" => skip = true,
|
|
||||||
"shorthand" => {
|
|
||||||
shorthand = Some(match arg {
|
|
||||||
Some(name) => Shorthand::Named(name),
|
|
||||||
None => Shorthand::Positional,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
"referenced" => referenced = true,
|
|
||||||
"resolve" => resolve = true,
|
|
||||||
"fold" => fold = true,
|
|
||||||
_ => bail!(ident, "invalid attribute"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if referenced && (fold || resolve) {
|
|
||||||
bail!(item.ident, "referenced is mutually exclusive with fold and resolve");
|
|
||||||
}
|
|
||||||
|
|
||||||
// The type of the property's value is what the user of our macro wrote as
|
|
||||||
// type of the const, but the real type of the const will be a unique `Key`
|
|
||||||
// type.
|
|
||||||
let value_ty = item.ty.clone();
|
|
||||||
let output_ty = if referenced {
|
|
||||||
parse_quote! { &'a #value_ty }
|
|
||||||
} else if fold && resolve {
|
|
||||||
parse_quote! {
|
|
||||||
<<#value_ty as ::typst::model::Resolve>::Output
|
|
||||||
as ::typst::model::Fold>::Output
|
|
||||||
}
|
|
||||||
} else if fold {
|
|
||||||
parse_quote! { <#value_ty as ::typst::model::Fold>::Output }
|
|
||||||
} else if resolve {
|
|
||||||
parse_quote! { <#value_ty as ::typst::model::Resolve>::Output }
|
|
||||||
} else {
|
|
||||||
value_ty.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Property {
|
|
||||||
attrs,
|
|
||||||
vis: item.vis.clone(),
|
|
||||||
name: item.ident.clone(),
|
|
||||||
value_ty,
|
|
||||||
output_ty,
|
|
||||||
default: item.expr.clone(),
|
|
||||||
skip,
|
|
||||||
shorthand,
|
|
||||||
referenced,
|
|
||||||
resolve,
|
|
||||||
fold,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Produce the necessary items for a type to become a node.
|
|
||||||
fn create(node: &Node) -> Result<TokenStream> {
|
|
||||||
let params = &node.params;
|
|
||||||
let self_ty = &node.self_ty;
|
|
||||||
|
|
||||||
let id_method = create_node_id_method();
|
|
||||||
let name_method = create_node_name_method(node);
|
|
||||||
let construct_func = create_node_construct_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 node_impl = quote! {
|
|
||||||
impl<#params> ::typst::model::Node for #self_ty {
|
|
||||||
#id_method
|
|
||||||
#name_method
|
|
||||||
#construct_func
|
|
||||||
#set_func
|
|
||||||
#properties_func
|
|
||||||
#field_method
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut modules: Vec<syn::ItemMod> = vec![];
|
|
||||||
let mut items: Vec<syn::ImplItem> = vec![];
|
|
||||||
let scope = quote::format_ident!("__{}_keys", node.self_name);
|
|
||||||
|
|
||||||
for property in node.properties.iter() {
|
|
||||||
let (key, module) = create_property_module(node, property);
|
|
||||||
modules.push(module);
|
|
||||||
|
|
||||||
let name = &property.name;
|
|
||||||
let attrs = &property.attrs;
|
|
||||||
let vis = &property.vis;
|
|
||||||
items.push(parse_quote! {
|
|
||||||
#(#attrs)*
|
#(#attrs)*
|
||||||
#vis const #name: #scope::#name::#key
|
#vis const #ident: #scope::#ident::Key<#ty>
|
||||||
= #scope::#name::Key(::std::marker::PhantomData);
|
= #scope::#ident::Key(::std::marker::PhantomData);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut body = node.body.clone();
|
quote! {
|
||||||
body.items = items;
|
#(#attrs)*
|
||||||
|
#[::typst::eval::func]
|
||||||
|
#[derive(Debug, Clone, Hash)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
#vis struct #ident(::typst::model::Content);
|
||||||
|
|
||||||
Ok(quote! {
|
impl #ident {
|
||||||
#body
|
#new
|
||||||
|
#(#builders)*
|
||||||
|
|
||||||
|
/// The node's span.
|
||||||
|
pub fn span(&self) -> Option<::typst::syntax::Span> {
|
||||||
|
self.0.span()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl #ident {
|
||||||
|
#(#accessors)*
|
||||||
|
#(#items)*
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ::typst::model::Node for #ident {
|
||||||
|
fn id() -> ::typst::model::NodeId {
|
||||||
|
static META: ::typst::model::NodeMeta = ::typst::model::NodeMeta {
|
||||||
|
name: #name,
|
||||||
|
vtable: #vtable,
|
||||||
|
};
|
||||||
|
::typst::model::NodeId::from_meta(&META)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pack(self) -> ::typst::model::Content {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#construct
|
||||||
|
#set
|
||||||
|
|
||||||
|
impl From<#ident> for ::typst::eval::Value {
|
||||||
|
fn from(value: #ident) -> Self {
|
||||||
|
value.0.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
mod #scope {
|
mod #scope {
|
||||||
use super::*;
|
use super::*;
|
||||||
#node_impl
|
|
||||||
#(#modules)*
|
#(#modules)*
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create the node's id method.
|
/// Create the `new` function for the node.
|
||||||
fn create_node_id_method() -> syn::ImplItemMethod {
|
fn create_new_func(node: &Node) -> TokenStream {
|
||||||
parse_quote! {
|
let relevant = node.inherent().filter(|field| field.required || field.variadic);
|
||||||
fn id(&self) -> ::typst::model::NodeId {
|
let params = relevant.clone().map(|field| {
|
||||||
::typst::model::NodeId::of::<Self>()
|
let ident = &field.ident;
|
||||||
|
let ty = &field.ty;
|
||||||
|
quote! { #ident: #ty }
|
||||||
|
});
|
||||||
|
let pushes = relevant.map(|field| {
|
||||||
|
let ident = &field.ident;
|
||||||
|
let with_ident = &field.with_ident;
|
||||||
|
quote! { .#with_ident(#ident) }
|
||||||
|
});
|
||||||
|
let defaults = node
|
||||||
|
.inherent()
|
||||||
|
.filter_map(|field| field.default.as_ref().map(|default| (field, default)))
|
||||||
|
.map(|(field, default)| {
|
||||||
|
let with_ident = &field.with_ident;
|
||||||
|
quote! { .#with_ident(#default) }
|
||||||
|
});
|
||||||
|
quote! {
|
||||||
|
/// Create a new node.
|
||||||
|
pub fn new(#(#params),*) -> Self {
|
||||||
|
Self(::typst::model::Content::new::<Self>())
|
||||||
|
#(#pushes)*
|
||||||
|
#(#defaults)*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create the node's name method.
|
/// Create a builder pattern method for a field.
|
||||||
fn create_node_name_method(node: &Node) -> syn::ImplItemMethod {
|
fn create_builder_method(field: &Field) -> TokenStream {
|
||||||
let name = node.self_name.trim_end_matches("Node").to_lowercase();
|
let Field { with_ident, ident, name, ty, .. } = field;
|
||||||
parse_quote! {
|
let doc = format!("Set the [`{}`](Self::{}) field.", name, ident);
|
||||||
fn name(&self) -> &'static str {
|
quote! {
|
||||||
#name
|
#[doc = #doc]
|
||||||
|
pub fn #with_ident(mut self, #ident: #ty) -> Self {
|
||||||
|
Self(self.0.with_field(#name, #ident))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create the node's `construct` function.
|
/// Create an accessor methods for a field.
|
||||||
fn create_node_construct_func(node: &Node) -> syn::ImplItemMethod {
|
fn create_accessor_method(field: &Field) -> TokenStream {
|
||||||
node.construct.clone().unwrap_or_else(|| {
|
let Field { attrs, vis, ident, name, ty, .. } = field;
|
||||||
parse_quote! {
|
quote! {
|
||||||
|
#(#attrs)*
|
||||||
|
#vis fn #ident(&self) -> #ty {
|
||||||
|
self.0.cast_field(#name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create the node's `Construct` implementation.
|
||||||
|
fn create_construct_impl(node: &Node) -> TokenStream {
|
||||||
|
let ident = &node.ident;
|
||||||
|
let shorthands = create_construct_shorthands(node);
|
||||||
|
let builders = node.inherent().map(create_construct_builder_call);
|
||||||
|
quote! {
|
||||||
|
impl ::typst::model::Construct for #ident {
|
||||||
fn construct(
|
fn construct(
|
||||||
_: &::typst::eval::Vm,
|
_: &::typst::eval::Vm,
|
||||||
args: &mut ::typst::eval::Args,
|
args: &mut ::typst::eval::Args,
|
||||||
) -> ::typst::diag::SourceResult<::typst::model::Content> {
|
) -> ::typst::diag::SourceResult<::typst::model::Content> {
|
||||||
::typst::diag::bail!(args.span, "cannot be constructed manually");
|
#(#shorthands)*
|
||||||
|
Ok(::typst::model::Node::pack(
|
||||||
|
Self(::typst::model::Content::new::<Self>())
|
||||||
|
#(#builders)*))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create let bindings for shorthands in the constructor.
|
||||||
|
fn create_construct_shorthands(node: &Node) -> impl Iterator<Item = TokenStream> + '_ {
|
||||||
|
let mut shorthands = vec![];
|
||||||
|
for field in node.inherent() {
|
||||||
|
if let Some(Shorthand::Named(named)) = &field.shorthand {
|
||||||
|
shorthands.push(named);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shorthands.sort();
|
||||||
|
shorthands.dedup_by_key(|ident| ident.to_string());
|
||||||
|
shorthands.into_iter().map(|ident| {
|
||||||
|
let string = ident.to_string();
|
||||||
|
quote! { let #ident = args.named(#string)?; }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create the node's `set` function.
|
/// Create a builder call for the constructor.
|
||||||
fn create_node_set_func(node: &Node) -> syn::ImplItemMethod {
|
fn create_construct_builder_call(field: &Field) -> TokenStream {
|
||||||
let user = node.set.as_ref().map(|method| {
|
let name = &field.name;
|
||||||
let block = &method.block;
|
let with_ident = &field.with_ident;
|
||||||
|
|
||||||
|
let mut value = if field.variadic {
|
||||||
|
quote! { args.all()? }
|
||||||
|
} else if field.required {
|
||||||
|
quote! { args.expect(#name)? }
|
||||||
|
} else if let Some(shorthand) = &field.shorthand {
|
||||||
|
match shorthand {
|
||||||
|
Shorthand::Positional => quote! { args.named_or_find(#name)? },
|
||||||
|
Shorthand::Named(named) => {
|
||||||
|
quote! { args.named(#name)?.or_else(|| #named.clone()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if field.named {
|
||||||
|
quote! { args.named(#name)? }
|
||||||
|
} else {
|
||||||
|
quote! { args.find()? }
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(default) = &field.default {
|
||||||
|
value = quote! { #value.unwrap_or(#default) };
|
||||||
|
}
|
||||||
|
|
||||||
|
quote! { .#with_ident(#value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create the node's `Set` implementation.
|
||||||
|
fn create_set_impl(node: &Node) -> TokenStream {
|
||||||
|
let ident = &node.ident;
|
||||||
|
let custom = node.set.as_ref().map(|block| {
|
||||||
quote! { (|| -> typst::diag::SourceResult<()> { #block; Ok(()) } )()?; }
|
quote! { (|| -> typst::diag::SourceResult<()> { #block; Ok(()) } )()?; }
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut shorthands = vec![];
|
let mut shorthands = vec![];
|
||||||
let sets: Vec<_> = node
|
let sets: Vec<_> = node
|
||||||
.properties
|
.settable()
|
||||||
.iter()
|
.filter(|field| !field.skip)
|
||||||
.filter(|p| !p.skip)
|
.map(|field| {
|
||||||
.map(|property| {
|
let ident = &field.ident;
|
||||||
let name = &property.name;
|
let name = &field.name;
|
||||||
let string = name.to_string().replace('_', "-").to_lowercase();
|
let value = match &field.shorthand {
|
||||||
let value = match &property.shorthand {
|
Some(Shorthand::Positional) => quote! { args.named_or_find(#name)? },
|
||||||
Some(Shorthand::Positional) => quote! { args.named_or_find(#string)? },
|
|
||||||
Some(Shorthand::Named(named)) => {
|
Some(Shorthand::Named(named)) => {
|
||||||
shorthands.push(named);
|
shorthands.push(named);
|
||||||
quote! { args.named(#string)?.or_else(|| #named.clone()) }
|
quote! { args.named(#name)?.or_else(|| #named.clone()) }
|
||||||
}
|
}
|
||||||
None => quote! { args.named(#string)? },
|
None => quote! { args.named(#name)? },
|
||||||
};
|
};
|
||||||
|
|
||||||
quote! { styles.set_opt(Self::#name, #value); }
|
quote! { styles.set_opt(Self::#ident, #value); }
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|
@ -315,30 +376,12 @@ fn create_node_set_func(node: &Node) -> syn::ImplItemMethod {
|
||||||
quote! { let #ident = args.named(#string)?; }
|
quote! { let #ident = args.named(#string)?; }
|
||||||
});
|
});
|
||||||
|
|
||||||
parse_quote! {
|
let infos = node.fields.iter().filter(|p| !p.skip).map(|field| {
|
||||||
fn set(
|
let name = &field.name;
|
||||||
args: &mut ::typst::eval::Args,
|
let value_ty = &field.ty;
|
||||||
constructor: bool,
|
let shorthand = matches!(field.shorthand, Some(Shorthand::Positional));
|
||||||
) -> ::typst::diag::SourceResult<::typst::model::StyleMap> {
|
let docs = documentation(&field.attrs);
|
||||||
let mut styles = ::typst::model::StyleMap::new();
|
|
||||||
#user
|
|
||||||
#(#bindings)*
|
|
||||||
#(#sets)*
|
|
||||||
Ok(styles)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create the node's `properties` function.
|
|
||||||
fn create_node_properties_func(node: &Node) -> syn::ImplItemMethod {
|
|
||||||
let infos = node.properties.iter().filter(|p| !p.skip).map(|property| {
|
|
||||||
let name = property.name.to_string().replace('_', "-").to_lowercase();
|
|
||||||
let value_ty = &property.value_ty;
|
|
||||||
let shorthand = matches!(property.shorthand, Some(Shorthand::Positional));
|
|
||||||
|
|
||||||
let docs = documentation(&property.attrs);
|
|
||||||
let docs = docs.trim();
|
let docs = docs.trim();
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
::typst::eval::ParamInfo {
|
::typst::eval::ParamInfo {
|
||||||
name: #name,
|
name: #name,
|
||||||
|
|
@ -355,167 +398,142 @@ fn create_node_properties_func(node: &Node) -> syn::ImplItemMethod {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
parse_quote! {
|
quote! {
|
||||||
fn properties() -> ::std::vec::Vec<::typst::eval::ParamInfo>
|
impl ::typst::model::Set for #ident {
|
||||||
where
|
fn set(
|
||||||
Self: Sized
|
args: &mut ::typst::eval::Args,
|
||||||
{
|
constructor: bool,
|
||||||
::std::vec![#(#infos),*]
|
) -> ::typst::diag::SourceResult<::typst::model::StyleMap> {
|
||||||
|
let mut styles = ::typst::model::StyleMap::new();
|
||||||
|
#custom
|
||||||
|
#(#bindings)*
|
||||||
|
#(#sets)*
|
||||||
|
Ok(styles)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn properties() -> ::std::vec::Vec<::typst::eval::ParamInfo> {
|
||||||
|
::std::vec![#(#infos),*]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create the node's `field` method.
|
/// Create the module for a single field.
|
||||||
fn create_node_field_method(node: &Node) -> syn::ImplItemMethod {
|
fn create_field_module(node: &Node, field: &Field) -> TokenStream {
|
||||||
node.field.clone().unwrap_or_else(|| {
|
let node_ident = &node.ident;
|
||||||
parse_quote! {
|
let ident = &field.ident;
|
||||||
fn field(
|
let name = &field.name;
|
||||||
&self,
|
let ty = &field.ty;
|
||||||
_: &str,
|
let default = &field.default;
|
||||||
) -> ::std::option::Option<::typst::eval::Value> {
|
|
||||||
None
|
let mut output = quote! { #ty };
|
||||||
}
|
if field.resolve {
|
||||||
|
output = quote! { <#output as ::typst::model::Resolve>::Output };
|
||||||
|
}
|
||||||
|
if field.fold {
|
||||||
|
output = quote! { <#output as ::typst::model::Fold>::Output };
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = if field.resolve && field.fold {
|
||||||
|
quote! {
|
||||||
|
values
|
||||||
|
.next()
|
||||||
|
.map(|value| {
|
||||||
|
::typst::model::Fold::fold(
|
||||||
|
::typst::model::Resolve::resolve(value, chain),
|
||||||
|
Self::get(chain, values),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.unwrap_or(#default)
|
||||||
}
|
}
|
||||||
})
|
} else if field.resolve {
|
||||||
}
|
quote! {
|
||||||
|
::typst::model::Resolve::resolve(
|
||||||
/// Process a single const item.
|
values.next().unwrap_or(#default),
|
||||||
fn create_property_module(node: &Node, property: &Property) -> (syn::Type, syn::ItemMod) {
|
chain
|
||||||
let params = &node.params;
|
)
|
||||||
let self_args = &node.self_args;
|
|
||||||
let name = &property.name;
|
|
||||||
let value_ty = &property.value_ty;
|
|
||||||
let output_ty = &property.output_ty;
|
|
||||||
|
|
||||||
let key = parse_quote! { Key<#value_ty, #self_args> };
|
|
||||||
let phantom_args = self_args.iter().filter(|arg| match arg {
|
|
||||||
syn::GenericArgument::Type(syn::Type::Path(path)) => {
|
|
||||||
node.params.iter().all(|param| match param {
|
|
||||||
syn::GenericParam::Const(c) => !path.path.is_ident(&c.ident),
|
|
||||||
_ => true,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
_ => true,
|
} else if field.fold {
|
||||||
});
|
quote! {
|
||||||
|
values
|
||||||
let name_const = create_property_name_const(node, property);
|
.next()
|
||||||
let node_func = create_property_node_func(node);
|
.map(|value| {
|
||||||
let get_method = create_property_get_method(property);
|
::typst::model::Fold::fold(
|
||||||
let copy_assertion = create_property_copy_assertion(property);
|
value,
|
||||||
|
Self::get(chain, values),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.unwrap_or(#default)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
values.next().unwrap_or(#default)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Generate the contents of the module.
|
// Generate the contents of the module.
|
||||||
let scope = quote! {
|
let scope = quote! {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub struct Key<__T, #params>(
|
pub struct Key<T>(pub ::std::marker::PhantomData<T>);
|
||||||
pub ::std::marker::PhantomData<(__T, #(#phantom_args,)*)>
|
impl ::std::marker::Copy for Key<#ty> {}
|
||||||
);
|
impl ::std::clone::Clone for Key<#ty> {
|
||||||
|
|
||||||
impl<#params> ::std::marker::Copy for #key {}
|
|
||||||
impl<#params> ::std::clone::Clone for #key {
|
|
||||||
fn clone(&self) -> Self { *self }
|
fn clone(&self) -> Self { *self }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<#params> ::typst::model::Key for #key {
|
impl ::typst::model::Key for Key<#ty> {
|
||||||
type Value = #value_ty;
|
type Value = #ty;
|
||||||
type Output<'a> = #output_ty;
|
type Output = #output;
|
||||||
#name_const
|
|
||||||
#node_func
|
|
||||||
#get_method
|
|
||||||
}
|
|
||||||
|
|
||||||
#copy_assertion
|
fn id() -> ::typst::model::KeyId {
|
||||||
|
static META: ::typst::model::KeyMeta = ::typst::model::KeyMeta {
|
||||||
|
name: #name,
|
||||||
|
};
|
||||||
|
::typst::model::KeyId::from_meta(&META)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn node() -> ::typst::model::NodeId {
|
||||||
|
::typst::model::NodeId::of::<#node_ident>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(
|
||||||
|
chain: ::typst::model::StyleChain,
|
||||||
|
mut values: impl ::std::iter::Iterator<Item = Self::Value>,
|
||||||
|
) -> Self::Output {
|
||||||
|
#value
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generate the module code.
|
// Generate the module code.
|
||||||
let module = parse_quote! {
|
quote! {
|
||||||
#[allow(non_snake_case)]
|
pub mod #ident { #scope }
|
||||||
pub mod #name { #scope }
|
|
||||||
};
|
|
||||||
|
|
||||||
(key, module)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create the property's node method.
|
|
||||||
fn create_property_name_const(node: &Node, property: &Property) -> syn::ImplItemConst {
|
|
||||||
// The display name, e.g. `TextNode::BOLD`.
|
|
||||||
let name = format!("{}::{}", node.self_name, &property.name);
|
|
||||||
parse_quote! {
|
|
||||||
const NAME: &'static str = #name;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create the property's node method.
|
/// Create the node's metadata vtable.
|
||||||
fn create_property_node_func(node: &Node) -> syn::ImplItemMethod {
|
fn create_vtable(node: &Node) -> TokenStream {
|
||||||
let self_ty = &node.self_ty;
|
let ident = &node.ident;
|
||||||
parse_quote! {
|
let checks =
|
||||||
fn node() -> ::typst::model::NodeId {
|
node.capable
|
||||||
::typst::model::NodeId::of::<#self_ty>()
|
.iter()
|
||||||
|
.filter(|&ident| ident != "Construct")
|
||||||
|
.map(|capability| {
|
||||||
|
quote! {
|
||||||
|
if id == ::std::any::TypeId::of::<dyn #capability>() {
|
||||||
|
return Some(unsafe {
|
||||||
|
::typst::util::fat::vtable(&
|
||||||
|
Self(::typst::model::Content::new::<#ident>()) as &dyn #capability
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
|id| {
|
||||||
|
#(#checks)*
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create the property's get method.
|
|
||||||
fn create_property_get_method(property: &Property) -> syn::ImplItemMethod {
|
|
||||||
let default = &property.default;
|
|
||||||
let value_ty = &property.value_ty;
|
|
||||||
|
|
||||||
let value = if property.referenced {
|
|
||||||
quote! {
|
|
||||||
values.next().unwrap_or_else(|| {
|
|
||||||
static LAZY: ::typst::model::once_cell::sync::Lazy<#value_ty>
|
|
||||||
= ::typst::model::once_cell::sync::Lazy::new(|| #default);
|
|
||||||
&*LAZY
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else if property.resolve && property.fold {
|
|
||||||
quote! {
|
|
||||||
match values.next().cloned() {
|
|
||||||
Some(value) => ::typst::model::Fold::fold(
|
|
||||||
::typst::model::Resolve::resolve(value, chain),
|
|
||||||
Self::get(chain, values),
|
|
||||||
),
|
|
||||||
None => #default,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if property.resolve {
|
|
||||||
quote! {
|
|
||||||
let value = values.next().cloned().unwrap_or_else(|| #default);
|
|
||||||
::typst::model::Resolve::resolve(value, chain)
|
|
||||||
}
|
|
||||||
} else if property.fold {
|
|
||||||
quote! {
|
|
||||||
match values.next().cloned() {
|
|
||||||
Some(value) => ::typst::model::Fold::fold(value, Self::get(chain, values)),
|
|
||||||
None => #default,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
quote! {
|
|
||||||
values.next().copied().unwrap_or(#default)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
parse_quote! {
|
|
||||||
fn get<'a>(
|
|
||||||
chain: ::typst::model::StyleChain<'a>,
|
|
||||||
mut values: impl ::std::iter::Iterator<Item = &'a Self::Value>,
|
|
||||||
) -> Self::Output<'a> {
|
|
||||||
#value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create the assertion if the property's value must be copyable.
|
|
||||||
fn create_property_copy_assertion(property: &Property) -> Option<TokenStream> {
|
|
||||||
let value_ty = &property.value_ty;
|
|
||||||
let must_be_copy = !property.fold && !property.resolve && !property.referenced;
|
|
||||||
must_be_copy.then(|| {
|
|
||||||
quote_spanned! { value_ty.span() =>
|
|
||||||
const _: fn() -> () = || {
|
|
||||||
fn must_be_copy_fold_resolve_or_referenced<T: ::std::marker::Copy>() {}
|
|
||||||
must_be_copy_fold_resolve_or_referenced::<#value_ty>();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,44 @@
|
||||||
use syn::ext::IdentExt;
|
|
||||||
use syn::parse::{Parse, ParseStream};
|
|
||||||
use syn::punctuated::Punctuated;
|
|
||||||
use syn::Token;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Expand the `symbols!` macro.
|
/// Expand the `symbols!` macro.
|
||||||
pub fn symbols(stream: TokenStream) -> Result<TokenStream> {
|
pub fn symbols(stream: TokenStream) -> Result<TokenStream> {
|
||||||
let list: List = syn::parse2(stream)?;
|
let list: Punctuated<Symbol, Token![,]> =
|
||||||
let pairs = list.0.iter().map(Symbol::expand);
|
Punctuated::parse_terminated.parse2(stream)?;
|
||||||
|
let pairs = list.iter().map(|symbol| {
|
||||||
|
let name = symbol.name.to_string();
|
||||||
|
let kind = match &symbol.kind {
|
||||||
|
Kind::Single(c) => quote! { typst::eval::Symbol::new(#c), },
|
||||||
|
Kind::Multiple(variants) => {
|
||||||
|
let variants = variants.iter().map(|variant| {
|
||||||
|
let name = &variant.name;
|
||||||
|
let c = &variant.c;
|
||||||
|
quote! { (#name, #c) }
|
||||||
|
});
|
||||||
|
quote! {
|
||||||
|
typst::eval::Symbol::list(&[#(#variants),*])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
quote! { (#name, #kind) }
|
||||||
|
});
|
||||||
Ok(quote! { &[#(#pairs),*] })
|
Ok(quote! { &[#(#pairs),*] })
|
||||||
}
|
}
|
||||||
|
|
||||||
struct List(Punctuated<Symbol, Token![,]>);
|
|
||||||
|
|
||||||
impl Parse for List {
|
|
||||||
fn parse(input: ParseStream) -> Result<Self> {
|
|
||||||
Punctuated::parse_terminated(input).map(Self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Symbol {
|
struct Symbol {
|
||||||
name: syn::Ident,
|
name: syn::Ident,
|
||||||
kind: Kind,
|
kind: Kind,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Kind {
|
||||||
|
Single(syn::LitChar),
|
||||||
|
Multiple(Punctuated<Variant, Token![,]>),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Variant {
|
||||||
|
name: String,
|
||||||
|
c: syn::LitChar,
|
||||||
|
}
|
||||||
|
|
||||||
impl Parse for Symbol {
|
impl Parse for Symbol {
|
||||||
fn parse(input: ParseStream) -> Result<Self> {
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
let name = input.call(Ident::parse_any)?;
|
let name = input.call(Ident::parse_any)?;
|
||||||
|
|
@ -34,19 +48,6 @@ impl Parse for Symbol {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Symbol {
|
|
||||||
fn expand(&self) -> TokenStream {
|
|
||||||
let name = self.name.to_string();
|
|
||||||
let kind = self.kind.expand();
|
|
||||||
quote! { (#name, #kind) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Kind {
|
|
||||||
Single(syn::LitChar),
|
|
||||||
Multiple(Punctuated<Variant, Token![,]>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for Kind {
|
impl Parse for Kind {
|
||||||
fn parse(input: ParseStream) -> Result<Self> {
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
if input.peek(syn::LitChar) {
|
if input.peek(syn::LitChar) {
|
||||||
|
|
@ -59,25 +60,6 @@ impl Parse for Kind {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Kind {
|
|
||||||
fn expand(&self) -> TokenStream {
|
|
||||||
match self {
|
|
||||||
Self::Single(c) => quote! { typst::eval::Symbol::new(#c), },
|
|
||||||
Self::Multiple(variants) => {
|
|
||||||
let variants = variants.iter().map(Variant::expand);
|
|
||||||
quote! {
|
|
||||||
typst::eval::Symbol::list(&[#(#variants),*])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Variant {
|
|
||||||
name: String,
|
|
||||||
c: syn::LitChar,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for Variant {
|
impl Parse for Variant {
|
||||||
fn parse(input: ParseStream) -> Result<Self> {
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
let mut name = String::new();
|
let mut name = String::new();
|
||||||
|
|
@ -94,11 +76,3 @@ impl Parse for Variant {
|
||||||
Ok(Self { name, c })
|
Ok(Self { name, c })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Variant {
|
|
||||||
fn expand(&self) -> TokenStream {
|
|
||||||
let name = &self.name;
|
|
||||||
let c = &self.c;
|
|
||||||
quote! { (#name, #c) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Return an error at the given item.
|
||||||
|
macro_rules! bail {
|
||||||
|
(callsite, $fmt:literal $($tts:tt)*) => {
|
||||||
|
return Err(syn::Error::new(
|
||||||
|
proc_macro2::Span::call_site(),
|
||||||
|
format!(concat!("typst: ", $fmt) $($tts)*)
|
||||||
|
))
|
||||||
|
};
|
||||||
|
($item:expr, $fmt:literal $($tts:tt)*) => {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
&$item,
|
||||||
|
format!(concat!("typst: ", $fmt) $($tts)*)
|
||||||
|
))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether an attribute list has a specified attribute.
|
||||||
|
pub fn has_attr(attrs: &mut Vec<syn::Attribute>, target: &str) -> bool {
|
||||||
|
take_attr(attrs, target).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether an attribute list has a specified attribute.
|
||||||
|
pub fn parse_attr<T: Parse>(
|
||||||
|
attrs: &mut Vec<syn::Attribute>,
|
||||||
|
target: &str,
|
||||||
|
) -> Result<Option<Option<T>>> {
|
||||||
|
take_attr(attrs, target)
|
||||||
|
.map(|attr| (!attr.tokens.is_empty()).then(|| attr.parse_args()).transpose())
|
||||||
|
.transpose()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether an attribute list has a specified attribute.
|
||||||
|
pub fn take_attr(
|
||||||
|
attrs: &mut Vec<syn::Attribute>,
|
||||||
|
target: &str,
|
||||||
|
) -> Option<syn::Attribute> {
|
||||||
|
attrs
|
||||||
|
.iter()
|
||||||
|
.position(|attr| attr.path.is_ident(target))
|
||||||
|
.map(|i| attrs.remove(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensure that no unrecognized attributes remain.
|
||||||
|
pub fn validate_attrs(attrs: &[syn::Attribute]) -> Result<()> {
|
||||||
|
for attr in attrs {
|
||||||
|
if !attr.path.is_ident("doc") {
|
||||||
|
let ident = attr.path.get_ident().unwrap();
|
||||||
|
bail!(ident, "unrecognized attribute: {:?}", ident.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert an identifier to a kebab-case string.
|
||||||
|
pub fn kebab_case(name: &Ident) -> String {
|
||||||
|
name.to_string().to_lowercase().replace('_', "-")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dedent documentation text.
|
||||||
|
pub fn dedent(text: &str) -> String {
|
||||||
|
text.lines()
|
||||||
|
.map(|s| s.strip_prefix(" ").unwrap_or(s))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract documentation comments from an attribute list.
|
||||||
|
pub fn documentation(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 {
|
||||||
|
let full = string.value();
|
||||||
|
let line = full.strip_prefix(' ').unwrap_or(&full);
|
||||||
|
doc.push_str(line);
|
||||||
|
doc.push('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
doc.trim().into()
|
||||||
|
}
|
||||||
132
src/doc.rs
132
src/doc.rs
|
|
@ -7,14 +7,14 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use ecow::EcoString;
|
use ecow::EcoString;
|
||||||
|
|
||||||
use crate::eval::{dict, Dict, Value};
|
use crate::eval::{cast_from_value, cast_to_value, dict, Dict, Value};
|
||||||
use crate::font::Font;
|
use crate::font::Font;
|
||||||
use crate::geom::{
|
use crate::geom::{
|
||||||
self, rounded_rect, Abs, Align, Axes, Color, Corners, Dir, Em, Geometry, Numeric,
|
self, rounded_rect, Abs, Align, Axes, Color, Corners, Dir, Em, Geometry, Length,
|
||||||
Paint, Point, Rel, RgbaColor, Shape, Sides, Size, Stroke, Transform,
|
Numeric, Paint, Point, Rel, RgbaColor, Shape, Sides, Size, Stroke, Transform,
|
||||||
};
|
};
|
||||||
use crate::image::Image;
|
use crate::image::Image;
|
||||||
use crate::model::{capable, node, Content, Fold, StableId, StyleChain};
|
use crate::model::{node, Content, Fold, StableId, StyleChain};
|
||||||
|
|
||||||
/// A finished document with metadata and page frames.
|
/// A finished document with metadata and page frames.
|
||||||
#[derive(Debug, Default, Clone, Hash)]
|
#[derive(Debug, Default, Clone, Hash)]
|
||||||
|
|
@ -274,7 +274,7 @@ impl Frame {
|
||||||
if self.is_empty() {
|
if self.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for meta in styles.get(Meta::DATA) {
|
for meta in styles.get(MetaNode::DATA) {
|
||||||
if matches!(meta, Meta::Hidden) {
|
if matches!(meta, Meta::Hidden) {
|
||||||
self.clear();
|
self.clear();
|
||||||
break;
|
break;
|
||||||
|
|
@ -283,6 +283,14 @@ impl Frame {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a background fill.
|
||||||
|
pub fn fill(&mut self, fill: Paint) {
|
||||||
|
self.prepend(
|
||||||
|
Point::zero(),
|
||||||
|
Element::Shape(Geometry::Rect(self.size()).filled(fill)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Add a fill and stroke with optional radius and outset to the frame.
|
/// Add a fill and stroke with optional radius and outset to the frame.
|
||||||
pub fn fill_and_stroke(
|
pub fn fill_and_stroke(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
@ -533,6 +541,15 @@ impl FromStr for Lang {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
Lang,
|
||||||
|
string: EcoString => Self::from_str(&string)?,
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: Lang => v.as_str().into()
|
||||||
|
}
|
||||||
|
|
||||||
/// An identifier for a region somewhere in the world.
|
/// An identifier for a region somewhere in the world.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
pub struct Region([u8; 2]);
|
pub struct Region([u8; 2]);
|
||||||
|
|
@ -559,8 +576,16 @@ impl FromStr for Region {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
Region,
|
||||||
|
string: EcoString => Self::from_str(&string)?,
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: Region => v.as_str().into()
|
||||||
|
}
|
||||||
|
|
||||||
/// Meta information that isn't visible or renderable.
|
/// Meta information that isn't visible or renderable.
|
||||||
#[capable]
|
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub enum Meta {
|
pub enum Meta {
|
||||||
/// An internal or external link.
|
/// An internal or external link.
|
||||||
|
|
@ -572,12 +597,16 @@ pub enum Meta {
|
||||||
Hidden,
|
Hidden,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Host for metadata.
|
||||||
#[node]
|
#[node]
|
||||||
impl Meta {
|
pub struct MetaNode {
|
||||||
/// Metadata that should be attached to all elements affected by this style
|
/// Metadata that should be attached to all elements affected by this style
|
||||||
/// property.
|
/// property.
|
||||||
#[property(fold, skip)]
|
#[settable]
|
||||||
pub const DATA: Vec<Meta> = vec![];
|
#[fold]
|
||||||
|
#[skip]
|
||||||
|
#[default]
|
||||||
|
pub data: Vec<Meta>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fold for Vec<Meta> {
|
impl Fold for Vec<Meta> {
|
||||||
|
|
@ -589,6 +618,16 @@ impl Fold for Vec<Meta> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
Meta: "meta",
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Meta {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
crate::util::hash128(self) == crate::util::hash128(other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A link destination.
|
/// A link destination.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum Destination {
|
pub enum Destination {
|
||||||
|
|
@ -598,6 +637,19 @@ pub enum Destination {
|
||||||
Url(EcoString),
|
Url(EcoString),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
Destination,
|
||||||
|
loc: Location => Self::Internal(loc),
|
||||||
|
string: EcoString => Self::Url(string),
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: Destination => match v {
|
||||||
|
Destination::Internal(loc) => loc.into(),
|
||||||
|
Destination::Url(url) => url.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A physical location in a document.
|
/// A physical location in a document.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct Location {
|
pub struct Location {
|
||||||
|
|
@ -607,53 +659,21 @@ pub struct Location {
|
||||||
pub pos: Point,
|
pub pos: Point,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Location {
|
cast_from_value! {
|
||||||
/// Encode into a user-facing dictionary.
|
Location,
|
||||||
pub fn encode(&self) -> Dict {
|
mut dict: Dict => {
|
||||||
dict! {
|
let page = dict.take("page")?.cast()?;
|
||||||
"page" => Value::Int(self.page.get() as i64),
|
let x: Length = dict.take("x")?.cast()?;
|
||||||
"x" => Value::Length(self.pos.x.into()),
|
let y: Length = dict.take("y")?.cast()?;
|
||||||
"y" => Value::Length(self.pos.y.into()),
|
dict.finish(&["page", "x", "y"])?;
|
||||||
}
|
Self { page, pos: Point::new(x.abs, y.abs) }
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Standard semantic roles.
|
cast_to_value! {
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
v: Location => Value::Dict(dict! {
|
||||||
pub enum Role {
|
"page" => Value::Int(v.page.get() as i64),
|
||||||
/// A paragraph.
|
"x" => Value::Length(v.pos.x.into()),
|
||||||
Paragraph,
|
"y" => Value::Length(v.pos.y.into()),
|
||||||
/// A heading of the given level and whether it should be part of the
|
})
|
||||||
/// outline.
|
|
||||||
Heading { level: NonZeroUsize, outlined: bool },
|
|
||||||
/// A generic block-level subdivision.
|
|
||||||
GenericBlock,
|
|
||||||
/// A generic inline subdivision.
|
|
||||||
GenericInline,
|
|
||||||
/// A list and whether it is ordered.
|
|
||||||
List { ordered: bool },
|
|
||||||
/// A list item. Must have a list parent.
|
|
||||||
ListItem,
|
|
||||||
/// The label of a list item. Must have a list item parent.
|
|
||||||
ListLabel,
|
|
||||||
/// The body of a list item. Must have a list item parent.
|
|
||||||
ListItemBody,
|
|
||||||
/// A mathematical formula.
|
|
||||||
Formula,
|
|
||||||
/// A table.
|
|
||||||
Table,
|
|
||||||
/// A table row. Must have a table parent.
|
|
||||||
TableRow,
|
|
||||||
/// A table cell. Must have a table row parent.
|
|
||||||
TableCell,
|
|
||||||
/// A code fragment.
|
|
||||||
Code,
|
|
||||||
/// A page header.
|
|
||||||
Header,
|
|
||||||
/// A page footer.
|
|
||||||
Footer,
|
|
||||||
/// A page background.
|
|
||||||
Background,
|
|
||||||
/// A page foreground.
|
|
||||||
Foreground,
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
592
src/eval/cast.rs
592
src/eval/cast.rs
|
|
@ -1,18 +1,12 @@
|
||||||
|
pub use typst_macros::{cast_from_value, cast_to_value};
|
||||||
|
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
use std::ops::Add;
|
use std::ops::Add;
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use ecow::EcoString;
|
use ecow::EcoString;
|
||||||
|
|
||||||
use super::{castable, Array, Dict, Func, Regex, Str, Value};
|
use super::{Array, Str, Value};
|
||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
use crate::doc::{Destination, Lang, Location, Region};
|
|
||||||
use crate::font::{FontStretch, FontStyle, FontWeight};
|
|
||||||
use crate::geom::{
|
|
||||||
Axes, Color, Corners, Dir, GenAlign, Get, Length, Paint, PartialStroke, Point, Ratio,
|
|
||||||
Rel, Sides, Smart,
|
|
||||||
};
|
|
||||||
use crate::model::{Content, Label, Selector, Transform};
|
|
||||||
use crate::syntax::Spanned;
|
use crate::syntax::Spanned;
|
||||||
|
|
||||||
/// Cast from a value to a specific type.
|
/// Cast from a value to a specific type.
|
||||||
|
|
@ -32,6 +26,191 @@ pub trait Cast<V = Value>: Sized {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Cast for Value {
|
||||||
|
fn is(_: &Value) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cast(value: Value) -> StrResult<Self> {
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn describe() -> CastInfo {
|
||||||
|
CastInfo::Any
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Cast> Cast<Spanned<Value>> for T {
|
||||||
|
fn is(value: &Spanned<Value>) -> bool {
|
||||||
|
T::is(&value.v)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cast(value: Spanned<Value>) -> StrResult<Self> {
|
||||||
|
T::cast(value.v)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn describe() -> CastInfo {
|
||||||
|
T::describe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Cast> Cast<Spanned<Value>> for Spanned<T> {
|
||||||
|
fn is(value: &Spanned<Value>) -> bool {
|
||||||
|
T::is(&value.v)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cast(value: Spanned<Value>) -> StrResult<Self> {
|
||||||
|
let span = value.span;
|
||||||
|
T::cast(value.v).map(|t| Spanned::new(t, span))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn describe() -> CastInfo {
|
||||||
|
T::describe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: u8 => Value::Int(v as i64)
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: u16 => Value::Int(v as i64)
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
u32,
|
||||||
|
int: i64 => int.try_into().map_err(|_| {
|
||||||
|
if int < 0 {
|
||||||
|
"number must be at least zero"
|
||||||
|
} else {
|
||||||
|
"number too large"
|
||||||
|
}
|
||||||
|
})?,
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: u32 => Value::Int(v as i64)
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: i32 => Value::Int(v as i64)
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
usize,
|
||||||
|
int: i64 => int.try_into().map_err(|_| {
|
||||||
|
if int < 0 {
|
||||||
|
"number must be at least zero"
|
||||||
|
} else {
|
||||||
|
"number too large"
|
||||||
|
}
|
||||||
|
})?,
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: usize => Value::Int(v as i64)
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
NonZeroUsize,
|
||||||
|
int: i64 => int
|
||||||
|
.try_into()
|
||||||
|
.and_then(|int: usize| int.try_into())
|
||||||
|
.map_err(|_| if int <= 0 {
|
||||||
|
"number must be positive"
|
||||||
|
} else {
|
||||||
|
"number too large"
|
||||||
|
})?,
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: NonZeroUsize => Value::Int(v.get() as i64)
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
char,
|
||||||
|
string: Str => {
|
||||||
|
let mut chars = string.chars();
|
||||||
|
match (chars.next(), chars.next()) {
|
||||||
|
(Some(c), None) => c,
|
||||||
|
_ => Err("expected exactly one character")?,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: char => Value::Str(v.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: &str => Value::Str(v.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
EcoString,
|
||||||
|
v: Str => v.into(),
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: EcoString => Value::Str(v.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
String,
|
||||||
|
v: Str => v.into(),
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: String => Value::Str(v.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Cast> Cast for Option<T> {
|
||||||
|
fn is(value: &Value) -> bool {
|
||||||
|
matches!(value, Value::None) || T::is(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cast(value: Value) -> StrResult<Self> {
|
||||||
|
match value {
|
||||||
|
Value::None => Ok(None),
|
||||||
|
v if T::is(&v) => Ok(Some(T::cast(v)?)),
|
||||||
|
_ => <Self as Cast>::error(value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn describe() -> CastInfo {
|
||||||
|
T::describe() + CastInfo::Type("none")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Into<Value>> From<Option<T>> for Value {
|
||||||
|
fn from(v: Option<T>) -> Self {
|
||||||
|
match v {
|
||||||
|
Some(v) => v.into(),
|
||||||
|
None => Value::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Cast> Cast for Vec<T> {
|
||||||
|
fn is(value: &Value) -> bool {
|
||||||
|
Array::is(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cast(value: Value) -> StrResult<Self> {
|
||||||
|
value.cast::<Array>()?.into_iter().map(Value::cast).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn describe() -> CastInfo {
|
||||||
|
<Array as Cast>::describe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Into<Value>> From<Vec<T>> for Value {
|
||||||
|
fn from(v: Vec<T>) -> Self {
|
||||||
|
Value::Array(v.into_iter().map(Into::into).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Describes a possible value for a cast.
|
/// Describes a possible value for a cast.
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub enum CastInfo {
|
pub enum CastInfo {
|
||||||
|
|
@ -114,400 +293,19 @@ impl Add for CastInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cast for Value {
|
/// Castable from nothing.
|
||||||
|
pub enum Never {}
|
||||||
|
|
||||||
|
impl Cast for Never {
|
||||||
fn is(_: &Value) -> bool {
|
fn is(_: &Value) -> bool {
|
||||||
true
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cast(value: Value) -> StrResult<Self> {
|
fn cast(value: Value) -> StrResult<Self> {
|
||||||
Ok(value)
|
<Self as Cast>::error(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn describe() -> CastInfo {
|
fn describe() -> CastInfo {
|
||||||
CastInfo::Any
|
CastInfo::Union(vec![])
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Cast> Cast<Spanned<Value>> for T {
|
|
||||||
fn is(value: &Spanned<Value>) -> bool {
|
|
||||||
T::is(&value.v)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cast(value: Spanned<Value>) -> StrResult<Self> {
|
|
||||||
T::cast(value.v)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn describe() -> CastInfo {
|
|
||||||
T::describe()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Cast> Cast<Spanned<Value>> for Spanned<T> {
|
|
||||||
fn is(value: &Spanned<Value>) -> bool {
|
|
||||||
T::is(&value.v)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cast(value: Spanned<Value>) -> StrResult<Self> {
|
|
||||||
let span = value.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! {
|
|
||||||
usize,
|
|
||||||
int: i64 => int.try_into().map_err(|_| {
|
|
||||||
if int < 0 {
|
|
||||||
"number must be at least zero"
|
|
||||||
} else {
|
|
||||||
"number too large"
|
|
||||||
}
|
|
||||||
})?,
|
|
||||||
}
|
|
||||||
|
|
||||||
castable! {
|
|
||||||
NonZeroUsize,
|
|
||||||
int: i64 => int
|
|
||||||
.try_into()
|
|
||||||
.and_then(|int: usize| int.try_into())
|
|
||||||
.map_err(|_| if int <= 0 {
|
|
||||||
"number must be positive"
|
|
||||||
} else {
|
|
||||||
"number too large"
|
|
||||||
})?,
|
|
||||||
}
|
|
||||||
|
|
||||||
castable! {
|
|
||||||
Paint,
|
|
||||||
color: Color => Self::Solid(color),
|
|
||||||
}
|
|
||||||
|
|
||||||
castable! {
|
|
||||||
char,
|
|
||||||
string: Str => {
|
|
||||||
let mut chars = string.chars();
|
|
||||||
match (chars.next(), chars.next()) {
|
|
||||||
(Some(c), None) => c,
|
|
||||||
_ => Err("expected exactly one character")?,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
castable! {
|
|
||||||
EcoString,
|
|
||||||
string: Str => string.into(),
|
|
||||||
}
|
|
||||||
|
|
||||||
castable! {
|
|
||||||
String,
|
|
||||||
string: Str => string.into(),
|
|
||||||
}
|
|
||||||
|
|
||||||
castable! {
|
|
||||||
Transform,
|
|
||||||
content: Content => Self::Content(content),
|
|
||||||
func: Func => {
|
|
||||||
if func.argc().map_or(false, |count| count != 1) {
|
|
||||||
Err("function must have exactly one parameter")?
|
|
||||||
}
|
|
||||||
Self::Func(func)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
castable! {
|
|
||||||
Axes<Option<GenAlign>>,
|
|
||||||
align: GenAlign => {
|
|
||||||
let mut aligns = Axes::default();
|
|
||||||
aligns.set(align.axis(), Some(align));
|
|
||||||
aligns
|
|
||||||
},
|
|
||||||
aligns: Axes<GenAlign> => aligns.map(Some),
|
|
||||||
}
|
|
||||||
|
|
||||||
castable! {
|
|
||||||
Axes<Rel<Length>>,
|
|
||||||
array: Array => {
|
|
||||||
let mut iter = array.into_iter();
|
|
||||||
match (iter.next(), iter.next(), iter.next()) {
|
|
||||||
(Some(a), Some(b), None) => Axes::new(a.cast()?, b.cast()?),
|
|
||||||
_ => Err("point array must contain exactly two entries")?,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
castable! {
|
|
||||||
Location,
|
|
||||||
mut dict: Dict => {
|
|
||||||
let page = dict.take("page")?.cast()?;
|
|
||||||
let x: Length = dict.take("x")?.cast()?;
|
|
||||||
let y: Length = dict.take("y")?.cast()?;
|
|
||||||
dict.finish(&["page", "x", "y"])?;
|
|
||||||
Self { page, pos: Point::new(x.abs, y.abs) }
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
castable! {
|
|
||||||
Destination,
|
|
||||||
loc: Location => Self::Internal(loc),
|
|
||||||
string: EcoString => Self::Url(string),
|
|
||||||
}
|
|
||||||
|
|
||||||
castable! {
|
|
||||||
FontStyle,
|
|
||||||
/// The default, typically upright style.
|
|
||||||
"normal" => Self::Normal,
|
|
||||||
/// A cursive style with custom letterform.
|
|
||||||
"italic" => Self::Italic,
|
|
||||||
/// Just a slanted version of the normal style.
|
|
||||||
"oblique" => Self::Oblique,
|
|
||||||
}
|
|
||||||
|
|
||||||
castable! {
|
|
||||||
FontWeight,
|
|
||||||
v: i64 => Self::from_number(v.clamp(0, u16::MAX as i64) as u16),
|
|
||||||
/// Thin weight (100).
|
|
||||||
"thin" => Self::THIN,
|
|
||||||
/// Extra light weight (200).
|
|
||||||
"extralight" => Self::EXTRALIGHT,
|
|
||||||
/// Light weight (300).
|
|
||||||
"light" => Self::LIGHT,
|
|
||||||
/// Regular weight (400).
|
|
||||||
"regular" => Self::REGULAR,
|
|
||||||
/// Medium weight (500).
|
|
||||||
"medium" => Self::MEDIUM,
|
|
||||||
/// Semibold weight (600).
|
|
||||||
"semibold" => Self::SEMIBOLD,
|
|
||||||
/// Bold weight (700).
|
|
||||||
"bold" => Self::BOLD,
|
|
||||||
/// Extrabold weight (800).
|
|
||||||
"extrabold" => Self::EXTRABOLD,
|
|
||||||
/// Black weight (900).
|
|
||||||
"black" => Self::BLACK,
|
|
||||||
}
|
|
||||||
|
|
||||||
castable! {
|
|
||||||
FontStretch,
|
|
||||||
v: Ratio => Self::from_ratio(v.get() as f32),
|
|
||||||
}
|
|
||||||
|
|
||||||
castable! {
|
|
||||||
Lang,
|
|
||||||
string: EcoString => Self::from_str(&string)?,
|
|
||||||
}
|
|
||||||
|
|
||||||
castable! {
|
|
||||||
Region,
|
|
||||||
string: EcoString => 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> {
|
|
||||||
fn is(value: &Value) -> bool {
|
|
||||||
matches!(value, Value::None) || T::is(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cast(value: Value) -> StrResult<Self> {
|
|
||||||
match value {
|
|
||||||
Value::None => Ok(None),
|
|
||||||
v if T::is(&v) => Ok(Some(T::cast(v)?)),
|
|
||||||
_ => <Self as Cast>::error(value),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn describe() -> CastInfo {
|
|
||||||
T::describe() + CastInfo::Type("none")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Castable from [`Value::Auto`].
|
|
||||||
pub struct AutoValue;
|
|
||||||
|
|
||||||
impl Cast for AutoValue {
|
|
||||||
fn is(value: &Value) -> bool {
|
|
||||||
matches!(value, Value::Auto)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cast(value: Value) -> StrResult<Self> {
|
|
||||||
match value {
|
|
||||||
Value::Auto => Ok(Self),
|
|
||||||
_ => <Self as Cast>::error(value),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn describe() -> CastInfo {
|
|
||||||
CastInfo::Type("auto")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Cast> Cast for Smart<T> {
|
|
||||||
fn is(value: &Value) -> bool {
|
|
||||||
matches!(value, Value::Auto) || T::is(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cast(value: Value) -> StrResult<Self> {
|
|
||||||
match value {
|
|
||||||
Value::Auto => Ok(Self::Auto),
|
|
||||||
v if T::is(&v) => Ok(Self::Custom(T::cast(v)?)),
|
|
||||||
_ => <Self as Cast>::error(value),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn describe() -> CastInfo {
|
|
||||||
T::describe() + CastInfo::Type("auto")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Cast for Sides<Option<T>>
|
|
||||||
where
|
|
||||||
T: Cast + Copy,
|
|
||||||
{
|
|
||||||
fn is(value: &Value) -> bool {
|
|
||||||
matches!(value, Value::Dict(_)) || T::is(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cast(mut value: Value) -> StrResult<Self> {
|
|
||||||
if let Value::Dict(dict) = &mut value {
|
|
||||||
let mut take = |key| dict.take(key).ok().map(T::cast).transpose();
|
|
||||||
|
|
||||||
let rest = take("rest")?;
|
|
||||||
let x = take("x")?.or(rest);
|
|
||||||
let y = take("y")?.or(rest);
|
|
||||||
let sides = Sides {
|
|
||||||
left: take("left")?.or(x),
|
|
||||||
top: take("top")?.or(y),
|
|
||||||
right: take("right")?.or(x),
|
|
||||||
bottom: take("bottom")?.or(y),
|
|
||||||
};
|
|
||||||
|
|
||||||
dict.finish(&["left", "top", "right", "bottom", "x", "y", "rest"])?;
|
|
||||||
|
|
||||||
Ok(sides)
|
|
||||||
} else if T::is(&value) {
|
|
||||||
Ok(Self::splat(Some(T::cast(value)?)))
|
|
||||||
} else {
|
|
||||||
<Self as Cast>::error(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn describe() -> CastInfo {
|
|
||||||
T::describe() + CastInfo::Type("dictionary")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Cast for Corners<Option<T>>
|
|
||||||
where
|
|
||||||
T: Cast + Copy,
|
|
||||||
{
|
|
||||||
fn is(value: &Value) -> bool {
|
|
||||||
matches!(value, Value::Dict(_)) || T::is(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cast(mut value: Value) -> StrResult<Self> {
|
|
||||||
if let Value::Dict(dict) = &mut value {
|
|
||||||
let mut take = |key| dict.take(key).ok().map(T::cast).transpose();
|
|
||||||
|
|
||||||
let rest = take("rest")?;
|
|
||||||
let left = take("left")?.or(rest);
|
|
||||||
let top = take("top")?.or(rest);
|
|
||||||
let right = take("right")?.or(rest);
|
|
||||||
let bottom = take("bottom")?.or(rest);
|
|
||||||
let corners = Corners {
|
|
||||||
top_left: take("top-left")?.or(top).or(left),
|
|
||||||
top_right: take("top-right")?.or(top).or(right),
|
|
||||||
bottom_right: take("bottom-right")?.or(bottom).or(right),
|
|
||||||
bottom_left: take("bottom-left")?.or(bottom).or(left),
|
|
||||||
};
|
|
||||||
|
|
||||||
dict.finish(&[
|
|
||||||
"top-left",
|
|
||||||
"top-right",
|
|
||||||
"bottom-right",
|
|
||||||
"bottom-left",
|
|
||||||
"left",
|
|
||||||
"top",
|
|
||||||
"right",
|
|
||||||
"bottom",
|
|
||||||
"rest",
|
|
||||||
])?;
|
|
||||||
|
|
||||||
Ok(corners)
|
|
||||||
} else if T::is(&value) {
|
|
||||||
Ok(Self::splat(Some(T::cast(value)?)))
|
|
||||||
} else {
|
|
||||||
<Self as Cast>::error(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn describe() -> CastInfo {
|
|
||||||
T::describe() + CastInfo::Type("dictionary")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
pub use typst_macros::func;
|
||||||
|
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ pub struct LangItems {
|
||||||
/// The id of the text node.
|
/// The id of the text node.
|
||||||
pub text_id: NodeId,
|
pub text_id: NodeId,
|
||||||
/// Get the string if this is a text node.
|
/// Get the string if this is a text node.
|
||||||
pub text_str: fn(&Content) -> Option<&str>,
|
pub text_str: fn(&Content) -> Option<EcoString>,
|
||||||
/// A smart quote: `'` or `"`.
|
/// A smart quote: `'` or `"`.
|
||||||
pub smart_quote: fn(double: bool) -> Content,
|
pub smart_quote: fn(double: bool) -> Content,
|
||||||
/// A paragraph break.
|
/// A paragraph break.
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,6 @@ mod ops;
|
||||||
mod scope;
|
mod scope;
|
||||||
mod symbol;
|
mod symbol;
|
||||||
|
|
||||||
pub use typst_macros::{castable, func};
|
|
||||||
|
|
||||||
pub use self::args::*;
|
pub use self::args::*;
|
||||||
pub use self::array::*;
|
pub use self::array::*;
|
||||||
pub use self::cast::*;
|
pub use self::cast::*;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use std::ops::{Add, AddAssign, Deref};
|
||||||
use ecow::EcoString;
|
use ecow::EcoString;
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use super::{castable, dict, Array, Dict, Value};
|
use super::{cast_from_value, dict, Array, Dict, Value};
|
||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
use crate::geom::GenAlign;
|
use crate::geom::GenAlign;
|
||||||
|
|
||||||
|
|
@ -479,6 +479,10 @@ impl Hash for Regex {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
Regex: "regular expression",
|
||||||
|
}
|
||||||
|
|
||||||
/// A pattern which can be searched for in a string.
|
/// A pattern which can be searched for in a string.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum StrPattern {
|
pub enum StrPattern {
|
||||||
|
|
@ -488,7 +492,7 @@ pub enum StrPattern {
|
||||||
Regex(Regex),
|
Regex(Regex),
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
StrPattern,
|
StrPattern,
|
||||||
text: Str => Self::Str(text),
|
text: Str => Self::Str(text),
|
||||||
regex: Regex => Self::Regex(regex),
|
regex: Regex => Self::Regex(regex),
|
||||||
|
|
@ -504,7 +508,7 @@ pub enum StrSide {
|
||||||
End,
|
End,
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
cast_from_value! {
|
||||||
StrSide,
|
StrSide,
|
||||||
align: GenAlign => match align {
|
align: GenAlign => match align {
|
||||||
GenAlign::Start => Self::Start,
|
GenAlign::Start => Self::Start,
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,15 @@ use std::fmt::{self, Debug, Formatter};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use ecow::{eco_format, EcoString};
|
use ecow::eco_format;
|
||||||
use siphasher::sip128::{Hasher128, SipHasher};
|
use siphasher::sip128::{Hasher128, SipHasher};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
format_str, ops, Args, Array, Cast, CastInfo, Content, Dict, Func, Label, Module,
|
cast_to_value, format_str, ops, Args, Array, Cast, CastInfo, Content, Dict, Func,
|
||||||
Str, Symbol,
|
Label, Module, Str, Symbol,
|
||||||
};
|
};
|
||||||
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};
|
||||||
use crate::syntax::{ast, Span};
|
use crate::syntax::{ast, Span};
|
||||||
|
|
||||||
/// A computational value.
|
/// A computational value.
|
||||||
|
|
@ -122,6 +122,7 @@ impl Value {
|
||||||
Self::Dict(dict) => dict.at(&field).cloned(),
|
Self::Dict(dict) => dict.at(&field).cloned(),
|
||||||
Self::Content(content) => content
|
Self::Content(content) => content
|
||||||
.field(&field)
|
.field(&field)
|
||||||
|
.cloned()
|
||||||
.ok_or_else(|| eco_format!("unknown field `{field}`")),
|
.ok_or_else(|| eco_format!("unknown field `{field}`")),
|
||||||
Self::Module(module) => module.get(&field).cloned(),
|
Self::Module(module) => module.get(&field).cloned(),
|
||||||
v => Err(eco_format!("cannot access fields on type {}", v.type_name())),
|
v => Err(eco_format!("cannot access fields on type {}", v.type_name())),
|
||||||
|
|
@ -241,60 +242,6 @@ impl Hash for Value {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<i32> for Value {
|
|
||||||
fn from(v: i32) -> Self {
|
|
||||||
Self::Int(v as i64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<usize> for Value {
|
|
||||||
fn from(v: usize) -> Self {
|
|
||||||
Self::Int(v as i64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Abs> for Value {
|
|
||||||
fn from(v: Abs) -> Self {
|
|
||||||
Self::Length(v.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Em> for Value {
|
|
||||||
fn from(v: Em) -> Self {
|
|
||||||
Self::Length(v.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<RgbaColor> for Value {
|
|
||||||
fn from(v: RgbaColor) -> Self {
|
|
||||||
Self::Color(v.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&str> for Value {
|
|
||||||
fn from(v: &str) -> Self {
|
|
||||||
Self::Str(v.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<EcoString> for Value {
|
|
||||||
fn from(v: EcoString) -> Self {
|
|
||||||
Self::Str(v.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<String> for Value {
|
|
||||||
fn from(v: String) -> Self {
|
|
||||||
Self::Str(v.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Dynamic> for Value {
|
|
||||||
fn from(v: Dynamic) -> Self {
|
|
||||||
Self::Dyn(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A dynamic value.
|
/// A dynamic value.
|
||||||
#[derive(Clone, Hash)]
|
#[derive(Clone, Hash)]
|
||||||
pub struct Dynamic(Arc<dyn Bounds>);
|
pub struct Dynamic(Arc<dyn Bounds>);
|
||||||
|
|
@ -336,6 +283,10 @@ impl PartialEq for Dynamic {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: Dynamic => Value::Dyn(v)
|
||||||
|
}
|
||||||
|
|
||||||
trait Bounds: Debug + Sync + Send + 'static {
|
trait Bounds: Debug + Sync + Send + 'static {
|
||||||
fn as_any(&self) -> &dyn Any;
|
fn as_any(&self) -> &dyn Any;
|
||||||
fn dyn_eq(&self, other: &Dynamic) -> bool;
|
fn dyn_eq(&self, other: &Dynamic) -> bool;
|
||||||
|
|
@ -462,6 +413,7 @@ primitive! { Args: "arguments", Args }
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::eval::{array, dict};
|
use crate::eval::{array, dict};
|
||||||
|
use crate::geom::RgbaColor;
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn test(value: impl Into<Value>, exp: &str) {
|
fn test(value: impl Into<Value>, exp: &str) {
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use ttf_parser::GlyphId;
|
use ttf_parser::GlyphId;
|
||||||
|
|
||||||
|
use crate::eval::{cast_from_value, cast_to_value, Value};
|
||||||
use crate::geom::Em;
|
use crate::geom::Em;
|
||||||
use crate::util::Buffer;
|
use crate::util::Buffer;
|
||||||
|
|
||||||
|
|
@ -249,3 +250,27 @@ pub enum VerticalFontMetric {
|
||||||
/// present and falls back to the descender from the `hhea` table otherwise.
|
/// present and falls back to the descender from the `hhea` table otherwise.
|
||||||
Descender,
|
Descender,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
VerticalFontMetric,
|
||||||
|
/// The font's ascender, which typically exceeds the height of all glyphs.
|
||||||
|
"ascender" => Self::Ascender,
|
||||||
|
/// The approximate height of uppercase letters.
|
||||||
|
"cap-height" => Self::CapHeight,
|
||||||
|
/// The approximate height of non-ascending lowercase letters.
|
||||||
|
"x-height" => Self::XHeight,
|
||||||
|
/// The baseline on which the letters rest.
|
||||||
|
"baseline" => Self::Baseline,
|
||||||
|
/// The font's ascender, which typically exceeds the depth of all glyphs.
|
||||||
|
"descender" => Self::Descender,
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: VerticalFontMetric => Value::from(match v {
|
||||||
|
VerticalFontMetric::Ascender => "ascender",
|
||||||
|
VerticalFontMetric::CapHeight => "cap-height",
|
||||||
|
VerticalFontMetric::XHeight => "x-height",
|
||||||
|
VerticalFontMetric::Baseline => "baseline" ,
|
||||||
|
VerticalFontMetric::Descender => "descender",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,9 @@ use std::fmt::{self, Debug, Formatter};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::eval::{cast_from_value, cast_to_value, Value};
|
||||||
|
use crate::geom::Ratio;
|
||||||
|
|
||||||
/// Properties that distinguish a font from other fonts in the same family.
|
/// Properties that distinguish a font from other fonts in the same family.
|
||||||
#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
|
@ -59,6 +62,24 @@ impl Default for FontStyle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
FontStyle,
|
||||||
|
/// The default, typically upright style.
|
||||||
|
"normal" => Self::Normal,
|
||||||
|
/// A cursive style with custom letterform.
|
||||||
|
"italic" => Self::Italic,
|
||||||
|
/// Just a slanted version of the normal style.
|
||||||
|
"oblique" => Self::Oblique,
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: FontStyle => Value::from(match v {
|
||||||
|
FontStyle::Normal => "normal",
|
||||||
|
FontStyle::Italic => "italic",
|
||||||
|
FontStyle::Oblique => "oblique",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// The weight of a font.
|
/// The weight of a font.
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
|
@ -127,6 +148,44 @@ impl Debug for FontWeight {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
FontWeight,
|
||||||
|
v: i64 => Self::from_number(v.clamp(0, u16::MAX as i64) as u16),
|
||||||
|
/// Thin weight (100).
|
||||||
|
"thin" => Self::THIN,
|
||||||
|
/// Extra light weight (200).
|
||||||
|
"extralight" => Self::EXTRALIGHT,
|
||||||
|
/// Light weight (300).
|
||||||
|
"light" => Self::LIGHT,
|
||||||
|
/// Regular weight (400).
|
||||||
|
"regular" => Self::REGULAR,
|
||||||
|
/// Medium weight (500).
|
||||||
|
"medium" => Self::MEDIUM,
|
||||||
|
/// Semibold weight (600).
|
||||||
|
"semibold" => Self::SEMIBOLD,
|
||||||
|
/// Bold weight (700).
|
||||||
|
"bold" => Self::BOLD,
|
||||||
|
/// Extrabold weight (800).
|
||||||
|
"extrabold" => Self::EXTRABOLD,
|
||||||
|
/// Black weight (900).
|
||||||
|
"black" => Self::BLACK,
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: FontWeight => Value::from(match v {
|
||||||
|
FontWeight::THIN => "thin",
|
||||||
|
FontWeight::EXTRALIGHT => "extralight",
|
||||||
|
FontWeight::LIGHT => "light",
|
||||||
|
FontWeight::REGULAR => "regular",
|
||||||
|
FontWeight::MEDIUM => "medium",
|
||||||
|
FontWeight::SEMIBOLD => "semibold",
|
||||||
|
FontWeight::BOLD => "bold",
|
||||||
|
FontWeight::EXTRABOLD => "extrabold",
|
||||||
|
FontWeight::BLACK => "black",
|
||||||
|
_ => return v.to_number().into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// The width of a font.
|
/// The width of a font.
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
|
@ -163,8 +222,8 @@ impl FontStretch {
|
||||||
|
|
||||||
/// Create a font stretch from a ratio between 0.5 and 2.0, clamping it if
|
/// Create a font stretch from a ratio between 0.5 and 2.0, clamping it if
|
||||||
/// necessary.
|
/// necessary.
|
||||||
pub fn from_ratio(ratio: f32) -> Self {
|
pub fn from_ratio(ratio: Ratio) -> Self {
|
||||||
Self((ratio.max(0.5).min(2.0) * 1000.0) as u16)
|
Self((ratio.get().max(0.5).min(2.0) * 1000.0) as u16)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a font stretch from an OpenType-style number between 1 and 9,
|
/// Create a font stretch from an OpenType-style number between 1 and 9,
|
||||||
|
|
@ -184,12 +243,12 @@ impl FontStretch {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The ratio between 0.5 and 2.0 corresponding to this stretch.
|
/// The ratio between 0.5 and 2.0 corresponding to this stretch.
|
||||||
pub fn to_ratio(self) -> f32 {
|
pub fn to_ratio(self) -> Ratio {
|
||||||
self.0 as f32 / 1000.0
|
Ratio::new(self.0 as f64 / 1000.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The absolute ratio distance between this and another font stretch.
|
/// The absolute ratio distance between this and another font stretch.
|
||||||
pub fn distance(self, other: Self) -> f32 {
|
pub fn distance(self, other: Self) -> Ratio {
|
||||||
(self.to_ratio() - other.to_ratio()).abs()
|
(self.to_ratio() - other.to_ratio()).abs()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -202,10 +261,19 @@ impl Default for FontStretch {
|
||||||
|
|
||||||
impl Debug for FontStretch {
|
impl Debug for FontStretch {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
write!(f, "{}%", 100.0 * self.to_ratio())
|
self.to_ratio().fmt(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
FontStretch,
|
||||||
|
v: Ratio => Self::from_ratio(v),
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: FontStretch => v.to_ratio().into()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
||||||
|
|
@ -214,6 +214,10 @@ impl<'a> Sum<&'a Self> for Abs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: Abs => Value::Length(v.into())
|
||||||
|
}
|
||||||
|
|
||||||
/// Different units of absolute measurement.
|
/// Different units of absolute measurement.
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum AbsUnit {
|
pub enum AbsUnit {
|
||||||
|
|
|
||||||
|
|
@ -115,3 +115,51 @@ impl Debug for GenAlign {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
GenAlign: "alignment",
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
Axes<GenAlign>: "2d alignment",
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
Axes<Option<GenAlign>>,
|
||||||
|
align: GenAlign => {
|
||||||
|
let mut aligns = Axes::default();
|
||||||
|
aligns.set(align.axis(), Some(align));
|
||||||
|
aligns
|
||||||
|
},
|
||||||
|
aligns: Axes<GenAlign> => aligns.map(Some),
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: Axes<Option<GenAlign>> => match (v.x, v.y) {
|
||||||
|
(Some(x), Some(y)) => Axes::new(x, y).into(),
|
||||||
|
(Some(x), None) => x.into(),
|
||||||
|
(None, Some(y)) => y.into(),
|
||||||
|
(None, None) => Value::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Resolve for GenAlign {
|
||||||
|
type Output = Align;
|
||||||
|
|
||||||
|
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||||
|
let dir = item!(dir)(styles);
|
||||||
|
match self {
|
||||||
|
Self::Start => dir.start().into(),
|
||||||
|
Self::End => dir.end().into(),
|
||||||
|
Self::Specific(align) => align,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fold for GenAlign {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn fold(self, _: Self::Output) -> Self::Output {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use std::any::Any;
|
||||||
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Not};
|
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Not};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::eval::Array;
|
||||||
|
|
||||||
/// A container with a horizontal and vertical component.
|
/// A container with a horizontal and vertical component.
|
||||||
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
|
@ -272,3 +273,37 @@ impl BitAndAssign for Axes<bool> {
|
||||||
self.y &= rhs.y;
|
self.y &= rhs.y;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
Axes<Rel<Length>>,
|
||||||
|
array: Array => {
|
||||||
|
let mut iter = array.into_iter();
|
||||||
|
match (iter.next(), iter.next(), iter.next()) {
|
||||||
|
(Some(a), Some(b), None) => Axes::new(a.cast()?, b.cast()?),
|
||||||
|
_ => Err("point array must contain exactly two entries")?,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: Axes<Rel<Length>> => Value::Array(array![v.x, v.y])
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Resolve> Resolve for Axes<T> {
|
||||||
|
type Output = Axes<T::Output>;
|
||||||
|
|
||||||
|
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||||
|
self.map(|v| v.resolve(styles))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Fold> Fold for Axes<Option<T>> {
|
||||||
|
type Output = Axes<T::Output>;
|
||||||
|
|
||||||
|
fn fold(self, outer: Self::Output) -> Self::Output {
|
||||||
|
self.zip(outer).map(|(inner, outer)| match inner {
|
||||||
|
Some(value) => value.fold(outer),
|
||||||
|
None => outer,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -107,3 +107,100 @@ pub enum Corner {
|
||||||
/// The bottom left corner.
|
/// The bottom left corner.
|
||||||
BottomLeft,
|
BottomLeft,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> Cast for Corners<Option<T>>
|
||||||
|
where
|
||||||
|
T: Cast + Copy,
|
||||||
|
{
|
||||||
|
fn is(value: &Value) -> bool {
|
||||||
|
matches!(value, Value::Dict(_)) || T::is(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cast(mut value: Value) -> StrResult<Self> {
|
||||||
|
if let Value::Dict(dict) = &mut value {
|
||||||
|
let mut take = |key| dict.take(key).ok().map(T::cast).transpose();
|
||||||
|
|
||||||
|
let rest = take("rest")?;
|
||||||
|
let left = take("left")?.or(rest);
|
||||||
|
let top = take("top")?.or(rest);
|
||||||
|
let right = take("right")?.or(rest);
|
||||||
|
let bottom = take("bottom")?.or(rest);
|
||||||
|
let corners = Corners {
|
||||||
|
top_left: take("top-left")?.or(top).or(left),
|
||||||
|
top_right: take("top-right")?.or(top).or(right),
|
||||||
|
bottom_right: take("bottom-right")?.or(bottom).or(right),
|
||||||
|
bottom_left: take("bottom-left")?.or(bottom).or(left),
|
||||||
|
};
|
||||||
|
|
||||||
|
dict.finish(&[
|
||||||
|
"top-left",
|
||||||
|
"top-right",
|
||||||
|
"bottom-right",
|
||||||
|
"bottom-left",
|
||||||
|
"left",
|
||||||
|
"top",
|
||||||
|
"right",
|
||||||
|
"bottom",
|
||||||
|
"rest",
|
||||||
|
])?;
|
||||||
|
|
||||||
|
Ok(corners)
|
||||||
|
} else if T::is(&value) {
|
||||||
|
Ok(Self::splat(Some(T::cast(value)?)))
|
||||||
|
} else {
|
||||||
|
<Self as Cast>::error(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn describe() -> CastInfo {
|
||||||
|
T::describe() + CastInfo::Type("dictionary")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Resolve> Resolve for Corners<T> {
|
||||||
|
type Output = Corners<T::Output>;
|
||||||
|
|
||||||
|
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||||
|
self.map(|v| v.resolve(styles))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Fold> Fold for Corners<Option<T>> {
|
||||||
|
type Output = Corners<T::Output>;
|
||||||
|
|
||||||
|
fn fold(self, outer: Self::Output) -> Self::Output {
|
||||||
|
self.zip(outer).map(|(inner, outer)| match inner {
|
||||||
|
Some(value) => value.fold(outer),
|
||||||
|
None => outer,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<Corners<Option<T>>> for Value
|
||||||
|
where
|
||||||
|
T: PartialEq + Into<Value>,
|
||||||
|
{
|
||||||
|
fn from(corners: Corners<Option<T>>) -> Self {
|
||||||
|
if corners.is_uniform() {
|
||||||
|
if let Some(value) = corners.top_left {
|
||||||
|
return value.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut dict = Dict::new();
|
||||||
|
if let Some(top_left) = corners.top_left {
|
||||||
|
dict.insert("top-left".into(), top_left.into());
|
||||||
|
}
|
||||||
|
if let Some(top_right) = corners.top_right {
|
||||||
|
dict.insert("top-right".into(), top_right.into());
|
||||||
|
}
|
||||||
|
if let Some(bottom_right) = corners.bottom_right {
|
||||||
|
dict.insert("bottom-right".into(), bottom_right.into());
|
||||||
|
}
|
||||||
|
if let Some(bottom_left) = corners.bottom_left {
|
||||||
|
dict.insert("bottom-left".into(), bottom_left.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Value::Dict(dict)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,3 +73,7 @@ impl Debug for Dir {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
Dir: "direction",
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -134,3 +134,19 @@ impl Sum for Em {
|
||||||
Self(iter.map(|s| s.0).sum())
|
Self(iter.map(|s| s.0).sum())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: Em => Value::Length(v.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Resolve for Em {
|
||||||
|
type Output = Abs;
|
||||||
|
|
||||||
|
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||||
|
if self.is_zero() {
|
||||||
|
Abs::zero()
|
||||||
|
} else {
|
||||||
|
self.at(item!(em)(styles))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -124,3 +124,11 @@ assign_impl!(Length += Length);
|
||||||
assign_impl!(Length -= Length);
|
assign_impl!(Length -= Length);
|
||||||
assign_impl!(Length *= f64);
|
assign_impl!(Length *= f64);
|
||||||
assign_impl!(Length /= f64);
|
assign_impl!(Length /= f64);
|
||||||
|
|
||||||
|
impl Resolve for Length {
|
||||||
|
type Output = Abs;
|
||||||
|
|
||||||
|
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||||
|
self.abs + self.em.resolve(styles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ mod ratio;
|
||||||
mod rel;
|
mod rel;
|
||||||
mod rounded;
|
mod rounded;
|
||||||
mod scalar;
|
mod scalar;
|
||||||
|
mod shape;
|
||||||
mod sides;
|
mod sides;
|
||||||
mod size;
|
mod size;
|
||||||
mod smart;
|
mod smart;
|
||||||
|
|
@ -42,6 +43,7 @@ pub use self::ratio::*;
|
||||||
pub use self::rel::*;
|
pub use self::rel::*;
|
||||||
pub use self::rounded::*;
|
pub use self::rounded::*;
|
||||||
pub use self::scalar::*;
|
pub use self::scalar::*;
|
||||||
|
pub use self::shape::*;
|
||||||
pub use self::sides::*;
|
pub use self::sides::*;
|
||||||
pub use self::size::*;
|
pub use self::size::*;
|
||||||
pub use self::smart::*;
|
pub use self::smart::*;
|
||||||
|
|
@ -55,6 +57,10 @@ use std::hash::{Hash, Hasher};
|
||||||
use std::iter::Sum;
|
use std::iter::Sum;
|
||||||
use std::ops::*;
|
use std::ops::*;
|
||||||
|
|
||||||
|
use crate::diag::StrResult;
|
||||||
|
use crate::eval::{array, cast_from_value, cast_to_value, Cast, CastInfo, Dict, Value};
|
||||||
|
use crate::model::{Fold, Resolve, StyleChain};
|
||||||
|
|
||||||
/// Generic access to a structure's components.
|
/// Generic access to a structure's components.
|
||||||
pub trait Get<Index> {
|
pub trait Get<Index> {
|
||||||
/// The structure's component type.
|
/// The structure's component type.
|
||||||
|
|
@ -72,40 +78,6 @@ pub trait Get<Index> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A geometric shape with optional fill and stroke.
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
|
||||||
pub struct Shape {
|
|
||||||
/// The shape's geometry.
|
|
||||||
pub geometry: Geometry,
|
|
||||||
/// The shape's background fill.
|
|
||||||
pub fill: Option<Paint>,
|
|
||||||
/// The shape's border stroke.
|
|
||||||
pub stroke: Option<Stroke>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A shape's geometry.
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
|
||||||
pub enum Geometry {
|
|
||||||
/// A line to a point (relative to its position).
|
|
||||||
Line(Point),
|
|
||||||
/// A rectangle with its origin in the topleft corner.
|
|
||||||
Rect(Size),
|
|
||||||
/// A bezier path.
|
|
||||||
Path(Path),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Geometry {
|
|
||||||
/// Fill the geometry without a stroke.
|
|
||||||
pub fn filled(self, fill: Paint) -> Shape {
|
|
||||||
Shape { geometry: self, fill: Some(fill), stroke: None }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stroke the geometry without a fill.
|
|
||||||
pub fn stroked(self, stroke: Stroke) -> Shape {
|
|
||||||
Shape { geometry: self, fill: None, stroke: Some(stroke) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A numeric type.
|
/// A numeric type.
|
||||||
pub trait Numeric:
|
pub trait Numeric:
|
||||||
Sized
|
Sized
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,7 @@ pub enum Paint {
|
||||||
Solid(Color),
|
Solid(Color),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> From<T> for Paint
|
impl<T: Into<Color>> From<T> for Paint {
|
||||||
where
|
|
||||||
T: Into<Color>,
|
|
||||||
{
|
|
||||||
fn from(t: T) -> Self {
|
fn from(t: T) -> Self {
|
||||||
Self::Solid(t.into())
|
Self::Solid(t.into())
|
||||||
}
|
}
|
||||||
|
|
@ -26,6 +23,15 @@ impl Debug for Paint {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
Paint,
|
||||||
|
color: Color => Self::Solid(color),
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
Paint::Solid(color): Paint => Value::Color(color)
|
||||||
|
}
|
||||||
|
|
||||||
/// A color in a dynamic format.
|
/// A color in a dynamic format.
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum Color {
|
pub enum Color {
|
||||||
|
|
@ -274,15 +280,16 @@ impl Debug for RgbaColor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> From<T> for Color
|
impl<T: Into<RgbaColor>> From<T> for Color {
|
||||||
where
|
|
||||||
T: Into<RgbaColor>,
|
|
||||||
{
|
|
||||||
fn from(rgba: T) -> Self {
|
fn from(rgba: T) -> Self {
|
||||||
Self::Rgba(rgba.into())
|
Self::Rgba(rgba.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: RgbaColor => Value::Color(v.into())
|
||||||
|
}
|
||||||
|
|
||||||
/// An 8-bit CMYK color.
|
/// An 8-bit CMYK color.
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct CmykColor {
|
pub struct CmykColor {
|
||||||
|
|
|
||||||
|
|
@ -199,3 +199,31 @@ impl<T: Numeric> Add<Ratio> for Rel<T> {
|
||||||
self + Rel::from(other)
|
self + Rel::from(other)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> Resolve for Rel<T>
|
||||||
|
where
|
||||||
|
T: Resolve + Numeric,
|
||||||
|
<T as Resolve>::Output: Numeric,
|
||||||
|
{
|
||||||
|
type Output = Rel<<T as Resolve>::Output>;
|
||||||
|
|
||||||
|
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||||
|
self.map(|abs| abs.resolve(styles))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fold for Rel<Abs> {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn fold(self, _: Self::Output) -> Self::Output {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fold for Rel<Length> {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn fold(self, _: Self::Output) -> Self::Output {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// A geometric shape with optional fill and stroke.
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub struct Shape {
|
||||||
|
/// The shape's geometry.
|
||||||
|
pub geometry: Geometry,
|
||||||
|
/// The shape's background fill.
|
||||||
|
pub fill: Option<Paint>,
|
||||||
|
/// The shape's border stroke.
|
||||||
|
pub stroke: Option<Stroke>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A shape's geometry.
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub enum Geometry {
|
||||||
|
/// A line to a point (relative to its position).
|
||||||
|
Line(Point),
|
||||||
|
/// A rectangle with its origin in the topleft corner.
|
||||||
|
Rect(Size),
|
||||||
|
/// A bezier path.
|
||||||
|
Path(Path),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Geometry {
|
||||||
|
/// Fill the geometry without a stroke.
|
||||||
|
pub fn filled(self, fill: Paint) -> Shape {
|
||||||
|
Shape { geometry: self, fill: Some(fill), stroke: None }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stroke the geometry without a fill.
|
||||||
|
pub fn stroked(self, stroke: Stroke) -> Shape {
|
||||||
|
Shape { geometry: self, fill: None, stroke: Some(stroke) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -177,3 +177,88 @@ impl Side {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> Cast for Sides<Option<T>>
|
||||||
|
where
|
||||||
|
T: Default + Cast + Copy,
|
||||||
|
{
|
||||||
|
fn is(value: &Value) -> bool {
|
||||||
|
matches!(value, Value::Dict(_)) || T::is(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cast(mut value: Value) -> StrResult<Self> {
|
||||||
|
if let Value::Dict(dict) = &mut value {
|
||||||
|
let mut take = |key| dict.take(key).ok().map(T::cast).transpose();
|
||||||
|
|
||||||
|
let rest = take("rest")?;
|
||||||
|
let x = take("x")?.or(rest);
|
||||||
|
let y = take("y")?.or(rest);
|
||||||
|
let sides = Sides {
|
||||||
|
left: take("left")?.or(x),
|
||||||
|
top: take("top")?.or(y),
|
||||||
|
right: take("right")?.or(x),
|
||||||
|
bottom: take("bottom")?.or(y),
|
||||||
|
};
|
||||||
|
|
||||||
|
dict.finish(&["left", "top", "right", "bottom", "x", "y", "rest"])?;
|
||||||
|
|
||||||
|
Ok(sides)
|
||||||
|
} else if T::is(&value) {
|
||||||
|
Ok(Self::splat(Some(T::cast(value)?)))
|
||||||
|
} else {
|
||||||
|
<Self as Cast>::error(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn describe() -> CastInfo {
|
||||||
|
T::describe() + CastInfo::Type("dictionary")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<Sides<Option<T>>> for Value
|
||||||
|
where
|
||||||
|
T: PartialEq + Into<Value>,
|
||||||
|
{
|
||||||
|
fn from(sides: Sides<Option<T>>) -> Self {
|
||||||
|
if sides.is_uniform() {
|
||||||
|
if let Some(value) = sides.left {
|
||||||
|
return value.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut dict = Dict::new();
|
||||||
|
if let Some(left) = sides.left {
|
||||||
|
dict.insert("left".into(), left.into());
|
||||||
|
}
|
||||||
|
if let Some(top) = sides.top {
|
||||||
|
dict.insert("top".into(), top.into());
|
||||||
|
}
|
||||||
|
if let Some(right) = sides.right {
|
||||||
|
dict.insert("right".into(), right.into());
|
||||||
|
}
|
||||||
|
if let Some(bottom) = sides.bottom {
|
||||||
|
dict.insert("bottom".into(), bottom.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Value::Dict(dict)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Resolve> Resolve for Sides<T> {
|
||||||
|
type Output = Sides<T::Output>;
|
||||||
|
|
||||||
|
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||||
|
self.map(|v| v.resolve(styles))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Fold> Fold for Sides<Option<T>> {
|
||||||
|
type Output = Sides<T::Output>;
|
||||||
|
|
||||||
|
fn fold(self, outer: Self::Output) -> Self::Output {
|
||||||
|
self.zip(outer).map(|(inner, outer)| match inner {
|
||||||
|
Some(value) => value.fold(outer),
|
||||||
|
None => outer,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,18 @@ impl<T> Smart<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Map the contained custom value with `f` if it contains a custom value,
|
||||||
|
/// otherwise returns `default`.
|
||||||
|
pub fn map_or<F, U>(self, default: U, f: F) -> U
|
||||||
|
where
|
||||||
|
F: FnOnce(T) -> U,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::Auto => default,
|
||||||
|
Self::Custom(x) => f(x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Keeps `self` if it contains a custom value, otherwise returns `other`.
|
/// Keeps `self` if it contains a custom value, otherwise returns `other`.
|
||||||
pub fn or(self, other: Smart<T>) -> Self {
|
pub fn or(self, other: Smart<T>) -> Self {
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -72,3 +84,50 @@ impl<T> Default for Smart<T> {
|
||||||
Self::Auto
|
Self::Auto
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: Cast> Cast for Smart<T> {
|
||||||
|
fn is(value: &Value) -> bool {
|
||||||
|
matches!(value, Value::Auto) || T::is(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cast(value: Value) -> StrResult<Self> {
|
||||||
|
match value {
|
||||||
|
Value::Auto => Ok(Self::Auto),
|
||||||
|
v if T::is(&v) => Ok(Self::Custom(T::cast(v)?)),
|
||||||
|
_ => <Self as Cast>::error(value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn describe() -> CastInfo {
|
||||||
|
T::describe() + CastInfo::Type("auto")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Resolve> Resolve for Smart<T> {
|
||||||
|
type Output = Smart<T::Output>;
|
||||||
|
|
||||||
|
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||||
|
self.map(|v| v.resolve(styles))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Fold for Smart<T>
|
||||||
|
where
|
||||||
|
T: Fold,
|
||||||
|
T::Output: Default,
|
||||||
|
{
|
||||||
|
type Output = Smart<T::Output>;
|
||||||
|
|
||||||
|
fn fold(self, outer: Self::Output) -> Self::Output {
|
||||||
|
self.map(|inner| inner.fold(outer.unwrap_or_default()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Into<Value>> From<Smart<T>> for Value {
|
||||||
|
fn from(v: Smart<T>) -> Self {
|
||||||
|
match v {
|
||||||
|
Smart::Custom(v) => v.into(),
|
||||||
|
Smart::Auto => Value::Auto,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,3 +58,37 @@ impl<T: Debug> Debug for PartialStroke<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
PartialStroke: "stroke",
|
||||||
|
thickness: Length => Self {
|
||||||
|
paint: Smart::Auto,
|
||||||
|
thickness: Smart::Custom(thickness),
|
||||||
|
},
|
||||||
|
color: Color => Self {
|
||||||
|
paint: Smart::Custom(color.into()),
|
||||||
|
thickness: Smart::Auto,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Resolve for PartialStroke {
|
||||||
|
type Output = PartialStroke<Abs>;
|
||||||
|
|
||||||
|
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||||
|
PartialStroke {
|
||||||
|
paint: self.paint,
|
||||||
|
thickness: self.thickness.resolve(styles),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fold for PartialStroke<Abs> {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn fold(self, outer: Self::Output) -> Self::Output {
|
||||||
|
Self {
|
||||||
|
paint: self.paint.or(outer.paint),
|
||||||
|
thickness: self.thickness.or(outer.thickness),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -338,6 +338,11 @@ fn field_access_completions(ctx: &mut CompletionContext, value: &Value) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Value::Content(content) => {
|
||||||
|
for (name, value) in content.fields() {
|
||||||
|
ctx.value_completion(Some(name.clone()), value, false, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
Value::Dict(dict) => {
|
Value::Dict(dict) => {
|
||||||
for (name, value) in dict.iter() {
|
for (name, value) in dict.iter() {
|
||||||
ctx.value_completion(Some(name.clone().into()), value, false, None);
|
ctx.value_completion(Some(name.clone().into()), value, false, None);
|
||||||
|
|
|
||||||
|
|
@ -39,14 +39,13 @@ extern crate self as typst;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod util;
|
pub mod util;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod geom;
|
|
||||||
#[macro_use]
|
|
||||||
pub mod diag;
|
pub mod diag;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod eval;
|
pub mod eval;
|
||||||
pub mod doc;
|
pub mod doc;
|
||||||
pub mod export;
|
pub mod export;
|
||||||
pub mod font;
|
pub mod font;
|
||||||
|
pub mod geom;
|
||||||
pub mod ide;
|
pub mod ide;
|
||||||
pub mod image;
|
pub mod image;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,24 @@
|
||||||
use std::any::{Any, TypeId};
|
use std::any::TypeId;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter, Write};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::iter::{self, Sum};
|
use std::iter::{self, Sum};
|
||||||
use std::ops::{Add, AddAssign};
|
use std::ops::{Add, AddAssign};
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use comemo::Tracked;
|
use comemo::Tracked;
|
||||||
use ecow::{EcoString, EcoVec};
|
use ecow::{EcoString, EcoVec};
|
||||||
use siphasher::sip128::{Hasher128, SipHasher};
|
|
||||||
use typst_macros::node;
|
|
||||||
|
|
||||||
use super::{capability, capable, Guard, Key, Property, Recipe, Style, StyleMap};
|
use super::{node, Guard, Key, Property, Recipe, Style, StyleMap};
|
||||||
use crate::diag::{SourceResult, StrResult};
|
use crate::diag::{SourceResult, StrResult};
|
||||||
use crate::eval::{Args, ParamInfo, Value, Vm};
|
use crate::eval::{cast_from_value, Args, Cast, ParamInfo, Value, Vm};
|
||||||
use crate::syntax::Span;
|
use crate::syntax::Span;
|
||||||
use crate::util::ReadableTypeId;
|
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
/// Composable representation of styled content.
|
/// Composable representation of styled content.
|
||||||
#[derive(Clone, Hash)]
|
#[derive(Clone, Hash)]
|
||||||
pub struct Content {
|
pub struct Content {
|
||||||
obj: Arc<dyn Bounds>,
|
id: NodeId,
|
||||||
span: Option<Span>,
|
span: Option<Span>,
|
||||||
|
fields: EcoVec<(EcoString, Value)>,
|
||||||
modifiers: EcoVec<Modifier>,
|
modifiers: EcoVec<Modifier>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -30,55 +27,43 @@ pub struct Content {
|
||||||
enum Modifier {
|
enum Modifier {
|
||||||
Prepared,
|
Prepared,
|
||||||
Guard(Guard),
|
Guard(Guard),
|
||||||
Label(Label),
|
|
||||||
Field(EcoString, Value),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Content {
|
impl Content {
|
||||||
|
pub fn new<T: Node>() -> Self {
|
||||||
|
Self {
|
||||||
|
id: T::id(),
|
||||||
|
span: None,
|
||||||
|
fields: EcoVec::new(),
|
||||||
|
modifiers: EcoVec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Create empty content.
|
/// Create empty content.
|
||||||
pub fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
SequenceNode(vec![]).pack()
|
SequenceNode::new(vec![]).pack()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new sequence node from multiples nodes.
|
/// Create a new sequence node from multiples nodes.
|
||||||
pub fn sequence(seq: Vec<Self>) -> Self {
|
pub fn sequence(seq: Vec<Self>) -> Self {
|
||||||
match seq.as_slice() {
|
match seq.as_slice() {
|
||||||
[_] => seq.into_iter().next().unwrap(),
|
[_] => seq.into_iter().next().unwrap(),
|
||||||
_ => SequenceNode(seq).pack(),
|
_ => SequenceNode::new(seq).pack(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attach a span to the content.
|
/// Attach a span to the content.
|
||||||
pub fn spanned(mut self, span: Span) -> Self {
|
pub fn spanned(mut self, span: Span) -> Self {
|
||||||
if let Some(styled) = self.to_mut::<StyledNode>() {
|
if let Some(styled) = self.to::<StyledNode>() {
|
||||||
styled.sub.span = Some(span);
|
self = StyledNode::new(styled.sub().spanned(span), styled.map()).pack();
|
||||||
} else if let Some(styled) = self.to::<StyledNode>() {
|
|
||||||
self = StyledNode {
|
|
||||||
sub: styled.sub.clone().spanned(span),
|
|
||||||
map: styled.map.clone(),
|
|
||||||
}
|
|
||||||
.pack();
|
|
||||||
}
|
}
|
||||||
self.span = Some(span);
|
self.span = Some(span);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attach a label to the content.
|
/// Attach a label to the content.
|
||||||
pub fn labelled(mut self, label: Label) -> Self {
|
pub fn labelled(self, label: Label) -> Self {
|
||||||
for (i, modifier) in self.modifiers.iter().enumerate() {
|
self.with_field("label", label)
|
||||||
if matches!(modifier, Modifier::Label(_)) {
|
|
||||||
self.modifiers.make_mut()[i] = Modifier::Label(label);
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.modifiers.push(Modifier::Label(label));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attach a field to the content.
|
|
||||||
pub fn push_field(&mut self, name: impl Into<EcoString>, value: Value) {
|
|
||||||
self.modifiers.push(Modifier::Field(name.into(), value));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Style this content with a single style property.
|
/// Style this content with a single style property.
|
||||||
|
|
@ -87,31 +72,21 @@ impl Content {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Style this content with a style entry.
|
/// Style this content with a style entry.
|
||||||
pub fn styled_with_entry(mut self, style: Style) -> Self {
|
pub fn styled_with_entry(self, style: Style) -> Self {
|
||||||
if let Some(styled) = self.to_mut::<StyledNode>() {
|
self.styled_with_map(style.into())
|
||||||
styled.map.apply_one(style);
|
|
||||||
self
|
|
||||||
} else if let Some(styled) = self.to::<StyledNode>() {
|
|
||||||
let mut map = styled.map.clone();
|
|
||||||
map.apply_one(style);
|
|
||||||
StyledNode { sub: styled.sub.clone(), map }.pack()
|
|
||||||
} else {
|
|
||||||
StyledNode { sub: self, map: style.into() }.pack()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Style this content with a full style map.
|
/// Style this content with a full style map.
|
||||||
pub fn styled_with_map(mut self, styles: StyleMap) -> Self {
|
pub fn styled_with_map(self, styles: StyleMap) -> Self {
|
||||||
if styles.is_empty() {
|
if styles.is_empty() {
|
||||||
return self;
|
self
|
||||||
|
} else if let Some(styled) = self.to::<StyledNode>() {
|
||||||
|
let mut map = styled.map();
|
||||||
|
map.apply(styles);
|
||||||
|
StyledNode::new(styled.sub(), map).pack()
|
||||||
|
} else {
|
||||||
|
StyledNode::new(self, styles).pack()
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(styled) = self.to_mut::<StyledNode>() {
|
|
||||||
styled.map.apply(styles);
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledNode { sub: self, map: styles }.pack()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Style this content with a recipe, eagerly applying it if possible.
|
/// Style this content with a recipe, eagerly applying it if possible.
|
||||||
|
|
@ -139,12 +114,12 @@ impl Content {
|
||||||
impl Content {
|
impl Content {
|
||||||
/// The id of the contained node.
|
/// The id of the contained node.
|
||||||
pub fn id(&self) -> NodeId {
|
pub fn id(&self) -> NodeId {
|
||||||
(*self.obj).id()
|
self.id
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The node's human-readable name.
|
/// The node's human-readable name.
|
||||||
pub fn name(&self) -> &'static str {
|
pub fn name(&self) -> &'static str {
|
||||||
(*self.obj).name()
|
self.id.name()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The node's span.
|
/// The node's span.
|
||||||
|
|
@ -154,72 +129,86 @@ impl Content {
|
||||||
|
|
||||||
/// The content's label.
|
/// The content's label.
|
||||||
pub fn label(&self) -> Option<&Label> {
|
pub fn label(&self) -> Option<&Label> {
|
||||||
self.modifiers.iter().find_map(|modifier| match modifier {
|
match self.field("label")? {
|
||||||
Modifier::Label(label) => Some(label),
|
Value::Label(label) => Some(label),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access a field on this content.
|
pub fn with_field(
|
||||||
pub fn field(&self, name: &str) -> Option<Value> {
|
mut self,
|
||||||
if name == "label" {
|
name: impl Into<EcoString>,
|
||||||
return Some(match self.label() {
|
value: impl Into<Value>,
|
||||||
Some(label) => Value::Label(label.clone()),
|
) -> Self {
|
||||||
None => Value::None,
|
self.push_field(name, value);
|
||||||
});
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
for modifier in &self.modifiers {
|
/// Attach a field to the content.
|
||||||
if let Modifier::Field(other, value) = modifier {
|
pub fn push_field(&mut self, name: impl Into<EcoString>, value: impl Into<Value>) {
|
||||||
if name == other {
|
let name = name.into();
|
||||||
return Some(value.clone());
|
if let Some(i) = self.fields.iter().position(|(field, _)| *field == name) {
|
||||||
}
|
self.fields.make_mut()[i] = (name, value.into());
|
||||||
}
|
} else {
|
||||||
|
self.fields.push((name, value.into()));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.obj.field(name)
|
pub fn field(&self, name: &str) -> Option<&Value> {
|
||||||
|
static NONE: Value = Value::None;
|
||||||
|
self.fields
|
||||||
|
.iter()
|
||||||
|
.find(|(field, _)| field == name)
|
||||||
|
.map(|(_, value)| value)
|
||||||
|
.or_else(|| (name == "label").then(|| &NONE))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fields(&self) -> &[(EcoString, Value)] {
|
||||||
|
&self.fields
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
pub fn cast_field<T: Cast>(&self, name: &str) -> T {
|
||||||
|
match self.field(name) {
|
||||||
|
Some(value) => value.clone().cast().unwrap(),
|
||||||
|
None => field_is_missing(name),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the contained node is of type `T`.
|
/// Whether the contained node is of type `T`.
|
||||||
pub fn is<T>(&self) -> bool
|
pub fn is<T>(&self) -> bool
|
||||||
where
|
where
|
||||||
T: Capable + 'static,
|
T: Node + 'static,
|
||||||
{
|
{
|
||||||
(*self.obj).as_any().is::<T>()
|
self.id == NodeId::of::<T>()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cast to `T` if the contained node is of type `T`.
|
/// Cast to `T` if the contained node is of type `T`.
|
||||||
pub fn to<T>(&self) -> Option<&T>
|
pub fn to<T>(&self) -> Option<&T>
|
||||||
where
|
where
|
||||||
T: Capable + 'static,
|
T: Node + 'static,
|
||||||
{
|
{
|
||||||
(*self.obj).as_any().downcast_ref::<T>()
|
self.is::<T>().then(|| unsafe { std::mem::transmute(self) })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this content has the given capability.
|
/// Whether this content has the given capability.
|
||||||
pub fn has<C>(&self) -> bool
|
pub fn has<C>(&self) -> bool
|
||||||
where
|
where
|
||||||
C: Capability + ?Sized,
|
C: ?Sized + 'static,
|
||||||
{
|
{
|
||||||
self.obj.vtable(TypeId::of::<C>()).is_some()
|
(self.id.0.vtable)(TypeId::of::<C>()).is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cast to a trait object if this content has the given capability.
|
/// Cast to a trait object if this content has the given capability.
|
||||||
pub fn with<C>(&self) -> Option<&C>
|
pub fn with<C>(&self) -> Option<&C>
|
||||||
where
|
where
|
||||||
C: Capability + ?Sized,
|
C: ?Sized + 'static,
|
||||||
{
|
{
|
||||||
let node: &dyn Bounds = &*self.obj;
|
let vtable = (self.id.0.vtable)(TypeId::of::<C>())?;
|
||||||
let vtable = node.vtable(TypeId::of::<C>())?;
|
let data = self as *const Self as *const ();
|
||||||
let data = node as *const dyn Bounds as *const ();
|
|
||||||
Some(unsafe { &*crate::util::fat::from_raw_parts(data, vtable) })
|
Some(unsafe { &*crate::util::fat::from_raw_parts(data, vtable) })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to cast to a mutable instance of `T`.
|
|
||||||
fn to_mut<T: 'static>(&mut self) -> Option<&mut T> {
|
|
||||||
Arc::get_mut(&mut self.obj)?.as_any_mut().downcast_mut::<T>()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Disable a show rule recipe.
|
/// Disable a show rule recipe.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn guarded(mut self, id: Guard) -> Self {
|
pub fn guarded(mut self, id: Guard) -> Self {
|
||||||
|
|
@ -262,12 +251,40 @@ impl Content {
|
||||||
pub(super) fn copy_modifiers(&mut self, from: &Content) {
|
pub(super) fn copy_modifiers(&mut self, from: &Content) {
|
||||||
self.span = from.span;
|
self.span = from.span;
|
||||||
self.modifiers = from.modifiers.clone();
|
self.modifiers = from.modifiers.clone();
|
||||||
|
if let Some(label) = from.label() {
|
||||||
|
self.push_field("label", label.clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Content {
|
impl Debug for Content {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
self.obj.fmt(f)
|
struct Pad<'a>(&'a str);
|
||||||
|
impl Debug for Pad<'_> {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
f.pad(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(styled) = self.to::<StyledNode>() {
|
||||||
|
styled.map().fmt(f)?;
|
||||||
|
styled.sub().fmt(f)
|
||||||
|
} else if let Some(seq) = self.to::<SequenceNode>() {
|
||||||
|
f.debug_list().entries(&seq.children()).finish()
|
||||||
|
} else if self.id.name() == "space" {
|
||||||
|
' '.fmt(f)
|
||||||
|
} else if self.id.name() == "text" {
|
||||||
|
self.field("text").unwrap().fmt(f)
|
||||||
|
} else {
|
||||||
|
f.write_str(self.name())?;
|
||||||
|
if self.fields.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
f.write_char(' ')?;
|
||||||
|
f.debug_map()
|
||||||
|
.entries(self.fields.iter().map(|(name, value)| (Pad(name), value)))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -280,27 +297,19 @@ impl Default for Content {
|
||||||
impl Add for Content {
|
impl Add for Content {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
fn add(self, mut rhs: Self) -> Self::Output {
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
let mut lhs = self;
|
let lhs = self;
|
||||||
if let Some(lhs_mut) = lhs.to_mut::<SequenceNode>() {
|
|
||||||
if let Some(rhs_mut) = rhs.to_mut::<SequenceNode>() {
|
|
||||||
lhs_mut.0.append(&mut rhs_mut.0);
|
|
||||||
} else if let Some(rhs) = rhs.to::<SequenceNode>() {
|
|
||||||
lhs_mut.0.extend(rhs.0.iter().cloned());
|
|
||||||
} else {
|
|
||||||
lhs_mut.0.push(rhs);
|
|
||||||
}
|
|
||||||
return lhs;
|
|
||||||
}
|
|
||||||
|
|
||||||
let seq = match (lhs.to::<SequenceNode>(), rhs.to::<SequenceNode>()) {
|
let seq = match (lhs.to::<SequenceNode>(), rhs.to::<SequenceNode>()) {
|
||||||
(Some(lhs), Some(rhs)) => lhs.0.iter().chain(&rhs.0).cloned().collect(),
|
(Some(lhs), Some(rhs)) => {
|
||||||
(Some(lhs), None) => lhs.0.iter().cloned().chain(iter::once(rhs)).collect(),
|
lhs.children().into_iter().chain(rhs.children()).collect()
|
||||||
(None, Some(rhs)) => iter::once(lhs).chain(rhs.0.iter().cloned()).collect(),
|
}
|
||||||
|
(Some(lhs), None) => {
|
||||||
|
lhs.children().into_iter().chain(iter::once(rhs)).collect()
|
||||||
|
}
|
||||||
|
(None, Some(rhs)) => iter::once(lhs).chain(rhs.children()).collect(),
|
||||||
(None, None) => vec![lhs, rhs],
|
(None, None) => vec![lhs, rhs],
|
||||||
};
|
};
|
||||||
|
SequenceNode::new(seq).pack()
|
||||||
SequenceNode(seq).pack()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -316,73 +325,33 @@ impl Sum for Content {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait Bounds: Node + Debug + Sync + Send + 'static {
|
|
||||||
fn as_any(&self) -> &dyn Any;
|
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
|
||||||
fn hash128(&self) -> u128;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Bounds for T
|
|
||||||
where
|
|
||||||
T: Node + Debug + Hash + Sync + Send + 'static,
|
|
||||||
{
|
|
||||||
fn as_any(&self) -> &dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hash128(&self) -> u128 {
|
|
||||||
let mut state = SipHasher::new();
|
|
||||||
self.type_id().hash(&mut state);
|
|
||||||
self.hash(&mut state);
|
|
||||||
state.finish128().as_u128()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hash for dyn Bounds {
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
state.write_u128(self.hash128());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A node with applied styles.
|
/// A node with applied styles.
|
||||||
#[capable]
|
#[node]
|
||||||
#[derive(Clone, Hash)]
|
|
||||||
pub struct StyledNode {
|
pub struct StyledNode {
|
||||||
/// The styled content.
|
/// The styled content.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
pub sub: Content,
|
pub sub: Content,
|
||||||
|
|
||||||
/// The styles.
|
/// The styles.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
pub map: StyleMap,
|
pub map: StyleMap,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node]
|
cast_from_value! {
|
||||||
impl StyledNode {}
|
StyleMap: "style map",
|
||||||
|
|
||||||
impl Debug for StyledNode {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
self.map.fmt(f)?;
|
|
||||||
self.sub.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A sequence of nodes.
|
/// A sequence of nodes.
|
||||||
///
|
///
|
||||||
/// Combines other arbitrary content. So, when you write `[Hi] + [you]` in
|
/// Combines other arbitrary content. So, when you write `[Hi] + [you]` in
|
||||||
/// Typst, the two text nodes are combined into a single sequence node.
|
/// Typst, the two text nodes are combined into a single sequence node.
|
||||||
#[capable]
|
|
||||||
#[derive(Clone, Hash)]
|
|
||||||
pub struct SequenceNode(pub Vec<Content>);
|
|
||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
impl SequenceNode {}
|
pub struct SequenceNode {
|
||||||
|
#[variadic]
|
||||||
impl Debug for SequenceNode {
|
#[required]
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
pub children: Vec<Content>,
|
||||||
f.debug_list().entries(self.0.iter()).finish()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A label for a node.
|
/// A label for a node.
|
||||||
|
|
@ -396,80 +365,83 @@ impl Debug for Label {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A constructable, stylable content node.
|
/// A constructable, stylable content node.
|
||||||
pub trait Node: 'static + Capable {
|
pub trait Node: Construct + Set + Sized + 'static {
|
||||||
|
/// The node's ID.
|
||||||
|
fn id() -> NodeId;
|
||||||
|
|
||||||
/// Pack a node into type-erased content.
|
/// Pack a node into type-erased content.
|
||||||
fn pack(self) -> Content
|
fn pack(self) -> Content;
|
||||||
where
|
|
||||||
Self: Node + Debug + Hash + Sync + Send + Sized + 'static,
|
|
||||||
{
|
|
||||||
Content {
|
|
||||||
obj: Arc::new(self),
|
|
||||||
span: None,
|
|
||||||
modifiers: EcoVec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A unique identifier of the node type.
|
|
||||||
fn id(&self) -> NodeId;
|
|
||||||
|
|
||||||
/// The node's name.
|
|
||||||
fn name(&self) -> &'static str;
|
|
||||||
|
|
||||||
/// Construct a node from the arguments.
|
|
||||||
///
|
|
||||||
/// This is passed only the arguments that remain after execution of the
|
|
||||||
/// node's set rule.
|
|
||||||
fn construct(vm: &Vm, args: &mut Args) -> SourceResult<Content>
|
|
||||||
where
|
|
||||||
Self: Sized;
|
|
||||||
|
|
||||||
/// Parse relevant arguments into style properties for this node.
|
|
||||||
///
|
|
||||||
/// When `constructor` is true, [`construct`](Self::construct) will run
|
|
||||||
/// after this invocation of `set` with the remaining arguments.
|
|
||||||
fn set(args: &mut Args, constructor: bool) -> SourceResult<StyleMap>
|
|
||||||
where
|
|
||||||
Self: Sized;
|
|
||||||
|
|
||||||
/// List the settable properties.
|
|
||||||
fn properties() -> Vec<ParamInfo>
|
|
||||||
where
|
|
||||||
Self: Sized;
|
|
||||||
|
|
||||||
/// Access a field on this node.
|
|
||||||
fn field(&self, name: &str) -> Option<Value>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A unique identifier for a node type.
|
/// A unique identifier for a node.
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct NodeId(ReadableTypeId);
|
pub struct NodeId(&'static NodeMeta);
|
||||||
|
|
||||||
impl NodeId {
|
impl NodeId {
|
||||||
/// The id of the given node type.
|
pub fn of<T: Node>() -> Self {
|
||||||
pub fn of<T: 'static>() -> Self {
|
T::id()
|
||||||
Self(ReadableTypeId::of::<T>())
|
}
|
||||||
|
|
||||||
|
pub fn from_meta(meta: &'static NodeMeta) -> Self {
|
||||||
|
Self(meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The name of the identified node.
|
||||||
|
pub fn name(self) -> &'static str {
|
||||||
|
self.0.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for NodeId {
|
impl Debug for NodeId {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
self.0.fmt(f)
|
f.pad(self.name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A capability a node can have.
|
impl Hash for NodeId {
|
||||||
///
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
/// Should be implemented by trait objects that are accessible through
|
state.write_usize(self.0 as *const _ as usize);
|
||||||
/// [`Capable`].
|
}
|
||||||
pub trait Capability: 'static {}
|
}
|
||||||
|
|
||||||
/// Dynamically access a trait implementation at runtime.
|
impl Eq for NodeId {}
|
||||||
pub unsafe trait Capable {
|
|
||||||
/// Return the vtable pointer of the trait object with given type `id`
|
impl PartialEq for NodeId {
|
||||||
/// if `self` implements the trait.
|
fn eq(&self, other: &Self) -> bool {
|
||||||
fn vtable(&self, of: TypeId) -> Option<*const ()>;
|
std::ptr::eq(self.0, other.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub struct NodeMeta {
|
||||||
|
pub name: &'static str,
|
||||||
|
pub vtable: fn(of: TypeId) -> Option<*const ()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Construct {
|
||||||
|
/// Construct a node from the arguments.
|
||||||
|
///
|
||||||
|
/// This is passed only the arguments that remain after execution of the
|
||||||
|
/// node's set rule.
|
||||||
|
fn construct(vm: &Vm, args: &mut Args) -> SourceResult<Content>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Set {
|
||||||
|
/// Parse relevant arguments into style properties for this node.
|
||||||
|
///
|
||||||
|
/// When `constructor` is true, [`construct`](Construct::construct) will run
|
||||||
|
/// after this invocation of `set` with the remaining arguments.
|
||||||
|
fn set(args: &mut Args, constructor: bool) -> SourceResult<StyleMap>;
|
||||||
|
|
||||||
|
/// List the settable properties.
|
||||||
|
fn properties() -> Vec<ParamInfo>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Indicates that a node cannot be labelled.
|
/// Indicates that a node cannot be labelled.
|
||||||
#[capability]
|
|
||||||
pub trait Unlabellable {}
|
pub trait Unlabellable {}
|
||||||
|
|
||||||
|
#[cold]
|
||||||
|
#[track_caller]
|
||||||
|
fn field_is_missing(name: &str) -> ! {
|
||||||
|
panic!("required field `{name}` is missing")
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,4 +13,4 @@ pub use self::typeset::*;
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use once_cell;
|
pub use once_cell;
|
||||||
pub use typst_macros::{capability, capable, node};
|
pub use typst_macros::node;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use super::{capability, Content, NodeId, Recipe, Selector, StyleChain, Vt};
|
use super::{Content, NodeId, Recipe, Selector, StyleChain, Vt};
|
||||||
use crate::diag::SourceResult;
|
use crate::diag::SourceResult;
|
||||||
|
|
||||||
/// Whether the target is affected by show rules in the given style chain.
|
/// Whether the target is affected by show rules in the given style chain.
|
||||||
|
|
@ -105,7 +105,7 @@ fn try_apply(
|
||||||
let mut result = vec![];
|
let mut result = vec![];
|
||||||
let mut cursor = 0;
|
let mut cursor = 0;
|
||||||
|
|
||||||
for m in regex.find_iter(text) {
|
for m in regex.find_iter(&text) {
|
||||||
let start = m.start();
|
let start = m.start();
|
||||||
if cursor < start {
|
if cursor < start {
|
||||||
result.push(make(text[cursor..start].into()));
|
result.push(make(text[cursor..start].into()));
|
||||||
|
|
@ -133,7 +133,6 @@ fn try_apply(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Preparations before execution of any show rule.
|
/// Preparations before execution of any show rule.
|
||||||
#[capability]
|
|
||||||
pub trait Prepare {
|
pub trait Prepare {
|
||||||
/// Prepare the node for show rule application.
|
/// Prepare the node for show rule application.
|
||||||
fn prepare(
|
fn prepare(
|
||||||
|
|
@ -145,7 +144,6 @@ pub trait Prepare {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The base recipe for a node.
|
/// The base recipe for a node.
|
||||||
#[capability]
|
|
||||||
pub trait Show {
|
pub trait Show {
|
||||||
/// Execute the base recipe for this node.
|
/// Execute the base recipe for this node.
|
||||||
fn show(
|
fn show(
|
||||||
|
|
@ -157,7 +155,6 @@ pub trait Show {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Post-process a node after it was realized.
|
/// Post-process a node after it was realized.
|
||||||
#[capability]
|
|
||||||
pub trait Finalize {
|
pub trait Finalize {
|
||||||
/// Finalize the fully realized form of the node. Use this for effects that
|
/// Finalize the fully realized form of the node. Use this for effects that
|
||||||
/// should work even in the face of a user-defined show rule, for example
|
/// should work even in the face of a user-defined show rule, for example
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,16 @@
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::hash::Hash;
|
use std::hash::{Hash, Hasher};
|
||||||
use std::iter;
|
use std::iter;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use comemo::{Prehashed, Tracked};
|
use comemo::Tracked;
|
||||||
|
use ecow::EcoString;
|
||||||
|
|
||||||
use super::{Content, Label, NodeId};
|
use super::{Content, Label, Node, NodeId};
|
||||||
use crate::diag::{SourceResult, Trace, Tracepoint};
|
use crate::diag::{SourceResult, Trace, Tracepoint};
|
||||||
use crate::eval::{Args, Dict, Func, Regex, Value};
|
use crate::eval::{cast_from_value, Args, Cast, Dict, Func, Regex, Value};
|
||||||
use crate::geom::{
|
|
||||||
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::World;
|
use crate::World;
|
||||||
|
|
||||||
/// A map of style properties.
|
/// A map of style properties.
|
||||||
|
|
@ -76,7 +71,7 @@ impl StyleMap {
|
||||||
/// Mark all contained properties as _scoped_. This means that they only
|
/// Mark all contained properties as _scoped_. This means that they only
|
||||||
/// apply to the first descendant node (of their type) in the hierarchy and
|
/// apply to the first descendant node (of their type) in the hierarchy and
|
||||||
/// not its children, too. This is used by
|
/// not its children, too. This is used by
|
||||||
/// [constructors](super::Node::construct).
|
/// [constructors](super::Construct::construct).
|
||||||
pub fn scoped(mut self) -> Self {
|
pub fn scoped(mut self) -> Self {
|
||||||
for entry in &mut self.0 {
|
for entry in &mut self.0 {
|
||||||
if let Style::Property(property) = entry {
|
if let Style::Property(property) = entry {
|
||||||
|
|
@ -98,7 +93,7 @@ impl StyleMap {
|
||||||
|
|
||||||
/// Returns `Some(_)` with an optional span if this map contains styles for
|
/// Returns `Some(_)` with an optional span if this map contains styles for
|
||||||
/// the given `node`.
|
/// the given `node`.
|
||||||
pub fn interruption<T: 'static>(&self) -> Option<Option<Span>> {
|
pub fn interruption<T: Node>(&self) -> Option<Option<Span>> {
|
||||||
let node = NodeId::of::<T>();
|
let node = NodeId::of::<T>();
|
||||||
self.0.iter().find_map(|entry| match entry {
|
self.0.iter().find_map(|entry| match entry {
|
||||||
Style::Property(property) => property.is_of(node).then(|| property.origin),
|
Style::Property(property) => property.is_of(node).then(|| property.origin),
|
||||||
|
|
@ -114,6 +109,12 @@ impl From<Style> for StyleMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq for StyleMap {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
crate::util::hash128(self) == crate::util::hash128(other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Debug for StyleMap {
|
impl Debug for StyleMap {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
for entry in self.0.iter() {
|
for entry in self.0.iter() {
|
||||||
|
|
@ -154,13 +155,11 @@ impl Style {
|
||||||
|
|
||||||
impl Debug for Style {
|
impl Debug for Style {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
f.write_str("#[")?;
|
|
||||||
match self {
|
match self {
|
||||||
Self::Property(property) => property.fmt(f)?,
|
Self::Property(property) => property.fmt(f),
|
||||||
Self::Recipe(recipe) => recipe.fmt(f)?,
|
Self::Recipe(recipe) => recipe.fmt(f),
|
||||||
Self::Barrier(id) => write!(f, "Barrier for {id:?}")?,
|
Self::Barrier(id) => write!(f, "#[Barrier for {id:?}]"),
|
||||||
}
|
}
|
||||||
f.write_str("]")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,12 +174,9 @@ pub struct Property {
|
||||||
/// hierarchy. Used by constructors.
|
/// hierarchy. Used by constructors.
|
||||||
scoped: bool,
|
scoped: bool,
|
||||||
/// The property's value.
|
/// The property's value.
|
||||||
value: Arc<Prehashed<dyn Bounds>>,
|
value: Value,
|
||||||
/// The span of the set rule the property stems from.
|
/// The span of the set rule the property stems from.
|
||||||
origin: Option<Span>,
|
origin: Option<Span>,
|
||||||
/// The name of the property.
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
name: &'static str,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Property {
|
impl Property {
|
||||||
|
|
@ -189,11 +185,9 @@ impl Property {
|
||||||
Self {
|
Self {
|
||||||
key: KeyId::of::<K>(),
|
key: KeyId::of::<K>(),
|
||||||
node: K::node(),
|
node: K::node(),
|
||||||
value: Arc::new(Prehashed::new(value)),
|
value: value.into(),
|
||||||
scoped: false,
|
scoped: false,
|
||||||
origin: None,
|
origin: None,
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
name: K::NAME,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -208,9 +202,12 @@ impl Property {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access the property's value if it is of the given key.
|
/// Access the property's value if it is of the given key.
|
||||||
pub fn downcast<K: Key>(&self) -> Option<&K::Value> {
|
#[track_caller]
|
||||||
|
pub fn cast<K: Key>(&self) -> Option<K::Value> {
|
||||||
if self.key == KeyId::of::<K>() {
|
if self.key == KeyId::of::<K>() {
|
||||||
(**self.value).as_any().downcast_ref()
|
Some(self.value.clone().cast().unwrap_or_else(|err| {
|
||||||
|
panic!("{} (for {} with value {:?})", err, self.key.name(), self.value)
|
||||||
|
}))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
@ -234,9 +231,7 @@ impl Property {
|
||||||
|
|
||||||
impl Debug for Property {
|
impl Debug for Property {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
#[cfg(debug_assertions)]
|
write!(f, "#set {}({}: {:?})", self.node.name(), self.key.name(), self.value)?;
|
||||||
write!(f, "{} = ", self.name)?;
|
|
||||||
write!(f, "{:?}", self.value)?;
|
|
||||||
if self.scoped {
|
if self.scoped {
|
||||||
write!(f, " [scoped]")?;
|
write!(f, " [scoped]")?;
|
||||||
}
|
}
|
||||||
|
|
@ -267,47 +262,69 @@ where
|
||||||
|
|
||||||
/// A style property key.
|
/// A style property key.
|
||||||
///
|
///
|
||||||
/// This trait is not intended to be implemented manually, but rather through
|
/// This trait is not intended to be implemented manually.
|
||||||
/// the `#[node]` proc-macro.
|
|
||||||
pub trait Key: Copy + 'static {
|
pub trait Key: Copy + 'static {
|
||||||
/// The unfolded type which this property is stored as in a style map.
|
/// The unfolded type which this property is stored as in a style map.
|
||||||
type Value: Debug + Clone + Hash + Sync + Send + 'static;
|
type Value: Cast + Into<Value>;
|
||||||
|
|
||||||
/// The folded type of value that is returned when reading this property
|
/// The folded type of value that is returned when reading this property
|
||||||
/// from a style chain.
|
/// from a style chain.
|
||||||
type Output<'a>;
|
type Output;
|
||||||
|
|
||||||
/// The name of the property, used for debug printing.
|
/// The id of the property.
|
||||||
const NAME: &'static str;
|
fn id() -> KeyId;
|
||||||
|
|
||||||
/// The id of the node the key belongs to.
|
/// The id of the node the key belongs to.
|
||||||
fn node() -> NodeId;
|
fn node() -> NodeId;
|
||||||
|
|
||||||
/// Compute an output value from a sequence of values belonging to this key,
|
/// Compute an output value from a sequence of values belonging to this key,
|
||||||
/// folding if necessary.
|
/// folding if necessary.
|
||||||
fn get<'a>(
|
fn get(chain: StyleChain, values: impl Iterator<Item = Self::Value>) -> Self::Output;
|
||||||
chain: StyleChain<'a>,
|
|
||||||
values: impl Iterator<Item = &'a Self::Value>,
|
|
||||||
) -> Self::Output<'a>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A unique identifier for a property key.
|
/// A unique identifier for a style key.
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Copy, Clone)]
|
||||||
struct KeyId(ReadableTypeId);
|
pub struct KeyId(&'static KeyMeta);
|
||||||
|
|
||||||
impl KeyId {
|
impl KeyId {
|
||||||
/// The id of the given key.
|
|
||||||
pub fn of<T: Key>() -> Self {
|
pub fn of<T: Key>() -> Self {
|
||||||
Self(ReadableTypeId::of::<T>())
|
T::id()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_meta(meta: &'static KeyMeta) -> Self {
|
||||||
|
Self(meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(self) -> &'static str {
|
||||||
|
self.0.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for KeyId {
|
impl Debug for KeyId {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
self.0.fmt(f)
|
f.pad(self.name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Hash for KeyId {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
state.write_usize(self.0 as *const _ as usize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for KeyId {}
|
||||||
|
|
||||||
|
impl PartialEq for KeyId {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
std::ptr::eq(self.0, other.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub struct KeyMeta {
|
||||||
|
pub name: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
/// A show rule recipe.
|
/// A show rule recipe.
|
||||||
#[derive(Clone, Hash)]
|
#[derive(Clone, Hash)]
|
||||||
pub struct Recipe {
|
pub struct Recipe {
|
||||||
|
|
@ -362,7 +379,7 @@ impl Recipe {
|
||||||
|
|
||||||
impl Debug for Recipe {
|
impl Debug for Recipe {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
write!(f, "Recipe matching {:?}", self.selector)
|
write!(f, "#show {:?}: {:?}", self.selector, self.transform)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -382,7 +399,7 @@ pub enum Selector {
|
||||||
|
|
||||||
impl Selector {
|
impl Selector {
|
||||||
/// Define a simple node selector.
|
/// Define a simple node selector.
|
||||||
pub fn node<T: 'static>() -> Self {
|
pub fn node<T: Node>() -> Self {
|
||||||
Self::Node(NodeId::of::<T>(), None)
|
Self::Node(NodeId::of::<T>(), None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -399,17 +416,25 @@ impl Selector {
|
||||||
&& dict
|
&& dict
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|dict| dict.iter())
|
.flat_map(|dict| dict.iter())
|
||||||
.all(|(name, value)| target.field(name).as_ref() == Some(value))
|
.all(|(name, value)| target.field(name) == Some(value))
|
||||||
}
|
}
|
||||||
Self::Label(label) => target.label() == Some(label),
|
Self::Label(label) => target.label() == Some(label),
|
||||||
Self::Regex(regex) => {
|
Self::Regex(regex) => {
|
||||||
target.id() == item!(text_id)
|
target.id() == item!(text_id)
|
||||||
&& item!(text_str)(target).map_or(false, |text| regex.is_match(text))
|
&& item!(text_str)(target).map_or(false, |text| regex.is_match(&text))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
Selector: "selector",
|
||||||
|
text: EcoString => Self::text(&text),
|
||||||
|
label: Label => Self::Label(label),
|
||||||
|
func: Func => func.select(None)?,
|
||||||
|
regex: Regex => Self::Regex(regex),
|
||||||
|
}
|
||||||
|
|
||||||
/// A show rule transformation that can be applied to a match.
|
/// A show rule transformation that can be applied to a match.
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub enum Transform {
|
pub enum Transform {
|
||||||
|
|
@ -421,6 +446,17 @@ pub enum Transform {
|
||||||
Style(StyleMap),
|
Style(StyleMap),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
Transform,
|
||||||
|
content: Content => Self::Content(content),
|
||||||
|
func: Func => {
|
||||||
|
if func.argc().map_or(false, |count| count != 1) {
|
||||||
|
Err("function must have exactly one parameter")?
|
||||||
|
}
|
||||||
|
Self::Func(func)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
/// A chain of style maps, similar to a linked list.
|
/// A chain of style maps, similar to a linked list.
|
||||||
///
|
///
|
||||||
/// A style chain allows to combine properties from multiple style maps in a
|
/// A style chain allows to combine properties from multiple style maps in a
|
||||||
|
|
@ -478,7 +514,7 @@ impl<'a> StyleChain<'a> {
|
||||||
/// Returns the property's default value if no map in the chain contains an
|
/// Returns the property's default value if no map in the chain contains an
|
||||||
/// entry for it. Also takes care of resolving and folding and returns
|
/// entry for it. Also takes care of resolving and folding and returns
|
||||||
/// references where applicable.
|
/// references where applicable.
|
||||||
pub fn get<K: Key>(self, key: K) -> K::Output<'a> {
|
pub fn get<K: Key>(self, key: K) -> K::Output {
|
||||||
K::get(self, self.values(key))
|
K::get(self, self.values(key))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -534,10 +570,7 @@ impl Debug for StyleChain<'_> {
|
||||||
|
|
||||||
impl PartialEq for StyleChain<'_> {
|
impl PartialEq for StyleChain<'_> {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
let as_ptr = |s| s as *const _;
|
crate::util::hash128(self) == crate::util::hash128(other)
|
||||||
self.head.as_ptr() == other.head.as_ptr()
|
|
||||||
&& self.head.len() == other.head.len()
|
|
||||||
&& self.tail.map(as_ptr) == other.tail.map(as_ptr)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -585,13 +618,14 @@ struct Values<'a, K> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, K: Key> Iterator for Values<'a, K> {
|
impl<'a, K: Key> Iterator for Values<'a, K> {
|
||||||
type Item = &'a K::Value;
|
type Item = K::Value;
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
for entry in &mut self.entries {
|
for entry in &mut self.entries {
|
||||||
match entry {
|
match entry {
|
||||||
Style::Property(property) => {
|
Style::Property(property) => {
|
||||||
if let Some(value) = property.downcast::<K>() {
|
if let Some(value) = property.cast::<K>() {
|
||||||
if !property.scoped() || self.barriers <= 1 {
|
if !property.scoped() || self.barriers <= 1 {
|
||||||
return Some(value);
|
return Some(value);
|
||||||
}
|
}
|
||||||
|
|
@ -672,6 +706,20 @@ impl<T> StyleVec<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl StyleVec<Content> {
|
||||||
|
pub fn to_vec(self) -> Vec<Content> {
|
||||||
|
self.items
|
||||||
|
.into_iter()
|
||||||
|
.zip(
|
||||||
|
self.maps
|
||||||
|
.iter()
|
||||||
|
.flat_map(|(map, count)| iter::repeat(map).take(*count)),
|
||||||
|
)
|
||||||
|
.map(|(content, map)| content.styled_with_map(map.clone()))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T> Default for StyleVec<T> {
|
impl<T> Default for StyleVec<T> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self { items: vec![], maps: vec![] }
|
Self { items: vec![], maps: vec![] }
|
||||||
|
|
@ -791,26 +839,6 @@ pub trait Resolve {
|
||||||
fn resolve(self, styles: StyleChain) -> Self::Output;
|
fn resolve(self, styles: StyleChain) -> Self::Output;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Resolve for Em {
|
|
||||||
type Output = Abs;
|
|
||||||
|
|
||||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
|
||||||
if self.is_zero() {
|
|
||||||
Abs::zero()
|
|
||||||
} else {
|
|
||||||
self.at(item!(em)(styles))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Resolve for Length {
|
|
||||||
type Output = Abs;
|
|
||||||
|
|
||||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
|
||||||
self.abs + self.em.resolve(styles)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Resolve> Resolve for Option<T> {
|
impl<T: Resolve> Resolve for Option<T> {
|
||||||
type Output = Option<T::Output>;
|
type Output = Option<T::Output>;
|
||||||
|
|
||||||
|
|
@ -819,74 +847,6 @@ impl<T: Resolve> Resolve for Option<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Resolve> Resolve for Smart<T> {
|
|
||||||
type Output = Smart<T::Output>;
|
|
||||||
|
|
||||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
|
||||||
self.map(|v| v.resolve(styles))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Resolve> Resolve for Axes<T> {
|
|
||||||
type Output = Axes<T::Output>;
|
|
||||||
|
|
||||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
|
||||||
self.map(|v| v.resolve(styles))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Resolve> Resolve for Sides<T> {
|
|
||||||
type Output = Sides<T::Output>;
|
|
||||||
|
|
||||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
|
||||||
self.map(|v| v.resolve(styles))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Resolve> Resolve for Corners<T> {
|
|
||||||
type Output = Corners<T::Output>;
|
|
||||||
|
|
||||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
|
||||||
self.map(|v| v.resolve(styles))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Resolve for Rel<T>
|
|
||||||
where
|
|
||||||
T: Resolve + Numeric,
|
|
||||||
<T as Resolve>::Output: Numeric,
|
|
||||||
{
|
|
||||||
type Output = Rel<<T as Resolve>::Output>;
|
|
||||||
|
|
||||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
|
||||||
self.map(|abs| abs.resolve(styles))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Resolve for GenAlign {
|
|
||||||
type Output = Align;
|
|
||||||
|
|
||||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
|
||||||
let dir = item!(dir)(styles);
|
|
||||||
match self {
|
|
||||||
Self::Start => dir.start().into(),
|
|
||||||
Self::End => dir.end().into(),
|
|
||||||
Self::Specific(align) => align,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Resolve for PartialStroke {
|
|
||||||
type Output = PartialStroke<Abs>;
|
|
||||||
|
|
||||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
|
||||||
PartialStroke {
|
|
||||||
paint: self.paint,
|
|
||||||
thickness: self.thickness.resolve(styles),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A property that is folded to determine its final value.
|
/// A property that is folded to determine its final value.
|
||||||
pub trait Fold {
|
pub trait Fold {
|
||||||
/// The type of the folded output.
|
/// The type of the folded output.
|
||||||
|
|
@ -907,92 +867,3 @@ where
|
||||||
self.map(|inner| inner.fold(outer.unwrap_or_default()))
|
self.map(|inner| inner.fold(outer.unwrap_or_default()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Fold for Smart<T>
|
|
||||||
where
|
|
||||||
T: Fold,
|
|
||||||
T::Output: Default,
|
|
||||||
{
|
|
||||||
type Output = Smart<T::Output>;
|
|
||||||
|
|
||||||
fn fold(self, outer: Self::Output) -> Self::Output {
|
|
||||||
self.map(|inner| inner.fold(outer.unwrap_or_default()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Fold for Axes<Option<T>>
|
|
||||||
where
|
|
||||||
T: Fold,
|
|
||||||
{
|
|
||||||
type Output = Axes<T::Output>;
|
|
||||||
|
|
||||||
fn fold(self, outer: Self::Output) -> Self::Output {
|
|
||||||
self.zip(outer).map(|(inner, outer)| match inner {
|
|
||||||
Some(value) => value.fold(outer),
|
|
||||||
None => outer,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Fold for Sides<Option<T>>
|
|
||||||
where
|
|
||||||
T: Fold,
|
|
||||||
{
|
|
||||||
type Output = Sides<T::Output>;
|
|
||||||
|
|
||||||
fn fold(self, outer: Self::Output) -> Self::Output {
|
|
||||||
self.zip(outer).map(|(inner, outer)| match inner {
|
|
||||||
Some(value) => value.fold(outer),
|
|
||||||
None => outer,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Fold for Corners<Option<T>>
|
|
||||||
where
|
|
||||||
T: Fold,
|
|
||||||
{
|
|
||||||
type Output = Corners<T::Output>;
|
|
||||||
|
|
||||||
fn fold(self, outer: Self::Output) -> Self::Output {
|
|
||||||
self.zip(outer).map(|(inner, outer)| match inner {
|
|
||||||
Some(value) => value.fold(outer),
|
|
||||||
None => outer,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Fold for PartialStroke<Abs> {
|
|
||||||
type Output = Self;
|
|
||||||
|
|
||||||
fn fold(self, outer: Self::Output) -> Self::Output {
|
|
||||||
Self {
|
|
||||||
paint: self.paint.or(outer.paint),
|
|
||||||
thickness: self.thickness.or(outer.thickness),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Fold for Rel<Length> {
|
|
||||||
type Output = Self;
|
|
||||||
|
|
||||||
fn fold(self, _: Self::Output) -> Self::Output {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Fold for Rel<Abs> {
|
|
||||||
type Output = Self;
|
|
||||||
|
|
||||||
fn fold(self, _: Self::Output) -> Self::Output {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Fold for GenAlign {
|
|
||||||
type Output = Self;
|
|
||||||
|
|
||||||
fn fold(self, _: Self::Output) -> Self::Output {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ use comemo::{Track, Tracked, TrackedMut};
|
||||||
use super::{Content, Selector, StyleChain};
|
use super::{Content, Selector, StyleChain};
|
||||||
use crate::diag::SourceResult;
|
use crate::diag::SourceResult;
|
||||||
use crate::doc::{Document, Element, Frame, Location, Meta};
|
use crate::doc::{Document, Element, Frame, Location, Meta};
|
||||||
use crate::eval::Value;
|
|
||||||
use crate::geom::Transform;
|
use crate::geom::Transform;
|
||||||
use crate::util::hash128;
|
use crate::util::hash128;
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
@ -162,7 +161,7 @@ impl Introspector {
|
||||||
let pos = pos.transform(ts);
|
let pos = pos.transform(ts);
|
||||||
let mut node = content.clone();
|
let mut node = content.clone();
|
||||||
let loc = Location { page, pos };
|
let loc = Location { page, pos };
|
||||||
node.push_field("loc", Value::Dict(loc.encode()));
|
node.push_field("loc", loc);
|
||||||
self.nodes.push((id, node));
|
self.nodes.push((id, node));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,10 +60,7 @@ pub trait ArcExt<T> {
|
||||||
fn take(self) -> T;
|
fn take(self) -> T;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> ArcExt<T> for Arc<T>
|
impl<T: Clone> ArcExt<T> for Arc<T> {
|
||||||
where
|
|
||||||
T: Clone,
|
|
||||||
{
|
|
||||||
fn take(self) -> T {
|
fn take(self) -> T {
|
||||||
match Arc::try_unwrap(self) {
|
match Arc::try_unwrap(self) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
|
|
@ -146,9 +146,8 @@ impl Args {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn library() -> Library {
|
fn library() -> Library {
|
||||||
/// # Test
|
/// Category: test
|
||||||
/// ## Category
|
/// Display: Test
|
||||||
/// test
|
|
||||||
#[func]
|
#[func]
|
||||||
fn test(args: &mut typst::eval::Args) -> SourceResult<Value> {
|
fn test(args: &mut typst::eval::Args) -> SourceResult<Value> {
|
||||||
let lhs = args.expect::<Value>("left-hand side")?;
|
let lhs = args.expect::<Value>("left-hand side")?;
|
||||||
|
|
@ -159,9 +158,8 @@ fn library() -> Library {
|
||||||
Ok(Value::None)
|
Ok(Value::None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Print
|
/// Category: test
|
||||||
/// ## Category
|
/// Display: Print
|
||||||
/// test
|
|
||||||
#[func]
|
#[func]
|
||||||
fn print(args: &mut typst::eval::Args) -> SourceResult<Value> {
|
fn print(args: &mut typst::eval::Args) -> SourceResult<Value> {
|
||||||
print!("> ");
|
print!("> ");
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
// Override lists.
|
// Override lists.
|
||||||
#show list: it => "(" + it.items.join(", ") + ")"
|
#show list: it => "(" + it.items.map(item => item.body).join(", ") + ")"
|
||||||
|
|
||||||
- A
|
- A
|
||||||
- B
|
- B
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
#show terms: it => table(
|
#show terms: it => table(
|
||||||
columns: 2,
|
columns: 2,
|
||||||
inset: 3pt,
|
inset: 3pt,
|
||||||
..it.items.map(item => (emph(item.at(0)), item.at(1))).flatten(),
|
..it.items.map(item => (emph(item.term), item.description)).flatten(),
|
||||||
)
|
)
|
||||||
|
|
||||||
/ A: One letter
|
/ A: One letter
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ $ x = (-b plus.minus sqrt(b^2 - 4a c))/(2a) $
|
||||||
$ binom(circle, square) $
|
$ binom(circle, square) $
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 8-13 missing argument: lower index
|
// Error: 8-13 missing argument: lower
|
||||||
$ binom(x^2) $
|
$ binom(x^2) $
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue