mirror of
https://github.com/typst/typst
synced 2025-05-13 12:36:23 +08:00
Locatability and synthesis improvements
This commit is contained in:
parent
880b1847bd
commit
724e9b140c
@ -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())
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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))))
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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};
|
||||
|
@ -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 {
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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()))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user