From 7d33436e55f8b1aec06d136ebe095dd86bf23e57 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 1 Feb 2024 14:30:17 +0100 Subject: [PATCH] Fix show-set semantics (#3311) --- crates/typst-macros/src/elem.rs | 26 ++ crates/typst/src/foundations/content.rs | 41 ++- crates/typst/src/foundations/element.rs | 14 +- crates/typst/src/foundations/styles.rs | 10 +- crates/typst/src/math/equation.rs | 32 +-- crates/typst/src/model/bibliography.rs | 21 +- crates/typst/src/model/cite.rs | 3 - crates/typst/src/model/figure.rs | 34 +-- crates/typst/src/model/footnote.rs | 27 +- crates/typst/src/model/heading.rs | 30 +- crates/typst/src/model/outline.rs | 17 +- crates/typst/src/model/quote.rs | 30 +- crates/typst/src/model/reference.rs | 5 +- crates/typst/src/realize/mod.rs | 355 +++++++++++++++--------- crates/typst/src/text/raw.rs | 43 +-- tests/ref/compiler/content-field.png | Bin 24702 -> 8252 bytes tests/ref/compiler/show-set-func.png | Bin 0 -> 5772 bytes tests/ref/compiler/show-set.png | Bin 0 -> 22168 bytes tests/typ/compiler/content-field.typ | 29 +- tests/typ/compiler/show-selector.typ | 2 +- tests/typ/compiler/show-set-func.typ | 16 ++ tests/typ/compiler/show-set.typ | 55 ++++ 22 files changed, 485 insertions(+), 305 deletions(-) create mode 100644 tests/ref/compiler/show-set-func.png create mode 100644 tests/ref/compiler/show-set.png create mode 100644 tests/typ/compiler/show-set-func.typ create mode 100644 tests/typ/compiler/show-set.typ diff --git a/crates/typst-macros/src/elem.rs b/crates/typst-macros/src/elem.rs index 3a31d3ef0..eab359d6f 100644 --- a/crates/typst-macros/src/elem.rs +++ b/crates/typst-macros/src/elem.rs @@ -866,6 +866,28 @@ fn create_fields_impl(element: &Elem) -> TokenStream { quote! { Fields::#enum_ident => #expr } }); + // Sets fields from the style chain. + let materializes = visible_non_ghost() + .filter(|field| !field.required && !field.synthesized) + .map(|field| { + let Field { ident, .. } = field; + let value = create_style_chain_access( + field, + false, + if field.ghost { quote!(None) } else { quote!(self.#ident.as_ref()) }, + ); + + if field.fold { + quote! { self.#ident = Some(#value); } + } else { + quote! { + if self.#ident.is_none() { + self.#ident = Some(#value); + } + } + } + }); + // Creation of the `fields` dictionary for inherent fields. let field_inserts = visible_non_ghost().map(|field| { let Field { ident, name, .. } = field; @@ -917,6 +939,10 @@ fn create_fields_impl(element: &Elem) -> TokenStream { } } + fn materialize(&mut self, styles: #foundations::StyleChain) { + #(#materializes)* + } + fn fields(&self) -> #foundations::Dict { let mut fields = #foundations::Dict::new(); #(#field_inserts)* diff --git a/crates/typst/src/foundations/content.rs b/crates/typst/src/foundations/content.rs index 49497b8ff..6f912086e 100644 --- a/crates/typst/src/foundations/content.rs +++ b/crates/typst/src/foundations/content.rs @@ -14,11 +14,10 @@ use smallvec::smallvec; use crate::diag::{SourceResult, StrResult}; use crate::engine::Engine; use crate::foundations::{ - elem, func, scope, ty, Dict, Element, Fields, Finalize, Guard, IntoValue, Label, - NativeElement, Recipe, Repr, Selector, Str, Style, StyleChain, Styles, Synthesize, - Value, + elem, func, scope, ty, Dict, Element, Fields, Guard, IntoValue, Label, NativeElement, + Recipe, Repr, Selector, Str, Style, StyleChain, Styles, Value, }; -use crate::introspection::{Locatable, Location, Meta, MetaElem}; +use crate::introspection::{Location, Meta, MetaElem}; use crate::layout::{AlignElem, Alignment, Axes, Length, MoveElem, PadElem, Rel, Sides}; use crate::model::{Destination, EmphElem, StrongElem}; use crate::syntax::Span; @@ -142,6 +141,16 @@ impl Content { self } + /// Check whether a show rule recipe is disabled. + pub fn is_guarded(&self, guard: Guard) -> bool { + self.inner.lifecycle.contains(guard.0) + } + + /// Whether this content has already been prepared. + pub fn is_prepared(&self) -> bool { + self.inner.lifecycle.contains(0) + } + /// Set the location of the content. pub fn set_location(&mut self, location: Location) { self.make_mut().location = Some(location); @@ -153,25 +162,6 @@ impl Content { self } - /// Whether the content needs to be realized specially. - pub fn needs_preparation(&self) -> bool { - !self.is_prepared() - && (self.can::() - || self.can::() - || self.can::() - || self.label().is_some()) - } - - /// Check whether a show rule recipe is disabled. - pub fn is_guarded(&self, guard: Guard) -> bool { - self.inner.lifecycle.contains(guard.0) - } - - /// Whether this content has already been prepared. - pub fn is_prepared(&self) -> bool { - self.inner.lifecycle.contains(0) - } - /// Mark this content as prepared. pub fn mark_prepared(&mut self) { self.make_mut().lifecycle.insert(0); @@ -227,6 +217,11 @@ impl Content { self.get_by_name(name).ok_or_else(|| missing_field(name)) } + /// Resolve all fields with the styles and save them in-place. + pub fn materialize(&mut self, styles: StyleChain) { + self.make_mut().elem.materialize(styles); + } + /// Create a new sequence element from multiples elements. pub fn sequence(iter: impl IntoIterator) -> Self { let mut iter = iter.into_iter(); diff --git a/crates/typst/src/foundations/element.rs b/crates/typst/src/foundations/element.rs index 489077e4a..6d73a896d 100644 --- a/crates/typst/src/foundations/element.rs +++ b/crates/typst/src/foundations/element.rs @@ -223,6 +223,9 @@ pub trait Fields { /// Get the field with the given ID in the presence of styles. fn field_with_styles(&self, id: u8, styles: StyleChain) -> Option; + /// Resolve all fields with the styles and save them in-place. + fn materialize(&mut self, styles: StyleChain); + /// Get the fields of the element. fn fields(&self) -> Dict; } @@ -282,17 +285,20 @@ pub trait Synthesize { -> SourceResult<()>; } -/// The base recipe for an element. +/// Defines a built-in show rule for an element. pub trait Show { /// Execute the base recipe for this element. fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult; } -/// Post-process an element after it was realized. -pub trait Finalize { +/// Defines built-in show set rules for an element. +/// +/// This is a bit more powerful than a user-defined show-set because it can +/// access the element's fields. +pub trait ShowSet { /// Finalize the fully realized form of the element. Use this for effects /// that should work even in the face of a user-defined show rule. - fn finalize(&self, realized: Content, styles: StyleChain) -> Content; + fn show_set(&self, styles: StyleChain) -> Styles; } /// How the element interacts with other elements. diff --git a/crates/typst/src/foundations/styles.rs b/crates/typst/src/foundations/styles.rs index 6946d076d..12ba28763 100644 --- a/crates/typst/src/foundations/styles.rs +++ b/crates/typst/src/foundations/styles.rs @@ -147,9 +147,15 @@ impl Styles { } } +impl From> for Styles { + fn from(style: Prehashed