Refactor frame metadata into tags (#4212)

This commit is contained in:
Laurenz 2024-05-22 11:26:03 +02:00 committed by GitHub
parent a24052cb80
commit a52987a8c2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 257 additions and 225 deletions

View File

@ -1,7 +1,6 @@
use std::num::NonZeroUsize;
use ecow::EcoString;
use typst::introspection::Meta;
use typst::layout::{Frame, FrameItem, Point, Position, Size};
use typst::model::{Destination, Document};
use typst::syntax::{FileId, LinkedNode, Side, Source, Span, SyntaxKind};
@ -37,7 +36,7 @@ pub fn jump_from_click(
) -> Option<Jump> {
// Try to find a link first.
for (pos, item) in frame.items() {
if let FrameItem::Meta(Meta::Link(dest), size) = item {
if let FrameItem::Link(dest, size) = item {
if is_in_rect(*pos, *size, click) {
return Some(match dest {
Destination::Url(url) => Jump::Url(url.clone()),

View File

@ -12,7 +12,6 @@ use pdf_writer::types::{
};
use pdf_writer::writers::{PageLabel, Resources};
use pdf_writer::{Content, Filter, Finish, Name, Rect, Ref, Str, TextStr};
use typst::introspection::Meta;
use typst::layout::{
Abs, Em, Frame, FrameItem, GroupItem, Page, Point, Ratio, Size, Transform,
};
@ -749,11 +748,8 @@ pub(crate) fn write_frame(ctx: &mut PageContext, frame: &Frame) {
FrameItem::Text(text) => write_text(ctx, pos, text),
FrameItem::Shape(shape, _) => write_shape(ctx, pos, shape),
FrameItem::Image(image, size, _) => write_image(ctx, x, y, image, *size),
FrameItem::Meta(meta, size) => match meta {
Meta::Link(dest) => write_link(ctx, pos, dest, *size),
Meta::Elem(_) => {}
Meta::Hide => {}
},
FrameItem::Link(dest, size) => write_link(ctx, pos, dest, *size),
FrameItem::Tag(_) => {}
}
}
}

View File

@ -6,7 +6,6 @@ mod shape;
mod text;
use tiny_skia as sk;
use typst::introspection::Meta;
use typst::layout::{
Abs, Axes, Frame, FrameItem, FrameKind, GroupItem, Point, Size, Transform,
};
@ -162,11 +161,8 @@ fn render_frame(canvas: &mut sk::Pixmap, state: State, frame: &Frame) {
FrameItem::Image(image, size, _) => {
image::render_image(canvas, state.pre_translate(*pos), image, *size);
}
FrameItem::Meta(meta, _) => match meta {
Meta::Link(_) => {}
Meta::Elem(_) => {}
Meta::Hide => {}
},
FrameItem::Link(_, _) => {}
FrameItem::Tag(_) => {}
}
}
}

View File

@ -184,8 +184,9 @@ impl SVGRenderer {
}
for (pos, item) in frame.items() {
// File size optimization
if matches!(item, FrameItem::Meta(_, _)) {
// File size optimization.
// TODO: SVGs could contain links, couldn't they?
if matches!(item, FrameItem::Link(_, _) | FrameItem::Tag(_)) {
continue;
}
@ -206,7 +207,8 @@ impl SVGRenderer {
self.render_shape(state.pre_translate(*pos), shape)
}
FrameItem::Image(image, size, _) => self.render_image(image, size),
FrameItem::Meta(_, _) => unreachable!(),
FrameItem::Link(_, _) => unreachable!(),
FrameItem::Tag(_) => unreachable!(),
};
self.xml.end_element();

View File

@ -18,9 +18,9 @@ use crate::foundations::{
NativeElement, Recipe, RecipeIndex, Repr, Selector, Str, Style, StyleChain, Styles,
Value,
};
use crate::introspection::{Location, Meta, MetaElem};
use crate::introspection::{Location, TagElem};
use crate::layout::{AlignElem, Alignment, Axes, Length, MoveElem, PadElem, Rel, Sides};
use crate::model::{Destination, EmphElem, StrongElem};
use crate::model::{Destination, EmphElem, LinkElem, StrongElem};
use crate::realize::{Behave, Behaviour};
use crate::syntax::Span;
use crate::text::UnderlineElem;
@ -472,16 +472,16 @@ impl Content {
/// Link the content somewhere.
pub fn linked(self, dest: Destination) -> Self {
self.styled(MetaElem::set_data(smallvec![Meta::Link(dest)]))
self.styled(LinkElem::set_dests(smallvec![dest]))
}
/// Make the content linkable by `.linked(Destination::Location(loc))`.
///
/// Should be used in combination with [`Location::variant`].
pub fn backlinked(self, loc: Location) -> Self {
let mut backlink = Content::empty();
let mut backlink = Content::empty().spanned(self.span());
backlink.set_location(loc);
self.styled(MetaElem::set_data(smallvec![Meta::Elem(backlink)]))
TagElem::packed(backlink) + self
}
/// Set alignments for this content.

View File

@ -13,7 +13,7 @@ use crate::foundations::{
Element, Func, IntoValue, Label, LocatableSelector, NativeElement, Packed, Repr,
Selector, Show, Smart, Str, StyleChain, Value,
};
use crate::introspection::{Introspector, Locatable, Location, Locator, Meta};
use crate::introspection::{Introspector, Locatable, Location, Locator};
use crate::layout::{Frame, FrameItem, PageElem};
use crate::math::EquationElem;
use crate::model::{FigureElem, HeadingElem, Numbering, NumberingPattern};
@ -820,7 +820,7 @@ impl ManualPageCounter {
for (_, item) in page.items() {
match item {
FrameItem::Group(group) => self.visit(engine, &group.frame)?,
FrameItem::Meta(Meta::Elem(elem), _) => {
FrameItem::Tag(elem) => {
let Some(elem) = elem.to_packed::<CounterUpdateElem>() else {
continue;
};

View File

@ -10,7 +10,7 @@ use smallvec::SmallVec;
use crate::diag::{bail, StrResult};
use crate::foundations::{Content, Label, Repr, Selector};
use crate::introspection::{Location, Meta};
use crate::introspection::Location;
use crate::layout::{Frame, FrameItem, Page, Point, Position, Transform};
use crate::model::Numbering;
use crate::utils::NonZeroExt;
@ -61,18 +61,18 @@ impl Introspector {
.pre_concat(group.transform);
self.extract(&group.frame, page, ts);
}
FrameItem::Meta(Meta::Elem(content), _)
if !self.elems.contains_key(&content.location().unwrap()) =>
FrameItem::Tag(elem)
if !self.elems.contains_key(&elem.location().unwrap()) =>
{
let pos = pos.transform(ts);
let ret = self.elems.insert(
content.location().unwrap(),
(content.clone(), Position { page, point: pos }),
elem.location().unwrap(),
(elem.clone(), Position { page, point: pos }),
);
assert!(ret.is_none(), "duplicate locations");
// Build the label cache.
if let Some(label) = content.label() {
if let Some(label) = elem.label() {
self.labels.entry(label).or_default().push(self.elems.len() - 1);
}
}

View File

@ -3,7 +3,7 @@ use std::collections::HashMap;
use comemo::{Track, Tracked, Validate};
use crate::introspection::{Location, Meta};
use crate::introspection::Location;
use crate::layout::{Frame, FrameItem};
/// Provides locations for elements in the document.
@ -77,7 +77,7 @@ impl<'a> Locator<'a> {
for (_, item) in frame.items() {
match item {
FrameItem::Group(group) => self.visit_frame(&group.frame),
FrameItem::Meta(Meta::Elem(elem), _) => {
FrameItem::Tag(elem) => {
let hashes = self.hashes.get_mut();
let loc = elem.location().unwrap();
let entry = hashes.entry(loc.hash).or_default();

View File

@ -23,15 +23,12 @@ pub use self::metadata::*;
pub use self::query_::*;
pub use self::state::*;
use std::fmt::{self, Debug, Formatter};
use ecow::{eco_format, EcoString};
use smallvec::SmallVec;
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::NativeElement;
use crate::foundations::{
category, elem, ty, Category, Content, Packed, Repr, Scope, Unlabellable,
category, elem, Args, Category, Construct, Content, Packed, Scope, Unlabellable,
};
use crate::model::Destination;
use crate::realize::{Behave, Behaviour};
/// Interactions between document parts.
@ -59,59 +56,39 @@ pub fn define(global: &mut Scope) {
global.define_func::<locate>();
}
/// Hosts metadata and ensures metadata is produced even for empty elements.
#[elem(Behave, Unlabellable)]
pub struct MetaElem {
/// Metadata that should be attached to all elements affected by this style
/// property.
///
/// This must be accessed and applied to all frames produced by elements
/// that manually handle styles (because their children can have varying
/// styles). This currently includes flow, par, and equation.
///
/// Other elements don't manually need to handle it because their parents
/// that result from realization will take care of it and the metadata can
/// only apply to them as a whole, not part of it (because they don't manage
/// styles).
#[fold]
pub data: SmallVec<[Meta; 1]>,
/// Holds a locatable element that was realized.
///
/// The `TagElem` is handled by all layouters. The held element becomes
/// available for introspection in the next compiler iteration.
#[elem(Behave, Unlabellable, Construct)]
pub struct TagElem {
/// The introspectible element.
#[required]
#[internal]
pub elem: Content,
}
impl Unlabellable for Packed<MetaElem> {}
impl TagElem {
/// Create a packed tag element.
pub fn packed(elem: Content) -> Content {
let span = elem.span();
let mut content = Self::new(elem).pack().spanned(span);
// We can skip preparation for the `TagElem`.
content.mark_prepared();
content
}
}
impl Behave for Packed<MetaElem> {
impl Construct for TagElem {
fn construct(_: &mut Engine, args: &mut Args) -> SourceResult<Content> {
bail!(args.span, "cannot be constructed manually")
}
}
impl Unlabellable for Packed<TagElem> {}
impl Behave for Packed<TagElem> {
fn behaviour(&self) -> Behaviour {
Behaviour::Invisible
}
}
/// Meta information that isn't visible or renderable.
#[ty]
#[derive(Clone, PartialEq, Hash)]
pub enum Meta {
/// An internal or external link to a destination.
Link(Destination),
/// An identifiable element that produces something within the area this
/// metadata is attached to.
Elem(Content),
/// Indicates that content should be hidden. This variant doesn't appear
/// in the final frames as it is removed alongside the content that should
/// be hidden.
Hide,
}
impl Debug for Meta {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::Link(dest) => write!(f, "Link({dest:?})"),
Self::Elem(content) => write!(f, "Elem({:?})", content.func()),
Self::Hide => f.pad("Hide"),
}
}
}
impl Repr for Meta {
fn repr(&self) -> EcoString {
eco_format!("{self:?}")
}
}

View File

@ -11,7 +11,7 @@ use crate::engine::Engine;
use crate::foundations::{
elem, Content, NativeElement, Packed, Resolve, Smart, StyleChain, StyledElem,
};
use crate::introspection::{Meta, MetaElem};
use crate::introspection::TagElem;
use crate::layout::{
Abs, AlignElem, Axes, BlockElem, ColbreakElem, ColumnsElem, FixedAlignment, Fr,
Fragment, Frame, FrameItem, LayoutMultiple, LayoutSingle, PlaceElem, Point, Regions,
@ -55,8 +55,8 @@ impl LayoutMultiple for Packed<FlowElem> {
styles = outer.chain(&styled.styles);
}
if child.is::<MetaElem>() {
layouter.layout_meta(styles);
if let Some(elem) = child.to_packed::<TagElem>() {
layouter.layout_tag(elem);
} else if let Some(elem) = child.to_packed::<VElem>() {
layouter.layout_spacing(engine, elem, styles)?;
} else if let Some(placed) = child.to_packed::<PlaceElem>() {
@ -107,6 +107,8 @@ struct FlowLayouter<'a> {
last_was_par: bool,
/// Spacing and layouted blocks for the current region.
items: Vec<FlowItem>,
/// A queue of tags that will be attached to the next frame.
pending_tags: Vec<Content>,
/// A queue of floating elements.
pending_floats: Vec<FlowItem>,
/// Whether we have any footnotes in the current region.
@ -166,7 +168,9 @@ impl FlowItem {
Self::Placed { float: false, .. } => true,
Self::Frame { frame, .. } => {
frame.size().is_zero()
&& frame.items().all(|(_, item)| matches!(item, FrameItem::Meta(..)))
&& frame.items().all(|(_, item)| {
matches!(item, FrameItem::Link(_, _) | FrameItem::Tag(_))
})
}
_ => false,
}
@ -190,6 +194,7 @@ impl<'a> FlowLayouter<'a> {
initial: regions.size,
last_was_par: false,
items: vec![],
pending_tags: vec![],
pending_floats: vec![],
has_footnotes: false,
footnote_config: FootnoteConfig {
@ -202,15 +207,8 @@ impl<'a> FlowLayouter<'a> {
}
/// Place explicit metadata into the flow.
fn layout_meta(&mut self, styles: StyleChain) {
let mut frame = Frame::soft(Size::zero());
frame.meta(styles, true);
self.items.push(FlowItem::Frame {
frame,
align: Axes::splat(FixedAlignment::Start),
sticky: true,
movable: false,
});
fn layout_tag(&mut self, tag: &Packed<TagElem>) {
self.pending_tags.push(tag.elem.clone());
}
/// Layout vertical spacing.
@ -232,6 +230,27 @@ impl<'a> FlowLayouter<'a> {
)
}
/// Layout a placed element.
fn layout_placed(
&mut self,
engine: &mut Engine,
placed: &Packed<PlaceElem>,
styles: StyleChain,
) -> SourceResult<()> {
let float = placed.float(styles);
let clearance = placed.clearance(styles);
let alignment = placed.alignment(styles);
let delta = Axes::new(placed.dx(styles), placed.dy(styles)).resolve(styles);
let x_align = alignment.map_or(FixedAlignment::Center, |align| {
align.x().unwrap_or_default().resolve(styles)
});
let y_align = alignment.map(|align| align.y().map(|y| y.resolve(styles)));
let mut frame = placed.layout(engine, styles, self.regions.base())?.into_frame();
frame.post_process(styles);
let item = FlowItem::Placed { frame, x_align, y_align, delta, float, clearance };
self.layout_item(engine, item)
}
/// Layout a paragraph.
fn layout_par(
&mut self,
@ -279,11 +298,12 @@ impl<'a> FlowLayouter<'a> {
}
}
for (i, frame) in lines.into_iter().enumerate() {
for (i, mut frame) in lines.into_iter().enumerate() {
if i > 0 {
self.layout_item(engine, FlowItem::Absolute(leading, true))?;
}
self.drain_tag(&mut frame);
self.layout_item(
engine,
FlowItem::Frame { frame, align, sticky: false, movable: true },
@ -305,7 +325,8 @@ impl<'a> FlowLayouter<'a> {
let sticky = BlockElem::sticky_in(styles);
let pod = Regions::one(self.regions.base(), Axes::splat(false));
let mut frame = layoutable.layout(engine, styles, pod)?;
frame.meta(styles, false);
self.drain_tag(&mut frame);
frame.post_process(styles);
self.layout_item(
engine,
FlowItem::Frame { frame, align, sticky, movable: true },
@ -314,27 +335,6 @@ impl<'a> FlowLayouter<'a> {
Ok(())
}
/// Layout a placed element.
fn layout_placed(
&mut self,
engine: &mut Engine,
placed: &Packed<PlaceElem>,
styles: StyleChain,
) -> SourceResult<()> {
let float = placed.float(styles);
let clearance = placed.clearance(styles);
let alignment = placed.alignment(styles);
let delta = Axes::new(placed.dx(styles), placed.dy(styles)).resolve(styles);
let x_align = alignment.map_or(FixedAlignment::Center, |align| {
align.x().unwrap_or_default().resolve(styles)
});
let y_align = alignment.map(|align| align.y().map(|y| y.resolve(styles)));
let mut frame = placed.layout(engine, styles, self.regions.base())?.into_frame();
frame.meta(styles, false);
let item = FlowItem::Placed { frame, x_align, y_align, delta, float, clearance };
self.layout_item(engine, item)
}
/// Layout into multiple regions.
fn layout_multiple(
&mut self,
@ -380,7 +380,8 @@ impl<'a> FlowLayouter<'a> {
self.finish_region(engine, false)?;
}
frame.meta(styles, false);
self.drain_tag(&mut frame);
frame.post_process(styles);
self.layout_item(
engine,
FlowItem::Frame { frame, align, sticky, movable: false },
@ -396,6 +397,17 @@ impl<'a> FlowLayouter<'a> {
Ok(())
}
/// Attach currently pending metadata to the frame.
fn drain_tag(&mut self, frame: &mut Frame) {
if !self.pending_tags.is_empty() && !frame.is_empty() {
frame.prepend_multiple(
self.pending_tags
.drain(..)
.map(|elem| (Point::zero(), FrameItem::Tag(elem))),
);
}
}
/// Layout a finished frame.
fn layout_item(
&mut self,
@ -628,6 +640,13 @@ impl<'a> FlowLayouter<'a> {
}
}
if force && !self.pending_tags.is_empty() {
let pos = Point::with_y(offset);
output.push_multiple(
self.pending_tags.drain(..).map(|elem| (pos, FrameItem::Tag(elem))),
);
}
// Advance to the next region.
self.finished.push(output);
self.regions.next();
@ -782,10 +801,10 @@ fn find_footnotes(notes: &mut Vec<Packed<FootnoteElem>>, frame: &Frame) {
for (_, item) in frame.items() {
match item {
FrameItem::Group(group) => find_footnotes(notes, &group.frame),
FrameItem::Meta(Meta::Elem(content), _)
if !notes.iter().any(|note| note.location() == content.location()) =>
FrameItem::Tag(elem)
if !notes.iter().any(|note| note.location() == elem.location()) =>
{
let Some(footnote) = content.to_packed::<FootnoteElem>() else {
let Some(footnote) = elem.to_packed::<FootnoteElem>() else {
continue;
};
notes.push(footnote.clone());

View File

@ -4,11 +4,14 @@ use std::fmt::{self, Debug, Formatter};
use std::num::NonZeroUsize;
use std::sync::Arc;
use crate::foundations::{cast, dict, Dict, StyleChain, Value};
use crate::introspection::{Meta, MetaElem};
use smallvec::SmallVec;
use crate::foundations::{cast, dict, Content, Dict, StyleChain, Value};
use crate::layout::{
Abs, Axes, Corners, FixedAlignment, Length, Point, Rel, Sides, Size, Transform,
Abs, Axes, Corners, FixedAlignment, HideElem, Length, Point, Rel, Sides, Size,
Transform,
};
use crate::model::{Destination, LinkElem};
use crate::syntax::Span;
use crate::text::TextItem;
use crate::utils::{LazyHash, Numeric};
@ -150,6 +153,17 @@ impl Frame {
Arc::make_mut(&mut self.items).push((pos, item));
}
/// Add multiple items at a position in the foreground.
///
/// The first item in the iterator will be the one that is most in the
/// background.
pub fn push_multiple<I>(&mut self, items: I)
where
I: IntoIterator<Item = (Point, FrameItem)>,
{
Arc::make_mut(&mut self.items).extend(items);
}
/// Add a frame at a position in the foreground.
///
/// Automatically decides whether to inline the frame or to include it as a
@ -162,11 +176,6 @@ impl Frame {
}
}
/// Add zero-sized metadata at the origin.
pub fn push_positionless_meta(&mut self, meta: Meta) {
self.push(Point::zero(), FrameItem::Meta(meta, Size::zero()));
}
/// Insert an item at the given layer in the frame.
///
/// This panics if the layer is greater than the number of layers present.
@ -284,33 +293,42 @@ impl Frame {
}
}
/// Attach the metadata from this style chain to the frame.
/// Apply late-stage properties from the style chain to this frame. This
/// includes:
/// - `HideElem::hidden`
/// - `LinkElem::dests`
///
/// If `force` is true, then the metadata is attached even when
/// the frame is empty.
// TODO: when would you want to pass true to `force` as opposed to false?
pub fn meta(&mut self, styles: StyleChain, force: bool) {
if force || !self.is_empty() {
self.meta_iter(MetaElem::data_in(styles));
/// This must be called on all frames produced by elements
/// that manually handle styles (because their children can have varying
/// styles). This currently includes flow, par, and equation.
///
/// Other elements don't manually need to handle it because their parents
/// that result from realization will take care of it and the styles can
/// only apply to them as a whole, not part of it (because they don't manage
/// styles).
pub fn post_process(&mut self, styles: StyleChain) {
if !self.is_empty() {
self.post_process_raw(
LinkElem::dests_in(styles),
HideElem::hidden_in(styles),
);
}
}
/// Attach metadata from an iterator.
pub fn meta_iter(&mut self, iter: impl IntoIterator<Item = Meta>) {
let mut hide = false;
/// Apply raw late-stage properties from the raw data.
pub fn post_process_raw(&mut self, dests: SmallVec<[Destination; 1]>, hide: bool) {
if !self.is_empty() {
let size = self.size;
self.prepend_multiple(iter.into_iter().filter_map(|meta| {
if matches!(meta, Meta::Hide) {
hide = true;
None
} else {
Some((Point::zero(), FrameItem::Meta(meta, size)))
}
}));
self.push_multiple(
dests
.into_iter()
.map(|dest| (Point::zero(), FrameItem::Link(dest, size))),
);
if hide {
self.hide();
}
}
}
/// Hide all content in the frame, but keep metadata.
pub fn hide(&mut self) {
@ -319,7 +337,7 @@ impl Frame {
group.frame.hide();
!group.frame.is_empty()
}
FrameItem::Meta(Meta::Elem(_), _) => true,
FrameItem::Tag(_) => true,
_ => false,
});
}
@ -488,8 +506,10 @@ pub enum FrameItem {
Shape(Shape, Span),
/// An image and its size.
Image(Image, Size, Span),
/// Meta information and the region it applies to.
Meta(Meta, Size),
/// An internal or external link to a destination.
Link(Destination, Size),
/// An introspectable element that produced something within this frame.
Tag(Content),
}
impl Debug for FrameItem {
@ -499,7 +519,8 @@ impl Debug for FrameItem {
Self::Text(text) => write!(f, "{text:?}"),
Self::Shape(shape, _) => write!(f, "{shape:?}"),
Self::Image(image, _, _) => write!(f, "{image:?}"),
Self::Meta(meta, _) => write!(f, "{meta:?}"),
Self::Link(dest, _) => write!(f, "Link({dest:?})"),
Self::Tag(elem) => write!(f, "Tag({elem:?})"),
}
}
}

View File

@ -1,9 +1,6 @@
use smallvec::smallvec;
use crate::diag::SourceResult;
use crate::engine::Engine;
use crate::foundations::{elem, Content, Packed, Show, StyleChain};
use crate::introspection::{Meta, MetaElem};
/// Hides content without affecting layout.
///
@ -22,11 +19,16 @@ pub struct HideElem {
/// The content to hide.
#[required]
pub body: Content,
/// This style is set on the content contained in the `hide` element.
#[internal]
#[ghost]
pub hidden: bool,
}
impl Show for Packed<HideElem> {
#[typst_macros::time(name = "hide", span = self.span())]
fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
Ok(self.body().clone().styled(MetaElem::set_data(smallvec![Meta::Hide])))
Ok(self.body().clone().styled(HideElem::set_hidden(true)))
}
}

View File

@ -14,10 +14,10 @@ use crate::diag::{bail, SourceResult};
use crate::engine::{Engine, Route};
use crate::eval::Tracer;
use crate::foundations::{Content, Packed, Resolve, Smart, StyleChain, StyledElem};
use crate::introspection::{Introspector, Locator, MetaElem};
use crate::introspection::{Introspector, Locator, TagElem};
use crate::layout::{
Abs, AlignElem, Axes, BoxElem, Dir, Em, FixedAlignment, Fr, Fragment, Frame, HElem,
Point, Regions, Size, Sizing, Spacing,
Abs, AlignElem, Axes, BoxElem, Dir, Em, FixedAlignment, Fr, Fragment, Frame,
FrameItem, HElem, Point, Regions, Size, Sizing, Spacing,
};
use crate::math::{EquationElem, MathParItem};
use crate::model::{Linebreaks, ParElem};
@ -201,8 +201,8 @@ enum Segment<'a> {
Equation(Vec<MathParItem>),
/// A box with arbitrary content.
Box(&'a Packed<BoxElem>, bool),
/// Metadata.
Meta,
/// A tag.
Tag(&'a Packed<TagElem>),
}
impl Segment<'_> {
@ -220,7 +220,7 @@ impl Segment<'_> {
.chain([LTR_ISOLATE, POP_ISOLATE])
.map(char::len_utf8)
.sum(),
Self::Meta => 0,
Self::Tag(_) => 0,
}
}
}
@ -236,8 +236,8 @@ enum Item<'a> {
Fractional(Fr, Option<(&'a Packed<BoxElem>, StyleChain<'a>)>),
/// Layouted inline-level content.
Frame(Frame),
/// Metadata.
Meta(Frame),
/// A tag.
Tag(&'a Packed<TagElem>),
/// An item that is invisible and needs to be skipped, e.g. a Unicode
/// isolate.
Skip(char),
@ -266,7 +266,7 @@ impl<'a> Item<'a> {
Self::Text(shaped) => shaped.text.len(),
Self::Absolute(_) | Self::Fractional(_, _) => SPACING_REPLACE.len_utf8(),
Self::Frame(_) => OBJ_REPLACE.len_utf8(),
Self::Meta(_) => 0,
Self::Tag(_) => 0,
Self::Skip(c) => c.len_utf8(),
}
}
@ -277,7 +277,7 @@ impl<'a> Item<'a> {
Self::Text(shaped) => shaped.width,
Self::Absolute(v) => *v,
Self::Frame(frame) => frame.width(),
Self::Fractional(_, _) | Self::Meta(_) => Abs::zero(),
Self::Fractional(_, _) | Self::Tag(_) => Abs::zero(),
Self::Skip(_) => Abs::zero(),
}
}
@ -531,6 +531,9 @@ fn collect<'a>(
} else if child.is::<SpaceElem>()
|| child.is::<HElem>()
|| child.is::<LinebreakElem>()
// This is a temporary hack. We should rather skip these
// and peek at the next child.
|| child.is::<TagElem>()
{
Some(SPACING_REPLACE)
} else {
@ -548,7 +551,7 @@ fn collect<'a>(
let mut items = elem.layout_inline(engine, styles, pod)?;
for item in &mut items {
let MathParItem::Frame(frame) = item else { continue };
frame.meta(styles, false);
frame.post_process(styles);
}
full.push(LTR_ISOLATE);
full.extend(items.iter().map(MathParItem::text));
@ -558,8 +561,8 @@ fn collect<'a>(
let frac = elem.width(styles).is_fractional();
full.push(if frac { SPACING_REPLACE } else { OBJ_REPLACE });
Segment::Box(elem, frac)
} else if child.is::<MetaElem>() {
Segment::Meta
} else if let Some(elem) = child.to_packed::<TagElem>() {
Segment::Tag(elem)
} else {
bail!(child.span(), "unexpected paragraph child");
};
@ -643,15 +646,13 @@ fn prepare<'a>(
} else {
let pod = Regions::one(region, Axes::splat(false));
let mut frame = elem.layout(engine, styles, pod)?;
frame.meta(styles, false);
frame.post_process(styles);
frame.translate(Point::with_y(TextElem::baseline_in(styles)));
items.push(Item::Frame(frame));
}
}
Segment::Meta => {
let mut frame = Frame::soft(Size::zero());
frame.meta(styles, true);
items.push(Item::Meta(frame));
Segment::Tag(tag) => {
items.push(Item::Tag(tag));
}
}
@ -687,7 +688,7 @@ fn prepare<'a>(
/// See Requirements for Chinese Text Layout, Section 3.2.2 Mixed Text Composition in Horizontal
/// Written Mode
fn add_cjk_latin_spacing(items: &mut [Item]) {
let mut items = items.iter_mut().filter(|x| !matches!(x, Item::Meta(_))).peekable();
let mut items = items.iter_mut().filter(|x| !matches!(x, Item::Tag(_))).peekable();
let mut prev: Option<&ShapedGlyph> = None;
while let Some(item) = items.next() {
let Some(text) = item.text_mut() else {
@ -1410,7 +1411,7 @@ fn commit(
let region = Size::new(amount, full);
let pod = Regions::one(region, Axes::new(true, false));
let mut frame = elem.layout(engine, *styles, pod)?;
frame.meta(*styles, false);
frame.post_process(*styles);
frame.translate(Point::with_y(TextElem::baseline_in(*styles)));
push(&mut offset, frame);
} else {
@ -1420,12 +1421,17 @@ fn commit(
Item::Text(shaped) => {
let mut frame =
shaped.build(engine, justification_ratio, extra_justification);
frame.meta(shaped.styles, false);
frame.post_process(shaped.styles);
push(&mut offset, frame);
}
Item::Frame(frame) | Item::Meta(frame) => {
Item::Frame(frame) => {
push(&mut offset, frame.clone());
}
Item::Tag(tag) => {
let mut frame = Frame::soft(Size::zero());
frame.push(Point::zero(), FrameItem::Tag(tag.elem.clone()));
frames.push((offset, frame));
}
Item::Skip(_) => {}
}
}

View File

@ -6,11 +6,11 @@ use ttf_parser::{GlyphId, Rect};
use unicode_math_class::MathClass;
use crate::foundations::StyleChain;
use crate::introspection::{Meta, MetaElem};
use crate::layout::{Abs, Corner, Em, Frame, FrameItem, Point, Size};
use crate::layout::{Abs, Corner, Em, Frame, FrameItem, HideElem, Point, Size};
use crate::math::{
scaled_font_size, EquationElem, Limits, MathContext, MathSize, Scaled,
};
use crate::model::{Destination, LinkElem};
use crate::syntax::Span;
use crate::text::{Font, Glyph, Lang, Region, TextElem, TextItem};
use crate::visualize::Paint;
@ -218,7 +218,8 @@ pub struct GlyphFragment {
pub class: MathClass,
pub math_size: MathSize,
pub span: Span,
pub meta: SmallVec<[Meta; 1]>,
pub dests: SmallVec<[Destination; 1]>,
pub hidden: bool,
pub limits: Limits,
}
@ -273,7 +274,8 @@ impl GlyphFragment {
accent_attach: Abs::zero(),
class,
span,
meta: MetaElem::data_in(styles),
dests: LinkElem::dests_in(styles),
hidden: HideElem::hidden_in(styles),
};
fragment.set_id(ctx, id);
fragment
@ -357,7 +359,7 @@ impl GlyphFragment {
let mut frame = Frame::soft(size);
frame.set_baseline(self.ascent);
frame.push(Point::with_y(self.ascent + self.shift), FrameItem::Text(item));
frame.meta_iter(self.meta);
frame.post_process_raw(self.dests, self.hidden);
frame
}
@ -436,7 +438,7 @@ impl FrameFragment {
pub fn new(ctx: &MathContext, styles: StyleChain, mut frame: Frame) -> Self {
let base_ascent = frame.ascent();
let accent_attach = frame.width() / 2.0;
frame.meta(styles, false);
frame.post_process(styles);
Self {
frame,
font_size: scaled_font_size(ctx, styles),

View File

@ -41,12 +41,12 @@ use self::row::*;
use self::spacing::*;
use crate::diag::SourceResult;
use crate::foundations::SequenceElem;
use crate::foundations::StyledElem;
use crate::foundations::{
category, Category, Content, Module, Resolve, Scope, StyleChain,
category, Category, Content, Module, Resolve, Scope, SequenceElem, StyleChain,
StyledElem,
};
use crate::layout::{BoxElem, HElem, Spacing};
use crate::introspection::TagElem;
use crate::layout::{BoxElem, Frame, FrameItem, HElem, Point, Size, Spacing};
use crate::realize::{process, BehavedBuilder};
use crate::text::{LinebreakElem, SpaceElem, TextElem};
@ -296,6 +296,13 @@ impl LayoutMath for Content {
return Ok(());
}
if let Some(tag) = self.to_packed::<TagElem>() {
let mut frame = Frame::soft(Size::zero());
frame.push(Point::zero(), FrameItem::Tag(tag.elem.clone()));
ctx.push(FrameFragment::new(ctx, styles, frame));
return Ok(());
}
if let Some(elem) = self.with::<dyn LayoutMath>() {
return elem.layout_math(ctx, styles);
}

View File

@ -167,7 +167,7 @@ fn assemble(
let mut frame = Frame::soft(size);
let mut offset = Abs::zero();
frame.set_baseline(baseline);
frame.meta_iter(base.meta);
frame.post_process_raw(base.dests, base.hidden);
for (fragment, advance) in selected {
let pos = if horizontal {

View File

@ -1,4 +1,5 @@
use ecow::{eco_format, EcoString};
use smallvec::SmallVec;
use crate::diag::{At, SourceResult};
use crate::engine::Engine;
@ -79,6 +80,11 @@ pub struct LinkElem {
_ => args.expect("body")?,
})]
pub body: Content,
/// This style is set on the content contained in the `link` element.
#[internal]
#[ghost]
pub dests: SmallVec<[Destination; 1]>,
}
impl LinkElem {

View File

@ -23,7 +23,7 @@ use crate::engine::{Engine, Route};
use crate::foundations::{
Content, NativeElement, Packed, SequenceElem, StyleChain, StyledElem, Styles,
};
use crate::introspection::MetaElem;
use crate::introspection::TagElem;
use crate::layout::{
AlignElem, BlockElem, BoxElem, ColbreakElem, FlowElem, HElem, LayoutMultiple,
LayoutSingle, PageElem, PagebreakElem, Parity, PlaceElem, VElem,
@ -389,7 +389,7 @@ impl<'a> FlowBuilder<'a> {
if content.is::<VElem>()
|| content.is::<ColbreakElem>()
|| content.is::<MetaElem>()
|| content.is::<TagElem>()
|| content.is::<PlaceElem>()
{
self.0.push(content, styles);
@ -451,7 +451,7 @@ impl<'a> ParBuilder<'a> {
/// content could not be merged, and paragraph building should be
/// interrupted so that the content can be added elsewhere.
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
if content.is::<MetaElem>() {
if content.is::<TagElem>() {
if !self.0.is_empty() {
self.0.push(content, styles);
return true;
@ -612,7 +612,7 @@ impl<'a> CiteGroupBuilder<'a> {
/// interrupted so that the content can be added elsewhere.
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
if !self.items.is_empty()
&& (content.is::<SpaceElem>() || content.is::<MetaElem>())
&& (content.is::<SpaceElem>() || content.is::<TagElem>())
{
self.staged.push((content, styles));
return true;

View File

@ -1,7 +1,6 @@
use std::cell::OnceCell;
use comemo::{Track, Tracked};
use smallvec::smallvec;
use crate::diag::SourceResult;
use crate::engine::Engine;
@ -9,7 +8,7 @@ use crate::foundations::{
Content, Context, Packed, Recipe, RecipeIndex, Regex, Selector, Show, ShowSet, Style,
StyleChain, Styles, Synthesize, Transformation,
};
use crate::introspection::{Locatable, Meta, MetaElem};
use crate::introspection::{Locatable, TagElem};
use crate::text::TextElem;
use crate::utils::{hash128, BitSet};
@ -57,9 +56,9 @@ pub fn process(
// If the element isn't yet prepared (we're seeing it for the first time),
// prepare it.
let mut meta = None;
let mut tag = None;
if !prepared {
meta = prepare(engine, &mut target, &mut map, styles)?;
tag = prepare(engine, &mut target, &mut map, styles)?;
}
// Apply a step, if there is one.
@ -76,9 +75,9 @@ pub fn process(
None => target,
};
// If necessary, apply metadata generated in the preparation.
if let Some(meta) = meta {
output += meta.pack();
// If necessary, add the tag generated in the preparation.
if let Some(tag) = tag {
output = tag + output;
}
Ok(Some(output.styled_with_map(map)))
@ -194,7 +193,7 @@ fn prepare(
target: &mut Content,
map: &mut Styles,
styles: StyleChain,
) -> SourceResult<Option<Packed<MetaElem>>> {
) -> SourceResult<Option<Content>> {
// Generate a location for the element, which uniquely identifies it in
// the document. This has some overhead, so we only do it for elements
// that are explicitly marked as locatable and labelled elements.
@ -225,24 +224,18 @@ fn prepare(
// available in rules.
target.materialize(styles.chain(map));
if located {
// Apply metadata to be able to find the element in the frames. Do this
// after synthesis and materialization, so that it includes the
// synthesized fields. Do it before marking as prepared so that show-set
// rules will apply to this element when queried. This adds a style to
// the whole element's subtree identifying it as belonging to the
// element.
map.set(MetaElem::set_data(smallvec![Meta::Elem(target.clone())]));
}
// If the element is locatable, create a tag element to be able to find the
// element in the frames after layout. Do this after synthesis and
// materialization, so that it includes the synthesized fields. Do it before
// marking as prepared so that show-set rules will apply to this element
// when queried.
let tag = located.then(|| TagElem::packed(target.clone()));
// Ensure that this preparation only runs once by marking the element as
// prepared.
target.mark_prepared();
// If the element is located, return an extra meta elem that will be
// attached so that the metadata styles are not lost in case the element's
// show rule results in nothing.
Ok(located.then(|| Packed::new(MetaElem::new()).spanned(target.span())))
Ok(tag)
}
/// Apply a step.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 881 B

After

Width:  |  Height:  |  Size: 850 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -7,7 +7,6 @@ use tiny_skia as sk;
use typst::diag::SourceDiagnostic;
use typst::eval::Tracer;
use typst::foundations::Smart;
use typst::introspection::Meta;
use typst::layout::{Abs, Frame, FrameItem, Page, Transform};
use typst::model::Document;
use typst::visualize::Color;
@ -392,7 +391,7 @@ fn render_links(canvas: &mut sk::Pixmap, ts: sk::Transform, frame: &Frame) {
let ts = ts.pre_concat(to_sk_transform(&group.transform));
render_links(canvas, ts, &group.frame);
}
FrameItem::Meta(Meta::Link(_), size) => {
FrameItem::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();
@ -416,7 +415,7 @@ fn skippable(page: &Page) -> bool {
fn skippable_frame(frame: &Frame) -> bool {
frame.items().all(|(_, item)| match item {
FrameItem::Group(group) => skippable_frame(&group.frame),
FrameItem::Meta(..) => true,
FrameItem::Tag(_) => true,
_ => false,
})
}

View File

@ -4,6 +4,13 @@
= Introduction <intro>
#context test(locate(<intro>).position().y, 20pt)
--- locate-position-trailing-tag ---
// Test locating the position of a tag with no following content.
#context test(here().position().y, 10pt)
#box[]
#v(10pt)
#context test(here().position().y, 20pt)
--- locate-missing-label ---
// Error: 10-25 label `<intro>` does not exist in the document
#context locate(<intro>)