Refactor frame metadata into tags (#4212)
@ -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()),
|
||||
|
@ -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(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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.
|
||||
/// Holds a locatable element that was realized.
|
||||
///
|
||||
/// 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]>,
|
||||
/// 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:?}")
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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:?})"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)))
|
||||
}
|
||||
}
|
||||
|
@ -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(_) => {}
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
Before Width: | Height: | Size: 881 B After Width: | Height: | Size: 850 B |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
BIN
tests/ref/locate-position-trailing-tag.png
Normal file
After Width: | Height: | Size: 74 B |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
@ -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,
|
||||
})
|
||||
}
|
||||
|
@ -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>)
|
||||
|