summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-03-15 12:33:38 +0100
committerLaurenz <laurmaedje@gmail.com>2023-03-15 12:33:38 +0100
commitecb5543985cc0788d9c01e8c2e28d8ca6d8e19b6 (patch)
treefcd6dfaad54d4076ee6d767ceb5f388b3c84225b /src
parent85678118086b29b3820813411cf382fa283b39f0 (diff)
Node links
Diffstat (limited to 'src')
-rw-r--r--src/doc.rs34
-rw-r--r--src/export/pdf/mod.rs3
-rw-r--r--src/export/pdf/page.rs37
-rw-r--r--src/export/render.rs2
-rw-r--r--src/ide/analyze.rs5
-rw-r--r--src/ide/jump.rs22
-rw-r--r--src/model/typeset.rs94
7 files changed, 130 insertions, 67 deletions
diff --git a/src/doc.rs b/src/doc.rs
index ffa056cd..6add64fc 100644
--- a/src/doc.rs
+++ b/src/doc.rs
@@ -14,7 +14,7 @@ use crate::geom::{
Numeric, Paint, Point, Rel, RgbaColor, Shape, Sides, Size, Stroke, Transform,
};
use crate::image::Image;
-use crate::model::{node, Content, Fold, StyleChain};
+use crate::model::{node, Content, Fold, Introspector, StableId, StyleChain};
use crate::syntax::Span;
/// A finished document with metadata and page frames.
@@ -276,7 +276,7 @@ impl Frame {
return;
}
for meta in MetaNode::data_in(styles) {
- if matches!(meta, Meta::Hidden) {
+ if matches!(meta, Meta::Hide) {
self.clear();
break;
}
@@ -593,13 +593,37 @@ cast_to_value! {
/// Meta information that isn't visible or renderable.
#[derive(Debug, Clone, Hash)]
pub enum Meta {
+ /// Indicates that the content should be hidden.
+ Hide,
/// An internal or external link.
- Link(Destination),
+ Link(Link),
/// An identifiable piece of content that produces something within the
/// area this metadata is attached to.
Node(Content),
- /// Indicates that the content is hidden.
- Hidden,
+}
+
+/// A possibly unresolved link.
+#[derive(Debug, Clone, Hash)]
+pub enum Link {
+ /// A fully resolved.
+ Dest(Destination),
+ /// An unresolved link to a node.
+ Node(StableId),
+}
+
+impl Link {
+ /// Resolve a destination.
+ ///
+ /// Needs to lazily provide an introspector.
+ pub fn resolve<'a>(
+ &self,
+ introspector: impl FnOnce() -> &'a Introspector,
+ ) -> Option<Destination> {
+ match self {
+ Self::Dest(dest) => Some(dest.clone()),
+ Self::Node(id) => introspector().location(*id).map(Destination::Internal),
+ }
+ }
}
/// Host for metadata.
diff --git a/src/export/pdf/mod.rs b/src/export/pdf/mod.rs
index 3813bad5..bdbb2bb7 100644
--- a/src/export/pdf/mod.rs
+++ b/src/export/pdf/mod.rs
@@ -19,6 +19,7 @@ use crate::doc::{Document, Lang};
use crate::font::Font;
use crate::geom::{Abs, Dir, Em};
use crate::image::Image;
+use crate::model::Introspector;
/// Export a document into a PDF file.
///
@@ -40,6 +41,7 @@ const D65_GRAY: Name<'static> = Name(b"d65gray");
/// Context for exporting a whole PDF document.
pub struct PdfContext<'a> {
document: &'a Document,
+ introspector: Introspector,
writer: PdfWriter,
pages: Vec<Page>,
page_heights: Vec<f32>,
@@ -61,6 +63,7 @@ impl<'a> PdfContext<'a> {
let page_tree_ref = alloc.bump();
Self {
document,
+ introspector: Introspector::new(&document.pages),
writer: PdfWriter::new(),
pages: vec![],
page_heights: vec![],
diff --git a/src/export/pdf/page.rs b/src/export/pdf/page.rs
index 94af6c70..7f8c20ef 100644
--- a/src/export/pdf/page.rs
+++ b/src/export/pdf/page.rs
@@ -4,7 +4,7 @@ use pdf_writer::writers::ColorSpace;
use pdf_writer::{Content, Filter, Finish, Name, Rect, Ref, Str};
use super::{deflate, AbsExt, EmExt, PdfContext, RefExt, D65_GRAY, SRGB};
-use crate::doc::{Destination, Element, Frame, Group, Meta, Text};
+use crate::doc::{Destination, Element, Frame, Group, Link, Meta, Text};
use crate::font::Font;
use crate::geom::{
self, Abs, Color, Em, Geometry, Numeric, Paint, Point, Ratio, Shape, Size, Stroke,
@@ -110,22 +110,31 @@ fn write_page(ctx: &mut PdfContext, page: Page) {
page_writer.contents(content_id);
let mut annotations = page_writer.annotations();
- for (dest, rect) in page.links {
- let mut link = annotations.push();
- link.subtype(AnnotationType::Link).rect(rect);
- link.border(0.0, 0.0, 0.0, None);
+ for (link, rect) in page.links {
+ let mut annotation = annotations.push();
+ annotation.subtype(AnnotationType::Link).rect(rect);
+ annotation.border(0.0, 0.0, 0.0, None);
+
+ let dest = link.resolve(|| &ctx.introspector);
+ let Some(dest) = dest else { continue };
+
match dest {
Destination::Url(uri) => {
- link.action().action_type(ActionType::Uri).uri(Str(uri.as_bytes()));
+ annotation
+ .action()
+ .action_type(ActionType::Uri)
+ .uri(Str(uri.as_bytes()));
}
Destination::Internal(loc) => {
let index = loc.page.get() - 1;
+ let y = (loc.pos.y - Abs::pt(10.0)).max(Abs::zero());
if let Some(&height) = ctx.page_heights.get(index) {
- link.action()
+ annotation
+ .action()
.action_type(ActionType::GoTo)
.destination_direct()
.page(ctx.page_refs[index])
- .xyz(loc.pos.x.to_f32(), height - loc.pos.y.to_f32(), None);
+ .xyz(loc.pos.x.to_f32(), height - y.to_f32(), None);
}
}
}
@@ -148,7 +157,7 @@ pub struct Page {
/// The page's content stream.
pub content: Content,
/// Links in the PDF coordinate system.
- pub links: Vec<(Destination, Rect)>,
+ pub links: Vec<(Link, Rect)>,
}
/// An exporter for the contents of a single PDF page.
@@ -159,7 +168,7 @@ struct PageContext<'a, 'b> {
state: State,
saves: Vec<State>,
bottom: f32,
- links: Vec<(Destination, Rect)>,
+ links: Vec<(Link, Rect)>,
}
/// A simulated graphics state used to deduplicate graphics state changes and
@@ -287,9 +296,9 @@ fn write_frame(ctx: &mut PageContext, frame: &Frame) {
Element::Shape(shape) => write_shape(ctx, x, y, shape),
Element::Image(image, size) => write_image(ctx, x, y, image, *size),
Element::Meta(meta, size) => match meta {
- Meta::Link(dest) => write_link(ctx, pos, dest, *size),
+ Meta::Link(link) => write_link(ctx, pos, link, *size),
Meta::Node(_) => {}
- Meta::Hidden => {}
+ Meta::Hide => {}
},
}
}
@@ -449,7 +458,7 @@ fn write_image(ctx: &mut PageContext, x: f32, y: f32, image: &Image, size: Size)
}
/// Save a link for later writing in the annotations dictionary.
-fn write_link(ctx: &mut PageContext, pos: Point, dest: &Destination, size: Size) {
+fn write_link(ctx: &mut PageContext, pos: Point, link: &Link, size: Size) {
let mut min_x = Abs::inf();
let mut min_y = Abs::inf();
let mut max_x = -Abs::inf();
@@ -475,5 +484,5 @@ fn write_link(ctx: &mut PageContext, pos: Point, dest: &Destination, size: Size)
let y2 = min_y.to_f32();
let rect = Rect::new(x1, y1, x2, y2);
- ctx.links.push((dest.clone(), rect));
+ ctx.links.push((link.clone(), rect));
}
diff --git a/src/export/render.rs b/src/export/render.rs
index 2fca8827..bf183ebe 100644
--- a/src/export/render.rs
+++ b/src/export/render.rs
@@ -61,7 +61,7 @@ fn render_frame(
Element::Meta(meta, _) => match meta {
Meta::Link(_) => {}
Meta::Node(_) => {}
- Meta::Hidden => {}
+ Meta::Hide => {}
},
}
}
diff --git a/src/ide/analyze.rs b/src/ide/analyze.rs
index 7338ba57..ed868e53 100644
--- a/src/ide/analyze.rs
+++ b/src/ide/analyze.rs
@@ -74,12 +74,11 @@ pub fn analyze_labels(
frames: &[Frame],
) -> (Vec<(Label, Option<EcoString>)>, usize) {
let mut output = vec![];
- let mut introspector = Introspector::new();
+ let introspector = Introspector::new(frames);
let items = &world.library().items;
- introspector.update(frames);
// Labels in the document.
- for node in introspector.iter() {
+ for node in introspector.nodes() {
let Some(label) = node.label() else { continue };
let details = node
.field("caption")
diff --git a/src/ide/jump.rs b/src/ide/jump.rs
index 033d0f7f..95f2fa02 100644
--- a/src/ide/jump.rs
+++ b/src/ide/jump.rs
@@ -2,6 +2,7 @@ use std::num::NonZeroUsize;
use crate::doc::{Destination, Element, Frame, Location, Meta};
use crate::geom::{Point, Size};
+use crate::model::Introspector;
use crate::syntax::{LinkedNode, Source, SourceId, Span, SyntaxKind};
use crate::World;
@@ -15,11 +16,19 @@ pub enum Jump {
}
/// Determine where to jump to based on a click in a frame.
-pub fn jump_from_click(world: &dyn World, frame: &Frame, click: Point) -> Option<Jump> {
+pub fn jump_from_click(
+ world: &dyn World,
+ frames: &[Frame],
+ frame: &Frame,
+ click: Point,
+) -> Option<Jump> {
+ let mut introspector = None;
+
for (mut pos, element) in frame.elements() {
if let Element::Group(group) = element {
// TODO: Handle transformation.
- if let Some(span) = jump_from_click(world, &group.frame, click - pos) {
+ if let Some(span) = jump_from_click(world, frames, &group.frame, click - pos)
+ {
return Some(span);
}
}
@@ -55,9 +64,14 @@ pub fn jump_from_click(world: &dyn World, frame: &Frame, click: Point) -> Option
}
}
- if let Element::Meta(Meta::Link(dest), size) = element {
+ if let Element::Meta(Meta::Link(link), size) = element {
if is_in_rect(pos, *size, click) {
- return Some(Jump::Dest(dest.clone()));
+ let dest = link.resolve(|| {
+ introspector.get_or_insert_with(|| Introspector::new(frames))
+ });
+
+ let Some(dest) = dest else { continue };
+ return Some(Jump::Dest(dest));
}
}
}
diff --git a/src/model/typeset.rs b/src/model/typeset.rs
index 8120f58f..f68d337d 100644
--- a/src/model/typeset.rs
+++ b/src/model/typeset.rs
@@ -20,7 +20,7 @@ pub fn typeset(world: Tracked<dyn World>, content: &Content) -> SourceResult<Doc
let mut document;
let mut iter = 0;
- let mut introspector = Introspector::new();
+ let mut introspector = Introspector::new(&[]);
// Relayout until all introspections stabilize.
// If that doesn't happen within five attempts, we give up.
@@ -40,8 +40,6 @@ pub fn typeset(world: Tracked<dyn World>, content: &Content) -> SourceResult<Doc
}
}
- log::debug!("Took {iter} iterations");
-
Ok(document)
}
@@ -81,14 +79,11 @@ impl<'a> Vt<'a> {
self.introspector.init()
}
- /// Locate all metadata matches for the given selector.
- pub fn locate(&self, selector: Selector) -> impl Iterator<Item = &Content> {
- self.introspector.locate(selector).into_iter()
- }
-
/// Locate all metadata matches for the given node.
- pub fn locate_node<T: Node>(&self) -> impl Iterator<Item = &T> {
- self.locate(Selector::node::<T>())
+ pub fn query_node<T: Node>(&self) -> impl Iterator<Item = &T> {
+ self.introspector
+ .query(Selector::node::<T>())
+ .into_iter()
.map(|content| content.to::<T>().unwrap())
}
}
@@ -97,7 +92,14 @@ impl<'a> Vt<'a> {
///
/// This struct is created by [`Vt::identify`].
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub struct StableId(u128, u64);
+pub struct StableId(u128, u64, u64);
+
+impl StableId {
+ /// Produce a variant of this id.
+ pub fn variant(self, n: u64) -> Self {
+ Self(self.0, self.1, n)
+ }
+}
/// Provides stable identities to nodes.
#[derive(Clone)]
@@ -115,7 +117,7 @@ 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);
+ let id = StableId(hash, *slot, 0);
*slot += 1;
id
}
@@ -124,35 +126,33 @@ impl StabilityProvider {
/// Provides access to information about the document.
pub struct Introspector {
init: bool,
- nodes: Vec<Content>,
+ nodes: Vec<(Content, Location)>,
queries: RefCell<Vec<(Selector, u128)>>,
}
impl Introspector {
/// Create a new introspector.
- pub fn new() -> Self {
- Self {
+ pub fn new(frames: &[Frame]) -> Self {
+ let mut introspector = Self {
init: false,
nodes: vec![],
queries: RefCell::new(vec![]),
- }
+ };
+ introspector.extract_from_frames(frames);
+ introspector
}
/// Update the information given new frames and return whether we can stop
/// layouting.
pub fn update(&mut self, frames: &[Frame]) -> bool {
self.nodes.clear();
-
- for (i, frame) in frames.iter().enumerate() {
- let page = NonZeroUsize::new(1 + i).unwrap();
- self.extract(frame, page, Transform::identity());
- }
+ self.extract_from_frames(frames);
let was_init = std::mem::replace(&mut self.init, true);
let queries = std::mem::take(&mut self.queries).into_inner();
for (selector, hash) in &queries {
- let nodes = self.locate_impl(selector);
+ let nodes = self.query_impl(selector);
if hash128(&nodes) != *hash {
return false;
}
@@ -166,32 +166,36 @@ impl Introspector {
}
/// Iterate over all nodes.
- pub fn iter(&self) -> impl Iterator<Item = &Content> {
- self.nodes.iter()
+ pub fn nodes(&self) -> impl Iterator<Item = &Content> {
+ self.nodes.iter().map(|(node, _)| node)
+ }
+
+ /// Extract metadata from frames.
+ fn extract_from_frames(&mut self, frames: &[Frame]) {
+ for (i, frame) in frames.iter().enumerate() {
+ let page = NonZeroUsize::new(1 + i).unwrap();
+ self.extract_from_frame(frame, page, Transform::identity());
+ }
}
/// Extract metadata from a frame.
- fn extract(&mut self, frame: &Frame, page: NonZeroUsize, ts: Transform) {
+ fn extract_from_frame(&mut self, frame: &Frame, page: NonZeroUsize, ts: Transform) {
for (pos, element) in frame.elements() {
match element {
Element::Group(group) => {
let ts = ts
.pre_concat(Transform::translate(pos.x, pos.y))
.pre_concat(group.transform);
- self.extract(&group.frame, page, ts);
+ self.extract_from_frame(&group.frame, page, ts);
}
- Element::Meta(Meta::Node(content), _) => {
+ Element::Meta(Meta::Node(content), _)
if !self
.nodes
.iter()
- .any(|prev| prev.stable_id() == content.stable_id())
- {
- let pos = pos.transform(ts);
- let mut node = content.clone();
- let loc = Location { page, pos };
- node.push_field("location", loc);
- self.nodes.push(node);
- }
+ .any(|(prev, _)| prev.stable_id() == content.stable_id()) =>
+ {
+ let pos = pos.transform(ts);
+ self.nodes.push((content.clone(), Location { page, pos }));
}
_ => {}
}
@@ -206,19 +210,29 @@ impl Introspector {
self.init
}
- /// Locate all metadata matches for the given selector.
- pub fn locate(&self, selector: Selector) -> Vec<&Content> {
- let nodes = self.locate_impl(&selector);
+ /// Query for all metadata matches for the given selector.
+ pub fn query(&self, selector: Selector) -> Vec<&Content> {
+ let nodes = self.query_impl(&selector);
let mut queries = self.queries.borrow_mut();
if !queries.iter().any(|(prev, _)| prev == &selector) {
queries.push((selector, hash128(&nodes)));
}
nodes
}
+
+ /// Find the page number for the given stable id.
+ pub fn page(&self, id: StableId) -> Option<NonZeroUsize> {
+ Some(self.location(id)?.page)
+ }
+
+ /// Find the location for the given stable id.
+ pub fn location(&self, id: StableId) -> Option<Location> {
+ Some(self.nodes.iter().find(|(node, _)| node.stable_id() == Some(id))?.1)
+ }
}
impl Introspector {
- fn locate_impl(&self, selector: &Selector) -> Vec<&Content> {
- self.nodes.iter().filter(|target| selector.matches(target)).collect()
+ fn query_impl(&self, selector: &Selector) -> Vec<&Content> {
+ self.nodes().filter(|node| selector.matches(node)).collect()
}
}