diff options
36 files changed, 304 insertions, 231 deletions
diff --git a/docs/src/lib.rs b/docs/src/lib.rs index 2a7b7c5f..a9a640f6 100644 --- a/docs/src/lib.rs +++ b/docs/src/lib.rs @@ -492,6 +492,7 @@ pub struct ParamModel { pub example: Option<Html>, pub types: Vec<&'static str>, pub strings: Vec<StrParam>, + pub default: Option<Html>, pub positional: bool, pub named: 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)), types, strings, + default: info.default.map(|default| { + let node = typst::syntax::parse_code(&default().repr()); + Html::new(typst::ide::highlight_html(&node)) + }), positional: info.positional, named: info.named, required: info.required, @@ -721,6 +726,7 @@ fn method_model(resolver: &dyn Resolver, part: &'static str) -> MethodModel { example: None, types, strings: vec![], + default: None, positional, named, required, diff --git a/library/src/compute/calc.rs b/library/src/compute/calc.rs index 6008828c..bd673c54 100644 --- a/library/src/compute/calc.rs +++ b/library/src/compute/calc.rs @@ -412,7 +412,7 @@ pub fn tanh( pub fn log( /// The number whose logarithm to calculate. Must be strictly positive. 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] #[default(Spanned::new(10.0, Span::detached()))] base: Spanned<f64>, diff --git a/library/src/compute/construct.rs b/library/src/compute/construct.rs index 1c5775e5..d39b69dd 100644 --- a/library/src/compute/construct.rs +++ b/library/src/compute/construct.rs @@ -135,23 +135,18 @@ pub fn rgb( /// ] /// ``` #[external] - #[default] hex: EcoString, /// The red component. #[external] - #[default] red: Component, /// The green component. #[external] - #[default] green: Component, /// The blue component. #[external] - #[default] blue: Component, /// The alpha component. #[external] - #[default] alpha: Component, ) -> Value { Value::Color(if let Some(string) = args.find::<Spanned<EcoString>>()? { diff --git a/library/src/compute/data.rs b/library/src/compute/data.rs index d97a3782..90ac03c9 100644 --- a/library/src/compute/data.rs +++ b/library/src/compute/data.rs @@ -58,7 +58,6 @@ pub fn csv( path: Spanned<EcoString>, /// The delimiter that separates columns in the CSV file. /// Must be a single ASCII character. - /// Defaults to a comma. #[named] #[default] delimiter: Delimiter, @@ -69,7 +68,7 @@ pub fn csv( let mut builder = csv::ReaderBuilder::new(); 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 array = Array::new(); @@ -87,7 +86,7 @@ pub fn csv( } /// The delimiter to use when parsing CSV files. -struct Delimiter(u8); +struct Delimiter(char); cast_from_value! { Delimiter, @@ -102,13 +101,17 @@ cast_from_value! { 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 { 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 @@ -386,15 +391,15 @@ fn format_yaml_error(error: serde_yaml::Error) -> EcoString { /// /// ## Example { #example } /// ```example -/// #let findChild(elem, tag) = { +/// #let find-child(elem, tag) = { /// elem.children /// .find(e => "tag" in e and e.tag == tag) /// } /// /// #let article(elem) = { -/// let title = findChild(elem, "title") -/// let author = findChild(elem, "author") -/// let pars = findChild(elem, "content") +/// let title = find-child(elem, "title") +/// let author = find-child(elem, "author") +/// let pars = find-child(elem, "content") /// /// heading(title.children.first()) /// text(10pt, weight: "medium")[ @@ -411,9 +416,9 @@ fn format_yaml_error(error: serde_yaml::Error) -> EcoString { /// } /// /// #let data = xml("example.xml") -/// #for child in data.first().children { -/// if (type(child) == "dictionary") { -/// article(child) +/// #for elem in data.first().children { +/// if (type(elem) == "dictionary") { +/// article(elem) /// } /// } /// ``` diff --git a/library/src/layout/container.rs b/library/src/layout/container.rs index 04c58075..b7d0ba2f 100644 --- a/library/src/layout/container.rs +++ b/library/src/layout/container.rs @@ -229,7 +229,6 @@ pub struct BlockElem { /// Whether the block can be broken and continue on the next page. /// - /// Defaults to `{true}`. /// ```example /// #set page(height: 80pt) /// The following block will @@ -282,13 +281,16 @@ pub struct BlockElem { /// A second paragraph. /// ``` #[external] + #[default(Em::new(1.2).into())] pub spacing: Spacing, /// The spacing between this block and its predecessor. Takes precedence /// over `spacing`. Can be used in combination with a show rule to adjust /// the spacing around arbitrary block-level elements. - /// - /// The default value is `{1.2em}`. + #[external] + #[default(Em::new(1.2).into())] + pub above: Spacing, + #[internal] #[parse( let spacing = args.named("spacing")?; args.named("above")? @@ -300,8 +302,10 @@ pub struct BlockElem { /// The spacing between this block and its successor. Takes precedence /// over `spacing`. - /// - /// The default value is `{1.2em}`. + #[external] + #[default(Em::new(1.2).into())] + pub below: Spacing, + #[internal] #[parse( args.named("below")? .map(VElem::block_around) diff --git a/library/src/layout/enum.rs b/library/src/layout/enum.rs index f7117574..0fd0ebb2 100644 --- a/library/src/layout/enum.rs +++ b/library/src/layout/enum.rs @@ -121,7 +121,6 @@ pub struct EnumElem { /// Whether to display the full numbering, including the numbers of /// all parent enumerations. /// - /// Defaults to `{false}`. /// /// ```example /// #set enum(numbering: "1.a)", full: true) diff --git a/library/src/layout/list.rs b/library/src/layout/list.rs index 75fc7c3a..1e42d51b 100644 --- a/library/src/layout/list.rs +++ b/library/src/layout/list.rs @@ -66,8 +66,6 @@ pub struct ListElem { /// control, you may pass a function that maps the list's nesting depth /// (starting from `{0}`) to a desired marker. /// - /// Default: `•` - /// /// ```example /// #set list(marker: [--]) /// - A more classic list @@ -79,7 +77,7 @@ pub struct ListElem { /// - Items /// - Items /// ``` - #[default(ListMarker::Content(vec![]))] + #[default(ListMarker::Content(vec![TextElem::packed('•')]))] pub marker: ListMarker, /// The indent of each item. @@ -192,11 +190,9 @@ impl ListMarker { /// Resolve the marker for the given depth. fn resolve(&self, vt: &mut Vt, depth: usize) -> SourceResult<Content> { Ok(match self { - Self::Content(list) => list - .get(depth) - .or(list.last()) - .cloned() - .unwrap_or_else(|| TextElem::packed('•')), + Self::Content(list) => { + list.get(depth).or(list.last()).cloned().unwrap_or_default() + } Self::Func(func) => func.call_vt(vt, [Value::Int(depth as i64)])?.display(), }) } @@ -216,7 +212,11 @@ cast_from_value! { cast_to_value! { 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(), } } diff --git a/library/src/layout/page.rs b/library/src/layout/page.rs index cb0ed7dc..cfddc446 100644 --- a/library/src/layout/page.rs +++ b/library/src/layout/page.rs @@ -26,9 +26,9 @@ use crate::prelude::*; /// Category: layout #[element] pub struct PageElem { - /// A standard paper size to set width and height. When this is not - /// specified, Typst defaults to `{"a4"}` paper. + /// A standard paper size to set width and height. #[external] + #[default(Paper::A4)] pub paper: Paper, /// The width of the page. @@ -470,6 +470,8 @@ cast_to_value! { /// Specification of a paper. #[derive(Debug, Copy, Clone, Hash)] pub struct Paper { + /// The name of the paper. + name: &'static str, /// The width of the paper in millimeters. width: Scalar, /// The height of the paper in millimeters. @@ -490,12 +492,13 @@ impl Paper { /// Defines paper constants and a paper parsing implementation. macro_rules! papers { - ($(($var:ident: $width:expr, $height: expr, $pat:literal))*) => { + ($(($var:ident: $width:expr, $height: expr, $name:literal))*) => { /// Predefined papers. /// /// Each paper is parsable from its name in kebab-case. impl Paper { $(pub const $var: Self = Self { + name: $name, width: Scalar($width), height: Scalar($height), };)* @@ -506,7 +509,7 @@ macro_rules! papers { fn from_str(name: &str) -> Result<Self, Self::Err> { match name.to_lowercase().as_str() { - $($pat => Ok(Self::$var),)* + $($name => Ok(Self::$var),)* _ => Err("unknown paper size"), } } @@ -516,9 +519,13 @@ macro_rules! papers { Paper, $( /// Produces a paper of the respective size. - $pat => Self::$var, + $name => Self::$var, )* } + + cast_to_value! { + v: Paper => v.name.into() + } }; } diff --git a/library/src/layout/par.rs b/library/src/layout/par.rs index f8df63f4..f0dcbb11 100644 --- a/library/src/layout/par.rs +++ b/library/src/layout/par.rs @@ -20,8 +20,11 @@ use crate::text::{ /// /// ## Example { #example } /// ```example -/// #set par(first-line-indent: 1em, justify: true) /// #show par: set block(spacing: 0.65em) +/// #set par( +/// first-line-indent: 1em, +/// justify: true, +/// ) /// /// We proceed by contradiction. /// Suppose that there exists a set @@ -40,8 +43,6 @@ use crate::text::{ #[element(Construct)] pub struct ParElem { /// The spacing between lines. - /// - /// The default value is `{0.65em}`. #[resolve] #[default(Em::new(0.65).into())] pub leading: Length, @@ -102,6 +103,7 @@ pub struct ParElem { /// The contents of the paragraph. #[external] + #[required] pub body: Content, /// The paragraph's children. diff --git a/library/src/layout/table.rs b/library/src/layout/table.rs index bb24bd7c..fb4e4a3b 100644 --- a/library/src/layout/table.rs +++ b/library/src/layout/table.rs @@ -104,16 +104,18 @@ pub struct TableElem { /// How to stroke the cells. /// - /// This can be a color, a stroke width, both, or `{none}` to disable - /// the stroke. + /// See the [line's documentation]($func/line.stroke) for more details. + /// 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] #[fold] #[default(Some(PartialStroke::default()))] pub stroke: Option<PartialStroke>, /// How much to pad the cells's content. - /// - /// The default value is `{5pt}`. #[default(Abs::pt(5.0).into())] pub inset: Rel<Length>, diff --git a/library/src/layout/transform.rs b/library/src/layout/transform.rs index e3622ac5..f1f1e94a 100644 --- a/library/src/layout/transform.rs +++ b/library/src/layout/transform.rs @@ -83,10 +83,9 @@ pub struct RotateElem { /// The origin of the rotation. /// - /// By default, the origin is the center of the rotated element. If, - /// however, you wanted the bottom left corner of the rotated element to - /// stay aligned with the baseline, you would set the origin to `bottom + - /// left`. + /// If, for instance, you wanted the bottom left corner of the rotated + /// element to stay aligned with the baseline, you would set it to `bottom + + /// left` instead. /// /// ```example /// #set text(spacing: 8pt) @@ -98,6 +97,8 @@ pub struct RotateElem { /// #box(rotate(30deg, origin: bottom + right, square())) /// ``` #[resolve] + #[fold] + #[default(Align::CENTER_HORIZON)] pub origin: Axes<Option<GenAlign>>, /// The content to rotate. @@ -115,8 +116,8 @@ impl Layout for RotateElem { ) -> SourceResult<Fragment> { let pod = Regions::one(regions.base(), Axes::splat(false)); 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 } = origin.zip(frame.size()).map(|(o, s)| o.position(s)); + let Axes { x, y } = + self.origin(styles).zip(frame.size()).map(|(o, s)| o.position(s)); let ts = Transform::translate(x, y) .pre_concat(Transform::rotate(self.angle(styles))) .pre_concat(Transform::translate(-x, -y)); @@ -160,13 +161,13 @@ pub struct ScaleElem { /// The origin of the transformation. /// - /// By default, the origin is the center of the scaled element. - /// /// ```example /// A#box(scale(75%)[A])A \ /// B#box(scale(75%, origin: bottom + left)[B])B /// ``` #[resolve] + #[fold] + #[default(Align::CENTER_HORIZON)] pub origin: Axes<Option<GenAlign>>, /// The content to scale. @@ -184,8 +185,8 @@ impl Layout for ScaleElem { ) -> SourceResult<Fragment> { let pod = Regions::one(regions.base(), Axes::splat(false)); 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 } = origin.zip(frame.size()).map(|(o, s)| o.position(s)); + let Axes { x, y } = + self.origin(styles).zip(frame.size()).map(|(o, s)| o.position(s)); let transform = Transform::translate(x, y) .pre_concat(Transform::scale(self.x(styles), self.y(styles))) .pre_concat(Transform::translate(-x, -y)); diff --git a/library/src/math/cancel.rs b/library/src/math/cancel.rs index 9a0ffb7a..643eb9eb 100644 --- a/library/src/math/cancel.rs +++ b/library/src/math/cancel.rs @@ -24,8 +24,6 @@ pub struct CancelElem { /// the whole element being "cancelled". A value of `{100%}` would then have /// the line span precisely the element's diagonal. /// - /// Defaults to `{100% + 3pt}`. - /// /// ```example /// >>> #set page(width: 140pt) /// $ 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 /// of top right). /// - /// Defaults to `{false}`. - /// /// ```example /// >>> #set page(width: 140pt) /// $ (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 /// element. Overrides `inverted`. /// - /// Defaults to `{false}`. - /// /// ```example /// >>> #set page(width: 140pt) /// $ cancel(Pi, cross: #true) $ @@ -85,6 +79,11 @@ pub struct CancelElem { /// ``` #[resolve] #[fold] + #[default(PartialStroke { + // Default stroke has 0.5pt for better visuals. + thickness: Smart::Custom(Abs::pt(0.5)), + ..Default::default() + })] pub stroke: PartialStroke, } @@ -100,10 +99,8 @@ impl LayoutMath for CancelElem { let span = self.span(); let length = self.length(styles).resolve(styles); - // Default stroke has 0.5pt for better visuals. let stroke = self.stroke(styles).unwrap_or(Stroke { paint: TextElem::fill_in(styles), - thickness: Abs::pt(0.5), ..Default::default() }); diff --git a/library/src/math/delimited.rs b/library/src/math/delimited.rs index a4639de6..403f7922 100644 --- a/library/src/math/delimited.rs +++ b/library/src/math/delimited.rs @@ -19,8 +19,6 @@ pub(super) const DELIM_SHORT_FALL: Em = Em::new(0.1); #[element(LayoutMath)] pub struct LrElem { /// The size of the brackets, relative to the height of the wrapped content. - /// - /// Defaults to `{100%}`. pub size: Smart<Rel<Length>>, /// The delimited content, including the delimiters. diff --git a/library/src/math/op.rs b/library/src/math/op.rs index 1a3acaa3..cc57eb61 100644 --- a/library/src/math/op.rs +++ b/library/src/math/op.rs @@ -27,8 +27,6 @@ pub struct OpElem { pub text: EcoString, /// Whether the operator should force attachments to display as limits. - /// - /// Defaults to `{false}`. #[default(false)] pub limits: bool, } diff --git a/library/src/meta/figure.rs b/library/src/meta/figure.rs index f23cbacc..5476b367 100644 --- a/library/src/meta/figure.rs +++ b/library/src/meta/figure.rs @@ -144,8 +144,6 @@ pub struct FigureElem { /// How to number the figure. Accepts a /// [numbering pattern or function]($func/numbering). - /// - /// Defaults to `{"1"}`. #[default(Some(NumberingPattern::from_str("1").unwrap().into()))] pub numbering: Option<Numbering>, @@ -155,8 +153,6 @@ pub struct FigureElem { /// Whether the figure should appear in an [`outline`]($func/outline) /// of figures. - /// - /// Defaults to `{true}`. #[default(true)] pub outlined: bool, diff --git a/library/src/meta/outline.rs b/library/src/meta/outline.rs index a45b2a34..3114aa38 100644 --- a/library/src/meta/outline.rs +++ b/library/src/meta/outline.rs @@ -127,7 +127,7 @@ pub struct OutlineElem { pub indent: bool, /// 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 /// #outline(fill: line(length: 100%)) diff --git a/library/src/meta/query.rs b/library/src/meta/query.rs index ca4304b5..ad7274f1 100644 --- a/library/src/meta/query.rs +++ b/library/src/meta/query.rs @@ -98,7 +98,7 @@ use crate::prelude::*; /// /// Display: Query /// Category: meta -/// Returns: content +/// Returns: array #[func] pub fn query( /// Can be an element function like a `heading` or `figure`, a `{<label>}` diff --git a/library/src/text/deco.rs b/library/src/text/deco.rs index 9bc52b61..cfb06956 100644 --- a/library/src/text/deco.rs +++ b/library/src/text/deco.rs @@ -15,8 +15,11 @@ use crate::prelude::*; /// Category: text #[element(Show)] pub struct UnderlineElem { - /// How to stroke the line. The text color and thickness are read from the - /// font tables if `{auto}`. + /// How to stroke the line. + /// + /// 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 /// Take #underline( @@ -89,8 +92,11 @@ impl Show for UnderlineElem { /// Category: text #[element(Show)] pub struct OverlineElem { - /// How to stroke the line. The text color and thickness are read from the - /// font tables if `{auto}`. + /// How to stroke the line. + /// + /// 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 /// #set text(fill: olive) @@ -169,8 +175,11 @@ impl Show for OverlineElem { /// Category: text #[element(Show)] pub struct StrikeElem { - /// How to stroke the line. The text color and thickness are read from the - /// font tables if `{auto}`. + /// How to stroke the line. + /// + /// 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 /// copy paste the text. diff --git a/library/src/text/mod.rs b/library/src/text/mod.rs index a7cf4fd7..6e3f1acf 100644 --- a/library/src/text/mod.rs +++ b/library/src/text/mod.rs @@ -573,7 +573,11 @@ cast_from_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. diff --git a/library/src/visualize/path.rs b/library/src/visualize/path.rs index 9b3fd876..c9b596c7 100644 --- a/library/src/visualize/path.rs +++ b/library/src/visualize/path.rs @@ -27,8 +27,11 @@ pub struct PathElem { /// [non-zero winding rule](https://en.wikipedia.org/wiki/Nonzero-rule). pub fill: Option<Paint>, - /// How to stroke the path. See the - /// [polygon's documentation]($func/polygon.stroke) for more details. + /// How to stroke the path. This can be: + /// + /// 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] #[fold] pub stroke: Smart<Option<PartialStroke>>, diff --git a/library/src/visualize/polygon.rs b/library/src/visualize/polygon.rs index 2e75aff5..d2d3132a 100644 --- a/library/src/visualize/polygon.rs +++ b/library/src/visualize/polygon.rs @@ -29,15 +29,9 @@ pub struct PolygonElem { /// How to stroke the polygon. This can be: /// - /// - `{none}` to disable the stroke. - /// - `{auto}` for a stroke of `{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}`. + /// 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] #[fold] pub stroke: Smart<Option<PartialStroke>>, diff --git a/library/src/visualize/shape.rs b/library/src/visualize/shape.rs index ca780f90..6129b70b 100644 --- a/library/src/visualize/shape.rs +++ b/library/src/visualize/shape.rs @@ -38,32 +38,13 @@ pub struct RectElem { /// How to stroke the rectangle. This can be: /// - /// - `{none}` to disable the stroke. - /// - `{auto}` for a stroke of `{1pt}` black if and if only if no fill is + /// - `{none}` to disable stroking + /// - `{auto}` for a stroke of `{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}`. - /// - 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: + /// - Any kind of stroke that can also be used for + /// [lines]($func/line.stroke). + /// - A dictionary describing the stroke for each side inidvidually. The + /// dictionary can contain the following keys in order of precedence: /// - `top`: The top stroke. /// - `right`: The right stroke. /// - `bottom`: The bottom stroke. @@ -126,8 +107,6 @@ pub struct RectElem { /// 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 /// 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 /// documentation]($func/rect.inset) for more details. - /// - /// The default value is `{5pt}`. #[resolve] #[fold] #[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 /// documentation]($func/rect.inset) for more details. - /// - /// The default value is `{5pt}`. #[resolve] #[fold] #[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 /// documentation]($func/rect.inset) for more details. - /// - /// The default value is `{5pt}`. #[resolve] #[fold] #[default(Sides::splat(Abs::pt(5.0).into()))] diff --git a/macros/src/element.rs b/macros/src/element.rs index 61830939..d5882666 100644 --- a/macros/src/element.rs +++ b/macros/src/element.rs @@ -407,9 +407,29 @@ fn create_vtable_func(element: &Elem) -> TokenStream { /// Create a parameter info for a field. 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 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 { quote! { <#ty as ::typst::eval::Variadics>::Inner } } else { @@ -422,6 +442,7 @@ fn create_param_info(field: &Field) -> TokenStream { cast: <#ty as ::typst::eval::Cast< ::typst::syntax::Spanned<::typst::eval::Value> >>::describe(), + default: #default, positional: #positional, named: #named, variadic: #variadic, diff --git a/macros/src/func.rs b/macros/src/func.rs index 2d38c01a..2e63ee75 100644 --- a/macros/src/func.rs +++ b/macros/src/func.rs @@ -152,6 +152,14 @@ fn create_param_info(param: &Param) -> TokenStream { let Param { name, docs, named, variadic, ty, default, .. } = param; let positional = !named; 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 { quote! { <#ty as ::typst::eval::Variadics>::Inner } } else { @@ -164,6 +172,7 @@ fn create_param_info(param: &Param) -> TokenStream { cast: <#ty as ::typst::eval::Cast< ::typst::syntax::Spanned<::typst::eval::Value> >>::describe(), + default: #default, positional: #positional, named: #named, variadic: #variadic, diff --git a/src/eval/cast.rs b/src/eval/cast.rs index b85d6e76..7ef2f1d0 100644 --- a/src/eval/cast.rs +++ b/src/eval/cast.rs @@ -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 { fn from(v: Option<T>) -> Self { match v { diff --git a/src/eval/func.rs b/src/eval/func.rs index 75231fc9..a224c5b8 100644 --- a/src/eval/func.rs +++ b/src/eval/func.rs @@ -279,6 +279,8 @@ pub struct ParamInfo { pub docs: &'static str, /// Valid values for the parameter. pub cast: CastInfo, + /// Creates an instance of the parameter's default value. + pub default: Option<fn() -> Value>, /// Is the parameter positional? pub positional: bool, /// Is the parameter named? diff --git a/src/eval/value.rs b/src/eval/value.rs index bd612cce..36cda80b 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -54,7 +54,7 @@ pub enum Value { Styles(Styles), /// An array of values: `(1, "hi", 12cm)`. Array(Array), - /// A dictionary value: `(color: #f79143, pattern: dashed)`. + /// A dictionary value: `(a: 1, b: "hi")`. Dict(Dict), /// An executable function. Func(Func), diff --git a/src/geom/align.rs b/src/geom/align.rs index a15ec4e6..42fc493e 100644 --- a/src/geom/align.rs +++ b/src/geom/align.rs @@ -147,6 +147,10 @@ cast_from_value! { } cast_to_value! { + v: Axes<Align> => v.map(GenAlign::from).into() +} + +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(), @@ -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 /// on cast. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] diff --git a/src/geom/axes.rs b/src/geom/axes.rs index 8bc2456a..511e6ff5 100644 --- a/src/geom/axes.rs +++ b/src/geom/axes.rs @@ -141,9 +141,9 @@ where { fn fmt(&self, f: &mut Formatter) -> fmt::Result { 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>() { write!(f, "Size({:?}, {:?})", self.x, self.y) } else { diff --git a/src/geom/corners.rs b/src/geom/corners.rs index 26d4082b..20e9bed0 100644 --- a/src/geom/corners.rs +++ b/src/geom/corners.rs @@ -117,43 +117,47 @@ where } 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 { - 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()), - }; - - dict.finish(&[ - "top-left", - "top-right", - "bottom-right", - "bottom-left", - "left", - "top", - "right", - "bottom", - "rest", - ])?; - - Ok(corners) - } else if T::is(&value) { + 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()), + }; + + dict.finish(&keys)?; + return Ok(corners); + } + } + + if T::is(&value) { Ok(Self::splat(Some(T::cast(value)?))) } else { <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 T: PartialEq + Into<Value>, { - fn from(corners: Corners<Option<T>>) -> Self { + fn from(corners: Corners<T>) -> Self { if corners.is_uniform() { - if let Some(value) = corners.top_left { - return value.into(); - } + return corners.top_left.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()); - } + let mut handle = |key: &str, component: T| { + let value = component.into(); + if value != Value::None { + dict.insert(key.into(), value); + } + }; + + handle("top-left", corners.top_left); + handle("top-right", corners.top_right); + handle("bottom-right", corners.bottom_right); + handle("bottom-left", corners.bottom_left); Value::Dict(dict) } diff --git a/src/geom/rel.rs b/src/geom/rel.rs index 7288f380..aaa784f9 100644 --- a/src/geom/rel.rs +++ b/src/geom/rel.rs @@ -227,3 +227,7 @@ impl Fold for Rel<Length> { self } } + +cast_to_value! { + v: Rel<Abs> => v.map(Length::from).into() +} diff --git a/src/geom/sides.rs b/src/geom/sides.rs index d9a020da..a905a5f8 100644 --- a/src/geom/sides.rs +++ b/src/geom/sides.rs @@ -187,10 +187,10 @@ where } fn cast(mut value: Value) -> StrResult<Self> { + let keys = ["left", "top", "right", "bottom", "x", "y", "rest"]; 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 rest = take("rest")?; let x = take("x")?.or_else(|| rest.clone()); let y = take("y")?.or_else(|| rest.clone()); @@ -201,13 +201,8 @@ where bottom: take("bottom")?.or_else(|| y.clone()), }; - dict.finish(&["left", "top", "right", "bottom", "x", "y", "rest"])?; - - Ok(sides) - }; - - if let Ok(res) = try_cast() { - return Ok(res); + dict.finish(&keys)?; + return Ok(sides); } } @@ -223,35 +218,31 @@ where } } -impl<T> From<Sides<Option<T>>> for Value +impl<T> From<Sides<T>> for Value where T: PartialEq + Into<Value>, { - fn from(sides: Sides<Option<T>>) -> Self { + fn from(sides: Sides<T>) -> Self { if sides.is_uniform() { - if let Some(value) = sides.left { - return value.into(); - } + return sides.left.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()); - } + let mut handle = |key: &str, component: T| { + let value = component.into(); + if value != Value::None { + dict.insert(key.into(), value); + } + }; + + handle("left", sides.left); + handle("top", sides.top); + handle("right", sides.right); + handle("bottom", sides.bottom); Value::Dict(dict) } } - impl<T: Resolve> Resolve for Sides<T> { type Output = Sides<T::Output>; diff --git a/src/geom/stroke.rs b/src/geom/stroke.rs index ead30cbb..6539922c 100644 --- a/src/geom/stroke.rs +++ b/src/geom/stroke.rs @@ -51,6 +51,35 @@ pub struct PartialStroke<T = Length> { 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> { /// Unpack the stroke, filling missing fields from the `default`. 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::Auto, Smart::Custom(thickness)) => thickness.fmt(f), - (Smart::Auto, Smart::Auto) => f.pad("<stroke>"), + (Smart::Auto, Smart::Auto) => f.pad("1pt + black"), } } else { write!(f, "(")?; let mut sep = ""; if let Smart::Custom(paint) = &paint { - write!(f, "{}color: {:?}", sep, paint)?; + write!(f, "{}paint: {:?}", sep, paint)?; sep = ", "; } 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)] pub struct DashPattern<T = Length, DT = DashLength<T>> { /// The dash array. @@ -293,20 +322,12 @@ impl Resolve for DashPattern { cast_from_value! { PartialStroke: "stroke", thickness: Length => Self { - paint: Smart::Auto, thickness: Smart::Custom(thickness), - line_cap: Smart::Auto, - line_join: Smart::Auto, - dash_pattern: Smart::Auto, - miter_limit: Smart::Auto, + ..Default::default() }, color: Color => Self { paint: Smart::Custom(color.into()), - thickness: Smart::Auto, - line_cap: Smart::Auto, - line_join: Smart::Auto, - dash_pattern: Smart::Auto, - miter_limit: Smart::Auto, + ..Default::default() }, mut dict: Dict => { 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 dash_pattern = take::<Option<DashPattern>>(&mut dict, "dash")?; let miter_limit = take::<f64>(&mut dict, "miter-limit")?; - dict.finish(&["paint", "thickness", "cap", "join", "dash", "miter-limit"])?; Self { @@ -359,7 +379,11 @@ impl Fold for PartialStroke<Abs> { line_cap: self.line_cap.or(outer.line_cap), line_join: self.line_join.or(outer.line_join), 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() +} diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs index 7fdba4bd..c7dbc936 100644 --- a/src/syntax/parser.rs +++ b/src/syntax/parser.rs @@ -18,7 +18,10 @@ pub fn parse(text: &str) -> SyntaxNode { /// This is only used for syntax highlighting. pub fn parse_code(text: &str) -> SyntaxNode { 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() } @@ -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(); + 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()) { p.stop_at_newline(true); let prev = p.prev_end(); @@ -529,7 +537,6 @@ fn code(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) { p.unexpected(); } } - p.wrap(m, SyntaxKind::Code); } fn code_expr(p: &mut Parser) { @@ -1474,10 +1481,14 @@ impl<'s> Parser<'s> { fn wrap(&mut self, m: Marker, kind: SyntaxKind) { 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 children = self.nodes.drain(from..).collect(); self.nodes.push(SyntaxNode::inner(kind, children)); - self.skip(); } fn progress(&self, offset: usize) -> bool { diff --git a/tests/src/tests.rs b/tests/src/tests.rs index 2a0b74ea..f295869f 100644 --- a/tests/src/tests.rs +++ b/tests/src/tests.rs @@ -15,8 +15,7 @@ use comemo::{Prehashed, Track}; use elsa::FrozenVec; use once_cell::unsync::OnceCell; use oxipng::{InFile, Options, OutFile}; -use rayon::iter::ParallelBridge; -use rayon::iter::ParallelIterator; +use rayon::iter::{ParallelBridge, ParallelIterator}; use tiny_skia as sk; use unscanny::Scanner; use walkdir::WalkDir; diff --git a/tests/typ/compiler/for.typ b/tests/typ/compiler/for.typ index f525215f..72aaea20 100644 --- a/tests/typ/compiler/for.typ +++ b/tests/typ/compiler/for.typ @@ -8,7 +8,7 @@ #for x in () [Nope] // 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) [ #k: #v. ] |
