Introspection
This commit is contained in:
parent
5110a41de1
commit
9bc90c371f
@ -216,7 +216,7 @@ pub const ENUM: ListKind = 1;
|
||||
pub const DESC: ListKind = 2;
|
||||
|
||||
/// How to label a list or enumeration.
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub enum Label {
|
||||
/// The default labelling.
|
||||
Default,
|
||||
|
@ -59,9 +59,11 @@ impl LayoutRoot for Content {
|
||||
fn cached(
|
||||
node: &Content,
|
||||
world: comemo::Tracked<dyn World>,
|
||||
provider: TrackedMut<StabilityProvider>,
|
||||
introspector: Tracked<Introspector>,
|
||||
styles: StyleChain,
|
||||
) -> SourceResult<Document> {
|
||||
let mut vt = Vt { world };
|
||||
let mut vt = Vt { world, provider, introspector };
|
||||
let scratch = Scratch::default();
|
||||
let (realized, styles) = realize_root(&mut vt, &scratch, node, styles)?;
|
||||
realized
|
||||
@ -70,7 +72,13 @@ impl LayoutRoot for Content {
|
||||
.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(
|
||||
node: &Content,
|
||||
world: comemo::Tracked<dyn World>,
|
||||
provider: TrackedMut<StabilityProvider>,
|
||||
introspector: Tracked<Introspector>,
|
||||
styles: StyleChain,
|
||||
regions: &Regions,
|
||||
) -> SourceResult<Fragment> {
|
||||
let mut vt = Vt { world };
|
||||
let mut vt = Vt { world, provider, introspector };
|
||||
let scratch = Scratch::default();
|
||||
let (realized, styles) = realize_block(&mut vt, &scratch, node, styles)?;
|
||||
let barrier = Style::Barrier(realized.id());
|
||||
@ -111,7 +121,14 @@ impl Layout for Content {
|
||||
.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,
|
||||
styles: StyleChain<'a>,
|
||||
) -> 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>() {
|
||||
return self.styled(styled, styles);
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ use crate::prelude::*;
|
||||
use crate::text::TextNode;
|
||||
|
||||
/// Layouts its child onto one or multiple pages.
|
||||
#[derive(PartialEq, Clone, Hash)]
|
||||
#[derive(Clone, Hash)]
|
||||
pub struct PageNode(pub Content);
|
||||
|
||||
#[node]
|
||||
@ -157,7 +157,7 @@ impl PagebreakNode {
|
||||
}
|
||||
|
||||
/// A header, footer, foreground or background definition.
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub enum Marginal {
|
||||
/// Nothing,
|
||||
None,
|
||||
|
@ -57,13 +57,15 @@ impl ParNode {
|
||||
fn cached(
|
||||
par: &ParNode,
|
||||
world: Tracked<dyn World>,
|
||||
provider: TrackedMut<StabilityProvider>,
|
||||
introspector: Tracked<Introspector>,
|
||||
styles: StyleChain,
|
||||
consecutive: bool,
|
||||
width: Abs,
|
||||
base: Size,
|
||||
expand: bool,
|
||||
) -> SourceResult<Fragment> {
|
||||
let mut vt = Vt { world };
|
||||
let mut vt = Vt { world, provider, introspector };
|
||||
|
||||
// Collect all text into one string for BiDi analysis.
|
||||
let (text, segments) = collect(par, &styles, consecutive);
|
||||
@ -80,7 +82,17 @@ impl ParNode {
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,11 +27,9 @@ impl LayoutRoot for DocumentNode {
|
||||
}
|
||||
|
||||
Ok(Document {
|
||||
metadata: Metadata {
|
||||
title: styles.get(Self::TITLE).clone(),
|
||||
author: styles.get(Self::AUTHOR).clone(),
|
||||
},
|
||||
pages,
|
||||
title: styles.get(Self::TITLE).clone(),
|
||||
author: styles.get(Self::AUTHOR).clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -25,10 +25,6 @@ impl LinkNode {
|
||||
|
||||
#[node(Show, Finalize)]
|
||||
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> {
|
||||
let dest = args.expect::<Destination>("destination")?;
|
||||
Ok(match dest {
|
||||
@ -61,6 +57,6 @@ impl Show for LinkNode {
|
||||
|
||||
impl Finalize for LinkNode {
|
||||
fn finalize(&self, realized: Content) -> Content {
|
||||
realized.styled(Self::DEST, Some(self.dest.clone()))
|
||||
realized.styled(Meta::DATA, vec![Meta::Link(self.dest.clone())])
|
||||
}
|
||||
}
|
||||
|
@ -16,8 +16,9 @@ pub use typst::geom::*;
|
||||
#[doc(no_inline)]
|
||||
pub use typst::model::{
|
||||
array, capability, castable, dict, dynamic, format_str, node, Args, Array, Cast,
|
||||
Content, Dict, Finalize, Fold, Func, Label, Node, NodeId, Resolve, Selector, Show,
|
||||
Smart, Str, StyleChain, StyleMap, StyleVec, Unlabellable, Value, Vm, Vt,
|
||||
Content, Dict, Finalize, Fold, Func, Introspector, Label, Node, NodeId, Prepare,
|
||||
Resolve, Selector, Show, Smart, StabilityProvider, Str, StyleChain, StyleMap,
|
||||
StyleVec, Unlabellable, Value, Vm, Vt,
|
||||
};
|
||||
#[doc(no_inline)]
|
||||
pub use typst::syntax::{Span, Spanned};
|
||||
|
@ -13,6 +13,9 @@ pub trait ContentExt {
|
||||
/// Underline this content.
|
||||
fn underlined(self) -> Self;
|
||||
|
||||
/// Link the content to a destination.
|
||||
fn linked(self, dest: Destination) -> Self;
|
||||
|
||||
/// Force a size for this content.
|
||||
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()
|
||||
}
|
||||
|
||||
fn linked(self, dest: Destination) -> Self {
|
||||
self.styled(Meta::DATA, vec![Meta::Link(dest.clone())])
|
||||
}
|
||||
|
||||
fn boxed(self, sizing: Axes<Option<Rel<Length>>>) -> Self {
|
||||
crate::layout::BoxNode { sizing, child: self }.pack()
|
||||
}
|
||||
|
@ -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 {
|
||||
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> Content {
|
||||
let lang = styles.get(Self::LANG).as_ref().map(|s| s.to_lowercase());
|
||||
|
@ -6,7 +6,6 @@ use typst::font::{Font, FontVariant};
|
||||
use typst::util::SliceExt;
|
||||
|
||||
use super::*;
|
||||
use crate::meta::LinkNode;
|
||||
use crate::prelude::*;
|
||||
|
||||
/// The result of shaping text.
|
||||
@ -93,7 +92,6 @@ impl<'a> ShapedText<'a> {
|
||||
let lang = self.styles.get(TextNode::LANG);
|
||||
let decos = self.styles.get(TextNode::DECO);
|
||||
let fill = self.styles.get(TextNode::FILL);
|
||||
let link = self.styles.get(LinkNode::DEST);
|
||||
|
||||
for ((font, y_offset), group) in
|
||||
self.glyphs.as_ref().group_by_key(|g| (g.font.clone(), g.y_offset))
|
||||
@ -128,10 +126,8 @@ impl<'a> ShapedText<'a> {
|
||||
offset += width;
|
||||
}
|
||||
|
||||
// Apply link if it exists.
|
||||
if let Some(dest) = link {
|
||||
frame.link(dest.clone());
|
||||
}
|
||||
// Apply metadata.
|
||||
frame.meta(self.styles);
|
||||
|
||||
frame
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ use std::ffi::OsStr;
|
||||
|
||||
use typst::image::{Image, ImageFormat, RasterFormat, VectorFormat};
|
||||
|
||||
use crate::meta::LinkNode;
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Show a raster or vector graphic.
|
||||
@ -89,10 +88,8 @@ impl Layout for ImageNode {
|
||||
frame.clip();
|
||||
}
|
||||
|
||||
// Apply link if it exists.
|
||||
if let Some(url) = styles.get(LinkNode::DEST) {
|
||||
frame.link(url.clone());
|
||||
}
|
||||
// Apply metadata.
|
||||
frame.meta(styles);
|
||||
|
||||
Ok(Fragment::frame(frame))
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
use std::f64::consts::SQRT_2;
|
||||
|
||||
use crate::meta::LinkNode;
|
||||
use crate::prelude::*;
|
||||
|
||||
/// 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.
|
||||
if let Some(url) = styles.get(LinkNode::DEST) {
|
||||
frame.link(url.clone());
|
||||
}
|
||||
// Apply metadata.
|
||||
frame.meta(styles);
|
||||
|
||||
Ok(Fragment::frame(frame))
|
||||
}
|
||||
|
60
src/doc.rs
60
src/doc.rs
@ -10,21 +10,14 @@ use crate::geom::{
|
||||
Abs, Align, Axes, Dir, Em, Numeric, Paint, Point, Shape, Size, Transform,
|
||||
};
|
||||
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;
|
||||
|
||||
/// A finished document with metadata and page frames.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Document {
|
||||
/// The document's metadata.
|
||||
pub metadata: Metadata,
|
||||
/// The page frames.
|
||||
pub pages: Vec<Frame>,
|
||||
}
|
||||
|
||||
/// Document metadata.
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq)]
|
||||
pub struct Metadata {
|
||||
/// The document's title.
|
||||
pub title: Option<EcoString>,
|
||||
/// The document's author.
|
||||
@ -32,7 +25,7 @@ pub struct Metadata {
|
||||
}
|
||||
|
||||
/// A partial layout result.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
#[derive(Clone)]
|
||||
pub struct Fragment(Vec<Frame>);
|
||||
|
||||
impl Fragment {
|
||||
@ -113,7 +106,7 @@ impl<'a> IntoIterator for &'a mut Fragment {
|
||||
}
|
||||
|
||||
/// A finished layout with elements at fixed positions.
|
||||
#[derive(Default, Clone, Eq, PartialEq)]
|
||||
#[derive(Default, Clone)]
|
||||
pub struct Frame {
|
||||
/// The size of the frame.
|
||||
size: Size,
|
||||
@ -336,9 +329,11 @@ impl Frame {
|
||||
}
|
||||
}
|
||||
|
||||
/// Link the whole frame to a resource.
|
||||
pub fn link(&mut self, dest: Destination) {
|
||||
self.push(Point::zero(), Element::Link(dest, self.size));
|
||||
/// Attach the metadata from this style chain to the frame.
|
||||
pub fn meta(&mut self, styles: StyleChain) {
|
||||
for meta in styles.get(Meta::DATA) {
|
||||
self.push(Point::zero(), Element::Meta(meta, self.size));
|
||||
}
|
||||
}
|
||||
|
||||
/// Arbitrarily transform the contents of the frame.
|
||||
@ -374,7 +369,7 @@ impl Debug for Frame {
|
||||
}
|
||||
|
||||
/// The building block frames are composed of.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
#[derive(Clone)]
|
||||
pub enum Element {
|
||||
/// A group of elements.
|
||||
Group(Group),
|
||||
@ -384,8 +379,8 @@ pub enum Element {
|
||||
Shape(Shape),
|
||||
/// An image and its size.
|
||||
Image(Image, Size),
|
||||
/// A link to an external resource and its trigger region.
|
||||
Link(Destination, Size),
|
||||
/// Meta information and the region it applies to.
|
||||
Meta(Meta, Size),
|
||||
}
|
||||
|
||||
impl Debug for Element {
|
||||
@ -395,13 +390,13 @@ impl Debug for Element {
|
||||
Self::Text(text) => write!(f, "{text:?}"),
|
||||
Self::Shape(shape) => write!(f, "{shape:?}"),
|
||||
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.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
#[derive(Clone)]
|
||||
pub struct Group {
|
||||
/// The group's 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.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum Destination {
|
||||
|
@ -14,7 +14,7 @@ use pdf_writer::{Finish, Name, PdfWriter, Ref, TextStr};
|
||||
|
||||
use self::outline::HeadingNode;
|
||||
use self::page::Page;
|
||||
use crate::doc::{Document, Lang, Metadata};
|
||||
use crate::doc::{Document, Lang};
|
||||
use crate::font::Font;
|
||||
use crate::geom::{Abs, Dir, Em};
|
||||
use crate::image::Image;
|
||||
@ -23,7 +23,7 @@ use crate::image::Image;
|
||||
///
|
||||
/// Returns the raw bytes making up the PDF file.
|
||||
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);
|
||||
font::write_fonts(&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.
|
||||
pub struct PdfContext<'a> {
|
||||
metadata: &'a Metadata,
|
||||
document: &'a Document,
|
||||
writer: PdfWriter,
|
||||
pages: Vec<Page>,
|
||||
page_heights: Vec<f32>,
|
||||
@ -55,11 +55,11 @@ pub struct 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 page_tree_ref = alloc.bump();
|
||||
Self {
|
||||
metadata,
|
||||
document,
|
||||
writer: PdfWriter::new(),
|
||||
pages: vec![],
|
||||
page_heights: vec![],
|
||||
@ -116,10 +116,10 @@ fn write_catalog(ctx: &mut PdfContext) {
|
||||
|
||||
// Write the document information.
|
||||
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));
|
||||
}
|
||||
if let Some(author) = &ctx.metadata.author {
|
||||
if let Some(author) = &ctx.document.author {
|
||||
info.author(TextStr(author));
|
||||
}
|
||||
info.creator(TextStr("Typst"));
|
||||
|
@ -3,7 +3,7 @@ use pdf_writer::writers::ColorSpace;
|
||||
use pdf_writer::{Content, Filter, Finish, Name, Rect, Ref, Str};
|
||||
|
||||
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::geom::{
|
||||
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::Shape(shape) => write_shape(ctx, x, y, shape),
|
||||
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),
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ fn render_frame(
|
||||
Element::Image(image, size) => {
|
||||
render_image(canvas, ts, mask, image, *size);
|
||||
}
|
||||
Element::Link(_, _) => {}
|
||||
Element::Meta(_, _) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -255,17 +255,23 @@ castable! {
|
||||
}
|
||||
|
||||
castable! {
|
||||
Destination,
|
||||
Expected: "string or dictionary with `page`, `x`, and `y` keys",
|
||||
Value::Str(string) => Self::Url(string.into()),
|
||||
Location,
|
||||
Expected: "dictionary with `page`, `x`, and `y` keys",
|
||||
Value::Dict(dict) => {
|
||||
let page = dict.get("page")?.clone().cast()?;
|
||||
let x: Length = dict.get("x")?.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! {
|
||||
FontStyle,
|
||||
Expected: "string",
|
||||
|
@ -21,14 +21,16 @@ use crate::World;
|
||||
pub struct Content {
|
||||
obj: Arc<dyn Bounds>,
|
||||
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)]
|
||||
enum Meta {
|
||||
enum Modifier {
|
||||
Prepared,
|
||||
Guard(Guard),
|
||||
Label(Label),
|
||||
Field(EcoString, Value),
|
||||
}
|
||||
|
||||
impl Content {
|
||||
@ -62,17 +64,22 @@ impl Content {
|
||||
|
||||
/// Attach a label to the content.
|
||||
pub fn labelled(mut self, label: Label) -> Self {
|
||||
for meta in &mut self.meta {
|
||||
if let Meta::Label(prev) = meta {
|
||||
for modifier in &mut self.modifiers {
|
||||
if let Modifier::Label(prev) = modifier {
|
||||
*prev = label;
|
||||
return self;
|
||||
}
|
||||
}
|
||||
|
||||
self.meta.push(Meta::Label(label));
|
||||
self.modifiers.push(Modifier::Label(label));
|
||||
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.
|
||||
pub fn styled<K: Key>(self, key: K, value: K::Value) -> Self {
|
||||
self.styled_with_entry(Style::Property(Property::new(key, value)))
|
||||
@ -146,8 +153,8 @@ impl Content {
|
||||
|
||||
/// The content's label.
|
||||
pub fn label(&self) -> Option<&Label> {
|
||||
self.meta.iter().find_map(|meta| match meta {
|
||||
Meta::Label(label) => Some(label),
|
||||
self.modifiers.iter().find_map(|modifier| match modifier {
|
||||
Modifier::Label(label) => Some(label),
|
||||
_ => 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)
|
||||
}
|
||||
|
||||
@ -201,10 +216,23 @@ impl Content {
|
||||
/// Disable a show rule recipe.
|
||||
#[doc(hidden)]
|
||||
pub fn guarded(mut self, id: Guard) -> Self {
|
||||
self.meta.push(Meta::Guard(id));
|
||||
self.modifiers.push(Modifier::Guard(id));
|
||||
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.
|
||||
pub(super) fn labellable(&self) -> bool {
|
||||
!self.has::<dyn Unlabellable>()
|
||||
@ -212,18 +240,21 @@ impl Content {
|
||||
|
||||
/// Whether no show rule was executed for this node so far.
|
||||
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.
|
||||
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.
|
||||
pub(super) fn copy_meta(&mut self, from: &Content) {
|
||||
/// Copy the modifiers from another piece of content.
|
||||
pub(super) fn copy_modifiers(&mut self, from: &Content) {
|
||||
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 {
|
||||
type Output = Self;
|
||||
|
||||
@ -371,7 +396,7 @@ pub trait Node: 'static + Capable {
|
||||
Content {
|
||||
obj: Arc::new(self),
|
||||
span: None,
|
||||
meta: ThinVec::new(),
|
||||
modifiers: ThinVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -309,7 +309,6 @@ pub fn equal(lhs: &Value, rhs: &Value) -> bool {
|
||||
(Color(a), Color(b)) => a == b,
|
||||
(Str(a), Str(b)) => a == b,
|
||||
(Label(a), Label(b)) => a == b,
|
||||
(Content(a), Content(b)) => a == b,
|
||||
(Array(a), Array(b)) => a == b,
|
||||
(Dict(a), Dict(b)) => a == b,
|
||||
(Func(a), Func(b)) => a == b,
|
||||
|
@ -3,6 +3,10 @@ use crate::diag::SourceResult;
|
||||
|
||||
/// Whether the target is affected by show rules in the given style chain.
|
||||
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.
|
||||
let mut n = styles.recipes().count();
|
||||
|
||||
@ -90,7 +94,7 @@ fn try_apply(
|
||||
|
||||
let make = |s| {
|
||||
let mut content = item!(text)(s);
|
||||
content.copy_meta(&target);
|
||||
content.copy_modifiers(&target);
|
||||
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.
|
||||
#[capability]
|
||||
pub trait Show {
|
||||
|
@ -17,7 +17,7 @@ use crate::util::ReadableTypeId;
|
||||
use crate::World;
|
||||
|
||||
/// A map of style properties.
|
||||
#[derive(Default, Clone, PartialEq, Hash)]
|
||||
#[derive(Default, Clone, Hash)]
|
||||
pub struct StyleMap(Vec<Style>);
|
||||
|
||||
impl StyleMap {
|
||||
@ -117,7 +117,7 @@ impl Debug for StyleMap {
|
||||
}
|
||||
|
||||
/// A single style property, recipe or barrier.
|
||||
#[derive(Clone, PartialEq, Hash)]
|
||||
#[derive(Clone, Hash)]
|
||||
pub enum Style {
|
||||
/// A style property originating from a set rule or constructor.
|
||||
Property(Property),
|
||||
@ -302,7 +302,7 @@ impl Debug for KeyId {
|
||||
}
|
||||
|
||||
/// A show rule recipe.
|
||||
#[derive(Clone, PartialEq, Hash)]
|
||||
#[derive(Clone, Hash)]
|
||||
pub struct Recipe {
|
||||
/// The span errors are reported with.
|
||||
pub span: Span,
|
||||
@ -374,6 +374,11 @@ pub enum 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.
|
||||
pub fn text(text: &str) -> Self {
|
||||
Self::Regex(Regex::new(®ex::escape(text)).unwrap())
|
||||
@ -399,7 +404,7 @@ impl Selector {
|
||||
}
|
||||
|
||||
/// A show rule transformation that can be applied to a match.
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub enum Transform {
|
||||
/// Replacement content.
|
||||
Content(Content),
|
||||
|
@ -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::doc::Document;
|
||||
use crate::doc::{Document, Element, Frame, Location, Meta};
|
||||
use crate::geom::Transform;
|
||||
use crate::util::hash128;
|
||||
use crate::World;
|
||||
|
||||
/// 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> {
|
||||
let library = world.library();
|
||||
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.
|
||||
@ -22,6 +51,12 @@ pub struct Vt<'a> {
|
||||
/// The compilation environment.
|
||||
#[doc(hidden)]
|
||||
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> {
|
||||
@ -29,4 +64,132 @@ impl<'a> Vt<'a> {
|
||||
pub fn world(&self) -> Tracked<'a, dyn 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()
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ use elsa::FrozenVec;
|
||||
use once_cell::unsync::OnceCell;
|
||||
use tiny_skia as sk;
|
||||
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::geom::{Abs, RgbaColor, Sides};
|
||||
use typst::model::{Library, Smart, Value};
|
||||
@ -349,7 +349,7 @@ fn test(
|
||||
line += part.lines().count() + 1;
|
||||
}
|
||||
|
||||
let document = Document { pages: frames, metadata: Metadata::default() };
|
||||
let document = Document { pages: frames, ..Default::default() };
|
||||
if compare_ever {
|
||||
if let Some(pdf_path) = pdf_path {
|
||||
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());
|
||||
render_links(canvas, ts, &group.frame);
|
||||
}
|
||||
Element::Link(_, size) => {
|
||||
Element::Meta(Meta::Link(_), size) => {
|
||||
let w = size.x.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();
|
||||
|
@ -152,15 +152,11 @@
|
||||
#test(test == test, true)
|
||||
#test((() => {}) == (() => {}), false)
|
||||
|
||||
// Content compares by hash equality.
|
||||
// Content cannot be compared.
|
||||
#let t = [a]
|
||||
#test(t == t, true)
|
||||
#test([] == [], true)
|
||||
#test([a] == [a], true)
|
||||
#test([[a]] == [a], true)
|
||||
#test([] == [a], false)
|
||||
#test(box[] == box[], true)
|
||||
#test(box[a] == box[], false)
|
||||
#test(t == t, false)
|
||||
#test([] == [], false)
|
||||
#test([a] == [a], false)
|
||||
|
||||
---
|
||||
// Test comparison operators.
|
||||
|
Loading…
x
Reference in New Issue
Block a user