mirror of
https://github.com/typst/typst
synced 2025-05-15 09:35:28 +08:00
parent
46aace78ac
commit
551ea99d05
@ -492,6 +492,7 @@ pub struct ParamModel {
|
|||||||
pub example: Option<Html>,
|
pub example: Option<Html>,
|
||||||
pub types: Vec<&'static str>,
|
pub types: Vec<&'static str>,
|
||||||
pub strings: Vec<StrParam>,
|
pub strings: Vec<StrParam>,
|
||||||
|
pub default: Option<Html>,
|
||||||
pub positional: bool,
|
pub positional: bool,
|
||||||
pub named: bool,
|
pub named: bool,
|
||||||
pub required: bool,
|
pub required: bool,
|
||||||
@ -532,6 +533,10 @@ fn param_model(resolver: &dyn Resolver, info: &ParamInfo) -> ParamModel {
|
|||||||
example: example.map(|md| Html::markdown(resolver, md)),
|
example: example.map(|md| Html::markdown(resolver, md)),
|
||||||
types,
|
types,
|
||||||
strings,
|
strings,
|
||||||
|
default: info.default.map(|default| {
|
||||||
|
let node = typst::syntax::parse_code(&default().repr());
|
||||||
|
Html::new(typst::ide::highlight_html(&node))
|
||||||
|
}),
|
||||||
positional: info.positional,
|
positional: info.positional,
|
||||||
named: info.named,
|
named: info.named,
|
||||||
required: info.required,
|
required: info.required,
|
||||||
@ -721,6 +726,7 @@ fn method_model(resolver: &dyn Resolver, part: &'static str) -> MethodModel {
|
|||||||
example: None,
|
example: None,
|
||||||
types,
|
types,
|
||||||
strings: vec![],
|
strings: vec![],
|
||||||
|
default: None,
|
||||||
positional,
|
positional,
|
||||||
named,
|
named,
|
||||||
required,
|
required,
|
||||||
|
@ -412,7 +412,7 @@ pub fn tanh(
|
|||||||
pub fn log(
|
pub fn log(
|
||||||
/// The number whose logarithm to calculate. Must be strictly positive.
|
/// The number whose logarithm to calculate. Must be strictly positive.
|
||||||
value: Spanned<Num>,
|
value: Spanned<Num>,
|
||||||
/// The base of the logarithm. Defaults to `{10}` and may not be zero.
|
/// The base of the logarithm. May not be zero.
|
||||||
#[named]
|
#[named]
|
||||||
#[default(Spanned::new(10.0, Span::detached()))]
|
#[default(Spanned::new(10.0, Span::detached()))]
|
||||||
base: Spanned<f64>,
|
base: Spanned<f64>,
|
||||||
|
@ -135,23 +135,18 @@ pub fn rgb(
|
|||||||
/// ]
|
/// ]
|
||||||
/// ```
|
/// ```
|
||||||
#[external]
|
#[external]
|
||||||
#[default]
|
|
||||||
hex: EcoString,
|
hex: EcoString,
|
||||||
/// The red component.
|
/// The red component.
|
||||||
#[external]
|
#[external]
|
||||||
#[default]
|
|
||||||
red: Component,
|
red: Component,
|
||||||
/// The green component.
|
/// The green component.
|
||||||
#[external]
|
#[external]
|
||||||
#[default]
|
|
||||||
green: Component,
|
green: Component,
|
||||||
/// The blue component.
|
/// The blue component.
|
||||||
#[external]
|
#[external]
|
||||||
#[default]
|
|
||||||
blue: Component,
|
blue: Component,
|
||||||
/// The alpha component.
|
/// The alpha component.
|
||||||
#[external]
|
#[external]
|
||||||
#[default]
|
|
||||||
alpha: Component,
|
alpha: Component,
|
||||||
) -> Value {
|
) -> Value {
|
||||||
Value::Color(if let Some(string) = args.find::<Spanned<EcoString>>()? {
|
Value::Color(if let Some(string) = args.find::<Spanned<EcoString>>()? {
|
||||||
|
@ -58,7 +58,6 @@ pub fn csv(
|
|||||||
path: Spanned<EcoString>,
|
path: Spanned<EcoString>,
|
||||||
/// The delimiter that separates columns in the CSV file.
|
/// The delimiter that separates columns in the CSV file.
|
||||||
/// Must be a single ASCII character.
|
/// Must be a single ASCII character.
|
||||||
/// Defaults to a comma.
|
|
||||||
#[named]
|
#[named]
|
||||||
#[default]
|
#[default]
|
||||||
delimiter: Delimiter,
|
delimiter: Delimiter,
|
||||||
@ -69,7 +68,7 @@ pub fn csv(
|
|||||||
|
|
||||||
let mut builder = csv::ReaderBuilder::new();
|
let mut builder = csv::ReaderBuilder::new();
|
||||||
builder.has_headers(false);
|
builder.has_headers(false);
|
||||||
builder.delimiter(delimiter.0);
|
builder.delimiter(delimiter.0 as u8);
|
||||||
|
|
||||||
let mut reader = builder.from_reader(data.as_slice());
|
let mut reader = builder.from_reader(data.as_slice());
|
||||||
let mut array = Array::new();
|
let mut array = Array::new();
|
||||||
@ -87,7 +86,7 @@ pub fn csv(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The delimiter to use when parsing CSV files.
|
/// The delimiter to use when parsing CSV files.
|
||||||
struct Delimiter(u8);
|
struct Delimiter(char);
|
||||||
|
|
||||||
cast_from_value! {
|
cast_from_value! {
|
||||||
Delimiter,
|
Delimiter,
|
||||||
@ -102,13 +101,17 @@ cast_from_value! {
|
|||||||
Err("delimiter must be an ASCII character")?
|
Err("delimiter must be an ASCII character")?
|
||||||
}
|
}
|
||||||
|
|
||||||
Self(first as u8)
|
Self(first)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: Delimiter => v.0.into()
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for Delimiter {
|
impl Default for Delimiter {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self(b',')
|
Self(',')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,7 +316,9 @@ fn format_toml_error(error: toml::de::Error) -> EcoString {
|
|||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// #bookshelf(yaml("scifi-authors.yaml"))
|
/// #bookshelf(
|
||||||
|
/// yaml("scifi-authors.yaml")
|
||||||
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Display: YAML
|
/// Display: YAML
|
||||||
@ -386,15 +391,15 @@ fn format_yaml_error(error: serde_yaml::Error) -> EcoString {
|
|||||||
///
|
///
|
||||||
/// ## Example { #example }
|
/// ## Example { #example }
|
||||||
/// ```example
|
/// ```example
|
||||||
/// #let findChild(elem, tag) = {
|
/// #let find-child(elem, tag) = {
|
||||||
/// elem.children
|
/// elem.children
|
||||||
/// .find(e => "tag" in e and e.tag == tag)
|
/// .find(e => "tag" in e and e.tag == tag)
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// #let article(elem) = {
|
/// #let article(elem) = {
|
||||||
/// let title = findChild(elem, "title")
|
/// let title = find-child(elem, "title")
|
||||||
/// let author = findChild(elem, "author")
|
/// let author = find-child(elem, "author")
|
||||||
/// let pars = findChild(elem, "content")
|
/// let pars = find-child(elem, "content")
|
||||||
///
|
///
|
||||||
/// heading(title.children.first())
|
/// heading(title.children.first())
|
||||||
/// text(10pt, weight: "medium")[
|
/// text(10pt, weight: "medium")[
|
||||||
@ -411,9 +416,9 @@ fn format_yaml_error(error: serde_yaml::Error) -> EcoString {
|
|||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// #let data = xml("example.xml")
|
/// #let data = xml("example.xml")
|
||||||
/// #for child in data.first().children {
|
/// #for elem in data.first().children {
|
||||||
/// if (type(child) == "dictionary") {
|
/// if (type(elem) == "dictionary") {
|
||||||
/// article(child)
|
/// article(elem)
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -229,7 +229,6 @@ pub struct BlockElem {
|
|||||||
|
|
||||||
/// 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}`.
|
|
||||||
/// ```example
|
/// ```example
|
||||||
/// #set page(height: 80pt)
|
/// #set page(height: 80pt)
|
||||||
/// The following block will
|
/// The following block will
|
||||||
@ -282,13 +281,16 @@ pub struct BlockElem {
|
|||||||
/// A second paragraph.
|
/// A second paragraph.
|
||||||
/// ```
|
/// ```
|
||||||
#[external]
|
#[external]
|
||||||
|
#[default(Em::new(1.2).into())]
|
||||||
pub spacing: Spacing,
|
pub spacing: Spacing,
|
||||||
|
|
||||||
/// The spacing between this block and its predecessor. Takes precedence
|
/// The spacing between this block and its predecessor. Takes precedence
|
||||||
/// over `spacing`. Can be used in combination with a show rule to adjust
|
/// over `spacing`. Can be used in combination with a show rule to adjust
|
||||||
/// the spacing around arbitrary block-level elements.
|
/// the spacing around arbitrary block-level elements.
|
||||||
///
|
#[external]
|
||||||
/// The default value is `{1.2em}`.
|
#[default(Em::new(1.2).into())]
|
||||||
|
pub above: Spacing,
|
||||||
|
#[internal]
|
||||||
#[parse(
|
#[parse(
|
||||||
let spacing = args.named("spacing")?;
|
let spacing = args.named("spacing")?;
|
||||||
args.named("above")?
|
args.named("above")?
|
||||||
@ -300,8 +302,10 @@ pub struct BlockElem {
|
|||||||
|
|
||||||
/// The spacing between this block and its successor. Takes precedence
|
/// The spacing between this block and its successor. Takes precedence
|
||||||
/// over `spacing`.
|
/// over `spacing`.
|
||||||
///
|
#[external]
|
||||||
/// The default value is `{1.2em}`.
|
#[default(Em::new(1.2).into())]
|
||||||
|
pub below: Spacing,
|
||||||
|
#[internal]
|
||||||
#[parse(
|
#[parse(
|
||||||
args.named("below")?
|
args.named("below")?
|
||||||
.map(VElem::block_around)
|
.map(VElem::block_around)
|
||||||
|
@ -121,7 +121,6 @@ pub struct EnumElem {
|
|||||||
/// 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.
|
||||||
///
|
///
|
||||||
/// Defaults to `{false}`.
|
|
||||||
///
|
///
|
||||||
/// ```example
|
/// ```example
|
||||||
/// #set enum(numbering: "1.a)", full: true)
|
/// #set enum(numbering: "1.a)", full: true)
|
||||||
|
@ -66,8 +66,6 @@ pub struct ListElem {
|
|||||||
/// control, you may pass a function that maps the list's nesting depth
|
/// control, you may pass a function that maps the list's nesting depth
|
||||||
/// (starting from `{0}`) to a desired marker.
|
/// (starting from `{0}`) to a desired marker.
|
||||||
///
|
///
|
||||||
/// Default: `•`
|
|
||||||
///
|
|
||||||
/// ```example
|
/// ```example
|
||||||
/// #set list(marker: [--])
|
/// #set list(marker: [--])
|
||||||
/// - A more classic list
|
/// - A more classic list
|
||||||
@ -79,7 +77,7 @@ pub struct ListElem {
|
|||||||
/// - Items
|
/// - Items
|
||||||
/// - Items
|
/// - Items
|
||||||
/// ```
|
/// ```
|
||||||
#[default(ListMarker::Content(vec![]))]
|
#[default(ListMarker::Content(vec![TextElem::packed('•')]))]
|
||||||
pub marker: ListMarker,
|
pub marker: ListMarker,
|
||||||
|
|
||||||
/// The indent of each item.
|
/// The indent of each item.
|
||||||
@ -192,11 +190,9 @@ impl ListMarker {
|
|||||||
/// Resolve the marker for the given depth.
|
/// Resolve the marker for the given depth.
|
||||||
fn resolve(&self, vt: &mut Vt, depth: usize) -> SourceResult<Content> {
|
fn resolve(&self, vt: &mut Vt, depth: usize) -> SourceResult<Content> {
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
Self::Content(list) => list
|
Self::Content(list) => {
|
||||||
.get(depth)
|
list.get(depth).or(list.last()).cloned().unwrap_or_default()
|
||||||
.or(list.last())
|
}
|
||||||
.cloned()
|
|
||||||
.unwrap_or_else(|| TextElem::packed('•')),
|
|
||||||
Self::Func(func) => func.call_vt(vt, [Value::Int(depth as i64)])?.display(),
|
Self::Func(func) => func.call_vt(vt, [Value::Int(depth as i64)])?.display(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -216,7 +212,11 @@ cast_from_value! {
|
|||||||
|
|
||||||
cast_to_value! {
|
cast_to_value! {
|
||||||
v: ListMarker => match v {
|
v: ListMarker => match v {
|
||||||
ListMarker::Content(vec) => vec.into(),
|
ListMarker::Content(vec) => if vec.len() == 1 {
|
||||||
|
vec.into_iter().next().unwrap().into()
|
||||||
|
} else {
|
||||||
|
vec.into()
|
||||||
|
},
|
||||||
ListMarker::Func(func) => func.into(),
|
ListMarker::Func(func) => func.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,9 +26,9 @@ use crate::prelude::*;
|
|||||||
/// Category: layout
|
/// Category: layout
|
||||||
#[element]
|
#[element]
|
||||||
pub struct PageElem {
|
pub struct PageElem {
|
||||||
/// A standard paper size to set width and height. When this is not
|
/// A standard paper size to set width and height.
|
||||||
/// specified, Typst defaults to `{"a4"}` paper.
|
|
||||||
#[external]
|
#[external]
|
||||||
|
#[default(Paper::A4)]
|
||||||
pub paper: Paper,
|
pub paper: Paper,
|
||||||
|
|
||||||
/// The width of the page.
|
/// The width of the page.
|
||||||
@ -470,6 +470,8 @@ cast_to_value! {
|
|||||||
/// Specification of a paper.
|
/// Specification of a paper.
|
||||||
#[derive(Debug, Copy, Clone, Hash)]
|
#[derive(Debug, Copy, Clone, Hash)]
|
||||||
pub struct Paper {
|
pub struct Paper {
|
||||||
|
/// The name of the paper.
|
||||||
|
name: &'static str,
|
||||||
/// The width of the paper in millimeters.
|
/// The width of the paper in millimeters.
|
||||||
width: Scalar,
|
width: Scalar,
|
||||||
/// The height of the paper in millimeters.
|
/// The height of the paper in millimeters.
|
||||||
@ -490,12 +492,13 @@ impl Paper {
|
|||||||
|
|
||||||
/// Defines paper constants and a paper parsing implementation.
|
/// Defines paper constants and a paper parsing implementation.
|
||||||
macro_rules! papers {
|
macro_rules! papers {
|
||||||
($(($var:ident: $width:expr, $height: expr, $pat:literal))*) => {
|
($(($var:ident: $width:expr, $height: expr, $name:literal))*) => {
|
||||||
/// Predefined papers.
|
/// Predefined papers.
|
||||||
///
|
///
|
||||||
/// Each paper is parsable from its name in kebab-case.
|
/// Each paper is parsable from its name in kebab-case.
|
||||||
impl Paper {
|
impl Paper {
|
||||||
$(pub const $var: Self = Self {
|
$(pub const $var: Self = Self {
|
||||||
|
name: $name,
|
||||||
width: Scalar($width),
|
width: Scalar($width),
|
||||||
height: Scalar($height),
|
height: Scalar($height),
|
||||||
};)*
|
};)*
|
||||||
@ -506,7 +509,7 @@ macro_rules! papers {
|
|||||||
|
|
||||||
fn from_str(name: &str) -> Result<Self, Self::Err> {
|
fn from_str(name: &str) -> Result<Self, Self::Err> {
|
||||||
match name.to_lowercase().as_str() {
|
match name.to_lowercase().as_str() {
|
||||||
$($pat => Ok(Self::$var),)*
|
$($name => Ok(Self::$var),)*
|
||||||
_ => Err("unknown paper size"),
|
_ => Err("unknown paper size"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -516,9 +519,13 @@ macro_rules! papers {
|
|||||||
Paper,
|
Paper,
|
||||||
$(
|
$(
|
||||||
/// Produces a paper of the respective size.
|
/// Produces a paper of the respective size.
|
||||||
$pat => Self::$var,
|
$name => Self::$var,
|
||||||
)*
|
)*
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: Paper => v.name.into()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,8 +20,11 @@ use crate::text::{
|
|||||||
///
|
///
|
||||||
/// ## Example { #example }
|
/// ## Example { #example }
|
||||||
/// ```example
|
/// ```example
|
||||||
/// #set par(first-line-indent: 1em, justify: true)
|
|
||||||
/// #show par: set block(spacing: 0.65em)
|
/// #show par: set block(spacing: 0.65em)
|
||||||
|
/// #set par(
|
||||||
|
/// first-line-indent: 1em,
|
||||||
|
/// justify: true,
|
||||||
|
/// )
|
||||||
///
|
///
|
||||||
/// We proceed by contradiction.
|
/// We proceed by contradiction.
|
||||||
/// Suppose that there exists a set
|
/// Suppose that there exists a set
|
||||||
@ -40,8 +43,6 @@ use crate::text::{
|
|||||||
#[element(Construct)]
|
#[element(Construct)]
|
||||||
pub struct ParElem {
|
pub struct ParElem {
|
||||||
/// The spacing between lines.
|
/// The spacing between lines.
|
||||||
///
|
|
||||||
/// The default value is `{0.65em}`.
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[default(Em::new(0.65).into())]
|
#[default(Em::new(0.65).into())]
|
||||||
pub leading: Length,
|
pub leading: Length,
|
||||||
@ -102,6 +103,7 @@ pub struct ParElem {
|
|||||||
|
|
||||||
/// The contents of the paragraph.
|
/// The contents of the paragraph.
|
||||||
#[external]
|
#[external]
|
||||||
|
#[required]
|
||||||
pub body: Content,
|
pub body: Content,
|
||||||
|
|
||||||
/// The paragraph's children.
|
/// The paragraph's children.
|
||||||
|
@ -104,16 +104,18 @@ pub struct TableElem {
|
|||||||
|
|
||||||
/// How to stroke the cells.
|
/// How to stroke the cells.
|
||||||
///
|
///
|
||||||
/// This can be a color, a stroke width, both, or `{none}` to disable
|
/// See the [line's documentation]($func/line.stroke) for more details.
|
||||||
/// the stroke.
|
/// Strokes can be disabled by setting this to `{none}`.
|
||||||
|
///
|
||||||
|
/// _Note:_ Richer stroke customization for individual cells is not yet
|
||||||
|
/// implemented, but will be in the future. In the meantime, you can use
|
||||||
|
/// the third-party [tablex library](https://github.com/PgBiel/typst-tablex/).
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default(Some(PartialStroke::default()))]
|
#[default(Some(PartialStroke::default()))]
|
||||||
pub stroke: Option<PartialStroke>,
|
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}`.
|
|
||||||
#[default(Abs::pt(5.0).into())]
|
#[default(Abs::pt(5.0).into())]
|
||||||
pub inset: Rel<Length>,
|
pub inset: Rel<Length>,
|
||||||
|
|
||||||
|
@ -83,10 +83,9 @@ pub struct RotateElem {
|
|||||||
|
|
||||||
/// The origin of the rotation.
|
/// The origin of the rotation.
|
||||||
///
|
///
|
||||||
/// By default, the origin is the center of the rotated element. If,
|
/// If, for instance, you wanted the bottom left corner of the rotated
|
||||||
/// however, you wanted the bottom left corner of the rotated element to
|
/// element to stay aligned with the baseline, you would set it to `bottom +
|
||||||
/// stay aligned with the baseline, you would set the origin to `bottom +
|
/// left` instead.
|
||||||
/// left`.
|
|
||||||
///
|
///
|
||||||
/// ```example
|
/// ```example
|
||||||
/// #set text(spacing: 8pt)
|
/// #set text(spacing: 8pt)
|
||||||
@ -98,6 +97,8 @@ pub struct RotateElem {
|
|||||||
/// #box(rotate(30deg, origin: bottom + right, square()))
|
/// #box(rotate(30deg, origin: bottom + right, square()))
|
||||||
/// ```
|
/// ```
|
||||||
#[resolve]
|
#[resolve]
|
||||||
|
#[fold]
|
||||||
|
#[default(Align::CENTER_HORIZON)]
|
||||||
pub origin: Axes<Option<GenAlign>>,
|
pub origin: Axes<Option<GenAlign>>,
|
||||||
|
|
||||||
/// The content to rotate.
|
/// The content to rotate.
|
||||||
@ -115,8 +116,8 @@ impl Layout for RotateElem {
|
|||||||
) -> 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 = self.origin(styles).unwrap_or(Align::CENTER_HORIZON);
|
let Axes { x, y } =
|
||||||
let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s));
|
self.origin(styles).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(styles)))
|
.pre_concat(Transform::rotate(self.angle(styles)))
|
||||||
.pre_concat(Transform::translate(-x, -y));
|
.pre_concat(Transform::translate(-x, -y));
|
||||||
@ -160,13 +161,13 @@ pub struct ScaleElem {
|
|||||||
|
|
||||||
/// The origin of the transformation.
|
/// The origin of the transformation.
|
||||||
///
|
///
|
||||||
/// By default, the origin is the center of the scaled element.
|
|
||||||
///
|
|
||||||
/// ```example
|
/// ```example
|
||||||
/// 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
|
||||||
/// ```
|
/// ```
|
||||||
#[resolve]
|
#[resolve]
|
||||||
|
#[fold]
|
||||||
|
#[default(Align::CENTER_HORIZON)]
|
||||||
pub origin: Axes<Option<GenAlign>>,
|
pub origin: Axes<Option<GenAlign>>,
|
||||||
|
|
||||||
/// The content to scale.
|
/// The content to scale.
|
||||||
@ -184,8 +185,8 @@ impl Layout for ScaleElem {
|
|||||||
) -> 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 = self.origin(styles).unwrap_or(Align::CENTER_HORIZON);
|
let Axes { x, y } =
|
||||||
let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s));
|
self.origin(styles).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.x(styles), self.y(styles)))
|
.pre_concat(Transform::scale(self.x(styles), self.y(styles)))
|
||||||
.pre_concat(Transform::translate(-x, -y));
|
.pre_concat(Transform::translate(-x, -y));
|
||||||
|
@ -24,8 +24,6 @@ pub struct CancelElem {
|
|||||||
/// the whole element being "cancelled". A value of `{100%}` would then have
|
/// the whole element being "cancelled". A value of `{100%}` would then have
|
||||||
/// the line span precisely the element's diagonal.
|
/// the line span precisely the element's diagonal.
|
||||||
///
|
///
|
||||||
/// Defaults to `{100% + 3pt}`.
|
|
||||||
///
|
|
||||||
/// ```example
|
/// ```example
|
||||||
/// >>> #set page(width: 140pt)
|
/// >>> #set page(width: 140pt)
|
||||||
/// $ a + cancel(x, length: #200%)
|
/// $ a + cancel(x, length: #200%)
|
||||||
@ -37,8 +35,6 @@ pub struct CancelElem {
|
|||||||
/// If the cancel line should be inverted (pointing to the top left instead
|
/// If the cancel line should be inverted (pointing to the top left instead
|
||||||
/// of top right).
|
/// of top right).
|
||||||
///
|
///
|
||||||
/// Defaults to `{false}`.
|
|
||||||
///
|
|
||||||
/// ```example
|
/// ```example
|
||||||
/// >>> #set page(width: 140pt)
|
/// >>> #set page(width: 140pt)
|
||||||
/// $ (a cancel((b + c), inverted: #true)) /
|
/// $ (a cancel((b + c), inverted: #true)) /
|
||||||
@ -50,8 +46,6 @@ pub struct CancelElem {
|
|||||||
/// If two opposing cancel lines should be drawn, forming a cross over the
|
/// If two opposing cancel lines should be drawn, forming a cross over the
|
||||||
/// element. Overrides `inverted`.
|
/// element. Overrides `inverted`.
|
||||||
///
|
///
|
||||||
/// Defaults to `{false}`.
|
|
||||||
///
|
|
||||||
/// ```example
|
/// ```example
|
||||||
/// >>> #set page(width: 140pt)
|
/// >>> #set page(width: 140pt)
|
||||||
/// $ cancel(Pi, cross: #true) $
|
/// $ cancel(Pi, cross: #true) $
|
||||||
@ -85,6 +79,11 @@ pub struct CancelElem {
|
|||||||
/// ```
|
/// ```
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
|
#[default(PartialStroke {
|
||||||
|
// Default stroke has 0.5pt for better visuals.
|
||||||
|
thickness: Smart::Custom(Abs::pt(0.5)),
|
||||||
|
..Default::default()
|
||||||
|
})]
|
||||||
pub stroke: PartialStroke,
|
pub stroke: PartialStroke,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,10 +99,8 @@ impl LayoutMath for CancelElem {
|
|||||||
let span = self.span();
|
let span = self.span();
|
||||||
let length = self.length(styles).resolve(styles);
|
let length = self.length(styles).resolve(styles);
|
||||||
|
|
||||||
// Default stroke has 0.5pt for better visuals.
|
|
||||||
let stroke = self.stroke(styles).unwrap_or(Stroke {
|
let stroke = self.stroke(styles).unwrap_or(Stroke {
|
||||||
paint: TextElem::fill_in(styles),
|
paint: TextElem::fill_in(styles),
|
||||||
thickness: Abs::pt(0.5),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -19,8 +19,6 @@ pub(super) const DELIM_SHORT_FALL: Em = Em::new(0.1);
|
|||||||
#[element(LayoutMath)]
|
#[element(LayoutMath)]
|
||||||
pub struct LrElem {
|
pub struct LrElem {
|
||||||
/// The size of the brackets, relative to the height of the wrapped content.
|
/// The size of the brackets, relative to the height of the wrapped content.
|
||||||
///
|
|
||||||
/// Defaults to `{100%}`.
|
|
||||||
pub size: Smart<Rel<Length>>,
|
pub size: Smart<Rel<Length>>,
|
||||||
|
|
||||||
/// The delimited content, including the delimiters.
|
/// The delimited content, including the delimiters.
|
||||||
|
@ -27,8 +27,6 @@ pub struct OpElem {
|
|||||||
pub text: EcoString,
|
pub text: EcoString,
|
||||||
|
|
||||||
/// Whether the operator should force attachments to display as limits.
|
/// Whether the operator should force attachments to display as limits.
|
||||||
///
|
|
||||||
/// Defaults to `{false}`.
|
|
||||||
#[default(false)]
|
#[default(false)]
|
||||||
pub limits: bool,
|
pub limits: bool,
|
||||||
}
|
}
|
||||||
|
@ -144,8 +144,6 @@ pub struct FigureElem {
|
|||||||
|
|
||||||
/// How to number the figure. Accepts a
|
/// How to number the figure. Accepts a
|
||||||
/// [numbering pattern or function]($func/numbering).
|
/// [numbering pattern or function]($func/numbering).
|
||||||
///
|
|
||||||
/// Defaults to `{"1"}`.
|
|
||||||
#[default(Some(NumberingPattern::from_str("1").unwrap().into()))]
|
#[default(Some(NumberingPattern::from_str("1").unwrap().into()))]
|
||||||
pub numbering: Option<Numbering>,
|
pub numbering: Option<Numbering>,
|
||||||
|
|
||||||
@ -155,8 +153,6 @@ pub struct FigureElem {
|
|||||||
|
|
||||||
/// Whether the figure should appear in an [`outline`]($func/outline)
|
/// Whether the figure should appear in an [`outline`]($func/outline)
|
||||||
/// of figures.
|
/// of figures.
|
||||||
///
|
|
||||||
/// Defaults to `{true}`.
|
|
||||||
#[default(true)]
|
#[default(true)]
|
||||||
pub outlined: bool,
|
pub outlined: bool,
|
||||||
|
|
||||||
|
@ -127,7 +127,7 @@ pub struct OutlineElem {
|
|||||||
pub indent: bool,
|
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.
|
||||||
///
|
///
|
||||||
/// ```example
|
/// ```example
|
||||||
/// #outline(fill: line(length: 100%))
|
/// #outline(fill: line(length: 100%))
|
||||||
|
@ -98,7 +98,7 @@ use crate::prelude::*;
|
|||||||
///
|
///
|
||||||
/// Display: Query
|
/// Display: Query
|
||||||
/// Category: meta
|
/// Category: meta
|
||||||
/// Returns: content
|
/// Returns: array
|
||||||
#[func]
|
#[func]
|
||||||
pub fn query(
|
pub fn query(
|
||||||
/// Can be an element function like a `heading` or `figure`, a `{<label>}`
|
/// Can be an element function like a `heading` or `figure`, a `{<label>}`
|
||||||
|
@ -15,8 +15,11 @@ use crate::prelude::*;
|
|||||||
/// Category: text
|
/// Category: text
|
||||||
#[element(Show)]
|
#[element(Show)]
|
||||||
pub struct UnderlineElem {
|
pub struct UnderlineElem {
|
||||||
/// How to stroke the line. The text color and thickness are read from the
|
/// How to stroke the line.
|
||||||
/// font tables if `{auto}`.
|
///
|
||||||
|
/// See the [line's documentation]($func/line.stroke) for more details. If
|
||||||
|
/// set to `{auto}`, takes on the text's color and a thickness defined in
|
||||||
|
/// the current font.
|
||||||
///
|
///
|
||||||
/// ```example
|
/// ```example
|
||||||
/// Take #underline(
|
/// Take #underline(
|
||||||
@ -89,8 +92,11 @@ impl Show for UnderlineElem {
|
|||||||
/// Category: text
|
/// Category: text
|
||||||
#[element(Show)]
|
#[element(Show)]
|
||||||
pub struct OverlineElem {
|
pub struct OverlineElem {
|
||||||
/// How to stroke the line. The text color and thickness are read from the
|
/// How to stroke the line.
|
||||||
/// font tables if `{auto}`.
|
///
|
||||||
|
/// See the [line's documentation]($func/line.stroke) for more details. If
|
||||||
|
/// set to `{auto}`, takes on the text's color and a thickness defined in
|
||||||
|
/// the current font.
|
||||||
///
|
///
|
||||||
/// ```example
|
/// ```example
|
||||||
/// #set text(fill: olive)
|
/// #set text(fill: olive)
|
||||||
@ -169,8 +175,11 @@ impl Show for OverlineElem {
|
|||||||
/// Category: text
|
/// Category: text
|
||||||
#[element(Show)]
|
#[element(Show)]
|
||||||
pub struct StrikeElem {
|
pub struct StrikeElem {
|
||||||
/// How to stroke the line. The text color and thickness are read from the
|
/// How to stroke the line.
|
||||||
/// font tables if `{auto}`.
|
///
|
||||||
|
/// See the [line's documentation]($func/line.stroke) for more details. If
|
||||||
|
/// set to `{auto}`, takes on the text's color and a thickness defined in
|
||||||
|
/// the current font.
|
||||||
///
|
///
|
||||||
/// _Note:_ Please don't use this for real redaction as you can still
|
/// _Note:_ Please don't use this for real redaction as you can still
|
||||||
/// copy paste the text.
|
/// copy paste the text.
|
||||||
|
@ -573,7 +573,11 @@ cast_from_value! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cast_to_value! {
|
cast_to_value! {
|
||||||
v: FontList => v.0.into()
|
v: FontList => if v.0.len() == 1 {
|
||||||
|
v.0.into_iter().next().unwrap().0.into()
|
||||||
|
} else {
|
||||||
|
v.0.into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The size of text.
|
/// The size of text.
|
||||||
|
@ -27,8 +27,11 @@ pub struct PathElem {
|
|||||||
/// [non-zero winding rule](https://en.wikipedia.org/wiki/Nonzero-rule).
|
/// [non-zero winding rule](https://en.wikipedia.org/wiki/Nonzero-rule).
|
||||||
pub fill: Option<Paint>,
|
pub fill: Option<Paint>,
|
||||||
|
|
||||||
/// How to stroke the path. See the
|
/// How to stroke the path. This can be:
|
||||||
/// [polygon's documentation]($func/polygon.stroke) for more details.
|
///
|
||||||
|
/// See the [line's documentation]($func/line.stroke) for more details. Can
|
||||||
|
/// be set to `{none}` to disable the stroke or to `{auto}` for a stroke of
|
||||||
|
/// `{1pt}` black if and if only if no fill is given.
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
pub stroke: Smart<Option<PartialStroke>>,
|
pub stroke: Smart<Option<PartialStroke>>,
|
||||||
|
@ -29,15 +29,9 @@ pub struct PolygonElem {
|
|||||||
|
|
||||||
/// How to stroke the polygon. This can be:
|
/// How to stroke the polygon. This can be:
|
||||||
///
|
///
|
||||||
/// - `{none}` to disable the stroke.
|
/// See the [line's documentation]($func/line.stroke) for more details. Can
|
||||||
/// - `{auto}` for a stroke of `{1pt}` black if and if only if no fill is
|
/// be set to `{none}` to disable the stroke or to `{auto}` for a stroke of
|
||||||
/// given.
|
/// `{1pt}` black if and if only if no fill is given.
|
||||||
/// - A length specifying the stroke's thickness. The color is inherited,
|
|
||||||
/// defaulting to black.
|
|
||||||
/// - A color to use for the stroke. The thickness is inherited, defaulting
|
|
||||||
/// to `{1pt}`.
|
|
||||||
/// - A stroke combined from color and thickness using the `+` operator as
|
|
||||||
/// in `{2pt + red}`.
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
pub stroke: Smart<Option<PartialStroke>>,
|
pub stroke: Smart<Option<PartialStroke>>,
|
||||||
|
@ -38,32 +38,13 @@ pub struct RectElem {
|
|||||||
|
|
||||||
/// How to stroke the rectangle. This can be:
|
/// How to stroke the rectangle. This can be:
|
||||||
///
|
///
|
||||||
/// - `{none}` to disable the stroke.
|
/// - `{none}` to disable stroking
|
||||||
/// - `{auto}` for a stroke of `{1pt}` black if and if only if no fill is
|
/// - `{auto}` for a stroke of `{1pt + black}` if and if only if no fill is
|
||||||
/// given.
|
/// given.
|
||||||
/// - A length specifying the stroke's thickness. The color is inherited,
|
/// - Any kind of stroke that can also be used for
|
||||||
/// defaulting to black.
|
/// [lines]($func/line.stroke).
|
||||||
/// - A color to use for the stroke. The thickness is inherited, defaulting
|
/// - A dictionary describing the stroke for each side inidvidually. The
|
||||||
/// to `{1pt}`.
|
/// dictionary can contain the following keys in order of precedence:
|
||||||
/// - A stroke combined from color and thickness using the `+` operator as
|
|
||||||
/// in `{2pt + red}`.
|
|
||||||
/// - A stroke described by a dictionary with any of the following keys:
|
|
||||||
/// - `color`: the color to use for the stroke
|
|
||||||
/// - `thickness`: the stroke's thickness
|
|
||||||
/// - `cap`: one of `"butt"`, `"round"` or `"square"`, the line cap of the stroke
|
|
||||||
/// - `join`: one of `"miter"`, `"round"` or `"bevel"`, the line join of the stroke
|
|
||||||
/// - `miter-limit`: the miter limit to use if `join` is `"miter"`, defaults to 4.0
|
|
||||||
/// - `dash`: the dash pattern to use. Can be any of the following:
|
|
||||||
/// - One of the strings `"solid"`, `"dotted"`, `"densely-dotted"`, `"loosely-dotted"`,
|
|
||||||
/// `"dashed"`, `"densely-dashed"`, `"loosely-dashed"`, `"dash-dotted"`,
|
|
||||||
/// `"densely-dash-dotted"` or `"loosely-dash-dotted"`
|
|
||||||
/// - An array with elements that specify the lengths of dashes and gaps, alternating.
|
|
||||||
/// Elements can also be the string `"dot"` for a length equal to the line thickness.
|
|
||||||
/// - A dict with the keys `array`, same as the array above, and `phase`, the offset to
|
|
||||||
/// the start of the first dash.
|
|
||||||
/// - Another dictionary describing the stroke for each side inidvidually.
|
|
||||||
/// The dictionary can contain the following keys in order
|
|
||||||
/// of precedence:
|
|
||||||
/// - `top`: The top stroke.
|
/// - `top`: The top stroke.
|
||||||
/// - `right`: The right stroke.
|
/// - `right`: The right stroke.
|
||||||
/// - `bottom`: The bottom stroke.
|
/// - `bottom`: The bottom stroke.
|
||||||
@ -126,8 +107,6 @@ pub struct RectElem {
|
|||||||
|
|
||||||
/// How much to pad the rectangle's content.
|
/// How much to pad the rectangle's content.
|
||||||
///
|
///
|
||||||
/// The default value is `{5pt}`.
|
|
||||||
///
|
|
||||||
/// _Note:_ When the rectangle contains text, its exact size depends on the
|
/// _Note:_ When the rectangle contains text, its exact size depends on the
|
||||||
/// current [text edges]($func/text.top-edge).
|
/// current [text edges]($func/text.top-edge).
|
||||||
///
|
///
|
||||||
@ -242,8 +221,6 @@ pub struct SquareElem {
|
|||||||
|
|
||||||
/// 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}`.
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default(Sides::splat(Abs::pt(5.0).into()))]
|
#[default(Sides::splat(Abs::pt(5.0).into()))]
|
||||||
@ -326,8 +303,6 @@ pub struct EllipseElem {
|
|||||||
|
|
||||||
/// 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}`.
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default(Sides::splat(Abs::pt(5.0).into()))]
|
#[default(Sides::splat(Abs::pt(5.0).into()))]
|
||||||
@ -436,8 +411,6 @@ pub struct CircleElem {
|
|||||||
|
|
||||||
/// 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}`.
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default(Sides::splat(Abs::pt(5.0).into()))]
|
#[default(Sides::splat(Abs::pt(5.0).into()))]
|
||||||
|
@ -407,9 +407,29 @@ fn create_vtable_func(element: &Elem) -> TokenStream {
|
|||||||
|
|
||||||
/// Create a parameter info for a field.
|
/// Create a parameter info for a field.
|
||||||
fn create_param_info(field: &Field) -> TokenStream {
|
fn create_param_info(field: &Field) -> TokenStream {
|
||||||
let Field { name, docs, positional, variadic, required, ty, .. } = field;
|
let Field {
|
||||||
|
name,
|
||||||
|
docs,
|
||||||
|
positional,
|
||||||
|
variadic,
|
||||||
|
required,
|
||||||
|
default,
|
||||||
|
fold,
|
||||||
|
ty,
|
||||||
|
output,
|
||||||
|
..
|
||||||
|
} = field;
|
||||||
let named = !positional;
|
let named = !positional;
|
||||||
let settable = field.settable();
|
let settable = field.settable();
|
||||||
|
let default_ty = if *fold { &output } else { &ty };
|
||||||
|
let default = quote_option(&settable.then(|| {
|
||||||
|
quote! {
|
||||||
|
|| {
|
||||||
|
let typed: #default_ty = #default;
|
||||||
|
::typst::eval::Value::from(typed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
let ty = if *variadic {
|
let ty = if *variadic {
|
||||||
quote! { <#ty as ::typst::eval::Variadics>::Inner }
|
quote! { <#ty as ::typst::eval::Variadics>::Inner }
|
||||||
} else {
|
} else {
|
||||||
@ -422,6 +442,7 @@ fn create_param_info(field: &Field) -> TokenStream {
|
|||||||
cast: <#ty as ::typst::eval::Cast<
|
cast: <#ty as ::typst::eval::Cast<
|
||||||
::typst::syntax::Spanned<::typst::eval::Value>
|
::typst::syntax::Spanned<::typst::eval::Value>
|
||||||
>>::describe(),
|
>>::describe(),
|
||||||
|
default: #default,
|
||||||
positional: #positional,
|
positional: #positional,
|
||||||
named: #named,
|
named: #named,
|
||||||
variadic: #variadic,
|
variadic: #variadic,
|
||||||
|
@ -152,6 +152,14 @@ fn create_param_info(param: &Param) -> TokenStream {
|
|||||||
let Param { name, docs, named, variadic, ty, default, .. } = param;
|
let Param { name, docs, named, variadic, ty, default, .. } = param;
|
||||||
let positional = !named;
|
let positional = !named;
|
||||||
let required = default.is_none();
|
let required = default.is_none();
|
||||||
|
let default = quote_option(&default.as_ref().map(|_default| {
|
||||||
|
quote! {
|
||||||
|
|| {
|
||||||
|
let typed: #ty = #default;
|
||||||
|
::typst::eval::Value::from(typed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
let ty = if *variadic {
|
let ty = if *variadic {
|
||||||
quote! { <#ty as ::typst::eval::Variadics>::Inner }
|
quote! { <#ty as ::typst::eval::Variadics>::Inner }
|
||||||
} else {
|
} else {
|
||||||
@ -164,6 +172,7 @@ fn create_param_info(param: &Param) -> TokenStream {
|
|||||||
cast: <#ty as ::typst::eval::Cast<
|
cast: <#ty as ::typst::eval::Cast<
|
||||||
::typst::syntax::Spanned<::typst::eval::Value>
|
::typst::syntax::Spanned<::typst::eval::Value>
|
||||||
>>::describe(),
|
>>::describe(),
|
||||||
|
default: #default,
|
||||||
positional: #positional,
|
positional: #positional,
|
||||||
named: #named,
|
named: #named,
|
||||||
variadic: #variadic,
|
variadic: #variadic,
|
||||||
|
@ -230,6 +230,12 @@ impl<T: Cast> Cast for Option<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: Into<Value>> From<Spanned<T>> for Value {
|
||||||
|
fn from(spanned: Spanned<T>) -> Self {
|
||||||
|
spanned.v.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Into<Value>> From<Option<T>> for Value {
|
impl<T: Into<Value>> From<Option<T>> for Value {
|
||||||
fn from(v: Option<T>) -> Self {
|
fn from(v: Option<T>) -> Self {
|
||||||
match v {
|
match v {
|
||||||
|
@ -279,6 +279,8 @@ pub struct ParamInfo {
|
|||||||
pub docs: &'static str,
|
pub docs: &'static str,
|
||||||
/// Valid values for the parameter.
|
/// Valid values for the parameter.
|
||||||
pub cast: CastInfo,
|
pub cast: CastInfo,
|
||||||
|
/// Creates an instance of the parameter's default value.
|
||||||
|
pub default: Option<fn() -> Value>,
|
||||||
/// Is the parameter positional?
|
/// Is the parameter positional?
|
||||||
pub positional: bool,
|
pub positional: bool,
|
||||||
/// Is the parameter named?
|
/// Is the parameter named?
|
||||||
|
@ -54,7 +54,7 @@ pub enum Value {
|
|||||||
Styles(Styles),
|
Styles(Styles),
|
||||||
/// An array of values: `(1, "hi", 12cm)`.
|
/// An array of values: `(1, "hi", 12cm)`.
|
||||||
Array(Array),
|
Array(Array),
|
||||||
/// A dictionary value: `(color: #f79143, pattern: dashed)`.
|
/// A dictionary value: `(a: 1, b: "hi")`.
|
||||||
Dict(Dict),
|
Dict(Dict),
|
||||||
/// An executable function.
|
/// An executable function.
|
||||||
Func(Func),
|
Func(Func),
|
||||||
|
@ -146,6 +146,10 @@ cast_from_value! {
|
|||||||
aligns: Axes<GenAlign> => aligns.map(Some),
|
aligns: Axes<GenAlign> => aligns.map(Some),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: Axes<Align> => v.map(GenAlign::from).into()
|
||||||
|
}
|
||||||
|
|
||||||
cast_to_value! {
|
cast_to_value! {
|
||||||
v: Axes<Option<GenAlign>> => match (v.x, v.y) {
|
v: Axes<Option<GenAlign>> => match (v.x, v.y) {
|
||||||
(Some(x), Some(y)) => Axes::new(x, y).into(),
|
(Some(x), Some(y)) => Axes::new(x, y).into(),
|
||||||
@ -196,6 +200,14 @@ impl Fold for GenAlign {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Fold for Align {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn fold(self, _: Self::Output) -> Self::Output {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Utility struct to restrict a passed alignment value to the horizontal axis
|
/// Utility struct to restrict a passed alignment value to the horizontal axis
|
||||||
/// on cast.
|
/// on cast.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
@ -141,9 +141,9 @@ where
|
|||||||
{
|
{
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
if let Axes { x: Some(x), y: Some(y) } =
|
if let Axes { x: Some(x), y: Some(y) } =
|
||||||
self.as_ref().map(|v| (v as &dyn Any).downcast_ref::<Align>())
|
self.as_ref().map(|v| (v as &dyn Any).downcast_ref::<GenAlign>())
|
||||||
{
|
{
|
||||||
write!(f, "{:?}-{:?}", x, y)
|
write!(f, "{:?} + {:?}", x, y)
|
||||||
} else if (&self.x as &dyn Any).is::<Abs>() {
|
} else if (&self.x as &dyn Any).is::<Abs>() {
|
||||||
write!(f, "Size({:?}, {:?})", self.x, self.y)
|
write!(f, "Size({:?}, {:?})", self.x, self.y)
|
||||||
} else {
|
} else {
|
||||||
|
@ -117,43 +117,47 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn cast(mut value: Value) -> StrResult<Self> {
|
fn cast(mut value: Value) -> StrResult<Self> {
|
||||||
|
let keys = [
|
||||||
|
"top-left",
|
||||||
|
"top-right",
|
||||||
|
"bottom-right",
|
||||||
|
"bottom-left",
|
||||||
|
"left",
|
||||||
|
"top",
|
||||||
|
"right",
|
||||||
|
"bottom",
|
||||||
|
"rest",
|
||||||
|
];
|
||||||
|
|
||||||
if let Value::Dict(dict) = &mut value {
|
if let Value::Dict(dict) = &mut value {
|
||||||
let mut take = |key| dict.take(key).ok().map(T::cast).transpose();
|
if dict.iter().any(|(key, _)| keys.contains(&key.as_str())) {
|
||||||
|
let mut take = |key| dict.take(key).ok().map(T::cast).transpose();
|
||||||
|
let rest = take("rest")?;
|
||||||
|
let left = take("left")?.or_else(|| rest.clone());
|
||||||
|
let top = take("top")?.or_else(|| rest.clone());
|
||||||
|
let right = take("right")?.or_else(|| rest.clone());
|
||||||
|
let bottom = take("bottom")?.or_else(|| rest.clone());
|
||||||
|
let corners = Corners {
|
||||||
|
top_left: take("top-left")?
|
||||||
|
.or_else(|| top.clone())
|
||||||
|
.or_else(|| left.clone()),
|
||||||
|
top_right: take("top-right")?
|
||||||
|
.or_else(|| top.clone())
|
||||||
|
.or_else(|| right.clone()),
|
||||||
|
bottom_right: take("bottom-right")?
|
||||||
|
.or_else(|| bottom.clone())
|
||||||
|
.or_else(|| right.clone()),
|
||||||
|
bottom_left: take("bottom-left")?
|
||||||
|
.or_else(|| bottom.clone())
|
||||||
|
.or_else(|| left.clone()),
|
||||||
|
};
|
||||||
|
|
||||||
let rest = take("rest")?;
|
dict.finish(&keys)?;
|
||||||
let left = take("left")?.or_else(|| rest.clone());
|
return Ok(corners);
|
||||||
let top = take("top")?.or_else(|| rest.clone());
|
}
|
||||||
let right = take("right")?.or_else(|| rest.clone());
|
}
|
||||||
let bottom = take("bottom")?.or_else(|| rest.clone());
|
|
||||||
let corners = Corners {
|
|
||||||
top_left: take("top-left")?
|
|
||||||
.or_else(|| top.clone())
|
|
||||||
.or_else(|| left.clone()),
|
|
||||||
top_right: take("top-right")?
|
|
||||||
.or_else(|| top.clone())
|
|
||||||
.or_else(|| right.clone()),
|
|
||||||
bottom_right: take("bottom-right")?
|
|
||||||
.or_else(|| bottom.clone())
|
|
||||||
.or_else(|| right.clone()),
|
|
||||||
bottom_left: take("bottom-left")?
|
|
||||||
.or_else(|| bottom.clone())
|
|
||||||
.or_else(|| left.clone()),
|
|
||||||
};
|
|
||||||
|
|
||||||
dict.finish(&[
|
if T::is(&value) {
|
||||||
"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)?)))
|
Ok(Self::splat(Some(T::cast(value)?)))
|
||||||
} else {
|
} else {
|
||||||
<Self as Cast>::error(value)
|
<Self as Cast>::error(value)
|
||||||
@ -184,30 +188,27 @@ impl<T: Fold> Fold for Corners<Option<T>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> From<Corners<Option<T>>> for Value
|
impl<T> From<Corners<T>> for Value
|
||||||
where
|
where
|
||||||
T: PartialEq + Into<Value>,
|
T: PartialEq + Into<Value>,
|
||||||
{
|
{
|
||||||
fn from(corners: Corners<Option<T>>) -> Self {
|
fn from(corners: Corners<T>) -> Self {
|
||||||
if corners.is_uniform() {
|
if corners.is_uniform() {
|
||||||
if let Some(value) = corners.top_left {
|
return corners.top_left.into();
|
||||||
return value.into();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut dict = Dict::new();
|
let mut dict = Dict::new();
|
||||||
if let Some(top_left) = corners.top_left {
|
let mut handle = |key: &str, component: T| {
|
||||||
dict.insert("top-left".into(), top_left.into());
|
let value = component.into();
|
||||||
}
|
if value != Value::None {
|
||||||
if let Some(top_right) = corners.top_right {
|
dict.insert(key.into(), value);
|
||||||
dict.insert("top-right".into(), top_right.into());
|
}
|
||||||
}
|
};
|
||||||
if let Some(bottom_right) = corners.bottom_right {
|
|
||||||
dict.insert("bottom-right".into(), bottom_right.into());
|
handle("top-left", corners.top_left);
|
||||||
}
|
handle("top-right", corners.top_right);
|
||||||
if let Some(bottom_left) = corners.bottom_left {
|
handle("bottom-right", corners.bottom_right);
|
||||||
dict.insert("bottom-left".into(), bottom_left.into());
|
handle("bottom-left", corners.bottom_left);
|
||||||
}
|
|
||||||
|
|
||||||
Value::Dict(dict)
|
Value::Dict(dict)
|
||||||
}
|
}
|
||||||
|
@ -227,3 +227,7 @@ impl Fold for Rel<Length> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: Rel<Abs> => v.map(Length::from).into()
|
||||||
|
}
|
||||||
|
@ -187,10 +187,10 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn cast(mut value: Value) -> StrResult<Self> {
|
fn cast(mut value: Value) -> StrResult<Self> {
|
||||||
|
let keys = ["left", "top", "right", "bottom", "x", "y", "rest"];
|
||||||
if let Value::Dict(dict) = &mut value {
|
if let Value::Dict(dict) = &mut value {
|
||||||
let mut try_cast = || -> StrResult<_> {
|
if dict.iter().any(|(key, _)| keys.contains(&key.as_str())) {
|
||||||
let mut take = |key| dict.take(key).ok().map(T::cast).transpose();
|
let mut take = |key| dict.take(key).ok().map(T::cast).transpose();
|
||||||
|
|
||||||
let rest = take("rest")?;
|
let rest = take("rest")?;
|
||||||
let x = take("x")?.or_else(|| rest.clone());
|
let x = take("x")?.or_else(|| rest.clone());
|
||||||
let y = take("y")?.or_else(|| rest.clone());
|
let y = take("y")?.or_else(|| rest.clone());
|
||||||
@ -201,13 +201,8 @@ where
|
|||||||
bottom: take("bottom")?.or_else(|| y.clone()),
|
bottom: take("bottom")?.or_else(|| y.clone()),
|
||||||
};
|
};
|
||||||
|
|
||||||
dict.finish(&["left", "top", "right", "bottom", "x", "y", "rest"])?;
|
dict.finish(&keys)?;
|
||||||
|
return Ok(sides);
|
||||||
Ok(sides)
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Ok(res) = try_cast() {
|
|
||||||
return Ok(res);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,35 +218,31 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> From<Sides<Option<T>>> for Value
|
impl<T> From<Sides<T>> for Value
|
||||||
where
|
where
|
||||||
T: PartialEq + Into<Value>,
|
T: PartialEq + Into<Value>,
|
||||||
{
|
{
|
||||||
fn from(sides: Sides<Option<T>>) -> Self {
|
fn from(sides: Sides<T>) -> Self {
|
||||||
if sides.is_uniform() {
|
if sides.is_uniform() {
|
||||||
if let Some(value) = sides.left {
|
return sides.left.into();
|
||||||
return value.into();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut dict = Dict::new();
|
let mut dict = Dict::new();
|
||||||
if let Some(left) = sides.left {
|
let mut handle = |key: &str, component: T| {
|
||||||
dict.insert("left".into(), left.into());
|
let value = component.into();
|
||||||
}
|
if value != Value::None {
|
||||||
if let Some(top) = sides.top {
|
dict.insert(key.into(), value);
|
||||||
dict.insert("top".into(), top.into());
|
}
|
||||||
}
|
};
|
||||||
if let Some(right) = sides.right {
|
|
||||||
dict.insert("right".into(), right.into());
|
handle("left", sides.left);
|
||||||
}
|
handle("top", sides.top);
|
||||||
if let Some(bottom) = sides.bottom {
|
handle("right", sides.right);
|
||||||
dict.insert("bottom".into(), bottom.into());
|
handle("bottom", sides.bottom);
|
||||||
}
|
|
||||||
|
|
||||||
Value::Dict(dict)
|
Value::Dict(dict)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Resolve> Resolve for Sides<T> {
|
impl<T: Resolve> Resolve for Sides<T> {
|
||||||
type Output = Sides<T::Output>;
|
type Output = Sides<T::Output>;
|
||||||
|
|
||||||
|
@ -51,6 +51,35 @@ pub struct PartialStroke<T = Length> {
|
|||||||
pub miter_limit: Smart<Scalar>,
|
pub miter_limit: Smart<Scalar>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> PartialStroke<T> {
|
||||||
|
/// Map the contained lengths with `f`.
|
||||||
|
pub fn map<F, U>(self, f: F) -> PartialStroke<U>
|
||||||
|
where
|
||||||
|
F: Fn(T) -> U,
|
||||||
|
{
|
||||||
|
PartialStroke {
|
||||||
|
paint: self.paint,
|
||||||
|
thickness: self.thickness.map(&f),
|
||||||
|
line_cap: self.line_cap,
|
||||||
|
line_join: self.line_join,
|
||||||
|
dash_pattern: self.dash_pattern.map(|pattern| {
|
||||||
|
pattern.map(|pattern| DashPattern {
|
||||||
|
array: pattern
|
||||||
|
.array
|
||||||
|
.into_iter()
|
||||||
|
.map(|l| match l {
|
||||||
|
DashLength::Length(v) => DashLength::Length(f(v)),
|
||||||
|
DashLength::LineWidth => DashLength::LineWidth,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
phase: f(pattern.phase),
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
miter_limit: self.miter_limit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PartialStroke<Abs> {
|
impl PartialStroke<Abs> {
|
||||||
/// Unpack the stroke, filling missing fields from the `default`.
|
/// Unpack the stroke, filling missing fields from the `default`.
|
||||||
pub fn unwrap_or(self, default: Stroke) -> Stroke {
|
pub fn unwrap_or(self, default: Stroke) -> Stroke {
|
||||||
@ -106,13 +135,13 @@ impl<T: Debug> Debug for PartialStroke<T> {
|
|||||||
}
|
}
|
||||||
(Smart::Custom(paint), Smart::Auto) => paint.fmt(f),
|
(Smart::Custom(paint), Smart::Auto) => paint.fmt(f),
|
||||||
(Smart::Auto, Smart::Custom(thickness)) => thickness.fmt(f),
|
(Smart::Auto, Smart::Custom(thickness)) => thickness.fmt(f),
|
||||||
(Smart::Auto, Smart::Auto) => f.pad("<stroke>"),
|
(Smart::Auto, Smart::Auto) => f.pad("1pt + black"),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
write!(f, "(")?;
|
write!(f, "(")?;
|
||||||
let mut sep = "";
|
let mut sep = "";
|
||||||
if let Smart::Custom(paint) = &paint {
|
if let Smart::Custom(paint) = &paint {
|
||||||
write!(f, "{}color: {:?}", sep, paint)?;
|
write!(f, "{}paint: {:?}", sep, paint)?;
|
||||||
sep = ", ";
|
sep = ", ";
|
||||||
}
|
}
|
||||||
if let Smart::Custom(thickness) = &thickness {
|
if let Smart::Custom(thickness) = &thickness {
|
||||||
@ -176,7 +205,7 @@ impl Debug for LineJoin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A line dash pattern
|
/// A line dash pattern.
|
||||||
#[derive(Clone, Eq, PartialEq, Hash)]
|
#[derive(Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct DashPattern<T = Length, DT = DashLength<T>> {
|
pub struct DashPattern<T = Length, DT = DashLength<T>> {
|
||||||
/// The dash array.
|
/// The dash array.
|
||||||
@ -293,20 +322,12 @@ impl Resolve for DashPattern {
|
|||||||
cast_from_value! {
|
cast_from_value! {
|
||||||
PartialStroke: "stroke",
|
PartialStroke: "stroke",
|
||||||
thickness: Length => Self {
|
thickness: Length => Self {
|
||||||
paint: Smart::Auto,
|
|
||||||
thickness: Smart::Custom(thickness),
|
thickness: Smart::Custom(thickness),
|
||||||
line_cap: Smart::Auto,
|
..Default::default()
|
||||||
line_join: Smart::Auto,
|
|
||||||
dash_pattern: Smart::Auto,
|
|
||||||
miter_limit: Smart::Auto,
|
|
||||||
},
|
},
|
||||||
color: Color => Self {
|
color: Color => Self {
|
||||||
paint: Smart::Custom(color.into()),
|
paint: Smart::Custom(color.into()),
|
||||||
thickness: Smart::Auto,
|
..Default::default()
|
||||||
line_cap: Smart::Auto,
|
|
||||||
line_join: Smart::Auto,
|
|
||||||
dash_pattern: Smart::Auto,
|
|
||||||
miter_limit: Smart::Auto,
|
|
||||||
},
|
},
|
||||||
mut dict: Dict => {
|
mut dict: Dict => {
|
||||||
fn take<T: Cast<Value>>(dict: &mut Dict, key: &str) -> StrResult<Smart<T>> {
|
fn take<T: Cast<Value>>(dict: &mut Dict, key: &str) -> StrResult<Smart<T>> {
|
||||||
@ -320,7 +341,6 @@ cast_from_value! {
|
|||||||
let line_join = take::<LineJoin>(&mut dict, "join")?;
|
let line_join = take::<LineJoin>(&mut dict, "join")?;
|
||||||
let dash_pattern = take::<Option<DashPattern>>(&mut dict, "dash")?;
|
let dash_pattern = take::<Option<DashPattern>>(&mut dict, "dash")?;
|
||||||
let miter_limit = take::<f64>(&mut dict, "miter-limit")?;
|
let miter_limit = take::<f64>(&mut dict, "miter-limit")?;
|
||||||
|
|
||||||
dict.finish(&["paint", "thickness", "cap", "join", "dash", "miter-limit"])?;
|
dict.finish(&["paint", "thickness", "cap", "join", "dash", "miter-limit"])?;
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@ -359,7 +379,11 @@ impl Fold for PartialStroke<Abs> {
|
|||||||
line_cap: self.line_cap.or(outer.line_cap),
|
line_cap: self.line_cap.or(outer.line_cap),
|
||||||
line_join: self.line_join.or(outer.line_join),
|
line_join: self.line_join.or(outer.line_join),
|
||||||
dash_pattern: self.dash_pattern.or(outer.dash_pattern),
|
dash_pattern: self.dash_pattern.or(outer.dash_pattern),
|
||||||
miter_limit: self.miter_limit,
|
miter_limit: self.miter_limit.or(outer.miter_limit),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: PartialStroke<Abs> => v.map(Length::from).into()
|
||||||
|
}
|
||||||
|
@ -18,7 +18,10 @@ pub fn parse(text: &str) -> SyntaxNode {
|
|||||||
/// This is only used for syntax highlighting.
|
/// This is only used for syntax highlighting.
|
||||||
pub fn parse_code(text: &str) -> SyntaxNode {
|
pub fn parse_code(text: &str) -> SyntaxNode {
|
||||||
let mut p = Parser::new(text, 0, LexMode::Code);
|
let mut p = Parser::new(text, 0, LexMode::Code);
|
||||||
code(&mut p, |_| false);
|
let m = p.marker();
|
||||||
|
p.skip();
|
||||||
|
code_exprs(&mut p, |_| false);
|
||||||
|
p.wrap_skipless(m, SyntaxKind::Code);
|
||||||
p.finish().into_iter().next().unwrap()
|
p.finish().into_iter().next().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -511,8 +514,13 @@ fn maybe_wrap_in_math(p: &mut Parser, arg: Marker, named: Option<Marker>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn code(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) {
|
fn code(p: &mut Parser, stop: impl FnMut(SyntaxKind) -> bool) {
|
||||||
let m = p.marker();
|
let m = p.marker();
|
||||||
|
code_exprs(p, stop);
|
||||||
|
p.wrap(m, SyntaxKind::Code);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn code_exprs(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) {
|
||||||
while !p.eof() && !stop(p.current()) {
|
while !p.eof() && !stop(p.current()) {
|
||||||
p.stop_at_newline(true);
|
p.stop_at_newline(true);
|
||||||
let prev = p.prev_end();
|
let prev = p.prev_end();
|
||||||
@ -529,7 +537,6 @@ fn code(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) {
|
|||||||
p.unexpected();
|
p.unexpected();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.wrap(m, SyntaxKind::Code);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn code_expr(p: &mut Parser) {
|
fn code_expr(p: &mut Parser) {
|
||||||
@ -1474,10 +1481,14 @@ impl<'s> Parser<'s> {
|
|||||||
|
|
||||||
fn wrap(&mut self, m: Marker, kind: SyntaxKind) {
|
fn wrap(&mut self, m: Marker, kind: SyntaxKind) {
|
||||||
self.unskip();
|
self.unskip();
|
||||||
|
self.wrap_skipless(m, kind);
|
||||||
|
self.skip();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wrap_skipless(&mut self, m: Marker, kind: SyntaxKind) {
|
||||||
let from = m.0.min(self.nodes.len());
|
let from = m.0.min(self.nodes.len());
|
||||||
let children = self.nodes.drain(from..).collect();
|
let children = self.nodes.drain(from..).collect();
|
||||||
self.nodes.push(SyntaxNode::inner(kind, children));
|
self.nodes.push(SyntaxNode::inner(kind, children));
|
||||||
self.skip();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn progress(&self, offset: usize) -> bool {
|
fn progress(&self, offset: usize) -> bool {
|
||||||
|
@ -15,8 +15,7 @@ use comemo::{Prehashed, Track};
|
|||||||
use elsa::FrozenVec;
|
use elsa::FrozenVec;
|
||||||
use once_cell::unsync::OnceCell;
|
use once_cell::unsync::OnceCell;
|
||||||
use oxipng::{InFile, Options, OutFile};
|
use oxipng::{InFile, Options, OutFile};
|
||||||
use rayon::iter::ParallelBridge;
|
use rayon::iter::{ParallelBridge, ParallelIterator};
|
||||||
use rayon::iter::ParallelIterator;
|
|
||||||
use tiny_skia as sk;
|
use tiny_skia as sk;
|
||||||
use unscanny::Scanner;
|
use unscanny::Scanner;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
#for x in () [Nope]
|
#for x in () [Nope]
|
||||||
|
|
||||||
// Dictionary is traversed in insertion order.
|
// Dictionary is traversed in insertion order.
|
||||||
// Should output `Age: 2. Name: Typst.`.
|
// Should output `Name: Typst. Age: 2.`.
|
||||||
#for (k, v) in (Name: "Typst", Age: 2) [
|
#for (k, v) in (Name: "Typst", Age: 2) [
|
||||||
#k: #v.
|
#k: #v.
|
||||||
]
|
]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user