Fix show-set semantics (#3311)

This commit is contained in:
Laurenz 2024-02-01 14:30:17 +01:00 committed by GitHub
parent 426445edfc
commit 7d33436e55
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 485 additions and 305 deletions

View File

@ -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)*

View File

@ -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::<dyn Locatable>()
|| self.can::<dyn Synthesize>()
|| self.can::<dyn Finalize>()
|| 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<Item = Self>) -> Self {
let mut iter = iter.into_iter();

View File

@ -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<Value>;
/// 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<Content>;
}
/// 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.

View File

@ -147,9 +147,15 @@ impl Styles {
}
}
impl From<Prehashed<Style>> for Styles {
fn from(style: Prehashed<Style>) -> Self {
Self(eco_vec![style])
}
}
impl From<Style> for Styles {
fn from(entry: Style) -> Self {
Self(eco_vec![Prehashed::new(entry)])
fn from(style: Style) -> Self {
Self(eco_vec![Prehashed::new(style)])
}
}

View File

@ -5,7 +5,7 @@ use unicode_math_class::MathClass;
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
elem, Content, Finalize, NativeElement, Packed, Resolve, Smart, StyleChain,
elem, Content, NativeElement, Packed, Resolve, ShowSet, Smart, StyleChain, Styles,
Synthesize,
};
use crate::introspection::{Count, Counter, CounterUpdate, Locatable};
@ -49,7 +49,7 @@ use crate::World;
#[elem(
Locatable,
Synthesize,
Finalize,
ShowSet,
LayoutSingle,
LayoutMath,
Count,
@ -145,27 +145,23 @@ impl Synthesize for Packed<EquationElem> {
}
};
let elem = self.as_mut();
elem.push_block(elem.block(styles));
elem.push_numbering(elem.numbering(styles));
elem.push_supplement(Smart::Custom(Some(Supplement::Content(supplement))));
self.push_supplement(Smart::Custom(Some(Supplement::Content(supplement))));
Ok(())
}
}
impl Finalize for Packed<EquationElem> {
fn finalize(&self, realized: Content, style: StyleChain) -> Content {
let mut realized = realized;
if self.block(style) {
realized = realized.styled(AlignElem::set_alignment(Alignment::CENTER));
realized = realized.styled(EquationElem::set_size(MathSize::Display));
impl ShowSet for Packed<EquationElem> {
fn show_set(&self, styles: StyleChain) -> Styles {
let mut out = Styles::new();
if self.block(styles) {
out.set(AlignElem::set_alignment(Alignment::CENTER));
out.set(EquationElem::set_size(MathSize::Display));
}
realized
.styled(TextElem::set_weight(FontWeight::from_number(450)))
.styled(TextElem::set_font(FontList(vec![FontFamily::new(
"New Computer Modern Math",
)])))
out.set(TextElem::set_weight(FontWeight::from_number(450)));
out.set(TextElem::set_font(FontList(vec![FontFamily::new(
"New Computer Modern Math",
)])));
out
}
}

View File

@ -23,9 +23,9 @@ use crate::diag::{bail, error, At, FileError, SourceResult, StrResult};
use crate::engine::Engine;
use crate::eval::{eval_string, EvalMode};
use crate::foundations::{
cast, elem, ty, Args, Array, Bytes, CastInfo, Content, Finalize, FromValue,
IntoValue, Label, NativeElement, Packed, Reflect, Repr, Scope, Show, Smart, Str,
StyleChain, Synthesize, Type, Value,
cast, elem, ty, Args, Array, Bytes, CastInfo, Content, FromValue, IntoValue, Label,
NativeElement, Packed, Reflect, Repr, Scope, Show, ShowSet, Smart, Str, StyleChain,
Styles, Synthesize, Type, Value,
};
use crate::introspection::{Introspector, Locatable, Location};
use crate::layout::{
@ -84,7 +84,7 @@ use crate::World;
///
/// #bibliography("works.bib")
/// ```
#[elem(Locatable, Synthesize, Show, Finalize, LocalName)]
#[elem(Locatable, Synthesize, Show, ShowSet, LocalName)]
pub struct BibliographyElem {
/// Path(s) to Hayagriva `.yml` and/or BibLaTeX `.bib` files.
#[required]
@ -199,8 +199,6 @@ impl BibliographyElem {
impl Synthesize for Packed<BibliographyElem> {
fn synthesize(&mut self, _: &mut Engine, styles: StyleChain) -> SourceResult<()> {
let elem = self.as_mut();
elem.push_full(elem.full(styles));
elem.push_style(elem.style(styles));
elem.push_lang(TextElem::lang_in(styles));
elem.push_region(TextElem::region_in(styles));
Ok(())
@ -275,12 +273,13 @@ impl Show for Packed<BibliographyElem> {
}
}
impl Finalize for Packed<BibliographyElem> {
fn finalize(&self, realized: Content, _: StyleChain) -> Content {
impl ShowSet for Packed<BibliographyElem> {
fn show_set(&self, _: StyleChain) -> Styles {
const INDENT: Em = Em::new(1.0);
realized
.styled(HeadingElem::set_numbering(None))
.styled(PadElem::set_left(INDENT.into()))
let mut out = Styles::new();
out.set(HeadingElem::set_numbering(None));
out.set(PadElem::set_left(INDENT.into()));
out
}
}

View File

@ -111,9 +111,6 @@ pub struct CiteElem {
impl Synthesize for Packed<CiteElem> {
fn synthesize(&mut self, _: &mut Engine, styles: StyleChain) -> SourceResult<()> {
let elem = self.as_mut();
elem.push_supplement(elem.supplement(styles));
elem.push_form(elem.form(styles));
elem.push_style(elem.style(styles));
elem.push_lang(TextElem::lang_in(styles));
elem.push_region(TextElem::region_in(styles));
Ok(())

View File

@ -7,8 +7,8 @@ use ecow::EcoString;
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
cast, elem, scope, select_where, Content, Element, Finalize, NativeElement, Packed,
Selector, Show, Smart, StyleChain, Synthesize,
cast, elem, scope, select_where, Content, Element, NativeElement, Packed, Selector,
Show, ShowSet, Smart, StyleChain, Styles, Synthesize,
};
use crate::introspection::{
Count, Counter, CounterKey, CounterUpdate, Locatable, Location,
@ -101,7 +101,7 @@ use crate::visualize::ImageElem;
/// caption: [I'm up here],
/// )
/// ```
#[elem(scope, Locatable, Synthesize, Count, Show, Finalize, Refable, Outlinable)]
#[elem(scope, Locatable, Synthesize, Count, Show, ShowSet, Refable, Outlinable)]
pub struct FigureElem {
/// The content of the figure. Often, an [image]($image).
#[required]
@ -165,7 +165,6 @@ pub struct FigureElem {
/// supplement: [Atom],
/// )
/// ```
#[default(Smart::Auto)]
pub kind: Smart<FigureKind>,
/// The figure's supplement.
@ -230,9 +229,7 @@ impl Synthesize for Packed<FigureElem> {
) -> SourceResult<()> {
let span = self.span();
let location = self.location();
let elem = self.as_mut();
let placement = elem.placement(styles);
let numbering = elem.numbering(styles);
// Determine the figure's kind.
@ -295,13 +292,10 @@ impl Synthesize for Packed<FigureElem> {
caption.push_figure_location(location);
}
elem.push_placement(placement);
elem.push_caption(caption);
elem.push_kind(Smart::Custom(kind));
elem.push_supplement(Smart::Custom(supplement.map(Supplement::Content)));
elem.push_numbering(numbering);
elem.push_outlined(elem.outlined(styles));
elem.push_counter(Some(counter));
elem.push_caption(caption);
Ok(())
}
@ -342,10 +336,11 @@ impl Show for Packed<FigureElem> {
}
}
impl Finalize for Packed<FigureElem> {
fn finalize(&self, realized: Content, _: StyleChain) -> Content {
// Allow breakable figures with `show figure: set block(breakable: true)`.
realized.styled(BlockElem::set_breakable(false))
impl ShowSet for Packed<FigureElem> {
fn show_set(&self, _: StyleChain) -> Styles {
// Still allows breakable figures with
// `show figure: set block(breakable: true)`.
BlockElem::set_breakable(false).wrap().into()
}
}
@ -434,7 +429,7 @@ impl Outlinable for Packed<FigureElem> {
/// caption: [A rectangle],
/// )
/// ```
#[elem(name = "caption", Synthesize, Show)]
#[elem(name = "caption", Show)]
pub struct FigureCaption {
/// The caption's position in the figure. Either `{top}` or `{bottom}`.
///
@ -551,15 +546,6 @@ impl FigureCaption {
}
}
impl Synthesize for Packed<FigureCaption> {
fn synthesize(&mut self, _: &mut Engine, styles: StyleChain) -> SourceResult<()> {
let elem = self.as_mut();
elem.push_position(elem.position(styles));
elem.push_separator(Smart::Custom(elem.get_separator(styles)));
Ok(())
}
}
impl Show for Packed<FigureCaption> {
#[typst_macros::time(name = "figure.caption", span = self.span())]
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {

View File

@ -4,8 +4,8 @@ use std::str::FromStr;
use crate::diag::{bail, At, SourceResult, StrResult};
use crate::engine::Engine;
use crate::foundations::{
cast, elem, scope, Content, Finalize, Label, NativeElement, Packed, Show, Smart,
StyleChain, Synthesize,
cast, elem, scope, Content, Label, NativeElement, Packed, Show, ShowSet, Smart,
StyleChain, Styles,
};
use crate::introspection::{Count, Counter, CounterUpdate, Locatable, Location};
use crate::layout::{Abs, Em, HElem, Length, Ratio};
@ -50,7 +50,7 @@ use crate::visualize::{LineElem, Stroke};
/// apply to the footnote's content. See [here][issue] for more information.
///
/// [issue]: https://github.com/typst/typst/issues/1467#issuecomment-1588799440
#[elem(scope, Locatable, Synthesize, Show, Count)]
#[elem(scope, Locatable, Show, Count)]
pub struct FootnoteElem {
/// How to number footnotes.
///
@ -123,14 +123,6 @@ impl Packed<FootnoteElem> {
}
}
impl Synthesize for Packed<FootnoteElem> {
fn synthesize(&mut self, _: &mut Engine, styles: StyleChain) -> SourceResult<()> {
let elem = self.as_mut();
elem.push_numbering(elem.numbering(styles).clone());
Ok(())
}
}
impl Show for Packed<FootnoteElem> {
#[typst_macros::time(name = "footnote", span = self.span())]
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
@ -188,7 +180,7 @@ cast! {
/// #footnote[It's down here]
/// has red text!
/// ```
#[elem(name = "entry", title = "Footnote Entry", Show, Finalize)]
#[elem(name = "entry", title = "Footnote Entry", Show, ShowSet)]
pub struct FootnoteEntry {
/// The footnote for this entry. It's location can be used to determine
/// the footnote counter state.
@ -303,13 +295,14 @@ impl Show for Packed<FootnoteEntry> {
}
}
impl Finalize for Packed<FootnoteEntry> {
fn finalize(&self, realized: Content, _: StyleChain) -> Content {
impl ShowSet for Packed<FootnoteEntry> {
fn show_set(&self, _: StyleChain) -> Styles {
let text_size = Em::new(0.85);
let leading = Em::new(0.5);
realized
.styled(ParElem::set_leading(leading.into()))
.styled(TextElem::set_size(TextSize(text_size.into())))
let mut out = Styles::new();
out.set(ParElem::set_leading(leading.into()));
out.set(TextElem::set_size(TextSize(text_size.into())));
out
}
}

View File

@ -3,7 +3,7 @@ use std::num::NonZeroUsize;
use crate::diag::SourceResult;
use crate::engine::Engine;
use crate::foundations::{
elem, Content, Finalize, NativeElement, Packed, Show, Smart, StyleChain, Styles,
elem, Content, NativeElement, Packed, Show, ShowSet, Smart, StyleChain, Styles,
Synthesize,
};
use crate::introspection::{Count, Counter, CounterUpdate, Locatable};
@ -43,7 +43,7 @@ use crate::util::{option_eq, NonZeroExt};
/// Headings have dedicated syntax: They can be created by starting a line with
/// one or multiple equals signs, followed by a space. The number of equals
/// signs determines the heading's logical nesting depth.
#[elem(Locatable, Synthesize, Count, Show, Finalize, LocalName, Refable, Outlinable)]
#[elem(Locatable, Synthesize, Count, Show, ShowSet, LocalName, Refable, Outlinable)]
pub struct HeadingElem {
/// The logical nesting depth of the heading, starting from one.
#[default(NonZeroUsize::ONE)]
@ -140,13 +140,7 @@ impl Synthesize for Packed<HeadingElem> {
}
};
let elem = self.as_mut();
elem.push_level(elem.level(styles));
elem.push_numbering(elem.numbering(styles).clone());
elem.push_supplement(Smart::Custom(Some(Supplement::Content(supplement))));
elem.push_outlined(elem.outlined(styles));
elem.push_bookmarked(elem.bookmarked(styles));
self.push_supplement(Smart::Custom(Some(Supplement::Content(supplement))));
Ok(())
}
}
@ -166,8 +160,8 @@ impl Show for Packed<HeadingElem> {
}
}
impl Finalize for Packed<HeadingElem> {
fn finalize(&self, realized: Content, styles: StyleChain) -> Content {
impl ShowSet for Packed<HeadingElem> {
fn show_set(&self, styles: StyleChain) -> Styles {
let level = (**self).level(styles).get();
let scale = match level {
1 => 1.4,
@ -179,13 +173,13 @@ impl Finalize for Packed<HeadingElem> {
let above = Em::new(if level == 1 { 1.8 } else { 1.44 }) / scale;
let below = Em::new(0.75) / scale;
let mut styles = Styles::new();
styles.set(TextElem::set_size(TextSize(size.into())));
styles.set(TextElem::set_weight(FontWeight::BOLD));
styles.set(BlockElem::set_above(VElem::block_around(above.into())));
styles.set(BlockElem::set_below(VElem::block_around(below.into())));
styles.set(BlockElem::set_sticky(true));
realized.styled_with_map(styles)
let mut out = Styles::new();
out.set(TextElem::set_size(TextSize(size.into())));
out.set(TextElem::set_weight(FontWeight::BOLD));
out.set(BlockElem::set_above(VElem::block_around(above.into())));
out.set(BlockElem::set_below(VElem::block_around(below.into())));
out.set(BlockElem::set_sticky(true));
out
}
}

View File

@ -4,8 +4,8 @@ use std::str::FromStr;
use crate::diag::{bail, At, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
cast, elem, scope, select_where, Content, Finalize, Func, LocatableSelector,
NativeElement, Packed, Show, Smart, StyleChain,
cast, elem, scope, select_where, Content, Func, LocatableSelector, NativeElement,
Packed, Show, ShowSet, Smart, StyleChain, Styles,
};
use crate::introspection::{Counter, CounterKey, Locatable};
use crate::layout::{BoxElem, Fr, HElem, HideElem, Length, Rel, RepeatElem, Spacing};
@ -57,7 +57,7 @@ use crate::util::{option_eq, NonZeroExt};
/// `title` and `indent` parameters. If desired, however, it is possible to have
/// more control over the outline's look and style through the
/// [`outline.entry`]($outline.entry) element.
#[elem(scope, keywords = ["Table of Contents"], Show, Finalize, LocalName)]
#[elem(scope, keywords = ["Table of Contents"], Show, ShowSet, LocalName)]
pub struct OutlineElem {
/// The title of the outline.
///
@ -250,11 +250,12 @@ impl Show for Packed<OutlineElem> {
}
}
impl Finalize for Packed<OutlineElem> {
fn finalize(&self, realized: Content, _: StyleChain) -> Content {
realized
.styled(HeadingElem::set_outlined(false))
.styled(HeadingElem::set_numbering(None))
impl ShowSet for Packed<OutlineElem> {
fn show_set(&self, _: StyleChain) -> Styles {
let mut out = Styles::new();
out.set(HeadingElem::set_outlined(false));
out.set(HeadingElem::set_numbering(None));
out
}
}

View File

@ -1,8 +1,8 @@
use crate::diag::SourceResult;
use crate::engine::Engine;
use crate::foundations::{
cast, elem, Content, Finalize, Label, NativeElement, Packed, Show, Smart, StyleChain,
Synthesize,
cast, elem, Content, Label, NativeElement, Packed, Show, ShowSet, Smart, StyleChain,
Styles,
};
use crate::layout::{Alignment, BlockElem, Em, HElem, PadElem, Spacing, VElem};
use crate::model::{CitationForm, CiteElem};
@ -40,7 +40,7 @@ use crate::text::{SmartQuoteElem, SpaceElem, TextElem};
/// flame of Udûn. Go back to the Shadow! You cannot pass.
/// ]
/// ```
#[elem(Finalize, Show, Synthesize)]
#[elem(ShowSet, Show)]
pub struct QuoteElem {
/// Whether this is a block quote.
///
@ -145,15 +145,6 @@ cast! {
label: Label => Self::Label(label),
}
impl Synthesize for Packed<QuoteElem> {
fn synthesize(&mut self, _: &mut Engine, styles: StyleChain) -> SourceResult<()> {
let elem = self.as_mut();
elem.push_block(elem.block(styles));
elem.push_quotes(elem.quotes(styles));
Ok(())
}
}
impl Show for Packed<QuoteElem> {
#[typst_macros::time(name = "quote", span = self.span())]
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
@ -205,15 +196,16 @@ impl Show for Packed<QuoteElem> {
}
}
impl Finalize for Packed<QuoteElem> {
fn finalize(&self, realized: Content, _: StyleChain) -> Content {
impl ShowSet for Packed<QuoteElem> {
fn show_set(&self, _: StyleChain) -> Styles {
let x = Em::new(1.0).into();
let above = Em::new(2.4).into();
let below = Em::new(1.8).into();
realized
.styled(PadElem::set_left(x))
.styled(PadElem::set_right(x))
.styled(BlockElem::set_above(VElem::block_around(above)))
.styled(BlockElem::set_below(VElem::block_around(below)))
let mut out = Styles::new();
out.set(PadElem::set_left(x));
out.set(PadElem::set_right(x));
out.set(BlockElem::set_above(VElem::block_around(above)));
out.set(BlockElem::set_below(VElem::block_around(below)));
out
}
}

View File

@ -251,8 +251,11 @@ fn to_citation(
},
));
if let Some(loc) = reference.location() {
elem.set_location(loc);
}
elem.synthesize(engine, styles)?;
elem.set_location(reference.location().unwrap());
Ok(elem)
}

View File

@ -5,6 +5,7 @@ mod behave;
pub use self::behave::BehavedBuilder;
use std::borrow::Cow;
use std::cell::OnceCell;
use std::mem;
use smallvec::smallvec;
@ -13,8 +14,9 @@ use typed_arena::Arena;
use crate::diag::{bail, SourceResult};
use crate::engine::{Engine, Route};
use crate::foundations::{
Behave, Behaviour, Content, Finalize, Guard, NativeElement, Packed, Recipe, Selector,
Show, StyleChain, StyleVec, StyleVecBuilder, Styles, Synthesize,
Behave, Behaviour, Content, Guard, NativeElement, Packed, Recipe, Regex, Selector,
Show, ShowSet, StyleChain, StyleVec, StyleVecBuilder, Styles, Synthesize,
Transformation,
};
use crate::introspection::{Locatable, Meta, MetaElem};
use crate::layout::{
@ -56,7 +58,7 @@ pub fn realize_block<'a>(
) -> SourceResult<(Cow<'a, Content>, StyleChain<'a>)> {
// These elements implement `Layout` but still require a flow for
// proper layout.
if content.can::<dyn LayoutMultiple>() && !applicable(content, styles) {
if content.can::<dyn LayoutMultiple>() && verdict(engine, content, styles).is_none() {
return Ok((Cow::Borrowed(content), styles));
}
@ -69,161 +71,260 @@ pub fn realize_block<'a>(
Ok((Cow::Owned(FlowElem::new(children.to_vec()).pack().spanned(span)), shared))
}
/// Whether the target is affected by show rules in the given style chain.
pub fn applicable(target: &Content, styles: StyleChain) -> bool {
if target.needs_preparation() || target.can::<dyn Show>() {
return true;
}
// Find out how many recipes there are.
let mut n = styles.recipes().count();
// Find out whether any recipe matches and is unguarded.
for recipe in styles.recipes() {
if !target.is_guarded(Guard(n)) && recipe.applicable(target, styles) {
return true;
}
n -= 1;
}
false
}
/// Apply the show rules in the given style chain to a target.
/// Apply the show rules in the given style chain to a target element.
pub fn realize(
engine: &mut Engine,
target: &Content,
styles: StyleChain,
) -> SourceResult<Option<Content>> {
// Pre-process.
if target.needs_preparation() {
let mut elem = target.clone();
if target.can::<dyn Locatable>() || target.label().is_some() {
let location = engine.locator.locate(hash128(target));
elem.set_location(location);
}
let Some(Verdict { prepared, mut map, step }) = verdict(engine, target, styles)
else {
return Ok(None);
};
if let Some(synthesizable) = elem.with_mut::<dyn Synthesize>() {
synthesizable.synthesize(engine, styles)?;
}
// Create a fresh copy that we can mutate.
let mut target = target.clone();
elem.mark_prepared();
let span = elem.span();
let meta = elem.location().is_some().then(|| Meta::Elem(elem.clone()));
let mut content = elem;
if let Some(finalizable) = target.with::<dyn Finalize>() {
content = finalizable.finalize(content, styles);
}
if let Some(meta) = meta {
return Ok(Some(
(content + MetaElem::new().pack().spanned(span))
.styled(MetaElem::set_data(smallvec![meta])),
));
} else {
return Ok(Some(content));
}
// If the element isn't yet prepared (we're seeing it for the first time),
// prepare it.
let mut meta = None;
if !prepared {
meta = prepare(engine, &mut target, &mut map, styles)?;
}
// Find out how many recipes there are.
let mut n = styles.recipes().count();
// Apply the step.
let mut output = match step {
// Apply a user-defined show rule.
Some(Step::Recipe(recipe, guard)) => show(engine, target, recipe, guard)?,
// Find an applicable show rule recipe.
for recipe in styles.recipes() {
let guard = Guard(n);
if !target.is_guarded(guard) && recipe.applicable(target, styles) {
if let Some(content) = try_apply(engine, target, recipe, guard)? {
return Ok(Some(content));
// If the verdict picks this step, the `target` is guaranteed
// to have a built-in show rule.
Some(Step::Builtin) => {
target.with::<dyn Show>().unwrap().show(engine, styles.chain(&map))?
}
// Nothing to do.
None => target,
};
// If necessary, apply metadata generated in the preparation.
if let Some(meta) = meta {
output += meta.pack();
}
Ok(Some(output.styled_with_map(map)))
}
/// What to do with an element when encountering it during realization.
struct Verdict<'a> {
/// Whether the element is already prepated (i.e. things that should only
/// happen once have happened).
prepared: bool,
/// A map of styles to apply to the element.
map: Styles,
/// An optional transformation step to apply to the element.
step: Option<Step<'a>>,
}
/// An optional transformation step to apply to an element.
enum Step<'a> {
/// A user-defined transformational show rule.
Recipe(&'a Recipe, Guard),
/// The built-in show rule.
Builtin,
}
/// Inspects a target element and the current styles and determines how to
/// proceed with the styling.
fn verdict<'a>(
engine: &mut Engine,
target: &'a Content,
styles: StyleChain<'a>,
) -> Option<Verdict<'a>> {
let mut target = target;
let mut map = Styles::new();
let mut step = None;
let mut slot;
let depth = OnceCell::new();
let prepared = target.is_prepared();
// Do pre-synthesis on a cloned element to be able to match on synthesized
// fields before real synthesis runs (during preparation). It's really
// unfortunate that we have to do this, but otherwise
// `show figure.where(kind: table)` won't work :(
if !prepared && target.can::<dyn Synthesize>() {
slot = target.clone();
slot.with_mut::<dyn Synthesize>()
.unwrap()
.synthesize(engine, styles)
.ok();
target = &slot;
}
for (i, recipe) in styles.recipes().enumerate() {
// We're not interested in recipes that don't match.
if !recipe.applicable(target, styles) {
continue;
}
if let Transformation::Style(transform) = &recipe.transform {
// If this is a show-set for an unprepared element, we need to apply
// it.
if !prepared {
map.apply(transform.clone());
}
} else if step.is_none() {
// Lazily compute the total number of recipes in the style chain. We
// need it to determine whether a particular show rule was already
// applied to the `target` previously. For this purpose, show rules
// are indexed from the top of the chain as the chain might grow to
// the bottom.
let depth = *depth.get_or_init(|| styles.recipes().count());
let guard = Guard(depth - i);
if !target.is_guarded(guard) {
// If we find a matching, unguarded replacement show rule,
// remember it, but still continue searching for potential
// show-set styles that might change the verdict.
step = Some(Step::Recipe(recipe, guard));
// If we found a show rule and are already prepared, there is
// nothing else to do, so we can just break.
if prepared {
break;
}
}
}
n -= 1;
}
// Apply the built-in show rule if there was no matching recipe.
if let Some(showable) = target.with::<dyn Show>() {
return Ok(Some(showable.show(engine, styles)?));
// If we found no user-defined rule, also consider the built-in show rule.
if step.is_none() && target.can::<dyn Show>() {
step = Some(Step::Builtin);
}
// If there's no nothing to do, there is also no verdict.
if step.is_none()
&& map.is_empty()
&& (prepared || {
target.label().is_none()
&& !target.can::<dyn ShowSet>()
&& !target.can::<dyn Locatable>()
&& !target.can::<dyn Synthesize>()
})
{
return None;
}
Some(Verdict { prepared, map, step })
}
/// This is only executed the first time an element is visited.
fn prepare(
engine: &mut Engine,
target: &mut Content,
map: &mut Styles,
styles: StyleChain,
) -> SourceResult<Option<Packed<MetaElem>>> {
// Generate a location for the element, which uniquely identifies it in
// the document. This has some overhead, so we only do it for elements
// that are explicitly marked as locatable and labelled elements.
if target.can::<dyn Locatable>() || target.label().is_some() {
let location = engine.locator.locate(hash128(&target));
target.set_location(location);
}
// Apply built-in show-set rules. User-defined show-set rules are already
// considered in the map built while determining the verdict.
if let Some(show_settable) = target.with::<dyn ShowSet>() {
map.apply(show_settable.show_set(styles));
}
// If necessary, generated "synthesized" fields (which are derived from
// other fields or queries). Do this after show-set so that show-set styles
// are respected.
if let Some(synthesizable) = target.with_mut::<dyn Synthesize>() {
synthesizable.synthesize(engine, styles.chain(map))?;
}
// Copy style chain fields into the element itself, so that they are
// available in rules.
target.materialize(styles.chain(map));
// Ensure that this preparation only runs once by marking the element as
// prepared.
target.mark_prepared();
// Apply metadata be able to find the element in the frames.
// Do this after synthesis, so that it includes the synthesized fields.
if target.location().is_some() {
// Add a style to the whole element's subtree identifying it as
// belonging to the element.
map.set(MetaElem::set_data(smallvec![Meta::Elem(target.clone())]));
// Return an extra meta elem that will be attached so that the metadata
// styles are not lost in case the element's show rule results in
// nothing.
return Ok(Some(Packed::new(MetaElem::new()).spanned(target.span())));
}
Ok(None)
}
/// Try to apply a recipe to the target.
fn try_apply(
/// Apply a user-defined show rule.
fn show(
engine: &mut Engine,
target: &Content,
target: Content,
recipe: &Recipe,
guard: Guard,
) -> SourceResult<Option<Content>> {
) -> SourceResult<Content> {
match &recipe.selector {
Some(Selector::Elem(element, _)) => {
if target.func() != *element {
return Ok(None);
}
recipe.apply(engine, target.clone().guarded(guard)).map(Some)
}
Some(Selector::Label(label)) => {
if target.label() != Some(*label) {
return Ok(None);
}
recipe.apply(engine, target.clone().guarded(guard)).map(Some)
}
Some(Selector::Regex(regex)) => {
let Some(elem) = target.to_packed::<TextElem>() else {
return Ok(None);
};
// If the verdict picks this rule, the `target` is guaranteed
// to be a text element.
let text = target.into_packed::<TextElem>().unwrap();
show_regex(engine, &text, regex, recipe, guard)
}
_ => recipe.apply(engine, target.guarded(guard)),
}
}
let make = |s: &str| {
let mut fresh = elem.clone();
fresh.push_text(s.into());
fresh.pack()
};
/// Apply a regex show rule recipe to a target.
fn show_regex(
engine: &mut Engine,
elem: &Packed<TextElem>,
regex: &Regex,
recipe: &Recipe,
guard: Guard,
) -> SourceResult<Content> {
let make = |s: &str| {
let mut fresh = elem.clone();
fresh.push_text(s.into());
fresh.pack()
};
let mut result = vec![];
let mut cursor = 0;
let mut result = vec![];
let mut cursor = 0;
let text = elem.text();
let text = elem.text();
for m in regex.find_iter(elem.text()) {
let start = m.start();
if cursor < start {
result.push(make(&text[cursor..start]));
}
let piece = make(m.as_str()).guarded(guard);
let transformed = recipe.apply(engine, piece)?;
result.push(transformed);
cursor = m.end();
}
if result.is_empty() {
return Ok(None);
}
if cursor < text.len() {
result.push(make(&text[cursor..]));
}
Ok(Some(Content::sequence(result)))
for m in regex.find_iter(elem.text()) {
let start = m.start();
if cursor < start {
result.push(make(&text[cursor..start]));
}
// Not supported here.
Some(
Selector::Or(_)
| Selector::And(_)
| Selector::Location(_)
| Selector::Can(_)
| Selector::Before { .. }
| Selector::After { .. },
) => Ok(None),
None => Ok(None),
let piece = make(m.as_str()).guarded(guard);
let transformed = recipe.apply(engine, piece)?;
result.push(transformed);
cursor = m.end();
}
if cursor < text.len() {
result.push(make(&text[cursor..]));
}
Ok(Content::sequence(result))
}
/// Builds a document or a flow element from content.

View File

@ -12,8 +12,8 @@ use unicode_segmentation::UnicodeSegmentation;
use crate::diag::{At, FileError, SourceResult, StrResult};
use crate::engine::Engine;
use crate::foundations::{
cast, elem, scope, Args, Array, Bytes, Content, Finalize, Fold, NativeElement,
Packed, PlainText, Show, Smart, StyleChain, Styles, Synthesize, Value,
cast, elem, scope, Args, Array, Bytes, Content, Fold, NativeElement, Packed,
PlainText, Show, ShowSet, Smart, StyleChain, Styles, Synthesize, Value,
};
use crate::layout::{BlockElem, Em, HAlignment};
use crate::model::Figurable;
@ -74,7 +74,7 @@ type LineFn<'a> = &'a mut dyn FnMut(i64, Range<usize>, &mut Vec<Content>);
title = "Raw Text / Code",
Synthesize,
Show,
Finalize,
ShowSet,
LocalName,
Figurable,
PlainText
@ -289,11 +289,17 @@ impl RawElem {
impl Synthesize for Packed<RawElem> {
fn synthesize(&mut self, _: &mut Engine, styles: StyleChain) -> SourceResult<()> {
let span = self.span();
let elem = self.as_mut();
let seq = self.highlight(styles);
self.push_lines(seq);
Ok(())
}
}
let lang = elem.lang(styles).clone();
elem.push_lang(lang);
impl Packed<RawElem> {
#[comemo::memoize]
fn highlight(&self, styles: StyleChain) -> Vec<Packed<RawLine>> {
let elem = self.as_ref();
let span = self.span();
let mut text = elem.text().clone();
if text.contains('\t') {
@ -389,9 +395,7 @@ impl Synthesize for Packed<RawElem> {
}));
};
elem.push_lines(seq);
Ok(())
seq
}
}
@ -421,16 +425,15 @@ impl Show for Packed<RawElem> {
}
}
impl Finalize for Packed<RawElem> {
fn finalize(&self, realized: Content, _: StyleChain) -> Content {
let mut styles = Styles::new();
styles.set(TextElem::set_overhang(false));
styles.set(TextElem::set_hyphenate(Hyphenate(Smart::Custom(false))));
styles.set(TextElem::set_size(TextSize(Em::new(0.8).into())));
styles
.set(TextElem::set_font(FontList(vec![FontFamily::new("DejaVu Sans Mono")])));
styles.set(SmartQuoteElem::set_enabled(false));
realized.styled_with_map(styles)
impl ShowSet for Packed<RawElem> {
fn show_set(&self, _: StyleChain) -> Styles {
let mut out = Styles::new();
out.set(TextElem::set_overhang(false));
out.set(TextElem::set_hyphenate(Hyphenate(Smart::Custom(false))));
out.set(TextElem::set_size(TextSize(Em::new(0.8).into())));
out.set(TextElem::set_font(FontList(vec![FontFamily::new("DejaVu Sans Mono")])));
out.set(SmartQuoteElem::set_enabled(false));
out
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -1,19 +1,30 @@
// Tests for field introspection.
// Tests content field access.
---
// Verify that non-inherent fields are hidden if not set.
#show figure: it => [
`repr(it)`: #repr(it) \
`it.has("gap"): `#repr(it.has("gap")) \
]
// Ensure that fields from set rules are materialized into the element before
// a show rule runs.
#set table(columns: (10pt, auto))
#show table: it => it.columns
#table[A][B][C][D]
#figure[]
---
// Test it again with a different element.
#set heading(numbering: "(I)")
#show heading: set text(size: 11pt, weight: "regular")
#show heading: it => it.numbering
= Heading
#figure([], gap: 1pt)
---
// Test it with query.
#set raw(lang: "rust")
#locate(loc => {
let elem = query(<myraw>, loc).first()
elem.lang
})
`raw` <myraw>
---
// Integrated test for content fields.
#let compute(equation, ..vars) = {
let vars = vars.named()
let f(elem) = {

View File

@ -31,9 +31,9 @@ You can use the ```rs *const T``` pointer or
the ```rs &mut T``` reference.
---
#show heading: set text(green)
#show heading.where(level: 1): set text(red)
#show heading.where(level: 2): set text(blue)
#show heading: set text(green)
= Red
== Blue
=== Green

View File

@ -0,0 +1,16 @@
// Test set rules on an element in show rules for said element.
---
// These are both red because in the expanded form, `set text(red)` ends up
// closer to the content than `set text(blue)`.
#show strong: it => { set text(red); it }
Hello *World*
#show strong: it => { set text(blue); it }
Hello *World*
---
// This doesn't have an effect. An element is materialized before any show
// rules run.
#show heading: it => { set heading(numbering: "(I)"); it }
= Heading

View File

@ -0,0 +1,55 @@
// Test show-set rules.
---
// Test overriding show-set rules.
#show strong: set text(red)
Hello *World*
#show strong: set text(blue)
Hello *World*
---
// Test show-set rule on the same element.
#set figure(supplement: [Default])
#show figure.where(kind: table): set figure(supplement: [Tableau])
#figure(
table(columns: 2)[A][B][C][D],
caption: [Four letters],
)
---
// Test both things at once.
#show heading: set text(red)
= Level 1
== Level 2
#show heading.where(level: 1): set text(blue)
#show heading.where(level: 1): set text(green)
#show heading.where(level: 1): set heading(numbering: "(I)")
= Level 1
== Level 2
---
// Test setting the thing we just matched on.
// This is quite cursed, but it works.
#set heading(numbering: "(I)")
#show heading.where(numbering: "(I)"): set heading(numbering: "1.")
= Heading
---
// Same thing, but even more cursed, because `kind` is synthesized.
#show figure.where(kind: table): set figure(kind: raw)
#figure(table[A], caption: [Code])
---
// Test that show-set rules on the same element don't affect each other. This
// could be implemented, but isn't as of yet.
#show heading.where(level: 1): set heading(numbering: "(I)")
#show heading.where(numbering: "(I)"): set text(red)
= Heading
---
// Test show-set rules on layoutable element to ensure it is realized
// even though it implements `LayoutMultiple`.
#show table: set text(red)
#pad(table(columns: 4)[A][B][C][D])