summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/typst-macros/src/elem.rs60
-rw-r--r--crates/typst/src/foundations/content.rs18
-rw-r--r--crates/typst/src/foundations/element.rs3
-rw-r--r--crates/typst/src/foundations/selector.rs21
-rw-r--r--crates/typst/src/foundations/styles.rs4
-rw-r--r--crates/typst/src/introspection/introspector.rs8
-rw-r--r--crates/typst/src/realize/mod.rs4
-rw-r--r--tests/ref/compiler/select-where-styles.pngbin0 -> 17334 bytes
-rw-r--r--tests/typ/compiler/select-where-styles.typ91
9 files changed, 175 insertions, 34 deletions
diff --git a/crates/typst-macros/src/elem.rs b/crates/typst-macros/src/elem.rs
index a3e2a007..d689aa11 100644
--- a/crates/typst-macros/src/elem.rs
+++ b/crates/typst-macros/src/elem.rs
@@ -93,7 +93,7 @@ impl Elem {
/// Fields that are visible to the user.
fn visible_fields(&self) -> impl Iterator<Item = &Field> + Clone {
- self.real_fields().filter(|field| !field.internal && !field.ghost)
+ self.real_fields().filter(|field| !field.internal)
}
}
@@ -509,7 +509,11 @@ fn create_field_method(field: &Field) -> TokenStream {
quote! { (&self, styles: #foundations::StyleChain) -> #output }
};
- let mut value = create_style_chain_access(field, quote! { self.#ident.as_ref() });
+ let mut value = create_style_chain_access(
+ field,
+ field.borrowed,
+ quote! { self.#ident.as_ref() },
+ );
if field.resolve {
value = quote! { #foundations::Resolve::resolve(#value, styles) };
}
@@ -530,7 +534,7 @@ fn create_field_in_method(field: &Field) -> TokenStream {
let ref_ = field.borrowed.then(|| quote! { & });
- let mut value = create_style_chain_access(field, quote! { None });
+ let mut value = create_style_chain_access(field, field.borrowed, quote! { None });
if field.resolve {
value = quote! { #foundations::Resolve::resolve(#value, styles) };
}
@@ -560,16 +564,20 @@ fn create_set_field_method(field: &Field) -> TokenStream {
}
/// Create a style chain access method for a field.
-fn create_style_chain_access(field: &Field, inherent: TokenStream) -> TokenStream {
+fn create_style_chain_access(
+ field: &Field,
+ borrowed: bool,
+ inherent: TokenStream,
+) -> TokenStream {
let Field { ty, default, enum_ident, const_ident, .. } = field;
- let getter = match (field.fold, field.borrowed) {
+ let getter = match (field.fold, borrowed) {
(false, false) => quote! { get },
(false, true) => quote! { get_ref },
(true, _) => quote! { get_folded },
};
- let default = if field.borrowed {
+ let default = if borrowed {
quote! { || &#const_ident }
} else {
match default {
@@ -821,9 +829,10 @@ fn create_capable_impl(element: &Elem) -> TokenStream {
/// Creates the element's `Fields` implementation.
fn create_fields_impl(element: &Elem) -> TokenStream {
let into_value = quote! { #foundations::IntoValue::into_value };
+ let visible_non_ghost = || element.visible_fields().filter(|field| !field.ghost);
// Fields that can be checked using the `has` method.
- let has_arms = element.visible_fields().map(|field| {
+ let has_arms = visible_non_ghost().map(|field| {
let Field { enum_ident, ident, .. } = field;
let expr = if field.inherent() {
@@ -836,7 +845,7 @@ fn create_fields_impl(element: &Elem) -> TokenStream {
});
// Fields that can be accessed using the `field` method.
- let field_arms = element.visible_fields().map(|field| {
+ let field_arms = visible_non_ghost().filter(|field| !field.ghost).map(|field| {
let Field { enum_ident, ident, .. } = field;
let expr = if field.inherent() {
@@ -848,8 +857,29 @@ fn create_fields_impl(element: &Elem) -> TokenStream {
quote! { Fields::#enum_ident => #expr }
});
+ // Fields that can be accessed using the `field_with_styles` method.
+ let field_with_styles_arms = element.visible_fields().map(|field| {
+ let Field { enum_ident, ident, .. } = field;
+
+ let expr = if field.inherent() {
+ quote! { Some(#into_value(self.#ident.clone())) }
+ } else if field.synthesized && field.default.is_none() {
+ quote! { self.#ident.clone().map(#into_value) }
+ } else {
+ let value = create_style_chain_access(
+ field,
+ false,
+ if field.ghost { quote!(None) } else { quote!(self.#ident.as_ref()) },
+ );
+
+ quote! { Some(#into_value(#value)) }
+ };
+
+ quote! { Fields::#enum_ident => #expr }
+ });
+
// Creation of the `fields` dictionary for inherent fields.
- let field_inserts = element.visible_fields().map(|field| {
+ let field_inserts = visible_non_ghost().map(|field| {
let Field { ident, name, .. } = field;
let string = quote! { #name.into() };
@@ -873,7 +903,7 @@ fn create_fields_impl(element: &Elem) -> TokenStream {
type Enum = Fields;
fn has(&self, id: u8) -> bool {
- let Ok(id) = <#ident as #foundations::Fields>::Enum::try_from(id) else {
+ let Ok(id) = Fields::try_from(id) else {
return false;
};
@@ -884,13 +914,21 @@ fn create_fields_impl(element: &Elem) -> TokenStream {
}
fn field(&self, id: u8) -> Option<#foundations::Value> {
- let id = <#ident as #foundations::Fields>::Enum::try_from(id).ok()?;
+ let id = Fields::try_from(id).ok()?;
match id {
#(#field_arms,)*
_ => None,
}
}
+ fn field_with_styles(&self, id: u8, styles: #foundations::StyleChain) -> Option<#foundations::Value> {
+ let id = Fields::try_from(id).ok()?;
+ match id {
+ #(#field_with_styles_arms,)*
+ _ => None,
+ }
+ }
+
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 8a734635..49497b8f 100644
--- a/crates/typst/src/foundations/content.rs
+++ b/crates/typst/src/foundations/content.rs
@@ -15,7 +15,8 @@ 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, Styles, Synthesize, Value,
+ NativeElement, Recipe, Repr, Selector, Str, Style, StyleChain, Styles, Synthesize,
+ Value,
};
use crate::introspection::{Locatable, Location, Meta, MetaElem};
use crate::layout::{AlignElem, Alignment, Axes, Length, MoveElem, PadElem, Rel, Sides};
@@ -181,13 +182,16 @@ impl Content {
/// This is the preferred way to access fields. However, you can only use it
/// if you have set the field IDs yourself or are using the field IDs
/// generated by the `#[elem]` macro.
- pub fn get(&self, id: u8) -> Option<Value> {
+ pub fn get(&self, id: u8, styles: Option<StyleChain>) -> Option<Value> {
if id == 255 {
if let Some(label) = self.label() {
return Some(label.into_value());
}
}
- self.inner.elem.field(id)
+ match styles {
+ Some(styles) => self.inner.elem.field_with_styles(id, styles),
+ None => self.inner.elem.field(id),
+ }
}
/// Get a field by name.
@@ -201,7 +205,7 @@ impl Content {
}
}
let id = self.elem().field_id(name)?;
- self.get(id)
+ self.get(id, None)
}
/// Get a field by ID, returning a missing field error if it does not exist.
@@ -210,7 +214,7 @@ impl Content {
/// if you have set the field IDs yourself or are using the field IDs
/// generated by the `#[elem]` macro.
pub fn field(&self, id: u8) -> StrResult<Value> {
- self.get(id)
+ self.get(id, None)
.ok_or_else(|| missing_field(self.elem().field_name(id).unwrap()))
}
@@ -400,7 +404,7 @@ impl Content {
pub fn query(&self, selector: Selector) -> Vec<Content> {
let mut results = Vec::new();
self.traverse(&mut |element| {
- if selector.matches(&element) {
+ if selector.matches(&element, None) {
results.push(element);
}
});
@@ -414,7 +418,7 @@ impl Content {
pub fn query_first(&self, selector: Selector) -> Option<Content> {
let mut result = None;
self.traverse(&mut |element| {
- if result.is_none() && selector.matches(&element) {
+ if result.is_none() && selector.matches(&element, None) {
result = Some(element);
}
});
diff --git a/crates/typst/src/foundations/element.rs b/crates/typst/src/foundations/element.rs
index 24e5cf00..489077e4 100644
--- a/crates/typst/src/foundations/element.rs
+++ b/crates/typst/src/foundations/element.rs
@@ -220,6 +220,9 @@ pub trait Fields {
/// Get the field with the given field ID.
fn field(&self, id: u8) -> Option<Value>;
+ /// Get the field with the given ID in the presence of styles.
+ fn field_with_styles(&self, id: u8, styles: StyleChain) -> Option<Value>;
+
/// Get the fields of the element.
fn fields(&self) -> Dict;
}
diff --git a/crates/typst/src/foundations/selector.rs b/crates/typst/src/foundations/selector.rs
index 2321f317..16bd721d 100644
--- a/crates/typst/src/foundations/selector.rs
+++ b/crates/typst/src/foundations/selector.rs
@@ -7,7 +7,7 @@ use smallvec::SmallVec;
use crate::diag::{bail, StrResult};
use crate::foundations::{
cast, func, repr, scope, ty, CastInfo, Content, Dict, Element, FromValue, Func,
- Label, Reflect, Regex, Repr, Str, Type, Value,
+ Label, Reflect, Regex, Repr, Str, StyleChain, Type, Value,
};
use crate::introspection::{Locatable, Location};
use crate::symbols::Symbol;
@@ -128,23 +128,26 @@ impl Selector {
}
/// Whether the selector matches for the target.
- pub fn matches(&self, target: &Content) -> bool {
- // TODO: optimize field access to not clone.
+ pub fn matches(&self, target: &Content, styles: Option<StyleChain>) -> bool {
match self {
Self::Elem(element, dict) => {
+ // TODO: Optimize field access to not clone.
target.func() == *element
- && dict
- .iter()
- .flat_map(|dict| dict.iter())
- .all(|(id, value)| target.get(*id).as_ref() == Some(value))
+ && dict.iter().flat_map(|dict| dict.iter()).all(|(id, value)| {
+ target.get(*id, styles).as_ref() == Some(value)
+ })
}
Self::Label(label) => target.label() == Some(*label),
Self::Regex(regex) => target
.to_packed::<TextElem>()
.map_or(false, |elem| regex.is_match(elem.text())),
Self::Can(cap) => target.func().can_type_id(*cap),
- Self::Or(selectors) => selectors.iter().any(move |sel| sel.matches(target)),
- Self::And(selectors) => selectors.iter().all(move |sel| sel.matches(target)),
+ Self::Or(selectors) => {
+ selectors.iter().any(move |sel| sel.matches(target, styles))
+ }
+ Self::And(selectors) => {
+ selectors.iter().all(move |sel| sel.matches(target, styles))
+ }
Self::Location(location) => target.location() == Some(*location),
// Not supported here.
Self::Before { .. } | Self::After { .. } => false,
diff --git a/crates/typst/src/foundations/styles.rs b/crates/typst/src/foundations/styles.rs
index bac92887..6946d076 100644
--- a/crates/typst/src/foundations/styles.rs
+++ b/crates/typst/src/foundations/styles.rs
@@ -369,10 +369,10 @@ impl Recipe {
}
/// Whether the recipe is applicable to the target.
- pub fn applicable(&self, target: &Content) -> bool {
+ pub fn applicable(&self, target: &Content, styles: StyleChain) -> bool {
self.selector
.as_ref()
- .map_or(false, |selector| selector.matches(target))
+ .map_or(false, |selector| selector.matches(target, Some(styles)))
}
/// Apply the recipe to the given content.
diff --git a/crates/typst/src/introspection/introspector.rs b/crates/typst/src/introspection/introspector.rs
index 75882edf..f8270025 100644
--- a/crates/typst/src/introspection/introspector.rs
+++ b/crates/typst/src/introspection/introspector.rs
@@ -127,9 +127,11 @@ impl Introspector {
indices.iter().map(|&index| self.elems[index].0.clone()).collect()
})
.unwrap_or_default(),
- Selector::Elem(..) | Selector::Regex(_) | Selector::Can(_) => {
- self.all().filter(|elem| selector.matches(elem)).cloned().collect()
- }
+ Selector::Elem(..) | Selector::Regex(_) | Selector::Can(_) => self
+ .all()
+ .filter(|elem| selector.matches(elem, None))
+ .cloned()
+ .collect(),
Selector::Location(location) => {
self.get(location).cloned().into_iter().collect()
}
diff --git a/crates/typst/src/realize/mod.rs b/crates/typst/src/realize/mod.rs
index cd40ac86..f5555597 100644
--- a/crates/typst/src/realize/mod.rs
+++ b/crates/typst/src/realize/mod.rs
@@ -80,7 +80,7 @@ pub fn applicable(target: &Content, styles: StyleChain) -> bool {
// Find out whether any recipe matches and is unguarded.
for recipe in styles.recipes() {
- if !target.is_guarded(Guard(n)) && recipe.applicable(target) {
+ if !target.is_guarded(Guard(n)) && recipe.applicable(target, styles) {
return true;
}
n -= 1;
@@ -133,7 +133,7 @@ pub fn realize(
// Find an applicable show rule recipe.
for recipe in styles.recipes() {
let guard = Guard(n);
- if !target.is_guarded(guard) && recipe.applicable(target) {
+ if !target.is_guarded(guard) && recipe.applicable(target, styles) {
if let Some(content) = try_apply(engine, target, recipe, guard)? {
return Ok(Some(content));
}
diff --git a/tests/ref/compiler/select-where-styles.png b/tests/ref/compiler/select-where-styles.png
new file mode 100644
index 00000000..ffdc4bab
--- /dev/null
+++ b/tests/ref/compiler/select-where-styles.png
Binary files differ
diff --git a/tests/typ/compiler/select-where-styles.typ b/tests/typ/compiler/select-where-styles.typ
new file mode 100644
index 00000000..028be2e9
--- /dev/null
+++ b/tests/typ/compiler/select-where-styles.typ
@@ -0,0 +1,91 @@
+// Test that where selectors also work with settable fields.
+
+---
+// Test that where selectors also trigger on set rule fields.
+#show raw.where(block: false): box.with(
+ fill: luma(220),
+ inset: (x: 3pt, y: 0pt),
+ outset: (y: 3pt),
+ radius: 2pt,
+)
+
+This is #raw("fn main() {}") some text.
+
+---
+// Note: This show rule is horribly inefficient because it triggers for
+// every individual text element. But it should still work.
+#show text.where(lang: "de"): set text(red)
+
+#set text(lang: "es")
+Hola, mundo!
+
+#set text(lang: "de")
+Hallo Welt!
+
+#set text(lang: "en")
+Hello World!
+
+---
+// Test that folding is taken into account.
+#set text(5pt)
+#set text(2em)
+
+#[
+ #show text.where(size: 2em): set text(blue)
+ 2em not blue
+]
+
+#[
+ #show text.where(size: 10pt): set text(blue)
+ 10pt blue
+]
+
+---
+// Test again that folding is taken into account.
+#set rect(width: 40pt, height: 10pt)
+#set rect(stroke: blue)
+#set rect(stroke: 2pt)
+
+#{
+ show rect.where(stroke: blue): "Not Triggered"
+ rect()
+}
+#{
+ show rect.where(stroke: 2pt): "Not Triggered"
+ rect()
+}
+#{
+ show rect.where(stroke: 2pt + blue): "Triggered"
+ rect()
+}
+
+---
+// Test that resolving is *not* taken into account.
+#set line(start: (1em, 1em + 2pt))
+
+#{
+ show line.where(start: (1em, 1em + 2pt)): "Triggered"
+ line()
+}
+#{
+ show line.where(start: (10pt, 12pt)): "Not Triggered"
+ line()
+}
+
+
+---
+// Test again that resolving is *not* taken into account.
+#set text(hyphenate: auto)
+
+#[
+ #show text.where(hyphenate: auto): underline
+ Auto
+]
+#[
+ #show text.where(hyphenate: true): underline
+ True
+]
+#[
+ #show text.where(hyphenate: false): underline
+ False
+]