diff --git a/crates/typst-library/src/layout/par.rs b/crates/typst-library/src/layout/par.rs index 2028c9fd0..2d7c90801 100644 --- a/crates/typst-library/src/layout/par.rs +++ b/crates/typst-library/src/layout/par.rs @@ -42,6 +42,7 @@ use crate::text::{ pub struct ParElem { /// The spacing between lines. #[resolve] + #[ghost] #[default(Em::new(0.65).into())] pub leading: Length, @@ -54,6 +55,7 @@ pub struct ParElem { /// Note that the current [alignment]($align) still has an effect on the /// placement of the last line except if it ends with a /// [justified line break]($linebreak.justify). + #[ghost] #[default(false)] pub justify: bool, @@ -78,6 +80,7 @@ pub struct ParElem { /// challenging to break in a visually /// pleasing way. /// ``` + #[ghost] pub linebreaks: Smart, /// The indent the first line of a paragraph should have. @@ -90,9 +93,11 @@ pub struct ParElem { /// the [paragraph spacing]($block.spacing) to the [`leading`] when /// using this property (e.g. using /// `[#show par: set block(spacing: 0.65em)]`). + #[ghost] pub first_line_indent: Length, /// The indent all but the first line of a paragraph should have. + #[ghost] #[resolve] pub hanging_indent: Length, diff --git a/crates/typst-library/src/text/mod.rs b/crates/typst-library/src/text/mod.rs index 84a68d6f5..202ab2c5e 100644 --- a/crates/typst-library/src/text/mod.rs +++ b/crates/typst-library/src/text/mod.rs @@ -98,6 +98,7 @@ pub struct TextElem { /// ``` #[default(FontList(vec![FontFamily::new("Linux Libertine")]))] #[borrowed] + #[ghost] pub font: FontList, /// Whether to allow last resort font fallback when the primary font list @@ -118,6 +119,7 @@ pub struct TextElem { /// هذا عربي /// ``` #[default(true)] + #[ghost] pub fallback: bool, /// The desired font style. @@ -137,6 +139,7 @@ pub struct TextElem { /// #text(font: "Linux Libertine", style: "italic")[Italic] /// #text(font: "DejaVu Sans", style: "oblique")[Oblique] /// ``` + #[ghost] pub style: FontStyle, /// The desired thickness of the font's glyphs. Accepts an integer between @@ -158,6 +161,7 @@ pub struct TextElem { /// #text(weight: 500)[Medium] \ /// #text(weight: "bold")[Bold] /// ``` + #[ghost] pub weight: FontWeight, /// The desired width of the glyphs. Accepts a ratio between `{50%}` and @@ -173,6 +177,7 @@ pub struct TextElem { /// #text(stretch: 75%)[Condensed] \ /// #text(stretch: 100%)[Normal] /// ``` + #[ghost] pub stretch: FontStretch, /// The size of the glyphs. This value forms the basis of the `em` unit: @@ -188,6 +193,7 @@ pub struct TextElem { #[parse(args.named_or_find("size")?)] #[fold] #[default(Abs::pt(11.0))] + #[ghost] pub size: TextSize, /// The glyph fill paint. @@ -214,6 +220,7 @@ pub struct TextElem { paint.map(|paint| paint.v) })] #[default(Color::BLACK.into())] + #[ghost] pub fill: Paint, /// The amount of space that should be added between characters. @@ -223,6 +230,7 @@ pub struct TextElem { /// Distant text. /// ``` #[resolve] + #[ghost] pub tracking: Length, /// The amount of space between words. @@ -239,6 +247,7 @@ pub struct TextElem { /// ``` #[resolve] #[default(Rel::one())] + #[ghost] pub spacing: Rel, /// Whether to automatically insert spacing between CJK and Latin characters. @@ -250,6 +259,7 @@ pub struct TextElem { /// #set text(cjk-latin-spacing: none) /// 第4章介绍了基本的API。 /// ``` + #[ghost] pub cjk_latin_spacing: Smart>, /// An amount to shift the text baseline by. @@ -259,6 +269,7 @@ pub struct TextElem { /// word. /// ``` #[resolve] + #[ghost] pub baseline: Length, /// Whether certain glyphs can hang over into the margin in justified text. @@ -278,6 +289,7 @@ pub struct TextElem { /// results in a clearer paragraph edge. /// ``` #[default(true)] + #[ghost] pub overhang: bool, /// The top end of the conceptual frame around the text used for layout and @@ -294,6 +306,7 @@ pub struct TextElem { /// #rect(fill: aqua)[Typst] /// ``` #[default(TopEdge::Metric(TopEdgeMetric::CapHeight))] + #[ghost] pub top_edge: TopEdge, /// The bottom end of the conceptual frame around the text used for layout @@ -310,6 +323,7 @@ pub struct TextElem { /// #rect(fill: aqua)[Typst] /// ``` #[default(BottomEdge::Metric(BottomEdgeMetric::Baseline))] + #[ghost] pub bottom_edge: BottomEdge, /// An [ISO 639-1/2/3 language code.](https://en.wikipedia.org/wiki/ISO_639) @@ -330,11 +344,13 @@ pub struct TextElem { /// In diesem Dokument, ... /// ``` #[default(Lang::ENGLISH)] + #[ghost] pub lang: Lang, /// An [ISO 3166-1 alpha-2 region code.](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) /// /// This lets the text processing pipeline make more informed choices. + #[ghost] pub region: Option, /// The OpenType writing script. @@ -364,6 +380,7 @@ pub struct TextElem { /// #set text(lang: "ro", script: "grek") /// #scedilla // S with a cedilla /// ``` + #[ghost] pub script: Smart, /// The dominant direction for text and inline objects. Possible values are: @@ -391,6 +408,7 @@ pub struct TextElem { /// هذا عربي. /// ``` #[resolve] + #[ghost] pub dir: TextDir, /// Whether to hyphenate text to improve line breaking. When `{auto}`, text @@ -413,6 +431,7 @@ pub struct TextElem { /// improve justification. /// ``` #[resolve] + #[ghost] pub hyphenate: Hyphenate, /// Whether to apply kerning. @@ -431,6 +450,7 @@ pub struct TextElem { /// Totally /// ``` #[default(true)] + #[ghost] pub kerning: bool, /// Whether to apply stylistic alternates. @@ -451,6 +471,7 @@ pub struct TextElem { /// 0, a, g, ß /// ``` #[default(false)] + #[ghost] pub alternates: bool, /// Which stylistic set to apply. Font designers can categorize alternative @@ -458,6 +479,7 @@ pub struct TextElem { /// you need to consult your font to know which sets are available. When set /// to an integer between `{1}` and `{20}`, enables the corresponding /// OpenType font feature from `ss01`, ..., `ss20`. + #[ghost] pub stylistic_set: Option, /// Whether standard ligatures are active. @@ -475,16 +497,19 @@ pub struct TextElem { /// A fine ligature. /// ``` #[default(true)] + #[ghost] pub ligatures: bool, /// Whether ligatures that should be used sparingly are active. Setting this /// to `{true}` enables the OpenType `dlig` font feature. #[default(false)] + #[ghost] pub discretionary_ligatures: bool, /// Whether historical ligatures are active. Setting this to `{true}` /// enables the OpenType `hlig` font feature. #[default(false)] + #[ghost] pub historical_ligatures: bool, /// Which kind of numbers / figures to select. When set to `{auto}`, the @@ -498,6 +523,7 @@ pub struct TextElem { /// #set text(number-type: "old-style") /// Number 9. /// ``` + #[ghost] pub number_type: Smart, /// The width of numbers / figures. When set to `{auto}`, the default @@ -513,6 +539,7 @@ pub struct TextElem { /// A 12 B 34. \ /// A 56 B 78. /// ``` + #[ghost] pub number_width: Smart, /// Whether to have a slash through the zero glyph. Setting this to `{true}` @@ -522,6 +549,7 @@ pub struct TextElem { /// 0, #text(slashed-zero: true)[0] /// ``` #[default(false)] + #[ghost] pub slashed_zero: bool, /// Whether to turn numbers into fractions. Setting this to `{true}` @@ -536,6 +564,7 @@ pub struct TextElem { /// #text(fractions: true)[1/2] /// ``` #[default(false)] + #[ghost] pub fractions: bool, /// Raw OpenType features to apply. @@ -551,6 +580,7 @@ pub struct TextElem { /// 1/2 /// ``` #[fold] + #[ghost] pub features: FontFeatures, /// Content in which all text is styled according to the other arguments. @@ -566,26 +596,31 @@ pub struct TextElem { /// A delta to apply on the font weight. #[internal] #[fold] + #[ghost] pub delta: Delta, /// Whether the font style should be inverted. #[internal] #[fold] #[default(false)] + #[ghost] pub emph: Toggle, /// Decorative lines. #[internal] #[fold] + #[ghost] pub deco: Decoration, /// A case transformation that should be applied to the text. #[internal] + #[ghost] pub case: Option, /// Whether small capital glyphs should be used. ("smcp") #[internal] #[default(false)] + #[ghost] pub smallcaps: bool, } diff --git a/crates/typst-macros/src/elem.rs b/crates/typst-macros/src/elem.rs index e5bde52b5..c36becb23 100644 --- a/crates/typst-macros/src/elem.rs +++ b/crates/typst-macros/src/elem.rs @@ -3,7 +3,7 @@ use super::*; /// Expand the `#[elem]` macro. pub fn elem(stream: TokenStream, body: syn::ItemStruct) -> Result { let element = parse(stream, &body)?; - Ok(create(&element)) + create(&element) } /// Details about an element. @@ -51,10 +51,20 @@ impl Elem { /// /// This includes: /// - Fields that are not external and therefore present in the struct. + /// - Fields that are ghost fields. fn real_fields(&self) -> impl Iterator + Clone { self.fields.iter().filter(|field| !field.external) } + /// Fields that are present in the struct. + /// + /// This includes: + /// - Fields that are not external and therefore present in the struct. + /// - Fields that are not ghost fields. + fn present_fields(&self) -> impl Iterator + Clone { + self.real_fields().filter(|field| !field.ghost) + } + /// Fields that are inherent to the element. fn inherent_fields(&self) -> impl Iterator + Clone { self.real_fields().filter(|field| field.inherent()) @@ -91,7 +101,7 @@ impl Elem { /// This includes: /// - Fields that are not synthesized (guarantees equality before and after synthesis). fn eq_fields(&self) -> impl Iterator + Clone { - self.real_fields().filter(|field| !field.synthesized) + self.present_fields().filter(|field| !field.synthesized) } /// Fields that are relevant for `Construct` impl. @@ -125,6 +135,7 @@ struct Field { external: bool, synthesized: bool, borrowed: bool, + ghost: bool, forced_variant: Option, parse: Option, default: Option, @@ -133,7 +144,7 @@ struct Field { impl Field { /// Whether the field is present on every instance of the element. fn inherent(&self) -> bool { - self.required || self.variadic + (self.required || self.variadic) && !self.ghost } /// Whether the field can be used with set rules. @@ -226,6 +237,7 @@ fn parse_field(field: &syn::Field) -> Result { synthesized: has_attr(&mut attrs, "synthesized"), fold: has_attr(&mut attrs, "fold"), resolve: has_attr(&mut attrs, "resolve"), + ghost: has_attr(&mut attrs, "ghost"), parse: parse_attr(&mut attrs, "parse")?.flatten(), default: parse_attr::(&mut attrs, "default")?.flatten(), vis: field.vis.clone(), @@ -264,20 +276,31 @@ fn parse_field(field: &syn::Field) -> Result { } /// Produce the element's definition. -fn create(element: &Elem) -> TokenStream { +fn create(element: &Elem) -> Result { let Elem { vis, ident, docs, .. } = element; + + if element.fields.iter().any(|field| field.ghost) + && element + .capabilities + .iter() + .all(|capability| capability != "Construct") + { + bail!(ident, "cannot have ghost fields and have `Construct` auto generated"); + } + let all = element.real_fields(); + let present = element.present_fields(); let settable = all.clone().filter(|field| !field.synthesized && field.settable()); - let fields = all.clone().map(create_field); + let fields = present.clone().map(create_field); let fields_enum = create_fields_enum(element); let new = create_new_func(element); let field_methods = all.clone().map(|field| create_field_method(element, field)); let field_in_methods = settable.clone().map(|field| create_field_in_method(element, field)); - let with_field_methods = all.clone().map(create_with_field_method); - let push_field_methods = all.clone().map(create_push_field_method); + let with_field_methods = present.clone().map(create_with_field_method); + let push_field_methods = present.clone().map(create_push_field_method); let field_style_methods = settable.clone().map(|field| create_set_field_method(element, field)); @@ -300,7 +323,7 @@ fn create(element: &Elem) -> TokenStream { } }); - quote! { + Ok(quote! { #[doc = #docs] #[derive(Debug, Clone, Hash)] #[allow(clippy::derived_hash_with_manual_eq)] @@ -335,7 +358,7 @@ fn create(element: &Elem) -> TokenStream { ::typst::eval::Value::Content(::typst::model::Content::new(self)) } } - } + }) } /// Create a field declaration. @@ -459,12 +482,14 @@ fn create_new_func(element: &Elem) -> TokenStream { let defaults = element .fields .iter() - .filter(|field| !field.external && !field.inherent() && !field.synthesized) + .filter(|field| { + !field.external && !field.inherent() && !field.synthesized && !field.ghost + }) .map(|Field { ident, .. }| quote! { #ident: None }); let default_synthesized = element .fields .iter() - .filter(|field| !field.external && field.synthesized) + .filter(|field| !field.external && field.synthesized && !field.ghost) .map(|Field { ident, default, .. }| { if let Some(expr) = default { quote! { #ident: #expr } @@ -591,7 +616,14 @@ fn create_field_in_method(element: &Elem, field: &Field) -> TokenStream { /// Create an accessor methods for a field. fn create_field_method(element: &Elem, field: &Field) -> TokenStream { - let Field { vis, docs, ident, output, .. } = field; + let Field { vis, docs, ident, output, ghost, .. } = field; + + let inherent = if *ghost { + quote! { None } + } else { + quote! { self.#ident.as_ref() } + }; + if (field.inherent() && !field.synthesized) || (field.synthesized && field.default.is_some()) { @@ -610,8 +642,7 @@ fn create_field_method(element: &Elem, field: &Field) -> TokenStream { } } } else if field.borrowed { - let access = - create_style_chain_access(element, field, quote! { self.#ident.as_ref() }); + let access = create_style_chain_access(element, field, inherent); quote! { #[doc = #docs] @@ -620,8 +651,7 @@ fn create_field_method(element: &Elem, field: &Field) -> TokenStream { } } } else { - let access = - create_style_chain_access(element, field, quote! { self.#ident.as_ref() }); + let access = create_style_chain_access(element, field, inherent); quote! { #[doc = #docs] @@ -697,16 +727,22 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream { let name = &field.enum_ident; let field_ident = &field.ident; - quote! { - <#elem as #model::ElementFields>::Fields::#name => Some( - ::typst::eval::IntoValue::into_value(self.#field_ident.clone()) - ), + if field.ghost { + quote! { + <#elem as #model::ElementFields>::Fields::#name => None, + } + } else { + quote! { + <#elem as #model::ElementFields>::Fields::#name => Some( + ::typst::eval::IntoValue::into_value(self.#field_ident.clone()) + ), + } } }); // Fields that can be set using the `set_field` method. let field_set_matches = element.visible_fields() - .filter(|field| field.settable() && !field.synthesized).map(|field| { + .filter(|field| field.settable() && !field.synthesized && !field.ghost).map(|field| { let elem = &element.ident; let name = &field.enum_ident; let field_ident = &field.ident; @@ -722,7 +758,7 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream { // Fields that are inherent. let field_inherent_matches = element .visible_fields() - .filter(|field| field.inherent()) + .filter(|field| field.inherent() && !field.ghost) .map(|field| { let elem = &element.ident; let name = &field.enum_ident; @@ -739,7 +775,7 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream { // Fields that cannot be set or are internal create an error. let field_not_set_matches = element .real_fields() - .filter(|field| field.internal || field.synthesized) + .filter(|field| field.internal || field.synthesized || field.ghost) .map(|field| { let elem = &element.ident; let ident = &field.enum_ident; @@ -798,7 +834,7 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream { // Creation of the fields dictionary for optional fields. let field_opt_dict = element .visible_fields() - .filter(|field| !field.inherent()) + .filter(|field| !field.inherent() && !field.ghost) .clone() .map(|field| { let name = &field.name;