Introspection

This commit is contained in:
Laurenz 2022-12-02 15:41:39 +01:00
parent 5110a41de1
commit 9bc90c371f
24 changed files with 385 additions and 112 deletions

View File

@ -216,7 +216,7 @@ pub const ENUM: ListKind = 1;
pub const DESC: ListKind = 2; pub const DESC: ListKind = 2;
/// How to label a list or enumeration. /// How to label a list or enumeration.
#[derive(Debug, Clone, PartialEq, Hash)] #[derive(Debug, Clone, Hash)]
pub enum Label { pub enum Label {
/// The default labelling. /// The default labelling.
Default, Default,

View File

@ -59,9 +59,11 @@ impl LayoutRoot for Content {
fn cached( fn cached(
node: &Content, node: &Content,
world: comemo::Tracked<dyn World>, world: comemo::Tracked<dyn World>,
provider: TrackedMut<StabilityProvider>,
introspector: Tracked<Introspector>,
styles: StyleChain, styles: StyleChain,
) -> SourceResult<Document> { ) -> SourceResult<Document> {
let mut vt = Vt { world }; let mut vt = Vt { world, provider, introspector };
let scratch = Scratch::default(); let scratch = Scratch::default();
let (realized, styles) = realize_root(&mut vt, &scratch, node, styles)?; let (realized, styles) = realize_root(&mut vt, &scratch, node, styles)?;
realized realized
@ -70,7 +72,13 @@ impl LayoutRoot for Content {
.layout_root(&mut vt, styles) .layout_root(&mut vt, styles)
} }
cached(self, vt.world, styles) cached(
self,
vt.world,
TrackedMut::reborrow_mut(&mut vt.provider),
vt.introspector,
styles,
)
} }
} }
@ -97,10 +105,12 @@ impl Layout for Content {
fn cached( fn cached(
node: &Content, node: &Content,
world: comemo::Tracked<dyn World>, world: comemo::Tracked<dyn World>,
provider: TrackedMut<StabilityProvider>,
introspector: Tracked<Introspector>,
styles: StyleChain, styles: StyleChain,
regions: &Regions, regions: &Regions,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
let mut vt = Vt { world }; let mut vt = Vt { world, provider, introspector };
let scratch = Scratch::default(); let scratch = Scratch::default();
let (realized, styles) = realize_block(&mut vt, &scratch, node, styles)?; let (realized, styles) = realize_block(&mut vt, &scratch, node, styles)?;
let barrier = Style::Barrier(realized.id()); let barrier = Style::Barrier(realized.id());
@ -111,7 +121,14 @@ impl Layout for Content {
.layout(&mut vt, styles, regions) .layout(&mut vt, styles, regions)
} }
cached(self, vt.world, styles, regions) cached(
self,
vt.world,
TrackedMut::reborrow_mut(&mut vt.provider),
vt.introspector,
styles,
regions,
)
} }
} }
@ -290,6 +307,15 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
content: &'a Content, content: &'a Content,
styles: StyleChain<'a>, styles: StyleChain<'a>,
) -> SourceResult<()> { ) -> SourceResult<()> {
// Prepare only if this is the first application for this node.
if let Some(node) = content.with::<dyn Prepare>() {
if !content.is_prepared() {
let prepared = node.prepare(self.vt, content.clone().prepared(), styles);
let stored = self.scratch.content.alloc(prepared);
return self.accept(stored, styles);
}
}
if let Some(styled) = content.to::<StyledNode>() { if let Some(styled) = content.to::<StyledNode>() {
return self.styled(styled, styles); return self.styled(styled, styles);
} }

View File

@ -5,7 +5,7 @@ use crate::prelude::*;
use crate::text::TextNode; use crate::text::TextNode;
/// Layouts its child onto one or multiple pages. /// Layouts its child onto one or multiple pages.
#[derive(PartialEq, Clone, Hash)] #[derive(Clone, Hash)]
pub struct PageNode(pub Content); pub struct PageNode(pub Content);
#[node] #[node]
@ -157,7 +157,7 @@ impl PagebreakNode {
} }
/// A header, footer, foreground or background definition. /// A header, footer, foreground or background definition.
#[derive(Debug, Clone, PartialEq, Hash)] #[derive(Debug, Clone, Hash)]
pub enum Marginal { pub enum Marginal {
/// Nothing, /// Nothing,
None, None,

View File

@ -57,13 +57,15 @@ impl ParNode {
fn cached( fn cached(
par: &ParNode, par: &ParNode,
world: Tracked<dyn World>, world: Tracked<dyn World>,
provider: TrackedMut<StabilityProvider>,
introspector: Tracked<Introspector>,
styles: StyleChain, styles: StyleChain,
consecutive: bool, consecutive: bool,
width: Abs, width: Abs,
base: Size, base: Size,
expand: bool, expand: bool,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
let mut vt = Vt { world }; let mut vt = Vt { world, provider, introspector };
// Collect all text into one string for BiDi analysis. // Collect all text into one string for BiDi analysis.
let (text, segments) = collect(par, &styles, consecutive); let (text, segments) = collect(par, &styles, consecutive);
@ -80,7 +82,17 @@ impl ParNode {
finalize(&mut vt, &p, &lines, width, base, expand) finalize(&mut vt, &p, &lines, width, base, expand)
} }
cached(self, vt.world, styles, consecutive, width, base, expand) cached(
self,
vt.world,
TrackedMut::reborrow_mut(&mut vt.provider),
vt.introspector,
styles,
consecutive,
width,
base,
expand,
)
} }
} }

View File

@ -27,11 +27,9 @@ impl LayoutRoot for DocumentNode {
} }
Ok(Document { Ok(Document {
metadata: Metadata {
title: styles.get(Self::TITLE).clone(),
author: styles.get(Self::AUTHOR).clone(),
},
pages, pages,
title: styles.get(Self::TITLE).clone(),
author: styles.get(Self::AUTHOR).clone(),
}) })
} }
} }

View File

@ -25,10 +25,6 @@ impl LinkNode {
#[node(Show, Finalize)] #[node(Show, Finalize)]
impl LinkNode { impl LinkNode {
/// A destination the text should be linked to.
#[property(skip, referenced)]
pub(crate) const DEST: Option<Destination> = None;
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
let dest = args.expect::<Destination>("destination")?; let dest = args.expect::<Destination>("destination")?;
Ok(match dest { Ok(match dest {
@ -61,6 +57,6 @@ impl Show for LinkNode {
impl Finalize for LinkNode { impl Finalize for LinkNode {
fn finalize(&self, realized: Content) -> Content { fn finalize(&self, realized: Content) -> Content {
realized.styled(Self::DEST, Some(self.dest.clone())) realized.styled(Meta::DATA, vec![Meta::Link(self.dest.clone())])
} }
} }

View File

@ -16,8 +16,9 @@ pub use typst::geom::*;
#[doc(no_inline)] #[doc(no_inline)]
pub use typst::model::{ pub use typst::model::{
array, capability, castable, dict, dynamic, format_str, node, Args, Array, Cast, array, capability, castable, dict, dynamic, format_str, node, Args, Array, Cast,
Content, Dict, Finalize, Fold, Func, Label, Node, NodeId, Resolve, Selector, Show, Content, Dict, Finalize, Fold, Func, Introspector, Label, Node, NodeId, Prepare,
Smart, Str, StyleChain, StyleMap, StyleVec, Unlabellable, Value, Vm, Vt, Resolve, Selector, Show, Smart, StabilityProvider, Str, StyleChain, StyleMap,
StyleVec, Unlabellable, Value, Vm, Vt,
}; };
#[doc(no_inline)] #[doc(no_inline)]
pub use typst::syntax::{Span, Spanned}; pub use typst::syntax::{Span, Spanned};

View File

@ -13,6 +13,9 @@ pub trait ContentExt {
/// Underline this content. /// Underline this content.
fn underlined(self) -> Self; fn underlined(self) -> Self;
/// Link the content to a destination.
fn linked(self, dest: Destination) -> Self;
/// Force a size for this content. /// Force a size for this content.
fn boxed(self, sizing: Axes<Option<Rel<Length>>>) -> Self; fn boxed(self, sizing: Axes<Option<Rel<Length>>>) -> Self;
@ -45,6 +48,10 @@ impl ContentExt for Content {
crate::text::DecoNode::<{ crate::text::UNDERLINE }>(self).pack() crate::text::DecoNode::<{ crate::text::UNDERLINE }>(self).pack()
} }
fn linked(self, dest: Destination) -> Self {
self.styled(Meta::DATA, vec![Meta::Link(dest.clone())])
}
fn boxed(self, sizing: Axes<Option<Rel<Length>>>) -> Self { fn boxed(self, sizing: Axes<Option<Rel<Length>>>) -> Self {
crate::layout::BoxNode { sizing, child: self }.pack() crate::layout::BoxNode { sizing, child: self }.pack()
} }

View File

@ -42,6 +42,19 @@ impl RawNode {
} }
} }
impl Prepare for RawNode {
fn prepare(&self, _: &mut Vt, mut this: Content, styles: StyleChain) -> Content {
this.push_field(
"lang",
match styles.get(Self::LANG) {
Some(lang) => Value::Str(lang.clone().into()),
None => Value::None,
},
);
this
}
}
impl Show for RawNode { impl Show for RawNode {
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> Content { fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> Content {
let lang = styles.get(Self::LANG).as_ref().map(|s| s.to_lowercase()); let lang = styles.get(Self::LANG).as_ref().map(|s| s.to_lowercase());

View File

@ -6,7 +6,6 @@ use typst::font::{Font, FontVariant};
use typst::util::SliceExt; use typst::util::SliceExt;
use super::*; use super::*;
use crate::meta::LinkNode;
use crate::prelude::*; use crate::prelude::*;
/// The result of shaping text. /// The result of shaping text.
@ -93,7 +92,6 @@ impl<'a> ShapedText<'a> {
let lang = self.styles.get(TextNode::LANG); let lang = self.styles.get(TextNode::LANG);
let decos = self.styles.get(TextNode::DECO); let decos = self.styles.get(TextNode::DECO);
let fill = self.styles.get(TextNode::FILL); let fill = self.styles.get(TextNode::FILL);
let link = self.styles.get(LinkNode::DEST);
for ((font, y_offset), group) in for ((font, y_offset), group) in
self.glyphs.as_ref().group_by_key(|g| (g.font.clone(), g.y_offset)) self.glyphs.as_ref().group_by_key(|g| (g.font.clone(), g.y_offset))
@ -128,10 +126,8 @@ impl<'a> ShapedText<'a> {
offset += width; offset += width;
} }
// Apply link if it exists. // Apply metadata.
if let Some(dest) = link { frame.meta(self.styles);
frame.link(dest.clone());
}
frame frame
} }

View File

@ -2,7 +2,6 @@ use std::ffi::OsStr;
use typst::image::{Image, ImageFormat, RasterFormat, VectorFormat}; use typst::image::{Image, ImageFormat, RasterFormat, VectorFormat};
use crate::meta::LinkNode;
use crate::prelude::*; use crate::prelude::*;
/// Show a raster or vector graphic. /// Show a raster or vector graphic.
@ -89,10 +88,8 @@ impl Layout for ImageNode {
frame.clip(); frame.clip();
} }
// Apply link if it exists. // Apply metadata.
if let Some(url) = styles.get(LinkNode::DEST) { frame.meta(styles);
frame.link(url.clone());
}
Ok(Fragment::frame(frame)) Ok(Fragment::frame(frame))
} }

View File

@ -1,6 +1,5 @@
use std::f64::consts::SQRT_2; use std::f64::consts::SQRT_2;
use crate::meta::LinkNode;
use crate::prelude::*; use crate::prelude::*;
/// A sizable and fillable shape with optional content. /// A sizable and fillable shape with optional content.
@ -160,10 +159,8 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> {
} }
} }
// Apply link if it exists. // Apply metadata.
if let Some(url) = styles.get(LinkNode::DEST) { frame.meta(styles);
frame.link(url.clone());
}
Ok(Fragment::frame(frame)) Ok(Fragment::frame(frame))
} }

View File

@ -10,21 +10,14 @@ use crate::geom::{
Abs, Align, Axes, Dir, Em, Numeric, Paint, Point, Shape, Size, Transform, Abs, Align, Axes, Dir, Em, Numeric, Paint, Point, Shape, Size, Transform,
}; };
use crate::image::Image; use crate::image::Image;
use crate::model::{dict, Dict, Value}; use crate::model::{dict, node, Content, Dict, Fold, StableId, StyleChain, Value};
use crate::util::EcoString; use crate::util::EcoString;
/// A finished document with metadata and page frames. /// A finished document with metadata and page frames.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Default, Clone)]
pub struct Document { pub struct Document {
/// The document's metadata.
pub metadata: Metadata,
/// The page frames. /// The page frames.
pub pages: Vec<Frame>, pub pages: Vec<Frame>,
}
/// Document metadata.
#[derive(Debug, Default, Clone, Eq, PartialEq)]
pub struct Metadata {
/// The document's title. /// The document's title.
pub title: Option<EcoString>, pub title: Option<EcoString>,
/// The document's author. /// The document's author.
@ -32,7 +25,7 @@ pub struct Metadata {
} }
/// A partial layout result. /// A partial layout result.
#[derive(Clone, Eq, PartialEq)] #[derive(Clone)]
pub struct Fragment(Vec<Frame>); pub struct Fragment(Vec<Frame>);
impl Fragment { impl Fragment {
@ -113,7 +106,7 @@ impl<'a> IntoIterator for &'a mut Fragment {
} }
/// A finished layout with elements at fixed positions. /// A finished layout with elements at fixed positions.
#[derive(Default, Clone, Eq, PartialEq)] #[derive(Default, Clone)]
pub struct Frame { pub struct Frame {
/// The size of the frame. /// The size of the frame.
size: Size, size: Size,
@ -336,9 +329,11 @@ impl Frame {
} }
} }
/// Link the whole frame to a resource. /// Attach the metadata from this style chain to the frame.
pub fn link(&mut self, dest: Destination) { pub fn meta(&mut self, styles: StyleChain) {
self.push(Point::zero(), Element::Link(dest, self.size)); for meta in styles.get(Meta::DATA) {
self.push(Point::zero(), Element::Meta(meta, self.size));
}
} }
/// Arbitrarily transform the contents of the frame. /// Arbitrarily transform the contents of the frame.
@ -374,7 +369,7 @@ impl Debug for Frame {
} }
/// The building block frames are composed of. /// The building block frames are composed of.
#[derive(Clone, Eq, PartialEq)] #[derive(Clone)]
pub enum Element { pub enum Element {
/// A group of elements. /// A group of elements.
Group(Group), Group(Group),
@ -384,8 +379,8 @@ pub enum Element {
Shape(Shape), Shape(Shape),
/// An image and its size. /// An image and its size.
Image(Image, Size), Image(Image, Size),
/// A link to an external resource and its trigger region. /// Meta information and the region it applies to.
Link(Destination, Size), Meta(Meta, Size),
} }
impl Debug for Element { impl Debug for Element {
@ -395,13 +390,13 @@ impl Debug for Element {
Self::Text(text) => write!(f, "{text:?}"), Self::Text(text) => write!(f, "{text:?}"),
Self::Shape(shape) => write!(f, "{shape:?}"), Self::Shape(shape) => write!(f, "{shape:?}"),
Self::Image(image, _) => write!(f, "{image:?}"), Self::Image(image, _) => write!(f, "{image:?}"),
Self::Link(dest, _) => write!(f, "Link({dest:?})"), Self::Meta(meta, _) => write!(f, "{meta:?}"),
} }
} }
} }
/// A group of elements with optional clipping. /// A group of elements with optional clipping.
#[derive(Clone, Eq, PartialEq)] #[derive(Clone)]
pub struct Group { pub struct Group {
/// The group's frame. /// The group's frame.
pub frame: Frame, pub frame: Frame,
@ -543,6 +538,33 @@ impl FromStr for Region {
} }
} }
/// Meta information that isn't visible or renderable.
#[derive(Debug, Clone, Hash)]
pub enum Meta {
/// An internal or external link.
Link(Destination),
/// An identifiable piece of content that produces something within the
/// area this metadata is attached to.
Node(StableId, Content),
}
#[node]
impl Meta {
/// Metadata that should be attached to all elements affected by this style
/// property.
#[property(fold, skip)]
pub const DATA: Vec<Meta> = vec![];
}
impl Fold for Vec<Meta> {
type Output = Self;
fn fold(mut self, outer: Self::Output) -> Self::Output {
self.extend(outer);
self
}
}
/// A link destination. /// A link destination.
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum Destination { pub enum Destination {

View File

@ -14,7 +14,7 @@ use pdf_writer::{Finish, Name, PdfWriter, Ref, TextStr};
use self::outline::HeadingNode; use self::outline::HeadingNode;
use self::page::Page; use self::page::Page;
use crate::doc::{Document, Lang, Metadata}; use crate::doc::{Document, Lang};
use crate::font::Font; use crate::font::Font;
use crate::geom::{Abs, Dir, Em}; use crate::geom::{Abs, Dir, Em};
use crate::image::Image; use crate::image::Image;
@ -23,7 +23,7 @@ use crate::image::Image;
/// ///
/// Returns the raw bytes making up the PDF file. /// Returns the raw bytes making up the PDF file.
pub fn pdf(document: &Document) -> Vec<u8> { pub fn pdf(document: &Document) -> Vec<u8> {
let mut ctx = PdfContext::new(&document.metadata); let mut ctx = PdfContext::new(document);
page::construct_pages(&mut ctx, &document.pages); page::construct_pages(&mut ctx, &document.pages);
font::write_fonts(&mut ctx); font::write_fonts(&mut ctx);
image::write_images(&mut ctx); image::write_images(&mut ctx);
@ -38,7 +38,7 @@ const D65_GRAY: Name<'static> = Name(b"d65gray");
/// Context for exporting a whole PDF document. /// Context for exporting a whole PDF document.
pub struct PdfContext<'a> { pub struct PdfContext<'a> {
metadata: &'a Metadata, document: &'a Document,
writer: PdfWriter, writer: PdfWriter,
pages: Vec<Page>, pages: Vec<Page>,
page_heights: Vec<f32>, page_heights: Vec<f32>,
@ -55,11 +55,11 @@ pub struct PdfContext<'a> {
} }
impl<'a> PdfContext<'a> { impl<'a> PdfContext<'a> {
fn new(metadata: &'a Metadata) -> Self { fn new(document: &'a Document) -> Self {
let mut alloc = Ref::new(1); let mut alloc = Ref::new(1);
let page_tree_ref = alloc.bump(); let page_tree_ref = alloc.bump();
Self { Self {
metadata, document,
writer: PdfWriter::new(), writer: PdfWriter::new(),
pages: vec![], pages: vec![],
page_heights: vec![], page_heights: vec![],
@ -116,10 +116,10 @@ fn write_catalog(ctx: &mut PdfContext) {
// Write the document information. // Write the document information.
let mut info = ctx.writer.document_info(ctx.alloc.bump()); let mut info = ctx.writer.document_info(ctx.alloc.bump());
if let Some(title) = &ctx.metadata.title { if let Some(title) = &ctx.document.title {
info.title(TextStr(title)); info.title(TextStr(title));
} }
if let Some(author) = &ctx.metadata.author { if let Some(author) = &ctx.document.author {
info.author(TextStr(author)); info.author(TextStr(author));
} }
info.creator(TextStr("Typst")); info.creator(TextStr("Typst"));

View File

@ -3,7 +3,7 @@ use pdf_writer::writers::ColorSpace;
use pdf_writer::{Content, Filter, Finish, Name, Rect, Ref, Str}; use pdf_writer::{Content, Filter, Finish, Name, Rect, Ref, Str};
use super::{deflate, AbsExt, EmExt, PdfContext, RefExt, D65_GRAY, SRGB}; use super::{deflate, AbsExt, EmExt, PdfContext, RefExt, D65_GRAY, SRGB};
use crate::doc::{Destination, Element, Frame, Group, Text}; use crate::doc::{Destination, Element, Frame, Group, Meta, Text};
use crate::font::Font; use crate::font::Font;
use crate::geom::{ use crate::geom::{
self, Abs, Color, Em, Geometry, Numeric, Paint, Point, Ratio, Shape, Size, Stroke, self, Abs, Color, Em, Geometry, Numeric, Paint, Point, Ratio, Shape, Size, Stroke,
@ -287,7 +287,10 @@ fn write_frame(ctx: &mut PageContext, frame: &Frame) {
Element::Text(text) => write_text(ctx, x, y, text), Element::Text(text) => write_text(ctx, x, y, text),
Element::Shape(shape) => write_shape(ctx, x, y, shape), Element::Shape(shape) => write_shape(ctx, x, y, shape),
Element::Image(image, size) => write_image(ctx, x, y, image, *size), Element::Image(image, size) => write_image(ctx, x, y, image, *size),
Element::Link(dest, size) => write_link(ctx, pos, dest, *size), Element::Meta(meta, size) => match meta {
Meta::Link(dest) => write_link(ctx, pos, dest, *size),
_ => {}
},
} }
} }
} }

View File

@ -57,7 +57,7 @@ fn render_frame(
Element::Image(image, size) => { Element::Image(image, size) => {
render_image(canvas, ts, mask, image, *size); render_image(canvas, ts, mask, image, *size);
} }
Element::Link(_, _) => {} Element::Meta(_, _) => {}
} }
} }
} }

View File

@ -255,17 +255,23 @@ castable! {
} }
castable! { castable! {
Destination, Location,
Expected: "string or dictionary with `page`, `x`, and `y` keys", Expected: "dictionary with `page`, `x`, and `y` keys",
Value::Str(string) => Self::Url(string.into()),
Value::Dict(dict) => { Value::Dict(dict) => {
let page = dict.get("page")?.clone().cast()?; let page = dict.get("page")?.clone().cast()?;
let x: Length = dict.get("x")?.clone().cast()?; let x: Length = dict.get("x")?.clone().cast()?;
let y: Length = dict.get("y")?.clone().cast()?; let y: Length = dict.get("y")?.clone().cast()?;
Self::Internal(Location { page, pos: Point::new(x.abs, y.abs) }) Self { page, pos: Point::new(x.abs, y.abs) }
}, },
} }
castable! {
Destination,
Expected: "string or dictionary with `page`, `x`, and `y` keys",
Value::Str(string) => Self::Url(string.into()),
v @ Value::Dict(_) => Self::Internal(v.cast()?),
}
castable! { castable! {
FontStyle, FontStyle,
Expected: "string", Expected: "string",

View File

@ -21,14 +21,16 @@ use crate::World;
pub struct Content { pub struct Content {
obj: Arc<dyn Bounds>, obj: Arc<dyn Bounds>,
span: Option<Span>, span: Option<Span>,
meta: ThinVec<Meta>, modifiers: ThinVec<Modifier>,
} }
/// Metadata that can be attached to content. /// Modifiers that can be attached to content.
#[derive(Debug, Clone, PartialEq, Hash)] #[derive(Debug, Clone, PartialEq, Hash)]
enum Meta { enum Modifier {
Prepared,
Guard(Guard), Guard(Guard),
Label(Label), Label(Label),
Field(EcoString, Value),
} }
impl Content { impl Content {
@ -62,17 +64,22 @@ impl Content {
/// Attach a label to the content. /// Attach a label to the content.
pub fn labelled(mut self, label: Label) -> Self { pub fn labelled(mut self, label: Label) -> Self {
for meta in &mut self.meta { for modifier in &mut self.modifiers {
if let Meta::Label(prev) = meta { if let Modifier::Label(prev) = modifier {
*prev = label; *prev = label;
return self; return self;
} }
} }
self.meta.push(Meta::Label(label)); self.modifiers.push(Modifier::Label(label));
self self
} }
/// Attach a field to the content.
pub fn push_field(&mut self, name: impl Into<EcoString>, value: Value) {
self.modifiers.push(Modifier::Field(name.into(), value));
}
/// Style this content with a single style property. /// Style this content with a single style property.
pub fn styled<K: Key>(self, key: K, value: K::Value) -> Self { pub fn styled<K: Key>(self, key: K, value: K::Value) -> Self {
self.styled_with_entry(Style::Property(Property::new(key, value))) self.styled_with_entry(Style::Property(Property::new(key, value)))
@ -146,8 +153,8 @@ impl Content {
/// The content's label. /// The content's label.
pub fn label(&self) -> Option<&Label> { pub fn label(&self) -> Option<&Label> {
self.meta.iter().find_map(|meta| match meta { self.modifiers.iter().find_map(|modifier| match modifier {
Meta::Label(label) => Some(label), Modifier::Label(label) => Some(label),
_ => None, _ => None,
}) })
} }
@ -161,6 +168,14 @@ impl Content {
}); });
} }
for modifier in &self.modifiers {
if let Modifier::Field(other, value) = modifier {
if name == other.as_str() {
return Some(value.clone());
}
}
}
self.obj.field(name) self.obj.field(name)
} }
@ -201,10 +216,23 @@ impl Content {
/// Disable a show rule recipe. /// Disable a show rule recipe.
#[doc(hidden)] #[doc(hidden)]
pub fn guarded(mut self, id: Guard) -> Self { pub fn guarded(mut self, id: Guard) -> Self {
self.meta.push(Meta::Guard(id)); self.modifiers.push(Modifier::Guard(id));
self self
} }
/// Mark this content as prepared.
#[doc(hidden)]
pub fn prepared(mut self) -> Self {
self.modifiers.push(Modifier::Prepared);
self
}
/// Whether this node was prepared.
#[doc(hidden)]
pub fn is_prepared(&self) -> bool {
self.modifiers.contains(&Modifier::Prepared)
}
/// Whether a label can be attached to the content. /// Whether a label can be attached to the content.
pub(super) fn labellable(&self) -> bool { pub(super) fn labellable(&self) -> bool {
!self.has::<dyn Unlabellable>() !self.has::<dyn Unlabellable>()
@ -212,18 +240,21 @@ impl Content {
/// Whether no show rule was executed for this node so far. /// Whether no show rule was executed for this node so far.
pub(super) fn is_pristine(&self) -> bool { pub(super) fn is_pristine(&self) -> bool {
!self.meta.iter().any(|meta| matches!(meta, Meta::Guard(_))) !self
.modifiers
.iter()
.any(|modifier| matches!(modifier, Modifier::Guard(_)))
} }
/// Check whether a show rule recipe is disabled. /// Check whether a show rule recipe is disabled.
pub(super) fn is_guarded(&self, id: Guard) -> bool { pub(super) fn is_guarded(&self, id: Guard) -> bool {
self.meta.contains(&Meta::Guard(id)) self.modifiers.contains(&Modifier::Guard(id))
} }
/// Copy the metadata from other content. /// Copy the modifiers from another piece of content.
pub(super) fn copy_meta(&mut self, from: &Content) { pub(super) fn copy_modifiers(&mut self, from: &Content) {
self.span = from.span; self.span = from.span;
self.meta = from.meta.clone(); self.modifiers = from.modifiers.clone();
} }
} }
@ -239,12 +270,6 @@ impl Default for Content {
} }
} }
impl PartialEq for Content {
fn eq(&self, other: &Self) -> bool {
(*self.obj).hash128() == (*other.obj).hash128()
}
}
impl Add for Content { impl Add for Content {
type Output = Self; type Output = Self;
@ -371,7 +396,7 @@ pub trait Node: 'static + Capable {
Content { Content {
obj: Arc::new(self), obj: Arc::new(self),
span: None, span: None,
meta: ThinVec::new(), modifiers: ThinVec::new(),
} }
} }

View File

@ -309,7 +309,6 @@ pub fn equal(lhs: &Value, rhs: &Value) -> bool {
(Color(a), Color(b)) => a == b, (Color(a), Color(b)) => a == b,
(Str(a), Str(b)) => a == b, (Str(a), Str(b)) => a == b,
(Label(a), Label(b)) => a == b, (Label(a), Label(b)) => a == b,
(Content(a), Content(b)) => a == b,
(Array(a), Array(b)) => a == b, (Array(a), Array(b)) => a == b,
(Dict(a), Dict(b)) => a == b, (Dict(a), Dict(b)) => a == b,
(Func(a), Func(b)) => a == b, (Func(a), Func(b)) => a == b,

View File

@ -3,6 +3,10 @@ use crate::diag::SourceResult;
/// Whether the target is affected by show rules in the given style chain. /// Whether the target is affected by show rules in the given style chain.
pub fn applicable(target: &Content, styles: StyleChain) -> bool { pub fn applicable(target: &Content, styles: StyleChain) -> bool {
if target.has::<dyn Prepare>() && !target.is_prepared() {
return true;
}
// Find out how many recipes there are. // Find out how many recipes there are.
let mut n = styles.recipes().count(); let mut n = styles.recipes().count();
@ -90,7 +94,7 @@ fn try_apply(
let make = |s| { let make = |s| {
let mut content = item!(text)(s); let mut content = item!(text)(s);
content.copy_meta(&target); content.copy_modifiers(&target);
content content
}; };
@ -124,6 +128,13 @@ fn try_apply(
} }
} }
/// Preparations before execution of any show rule.
#[capability]
pub trait Prepare {
/// Prepare the node for show rule application.
fn prepare(&self, vt: &mut Vt, this: Content, styles: StyleChain) -> Content;
}
/// The base recipe for a node. /// The base recipe for a node.
#[capability] #[capability]
pub trait Show { pub trait Show {

View File

@ -17,7 +17,7 @@ use crate::util::ReadableTypeId;
use crate::World; use crate::World;
/// A map of style properties. /// A map of style properties.
#[derive(Default, Clone, PartialEq, Hash)] #[derive(Default, Clone, Hash)]
pub struct StyleMap(Vec<Style>); pub struct StyleMap(Vec<Style>);
impl StyleMap { impl StyleMap {
@ -117,7 +117,7 @@ impl Debug for StyleMap {
} }
/// A single style property, recipe or barrier. /// A single style property, recipe or barrier.
#[derive(Clone, PartialEq, Hash)] #[derive(Clone, Hash)]
pub enum Style { pub enum Style {
/// A style property originating from a set rule or constructor. /// A style property originating from a set rule or constructor.
Property(Property), Property(Property),
@ -302,7 +302,7 @@ impl Debug for KeyId {
} }
/// A show rule recipe. /// A show rule recipe.
#[derive(Clone, PartialEq, Hash)] #[derive(Clone, Hash)]
pub struct Recipe { pub struct Recipe {
/// The span errors are reported with. /// The span errors are reported with.
pub span: Span, pub span: Span,
@ -374,6 +374,11 @@ pub enum Selector {
} }
impl Selector { impl Selector {
/// Define a simple node selector.
pub fn node<T: 'static>() -> Self {
Self::Node(NodeId::of::<T>(), None)
}
/// Define a simple text selector. /// Define a simple text selector.
pub fn text(text: &str) -> Self { pub fn text(text: &str) -> Self {
Self::Regex(Regex::new(&regex::escape(text)).unwrap()) Self::Regex(Regex::new(&regex::escape(text)).unwrap())
@ -399,7 +404,7 @@ impl Selector {
} }
/// A show rule transformation that can be applied to a match. /// A show rule transformation that can be applied to a match.
#[derive(Debug, Clone, PartialEq, Hash)] #[derive(Debug, Clone, Hash)]
pub enum Transform { pub enum Transform {
/// Replacement content. /// Replacement content.
Content(Content), Content(Content),

View File

@ -1,8 +1,15 @@
use comemo::Tracked; use std::cell::RefCell;
use std::collections::HashMap;
use std::hash::Hash;
use std::num::NonZeroUsize;
use super::{Content, StyleChain}; use comemo::{Track, Tracked, TrackedMut};
use super::{Content, Selector, StyleChain, Value};
use crate::diag::SourceResult; use crate::diag::SourceResult;
use crate::doc::Document; use crate::doc::{Document, Element, Frame, Location, Meta};
use crate::geom::Transform;
use crate::util::hash128;
use crate::World; use crate::World;
/// Typeset content into a fully layouted document. /// Typeset content into a fully layouted document.
@ -10,8 +17,30 @@ use crate::World;
pub fn typeset(world: Tracked<dyn World>, content: &Content) -> SourceResult<Document> { pub fn typeset(world: Tracked<dyn World>, content: &Content) -> SourceResult<Document> {
let library = world.library(); let library = world.library();
let styles = StyleChain::new(&library.styles); let styles = StyleChain::new(&library.styles);
let mut vt = Vt { world };
(library.items.layout)(&mut vt, content, styles) let mut document;
let mut iter = 0;
let mut introspector = Introspector::new();
// Relayout until all introspections stabilize.
// If that doesn't happen within five attempts, we give up.
loop {
let mut provider = StabilityProvider::new();
let mut vt = Vt {
world,
provider: provider.track_mut(),
introspector: introspector.track(),
};
document = (library.items.layout)(&mut vt, content, styles)?;
iter += 1;
if iter >= 5 || introspector.update(&document) {
break;
}
}
Ok(document)
} }
/// A virtual typesetter. /// A virtual typesetter.
@ -22,6 +51,12 @@ pub struct Vt<'a> {
/// The compilation environment. /// The compilation environment.
#[doc(hidden)] #[doc(hidden)]
pub world: Tracked<'a, dyn World>, pub world: Tracked<'a, dyn World>,
/// Provides stable identities to nodes.
#[doc(hidden)]
pub provider: TrackedMut<'a, StabilityProvider>,
/// Provides access to information about the document.
#[doc(hidden)]
pub introspector: Tracked<'a, Introspector>,
} }
impl<'a> Vt<'a> { impl<'a> Vt<'a> {
@ -29,4 +64,132 @@ impl<'a> Vt<'a> {
pub fn world(&self) -> Tracked<'a, dyn World> { pub fn world(&self) -> Tracked<'a, dyn World> {
self.world self.world
} }
/// Produce a stable identifier for this call site.
///
/// The key should be something that identifies the call site, but is not
/// necessarily unique. The stable marker incorporates the key's hash plus
/// additional disambiguation from other call sites with the same key.
///
/// The returned id can be attached to content as metadata is the then
/// locatable through [`locate`](Self::locate).
pub fn identify<T: Hash>(&mut self, key: &T) -> StableId {
self.provider.identify(hash128(key))
}
/// Locate all metadata matches for the given selector.
pub fn locate(&self, selector: Selector) -> Vec<(StableId, &Content)> {
self.introspector.locate(selector)
}
}
/// Stably identifies a call site across multiple layout passes.
///
/// This struct is created by [`Vt::identify`].
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct StableId(u128, u64);
/// Provides stable identities to nodes.
#[derive(Clone)]
#[doc(hidden)]
pub struct StabilityProvider(HashMap<u128, u64>);
impl StabilityProvider {
/// Create a new stability provider.
fn new() -> Self {
Self(HashMap::new())
}
}
#[comemo::track]
impl StabilityProvider {
/// Produce a stable identifier for this call site.
fn identify(&mut self, hash: u128) -> StableId {
let slot = self.0.entry(hash).or_default();
let id = StableId(hash, *slot);
*slot += 1;
id
}
}
/// Provides access to information about the document.
#[doc(hidden)]
pub struct Introspector {
nodes: Vec<(StableId, Content)>,
queries: RefCell<Vec<(Selector, u128)>>,
}
impl Introspector {
/// Create a new introspector.
fn new() -> Self {
Self { nodes: vec![], queries: RefCell::new(vec![]) }
}
/// Update the information given new frames and return whether we can stop
/// layouting.
fn update(&mut self, document: &Document) -> bool {
self.nodes.clear();
for (i, frame) in document.pages.iter().enumerate() {
let page = NonZeroUsize::new(1 + i).unwrap();
self.extract(frame, page, Transform::identity());
}
let queries = std::mem::take(&mut self.queries).into_inner();
for (selector, hash) in queries {
let nodes = self.locate_impl(&selector);
if hash128(&nodes) != hash {
return false;
}
}
true
}
/// Extract metadata from a frame.
fn extract(&mut self, frame: &Frame, page: NonZeroUsize, ts: Transform) {
for (pos, element) in frame.elements() {
match *element {
Element::Group(ref group) => {
let ts = ts
.pre_concat(Transform::translate(pos.x, pos.y))
.pre_concat(group.transform);
self.extract(&group.frame, page, ts);
}
Element::Meta(Meta::Node(id, ref content), _) => {
if !self.nodes.iter().any(|&(prev, _)| prev == id) {
let pos = pos.transform(ts);
let mut node = content.clone();
let loc = Location { page, pos };
node.push_field("loc", Value::Dict(loc.encode()));
self.nodes.push((id, node));
}
}
_ => {}
}
}
}
}
#[comemo::track]
impl Introspector {
/// Locate all metadata matches for the given selector.
fn locate(&self, selector: Selector) -> Vec<(StableId, &Content)> {
let nodes = self.locate_impl(&selector);
let mut queries = self.queries.borrow_mut();
if !queries.iter().any(|(prev, _)| prev == &selector) {
queries.push((selector, hash128(&nodes)));
}
nodes
}
}
impl Introspector {
fn locate_impl(&self, selector: &Selector) -> Vec<(StableId, &Content)> {
self.nodes
.iter()
.map(|(id, node)| (*id, node))
.filter(|(_, target)| selector.matches(target))
.collect()
}
} }

View File

@ -12,7 +12,7 @@ use elsa::FrozenVec;
use once_cell::unsync::OnceCell; use once_cell::unsync::OnceCell;
use tiny_skia as sk; use tiny_skia as sk;
use typst::diag::{bail, FileError, FileResult}; use typst::diag::{bail, FileError, FileResult};
use typst::doc::{Document, Element, Frame, Metadata}; use typst::doc::{Document, Element, Frame, Meta};
use typst::font::{Font, FontBook}; use typst::font::{Font, FontBook};
use typst::geom::{Abs, RgbaColor, Sides}; use typst::geom::{Abs, RgbaColor, Sides};
use typst::model::{Library, Smart, Value}; use typst::model::{Library, Smart, Value};
@ -349,7 +349,7 @@ fn test(
line += part.lines().count() + 1; line += part.lines().count() + 1;
} }
let document = Document { pages: frames, metadata: Metadata::default() }; let document = Document { pages: frames, ..Default::default() };
if compare_ever { if compare_ever {
if let Some(pdf_path) = pdf_path { if let Some(pdf_path) = pdf_path {
let pdf_data = typst::export::pdf(&document); let pdf_data = typst::export::pdf(&document);
@ -699,7 +699,7 @@ fn render_links(canvas: &mut sk::Pixmap, ts: sk::Transform, frame: &Frame) {
let ts = ts.pre_concat(group.transform.into()); let ts = ts.pre_concat(group.transform.into());
render_links(canvas, ts, &group.frame); render_links(canvas, ts, &group.frame);
} }
Element::Link(_, size) => { Element::Meta(Meta::Link(_), size) => {
let w = size.x.to_pt() as f32; let w = size.x.to_pt() as f32;
let h = size.y.to_pt() as f32; let h = size.y.to_pt() as f32;
let rect = sk::Rect::from_xywh(0.0, 0.0, w, h).unwrap(); let rect = sk::Rect::from_xywh(0.0, 0.0, w, h).unwrap();

View File

@ -152,15 +152,11 @@
#test(test == test, true) #test(test == test, true)
#test((() => {}) == (() => {}), false) #test((() => {}) == (() => {}), false)
// Content compares by hash equality. // Content cannot be compared.
#let t = [a] #let t = [a]
#test(t == t, true) #test(t == t, false)
#test([] == [], true) #test([] == [], false)
#test([a] == [a], true) #test([a] == [a], false)
#test([[a]] == [a], true)
#test([] == [a], false)
#test(box[] == box[], true)
#test(box[a] == box[], false)
--- ---
// Test comparison operators. // Test comparison operators.