Locatability and synthesis improvements

This commit is contained in:
Laurenz 2023-03-13 21:40:57 +01:00
parent 880b1847bd
commit 724e9b140c
14 changed files with 156 additions and 88 deletions

View File

@ -27,7 +27,7 @@ pub fn read(
let Spanned { v: path, span } = path;
let path = vm.locate(&path).at(span)?;
let data = vm.world().file(&path).at(span)?;
let text = String::from_utf8(data.to_vec())
let text = std::str::from_utf8(&data)
.map_err(|_| "file is not valid utf-8")
.at(span)?;
Value::Str(text.into())

View File

@ -23,7 +23,7 @@ use crate::text::TextNode;
///
/// Display: Figure
/// Category: meta
#[node(Synthesize, Show, LocalName)]
#[node(Locatable, Synthesize, Show, LocalName)]
pub struct FigureNode {
/// The content of the figure. Often, an [image]($func/image).
#[required]
@ -57,26 +57,24 @@ impl FigureNode {
}
impl Synthesize for FigureNode {
fn synthesize(&self, vt: &mut Vt, styles: StyleChain) -> Content {
let my_id = vt.identify(self);
fn synthesize(&mut self, vt: &Vt, styles: StyleChain) {
let my_id = self.0.stable_id().unwrap();
let element = self.element();
let numbering = self.numbering(styles);
let mut number = None;
let numbering = self.numbering(styles);
if numbering.is_some() {
number = NonZeroUsize::new(
1 + vt
.locate(Selector::node::<Self>())
.into_iter()
.locate_node::<Self>()
.take_while(|&(id, _)| id != my_id)
.filter(|(_, node)| node.to::<Self>().unwrap().element() == element)
.filter(|(_, figure)| figure.element() == element)
.count(),
);
}
let node = self.clone().with_number(number).with_numbering(numbering).pack();
let meta = Meta::Node(my_id, node.clone());
node.styled(MetaNode::set_data(vec![meta]))
self.push_number(number);
self.push_numbering(numbering);
}
}

View File

@ -40,7 +40,7 @@ use crate::text::{TextNode, TextSize};
///
/// Display: Heading
/// Category: meta
#[node(Synthesize, Show, Finalize, LocalName)]
#[node(Locatable, Synthesize, Show, Finalize, LocalName)]
pub struct HeadingNode {
/// The logical nesting depth of the heading, starting from one.
#[default(NonZeroUsize::new(1).unwrap())]
@ -83,19 +83,16 @@ pub struct HeadingNode {
}
impl Synthesize for HeadingNode {
fn synthesize(&self, vt: &mut Vt, styles: StyleChain) -> Content {
let my_id = vt.identify(self);
fn synthesize(&mut self, vt: &Vt, styles: StyleChain) {
let my_id = self.0.stable_id().unwrap();
let numbering = self.numbering(styles);
let mut counter = HeadingCounter::new();
if numbering.is_some() {
// Advance past existing headings.
for (_, node) in vt
.locate(Selector::node::<Self>())
.into_iter()
.take_while(|&(id, _)| id != my_id)
for (_, heading) in
vt.locate_node::<Self>().take_while(|&(id, _)| id != my_id)
{
let heading = node.to::<Self>().unwrap();
if heading.numbering(StyleChain::default()).is_some() {
counter.advance(heading);
}
@ -105,16 +102,10 @@ impl Synthesize for HeadingNode {
counter.advance(self);
}
let node = self
.clone()
.with_level(self.level(styles))
.with_outlined(self.outlined(styles))
.with_numbers(numbering.is_some().then(|| counter.take()))
.with_numbering(numbering)
.pack();
let meta = Meta::Node(my_id, node.clone());
node.styled(MetaNode::set_data(vec![meta]))
self.push_level(self.level(styles));
self.push_outlined(self.outlined(styles));
self.push_numbers(numbering.is_some().then(|| counter.take()));
self.push_numbering(numbering);
}
}

View File

@ -86,7 +86,7 @@ impl Show for LinkNode {
impl Finalize for LinkNode {
fn finalize(&self, realized: Content, _: StyleChain) -> Content {
realized
.styled(MetaNode::set_data(vec![Meta::Link(self.dest())]))
.linked(self.dest())
.styled(TextNode::set_hyphenate(Hyphenate(Smart::Custom(false))))
}
}

View File

@ -74,15 +74,14 @@ pub struct OutlineNode {
}
impl Synthesize for OutlineNode {
fn synthesize(&self, vt: &mut Vt, _: StyleChain) -> Content {
fn synthesize(&mut self, vt: &Vt, _: StyleChain) {
let headings = vt
.locate(Selector::node::<HeadingNode>())
.into_iter()
.map(|(_, node)| node.to::<HeadingNode>().unwrap().clone())
.locate_node::<HeadingNode>()
.map(|(_, node)| node.clone())
.filter(|node| node.outlined(StyleChain::default()))
.collect();
self.clone().with_headings(headings).pack()
self.push_headings(headings);
}
}

View File

@ -37,9 +37,9 @@ use crate::text::TextNode;
/// Category: meta
#[node(Synthesize, Show)]
pub struct RefNode {
/// The label that should be referenced.
/// The target label that should be referenced.
#[required]
pub label: Label,
pub target: Label,
/// The prefix before the referenced number.
///
@ -59,20 +59,19 @@ pub struct RefNode {
/// ```
pub prefix: Smart<Option<Func>>,
/// All elements with the `target` label in the document.
/// All elements with the target label in the document.
#[synthesized]
pub matches: Vec<Content>,
}
impl Synthesize for RefNode {
fn synthesize(&self, vt: &mut Vt, _: StyleChain) -> Content {
fn synthesize(&mut self, vt: &Vt, _: StyleChain) {
let matches = vt
.locate(Selector::Label(self.label()))
.into_iter()
.locate(Selector::Label(self.target()))
.map(|(_, node)| node.clone())
.collect();
self.clone().with_matches(matches).pack()
self.push_matches(matches);
}
}

View File

@ -22,9 +22,9 @@ pub use typst::eval::{
pub use typst::geom::*;
#[doc(no_inline)]
pub use typst::model::{
node, Construct, Content, Finalize, Fold, Introspector, Label, Node, NodeId, Resolve,
Selector, Set, Show, StabilityProvider, StyleChain, StyleMap, StyleVec, Synthesize,
Unlabellable, Vt,
node, Construct, Content, Finalize, Fold, Introspector, Label, Locatable, Node,
NodeId, Resolve, Selector, Set, Show, StabilityProvider, StableId, StyleChain,
StyleMap, StyleVec, Synthesize, Unlabellable, Vt,
};
#[doc(no_inline)]
pub use typst::syntax::{Span, Spanned};

View File

@ -42,7 +42,7 @@ impl ContentExt for Content {
}
fn linked(self, dest: Destination) -> Self {
self.styled(MetaNode::set_data(vec![Meta::Link(dest.clone())]))
self.styled(MetaNode::set_data(vec![Meta::Link(dest)]))
}
fn aligned(self, aligns: Axes<Option<GenAlign>>) -> Self {

View File

@ -121,8 +121,8 @@ impl RawNode {
}
impl Synthesize for RawNode {
fn synthesize(&self, _: &mut Vt, styles: StyleChain) -> Content {
self.clone().with_lang(self.lang(styles)).pack()
fn synthesize(&mut self, _: &Vt, styles: StyleChain) {
self.push_lang(self.lang(styles));
}
}

View File

@ -34,6 +34,7 @@ struct Field {
ident: Ident,
ident_in: Ident,
with_ident: Ident,
push_ident: Ident,
set_ident: Ident,
ty: syn::Type,
output: syn::Type,
@ -81,6 +82,10 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> {
let required = has_attr(&mut attrs, "required") || variadic;
let positional = has_attr(&mut attrs, "positional") || required;
if ident == "label" {
bail!(ident, "invalid field name");
}
let mut field = Field {
name: kebab_case(&ident),
docs: documentation(&attrs),
@ -100,6 +105,7 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> {
ident: ident.clone(),
ident_in: Ident::new(&format!("{}_in", ident), ident.span()),
with_ident: Ident::new(&format!("with_{}", ident), ident.span()),
push_ident: Ident::new(&format!("push_{}", ident), ident.span()),
set_ident: Ident::new(&format!("set_{}", ident), ident.span()),
ty: field.ty.clone(),
output: field.ty.clone(),
@ -162,17 +168,23 @@ fn create(node: &Node) -> TokenStream {
let new = create_new_func(node);
let field_methods = all.clone().map(create_field_method);
let field_in_methods = settable.clone().map(create_field_in_method);
let with_fields_methods = all.map(create_with_field_method);
let with_field_methods = all.clone().map(create_with_field_method);
let push_field_methods = all.map(create_push_field_method);
let field_style_methods = settable.map(create_set_field_method);
// Trait implementations.
let construct = node
let node_impl = create_node_impl(node);
let construct_impl = node
.capable
.iter()
.all(|capability| capability != "Construct")
.then(|| create_construct_impl(node));
let set = create_set_impl(node);
let node = create_node_impl(node);
let set_impl = create_set_impl(node);
let locatable_impl = node
.capable
.iter()
.any(|capability| capability == "Locatable")
.then(|| quote! { impl ::typst::model::Locatable for #ident {} });
quote! {
#[doc = #docs]
@ -184,7 +196,8 @@ fn create(node: &Node) -> TokenStream {
#new
#(#field_methods)*
#(#field_in_methods)*
#(#with_fields_methods)*
#(#with_field_methods)*
#(#push_field_methods)*
#(#field_style_methods)*
/// The node's span.
@ -193,9 +206,10 @@ fn create(node: &Node) -> TokenStream {
}
}
#node
#construct
#set
#node_impl
#construct_impl
#set_impl
#locatable_impl
impl From<#ident> for ::typst::eval::Value {
fn from(value: #ident) -> Self {
@ -232,6 +246,7 @@ fn create_field_method(field: &Field) -> TokenStream {
if field.inherent() || field.synthesized {
quote! {
#[doc = #docs]
#[track_caller]
#vis fn #ident(&self) -> #output {
self.0.expect_field(#name)
}
@ -293,6 +308,18 @@ fn create_with_field_method(field: &Field) -> TokenStream {
}
}
/// Create a set-style method for a field.
fn create_push_field_method(field: &Field) -> TokenStream {
let Field { vis, ident, push_ident, name, ty, .. } = field;
let doc = format!("Push the [`{}`](Self::{}) field.", name, ident);
quote! {
#[doc = #doc]
#vis fn #push_ident(&mut self, #ident: #ty) {
self.0.push_field(#name, #ident);
}
}
}
/// Create a setter method for a field.
fn create_set_field_method(field: &Field) -> TokenStream {
let Field { vis, ident, set_ident, name, ty, .. } = field;

View File

@ -8,7 +8,7 @@ use comemo::Tracked;
use ecow::{eco_format, EcoString, EcoVec};
use once_cell::sync::Lazy;
use super::{node, Guard, Recipe, Style, StyleMap};
use super::{node, Guard, Locatable, Recipe, StableId, Style, StyleMap, Synthesize};
use crate::diag::{SourceResult, StrResult};
use crate::eval::{
cast_from_value, cast_to_value, Args, Cast, Func, FuncInfo, Str, Value, Vm,
@ -29,8 +29,9 @@ pub struct Content {
/// Modifiers that can be attached to content.
#[derive(Debug, Clone, PartialEq, Hash)]
enum Modifier {
Synthesized,
Prepared,
Guard(Guard),
Id(StableId),
}
impl Content {
@ -101,6 +102,16 @@ impl Content {
Some(unsafe { &*crate::util::fat::from_raw_parts(data, vtable) })
}
/// Cast to a trait object if this content has the given capability.
pub fn with_mut<C>(&mut self) -> Option<&mut C>
where
C: ?Sized + 'static,
{
let vtable = (self.id.0.vtable)(TypeId::of::<C>())?;
let data = self as *mut Self as *mut ();
Some(unsafe { &mut *crate::util::fat::from_raw_parts_mut(data, vtable) })
}
/// The node's span.
pub fn span(&self) -> Span {
self.span
@ -233,17 +244,6 @@ impl Content {
self
}
/// Mark this content as prepared.
pub fn synthesized(mut self) -> Self {
self.modifiers.push(Modifier::Synthesized);
self
}
/// Whether this node was prepared.
pub fn is_synthesized(&self) -> bool {
self.modifiers.contains(&Modifier::Synthesized)
}
/// Whether no show rule was executed for this node so far.
pub(super) fn is_pristine(&self) -> bool {
!self
@ -257,6 +257,37 @@ impl Content {
self.modifiers.contains(&Modifier::Guard(id))
}
/// Whether this node was prepared.
pub fn is_prepared(&self) -> bool {
self.modifiers.contains(&Modifier::Prepared)
}
/// Whether the node needs to be realized specially.
pub fn needs_preparation(&self) -> bool {
(self.can::<dyn Locatable>()
|| self.can::<dyn Synthesize>()
|| self.label().is_some())
&& !self.is_prepared()
}
/// Mark this content as prepared.
pub fn mark_prepared(&mut self) {
self.modifiers.push(Modifier::Prepared);
}
/// Attach a stable id to this content.
pub fn set_stable_id(&mut self, id: StableId) {
self.modifiers.push(Modifier::Id(id));
}
/// This content's stable identifier.
pub fn stable_id(&self) -> Option<StableId> {
self.modifiers.iter().find_map(|modifier| match modifier {
Modifier::Id(id) => Some(*id),
_ => None,
})
}
/// Copy the modifiers from another piece of content.
pub(super) fn copy_modifiers(&mut self, from: &Content) {
self.span = from.span;

View File

@ -1,9 +1,10 @@
use super::{Content, NodeId, Recipe, Selector, StyleChain, Vt};
use crate::diag::SourceResult;
use crate::doc::{Meta, MetaNode};
/// Whether the target is affected by show rules in the given style chain.
pub fn applicable(target: &Content, styles: StyleChain) -> bool {
if target.can::<dyn Synthesize>() && !target.is_synthesized() {
if target.needs_preparation() {
return true;
}
@ -31,21 +32,31 @@ pub fn realize(
target: &Content,
styles: StyleChain,
) -> SourceResult<Option<Content>> {
// Pre-process.
if target.needs_preparation() {
let mut node = target.clone();
if target.can::<dyn Locatable>() || target.label().is_some() {
let id = vt.identify(target);
node.set_stable_id(id);
}
if let Some(node) = node.with_mut::<dyn Synthesize>() {
node.synthesize(vt, styles);
}
node.mark_prepared();
if let Some(id) = node.stable_id() {
let meta = Meta::Node(id, node.clone());
return Ok(Some(node.styled(MetaNode::set_data(vec![meta]))));
}
return Ok(Some(node));
}
// Find out how many recipes there are.
let mut n = styles.recipes().count();
// Synthesize if not already happened for this node.
if target.can::<dyn Synthesize>() && !target.is_synthesized() {
return Ok(Some(
target
.clone()
.synthesized()
.with::<dyn Synthesize>()
.unwrap()
.synthesize(vt, styles),
));
}
// Find an applicable recipe.
let mut realized = None;
for recipe in styles.recipes() {
@ -144,10 +155,13 @@ fn try_apply(
}
}
/// Makes this node locatable through `vt.locate`.
pub trait Locatable {}
/// Synthesize fields on a node. This happens before execution of any show rule.
pub trait Synthesize {
/// Prepare the node for show rule application.
fn synthesize(&self, vt: &mut Vt, styles: StyleChain) -> Content;
fn synthesize(&mut self, vt: &Vt, styles: StyleChain);
}
/// The base recipe for a node.

View File

@ -5,7 +5,7 @@ use std::num::NonZeroUsize;
use comemo::{Track, Tracked, TrackedMut};
use super::{Content, Selector, StyleChain};
use super::{Content, Node, Selector, StyleChain};
use crate::diag::SourceResult;
use crate::doc::{Document, Element, Frame, Location, Meta};
use crate::geom::Transform;
@ -83,8 +83,17 @@ impl<'a> Vt<'a> {
}
/// Locate all metadata matches for the given selector.
pub fn locate(&self, selector: Selector) -> Vec<(StableId, &Content)> {
self.introspector.locate(selector)
pub fn locate(
&self,
selector: Selector,
) -> impl Iterator<Item = (StableId, &Content)> {
self.introspector.locate(selector).into_iter()
}
/// Locate all metadata matches for the given node.
pub fn locate_node<T: Node>(&self) -> impl Iterator<Item = (StableId, &T)> {
self.locate(Selector::node::<T>())
.map(|(id, content)| (id, content.to::<T>().unwrap()))
}
}

View File

@ -51,8 +51,8 @@ Hello *#x*
---
// Test conditional set.
#show ref: it => {
set text(red) if it.label == <unknown>
"@" + str(it.label)
set text(red) if it.target == <unknown>
"@" + str(it.target)
}
@hello from the @unknown