diff --git a/crates/typst-library/src/model/bibliography.rs b/crates/typst-library/src/model/bibliography.rs index 51e3b03b0..fa25a79a4 100644 --- a/crates/typst-library/src/model/bibliography.rs +++ b/crates/typst-library/src/model/bibliography.rs @@ -142,12 +142,10 @@ pub struct BibliographyElem { pub style: Derived, /// The language setting where the bibliography is. - #[internal] #[synthesized] pub lang: Lang, /// The region setting where the bibliography is. - #[internal] #[synthesized] pub region: Option, } diff --git a/crates/typst-library/src/model/cite.rs b/crates/typst-library/src/model/cite.rs index 29497993d..fa03c28b2 100644 --- a/crates/typst-library/src/model/cite.rs +++ b/crates/typst-library/src/model/cite.rs @@ -110,12 +110,10 @@ pub struct CiteElem { pub style: Smart>, /// The text language setting where the citation is. - #[internal] #[synthesized] pub lang: Lang, /// The text region setting where the citation is. - #[internal] #[synthesized] pub region: Option, } diff --git a/crates/typst-library/src/model/figure.rs b/crates/typst-library/src/model/figure.rs index bec667d6e..9849b029e 100644 --- a/crates/typst-library/src/model/figure.rs +++ b/crates/typst-library/src/model/figure.rs @@ -7,6 +7,7 @@ use typst_utils::NonZeroExt; use crate::diag::{bail, SourceResult}; use crate::engine::Engine; +use crate::foundations::func; use crate::foundations::{ cast, elem, scope, select_where, Content, Element, NativeElement, Packed, Selector, Show, ShowSet, Smart, StyleChain, Styles, Synthesize, TargetElem, @@ -241,7 +242,7 @@ pub struct FigureElem { /// These are the counters you'll need to modify if you want to skip a /// number or reset the counter. #[synthesized] - pub counter: Option, + pub counter: Counter, } #[scope] @@ -315,15 +316,21 @@ impl Synthesize for Packed { if let Some(caption) = &mut caption { caption.synthesize(engine, styles)?; caption.push_kind(kind.clone()); - caption.push_supplement(supplement.clone()); - caption.push_numbering(numbering.clone()); - caption.push_counter(Some(counter.clone())); - caption.push_figure_location(location); + if let Some(supplement) = supplement.clone() { + caption.push_supplement(supplement); + } + if let Some(numbering) = numbering.clone() { + caption.push_numbering(numbering); + } + caption.push_counter(counter.clone()); + if let Some(location) = location { + caption.push_figure_location(location); + } } elem.push_kind(Smart::Custom(kind)); elem.push_supplement(Smart::Custom(supplement.map(Supplement::Content))); - elem.push_counter(Some(counter)); + elem.push_counter(counter); elem.push_caption(caption); Ok(()) @@ -423,7 +430,6 @@ impl Refable for Packed { (**self) .counter() .cloned() - .flatten() .unwrap_or_else(|| Counter::of(FigureElem::elem())) } @@ -460,20 +466,24 @@ impl Outlinable for Packed { /// customize the appearance of captions for all figures or figures of a /// specific kind. /// -/// In addition to its `position` and `body`, the `caption` also provides the -/// figure's `kind`, `supplement`, `counter`, and `numbering` as fields. These -/// parts can be used in [`where`]($function.where) selectors and show rules to +/// In addition to its `position` and `body`, it is possible to retrieve the +/// corresponding figure's `kind`, `supplement`, `counter`, and `numbering` by +/// using the corresponding methods. These parts can be used in show rules to /// build a completely custom caption. /// /// ```example -/// #show figure.caption: emph +/// #show figure.caption: it => [ +/// #underline(it.body) | +/// #it.supplement() +/// #context it.counter().display(it.numbering()) +/// ] /// /// #figure( /// rect[Hello], /// caption: [A rectangle], /// ) /// ``` -#[elem(name = "caption", Synthesize, Show)] +#[elem(scope, name = "caption", Synthesize, Show)] pub struct FigureCaption { /// The caption's position in the figure. Either `{top}` or `{bottom}`. /// @@ -519,22 +529,6 @@ pub struct FigureCaption { pub separator: Smart, /// The caption's body. - /// - /// Can be used alongside `kind`, `supplement`, `counter`, `numbering`, and - /// `location` to completely customize the caption. - /// - /// ```example - /// #show figure.caption: it => [ - /// #underline(it.body) | - /// #it.supplement - /// #context it.counter.display(it.numbering) - /// ] - /// - /// #figure( - /// rect[Hello], - /// caption: [A rectangle], - /// ) - /// ``` #[required] pub body: Content, @@ -544,20 +538,71 @@ pub struct FigureCaption { /// The figure's supplement. #[synthesized] - pub supplement: Option, + pub supplement: Content, /// How to number the figure. #[synthesized] - pub numbering: Option, + pub numbering: Numbering, /// The counter for the figure. #[synthesized] - pub counter: Option, + pub counter: Counter, /// The figure's location. - #[internal] #[synthesized] - pub figure_location: Option, + pub figure_location: Location, +} + +#[scope] +impl FigureCaption { + /// The kind of the corresponding figure. + /// + /// This returns `{none}` in case the corresponding figure does not appear + /// in the document. + #[func] + pub fn kind_(self) -> Option { + self.kind + } + + /// The supplement of the corresponding figure. + /// + /// This returns `{none}` in case the corresponding figure does not appear + /// in the document. + #[func] + pub fn supplement_(self) -> Option { + self.supplement + } + + /// The numbering of the corresponding figure. + /// + /// This returns `{none}` in case the corresponding figure does not appear + /// in the document. + #[func] + pub fn numbering_(self) -> Option { + self.numbering + } + + /// The counter for the corresponding figure: + /// `{counter(figure.where(kind: kind))}` for a figure of kind `{kind}`. + /// + /// For example, for a table figure, this returns + /// `{counter(figure.where(kind: table))}`. + /// + /// This returns `{none}` in case the corresponding figure does not appear + /// in the document, or it is not numbered. + #[func] + pub fn counter_(self) -> Option { + self.counter + } + + /// The location of the corresponding figure. + /// + /// This returns `{none}` in case the corresponding figure does not appear + /// in the document. + #[func] + pub fn figure_location_(self) -> Option { + self.figure_location + } } impl FigureCaption { @@ -595,12 +640,7 @@ impl Show for Packed { fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult { let mut realized = self.body.clone(); - if let ( - Some(Some(mut supplement)), - Some(Some(numbering)), - Some(Some(counter)), - Some(Some(location)), - ) = ( + if let (Some(mut supplement), Some(numbering), Some(counter), Some(location)) = ( self.supplement().cloned(), self.numbering(), self.counter(), diff --git a/crates/typst-library/src/model/reference.rs b/crates/typst-library/src/model/reference.rs index 7d44cccc0..71066a9fb 100644 --- a/crates/typst-library/src/model/reference.rs +++ b/crates/typst-library/src/model/reference.rs @@ -1,8 +1,9 @@ use comemo::Track; use ecow::eco_format; -use crate::diag::{bail, At, Hint, SourceResult}; +use crate::diag::{bail, At, Hint, SourceResult, StrResult}; use crate::engine::Engine; +use crate::foundations::func; use crate::foundations::{ cast, elem, Cast, Content, Context, Func, IntoValue, Label, NativeElement, Packed, Show, Smart, StyleChain, Synthesize, @@ -13,6 +14,7 @@ use crate::model::{ BibliographyElem, CiteElem, Destination, Figurable, FootnoteElem, Numbering, }; use crate::text::TextElem; +use typst_macros::scope; /// A reference to a label or bibliography. /// @@ -90,7 +92,7 @@ use crate::text::TextElem; /// /// #show ref: it => { /// let eq = math.equation -/// let el = it.element +/// let el = it.element() /// if el != none and el.func() == eq { /// // Override equation references. /// link(el.location(),numbering( @@ -107,7 +109,7 @@ use crate::text::TextElem; /// In @beginning we prove @pythagoras. /// $ a^2 + b^2 = c^2 $ /// ``` -#[elem(title = "Reference", Synthesize, Locatable, Show)] +#[elem(scope, title = "Reference", Locatable, Show)] pub struct RefElem { /// The target label that should be referenced. /// @@ -161,37 +163,6 @@ pub struct RefElem { /// ``` #[default(RefForm::Normal)] pub form: RefForm, - - /// A synthesized citation. - #[synthesized] - pub citation: Option>, - - /// The referenced element. - #[synthesized] - pub element: Option, -} - -impl Synthesize for Packed { - fn synthesize( - &mut self, - engine: &mut Engine, - styles: StyleChain, - ) -> SourceResult<()> { - let citation = to_citation(self, engine, styles)?; - - let elem = self.as_mut(); - elem.push_citation(Some(citation)); - elem.push_element(None); - - if !BibliographyElem::has(engine, elem.target) { - if let Ok(found) = engine.introspector.query_label(elem.target).cloned() { - elem.push_element(Some(found)); - return Ok(()); - } - } - - Ok(()) - } } impl Show for Packed { @@ -334,6 +305,25 @@ fn to_citation( Ok(elem) } +#[scope] +impl RefElem { + /// The referenced element. + /// + /// If this is a reference to a bibliography entry, this returns `{none}`. + #[func] + pub fn element(self, engine: &mut Engine) -> StrResult> { + if BibliographyElem::has(engine, self.target) { + return Ok(None); + } + engine.introspector.query_label(self.target).cloned().map(Some) + } +} + +cast! { + RefElem, + v: Content => v.unpack::().map_err(|_| "expected reference")? +} + /// Additional content for a reference. #[derive(Debug, Clone, PartialEq, Hash)] pub enum Supplement { diff --git a/crates/typst-library/src/text/raw.rs b/crates/typst-library/src/text/raw.rs index d5c07424d..977823412 100644 --- a/crates/typst-library/src/text/raw.rs +++ b/crates/typst-library/src/text/raw.rs @@ -6,6 +6,7 @@ use comemo::Tracked; use ecow::{eco_format, EcoString, EcoVec}; use syntect::highlighting as synt; use syntect::parsing::{SyntaxDefinition, SyntaxSet, SyntaxSetBuilder}; +use typst_macros::func; use typst_syntax::{split_newlines, LinkedNode, Span, Spanned}; use typst_utils::ManuallyHash; use unicode_segmentation::UnicodeSegmentation; @@ -14,8 +15,8 @@ use super::Lang; use crate::diag::{At, FileError, SourceResult, StrResult}; use crate::engine::Engine; use crate::foundations::{ - cast, elem, scope, Bytes, Content, Derived, NativeElement, OneOrMultiple, Packed, - PlainText, Show, ShowSet, Smart, StyleChain, Styles, Synthesize, TargetElem, + cast, elem, scope, Bytes, Content, Context, Derived, NativeElement, OneOrMultiple, + Packed, PlainText, Show, ShowSet, Smart, StyleChain, Styles, TargetElem, }; use crate::html::{tag, HtmlElem}; use crate::layout::{BlockBody, BlockElem, Em, HAlignment}; @@ -72,16 +73,7 @@ use crate::World; /// needed, start the text with a single space (which will be trimmed) or use /// the single backtick syntax. If your text should start or end with a /// backtick, put a space before or after it (it will be trimmed). -#[elem( - scope, - title = "Raw Text / Code", - Synthesize, - Show, - ShowSet, - LocalName, - Figurable, - PlainText -)] +#[elem(scope, title = "Raw Text / Code", Show, ShowSet, LocalName, Figurable, PlainText)] pub struct RawElem { /// The raw text. /// @@ -265,19 +257,27 @@ pub struct RawElem { /// ```` #[default(2)] pub tab_size: usize, - - /// The stylized lines of raw text. - /// - /// Made accessible for the [`raw.line` element]($raw.line). - /// Allows more styling control in `show` rules. - #[synthesized] - pub lines: Vec>, } #[scope] impl RawElem { #[elem] type RawLine; + + /// The stylized lines of raw text. + #[func(contextual)] + pub fn lines( + self, + context: Tracked, + span: Span, + ) -> SourceResult>> { + Ok(self.highlight(context.styles().at(span)?, span)) + } +} + +cast! { + RawElem, + v: Content => v.unpack::().map_err(|_| "expected raw text / code")? } impl RawElem { @@ -299,24 +299,13 @@ impl RawElem { ]) .collect() } -} -impl Synthesize for Packed { - fn synthesize(&mut self, _: &mut Engine, styles: StyleChain) -> SourceResult<()> { - let seq = self.highlight(styles); - self.push_lines(seq); - Ok(()) - } -} - -impl Packed { #[comemo::memoize] - fn highlight(&self, styles: StyleChain) -> Vec> { - let elem = self.as_ref(); - let lines = preprocess(&elem.text, styles, self.span()); + fn highlight(&self, styles: StyleChain, span: Span) -> Vec> { + let lines = preprocess(&self.text, styles, span); let count = lines.len() as i64; - let lang = elem + let lang = self .lang(styles) .as_ref() .as_ref() @@ -335,8 +324,8 @@ impl Packed { }) }; - let syntaxes = LazyCell::new(|| elem.syntaxes(styles)); - let theme: &synt::Theme = match elem.theme(styles) { + let syntaxes = LazyCell::new(|| self.syntaxes(styles)); + let theme: &synt::Theme = match self.theme(styles) { Smart::Auto => &RAW_THEME, Smart::Custom(Some(theme)) => theme.derived.get(), Smart::Custom(None) => return non_highlighted_result(lines).collect(), @@ -432,7 +421,7 @@ impl Packed { impl Show for Packed { #[typst_macros::time(name = "raw", span = self.span())] fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult { - let lines = self.lines().map(|v| v.as_slice()).unwrap_or_default(); + let lines = self.as_ref().highlight(styles, self.span()); let mut seq = EcoVec::with_capacity((2 * lines.len()).saturating_sub(1)); for (i, line) in lines.iter().enumerate() { diff --git a/crates/typst-macros/src/elem.rs b/crates/typst-macros/src/elem.rs index 67fe7ed6a..cf996b2f8 100644 --- a/crates/typst-macros/src/elem.rs +++ b/crates/typst-macros/src/elem.rs @@ -78,9 +78,7 @@ impl Elem { /// Fields that show up in the documentation. fn doc_fields(&self) -> impl Iterator + Clone { - self.fields - .iter() - .filter(|field| !field.internal && !field.synthesized) + self.fields.iter().filter(|field| !field.internal) } /// Fields that are relevant for `Construct` impl. @@ -89,9 +87,8 @@ impl Elem { /// because it's a pattern used a lot for parsing data from the input and /// then storing it in a field. fn construct_fields(&self) -> impl Iterator + Clone { - self.real_fields().filter(|field| { - field.parse.is_some() || (!field.synthesized && !field.internal) - }) + self.real_fields() + .filter(|field| field.parse.is_some() || !field.internal) } /// Fields that can be configured with set rules. @@ -242,6 +239,8 @@ fn parse_field(field: &syn::Field) -> Result { let variadic = has_attr(&mut attrs, "variadic"); let required = has_attr(&mut attrs, "required") || variadic; let positional = has_attr(&mut attrs, "positional") || required; + let synthesized = has_attr(&mut attrs, "synthesized"); + let internal = has_attr(&mut attrs, "internal") || synthesized; let mut field = Field { ident: ident.clone(), @@ -261,11 +260,11 @@ fn parse_field(field: &syn::Field) -> Result { variadic, resolve: has_attr(&mut attrs, "resolve"), fold: has_attr(&mut attrs, "fold"), - internal: has_attr(&mut attrs, "internal"), + internal, external: has_attr(&mut attrs, "external"), borrowed: has_attr(&mut attrs, "borrowed"), ghost: has_attr(&mut attrs, "ghost"), - synthesized: has_attr(&mut attrs, "synthesized"), + synthesized, parse: parse_attr(&mut attrs, "parse")?.flatten(), default: parse_attr::(&mut attrs, "default")?.flatten(), }; diff --git a/crates/typst-macros/src/lib.rs b/crates/typst-macros/src/lib.rs index 82e63ddc8..fdf707647 100644 --- a/crates/typst-macros/src/lib.rs +++ b/crates/typst-macros/src/lib.rs @@ -201,7 +201,9 @@ pub fn ty(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream { /// flexibility. /// - `#[synthesized]`: The field cannot be specified in a constructor or set /// rule. Instead, it is added to an element before its show rule runs -/// through the `Synthesize` trait. +/// through the `Synthesize` trait. This implies `#[internal]`. If a +/// synthesized field needs to be exposed to the user, that should be done via +/// a getter method. /// - `#[ghost]`: Allows creating fields that are only present in the style chain, /// this means that they *cannot* be accessed by the user, they cannot be set /// on an individual instantiated element, and must be set via the style chain. diff --git a/tests/suite/math/interactions.typ b/tests/suite/math/interactions.typ index 209f2f1b5..7cbf184b1 100644 --- a/tests/suite/math/interactions.typ +++ b/tests/suite/math/interactions.typ @@ -58,8 +58,7 @@ $x$$y$ --- issue-2821-missing-fields --- // Issue #2821: Setting a figure's supplement to none removes the field #show figure.caption: it => { - assert(it.has("supplement")) - assert(it.supplement == none) + assert(it.supplement() == none) } #figure([], caption: [], supplement: none) diff --git a/tests/suite/model/figure.typ b/tests/suite/model/figure.typ index 37fb4ecda..8c49bc568 100644 --- a/tests/suite/model/figure.typ +++ b/tests/suite/model/figure.typ @@ -97,7 +97,7 @@ We can clearly see that @fig-cylinder and if not it.numbering == none { title = it.supplement if not it.numbering == none { - title += " " + it.counter.display(it.numbering) + title += " " + counter(figure.where(kind: it.kind)).display(it.numbering) } } title = strong(title) @@ -168,7 +168,10 @@ We can clearly see that @fig-cylinder and --- figure-caption-where-selector --- // Test figure.caption element for specific figure kinds -#show figure.caption.where(kind: table): underline +#show figure.where(kind: table): it => { + show figure.caption: underline + it +} #figure( [Not a table], @@ -212,8 +215,8 @@ We can clearly see that @fig-cylinder and #show figure.caption: it => emph[ #it.body - (#it.supplement - #context it.counter.display(it.numbering)) + (#it.supplement() + #context it.counter().display(it.numbering())) ] #figure( diff --git a/tests/suite/text/raw.typ b/tests/suite/text/raw.typ index a7f58a8d0..b8bdf2ea0 100644 --- a/tests/suite/text/raw.typ +++ b/tests/suite/text/raw.typ @@ -513,7 +513,7 @@ fn main() { --- raw-line-alternating-fill --- #set page(width: 200pt) -#show raw: it => stack(dir: ttb, ..it.lines) +#show raw: it => stack(dir: ttb, ..it.lines()) #show raw.line: it => { box( width: 100%, @@ -564,21 +564,21 @@ print(y) // Test line extraction works. #show raw: code => { - for i in code.lines { + for i in code.lines() { test(i.count, 10) } - test(code.lines.at(0).text, "import numpy as np") - test(code.lines.at(1).text, "") - test(code.lines.at(2).text, "def f(x):") - test(code.lines.at(3).text, " return x**2") - test(code.lines.at(4).text, "") - test(code.lines.at(5).text, "x = np.linspace(0, 10, 100)") - test(code.lines.at(6).text, "y = f(x)") - test(code.lines.at(7).text, "") - test(code.lines.at(8).text, "print(x)") - test(code.lines.at(9).text, "print(y)") - test(code.lines.at(10, default: none), none) + test(code.lines().at(0).text, "import numpy as np") + test(code.lines().at(1).text, "") + test(code.lines().at(2).text, "def f(x):") + test(code.lines().at(3).text, " return x**2") + test(code.lines().at(4).text, "") + test(code.lines().at(5).text, "x = np.linspace(0, 10, 100)") + test(code.lines().at(6).text, "y = f(x)") + test(code.lines().at(7).text, "") + test(code.lines().at(8).text, "print(x)") + test(code.lines().at(9).text, "print(y)") + test(code.lines().at(10, default: none), none) } ```py