mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Introspection
This commit is contained in:
parent
5110a41de1
commit
9bc90c371f
@ -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,
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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())])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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};
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
|
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,
|
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 {
|
||||||
|
@ -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"));
|
||||||
|
@ -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),
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(_, _) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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 {
|
||||||
|
@ -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(®ex::escape(text)).unwrap())
|
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.
|
/// 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),
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user