diff --git a/library/src/layout/container.rs b/library/src/layout/container.rs index 009063f0a..a48933a70 100644 --- a/library/src/layout/container.rs +++ b/library/src/layout/container.rs @@ -141,7 +141,7 @@ impl Layout for BoxNode { if fill.is_some() || stroke.iter().any(Option::is_some) { let outset = self.outset(styles); let radius = self.radius(styles); - frame.fill_and_stroke(fill, stroke, outset, radius); + frame.fill_and_stroke(fill, stroke, outset, radius, self.span()); } // Apply metadata. @@ -383,7 +383,7 @@ impl Layout for BlockNode { let outset = self.outset(styles); let radius = self.radius(styles); for frame in frames.iter_mut().skip(skip as usize) { - frame.fill_and_stroke(fill, stroke, outset, radius); + frame.fill_and_stroke(fill, stroke, outset, radius, self.span()); } } diff --git a/library/src/layout/table.rs b/library/src/layout/table.rs index 012e63acc..6daafdbbf 100644 --- a/library/src/layout/table.rs +++ b/library/src/layout/table.rs @@ -166,14 +166,20 @@ impl Layout for TableNode { for offset in points(rows.iter().map(|piece| piece.height)) { let target = Point::with_x(frame.width() + thickness); let hline = Geometry::Line(target).stroked(stroke); - frame.prepend(Point::new(-half, offset), Element::Shape(hline)); + frame.prepend( + Point::new(-half, offset), + Element::Shape(hline, self.span()), + ); } // Render vertical lines. for offset in points(layout.cols.iter().copied()) { let target = Point::with_y(frame.height() + thickness); let vline = Geometry::Line(target).stroked(stroke); - frame.prepend(Point::new(offset, -half), Element::Shape(vline)); + frame.prepend( + Point::new(offset, -half), + Element::Shape(vline, self.span()), + ); } } @@ -186,7 +192,7 @@ impl Layout for TableNode { let pos = Point::new(dx, dy); let size = Size::new(col, row.height); let rect = Geometry::Rect(size).filled(fill); - frame.prepend(pos, Element::Shape(rect)); + frame.prepend(pos, Element::Shape(rect, self.span())); } dy += row.height; } diff --git a/library/src/math/frac.rs b/library/src/math/frac.rs index f670159d4..90bc69b3b 100644 --- a/library/src/math/frac.rs +++ b/library/src/math/frac.rs @@ -137,6 +137,7 @@ fn layout( paint: TextNode::fill_in(ctx.styles()), thickness, }), + span, ), ); ctx.push(FrameFragment::new(ctx, frame)); diff --git a/library/src/math/root.rs b/library/src/math/root.rs index 1ab99b2b6..b4756b9d4 100644 --- a/library/src/math/root.rs +++ b/library/src/math/root.rs @@ -127,6 +127,7 @@ fn layout( Element::Shape( Geometry::Line(Point::with_x(radicand.width())) .stroked(Stroke { paint: TextNode::fill_in(ctx.styles()), thickness }), + span, ), ); diff --git a/library/src/meta/bibliography.rs b/library/src/meta/bibliography.rs index 8549624af..e99905245 100644 --- a/library/src/meta/bibliography.rs +++ b/library/src/meta/bibliography.rs @@ -285,6 +285,7 @@ fn create( bibliography: &BibliographyNode, citations: Vec<&CiteNode>, ) -> Arc { + let span = bibliography.span(); let entries = load(world, &bibliography.path()).unwrap(); let style = bibliography.style(StyleChain::default()); let bib_id = bibliography.0.stable_id().unwrap(); @@ -369,7 +370,7 @@ fn create( } // Format and link to the reference entry. - content += format_display_string(&display, supplement) + content += format_display_string(&display, supplement, citation.span()) .linked(Link::Node(ref_id(entry))); } @@ -410,12 +411,12 @@ fn create( let prefix = reference.prefix.map(|prefix| { // Format and link to first citation. let bracketed = prefix.with_default_brackets(&*citation_style); - format_display_string(&bracketed, None) + format_display_string(&bracketed, None, span) .linked(Link::Node(ids[reference.entry.key()])) .styled(backlink.clone()) }); - let mut reference = format_display_string(&reference.display, None); + let mut reference = format_display_string(&reference.display, None, span); if prefix.is_none() { reference = reference.styled(backlink); } @@ -471,6 +472,7 @@ const SUPPLEMENT: &str = "cdc579c45cf3d648905c142c7082683f"; fn format_display_string( string: &DisplayString, mut supplement: Option, + span: Span, ) -> Content { let mut stops: Vec<_> = string .formatting @@ -498,7 +500,7 @@ fn format_display_string( let mut content = if segment == SUPPLEMENT && supplement.is_some() { supplement.take().unwrap_or_default() } else { - TextNode::packed(segment) + TextNode::packed(segment).spanned(span) }; for (range, fmt) in &string.formatting { diff --git a/library/src/text/deco.rs b/library/src/text/deco.rs index 27d302863..a29564f3a 100644 --- a/library/src/text/deco.rs +++ b/library/src/text/deco.rs @@ -285,7 +285,7 @@ pub(super) fn decorate( if target.x >= min_width || !deco.evade { let shape = Geometry::Line(target).stroked(stroke); - frame.push(origin, Element::Shape(shape)); + frame.push(origin, Element::Shape(shape, Span::detached())); } }; diff --git a/library/src/visualize/image.rs b/library/src/visualize/image.rs index 4509cf5a4..129e07c5b 100644 --- a/library/src/visualize/image.rs +++ b/library/src/visualize/image.rs @@ -97,7 +97,7 @@ impl Layout for ImageNode { // the frame to the target size, center aligning the image in the // process. let mut frame = Frame::new(fitted); - frame.push(Point::zero(), Element::Image(image, fitted)); + frame.push(Point::zero(), Element::Image(image, fitted, self.span())); frame.resize(target, Align::CENTER_HORIZON); // Create a clipping group if only part of the image should be visible. diff --git a/library/src/visualize/line.rs b/library/src/visualize/line.rs index b39170f0f..6614e3eef 100644 --- a/library/src/visualize/line.rs +++ b/library/src/visualize/line.rs @@ -76,7 +76,7 @@ impl Layout for LineNode { let mut frame = Frame::new(target); let shape = Geometry::Line(delta.to_point()).stroked(stroke); - frame.push(start.to_point(), Element::Shape(shape)); + frame.push(start.to_point(), Element::Shape(shape, self.span())); Ok(Fragment::frame(frame)) } } diff --git a/library/src/visualize/shape.rs b/library/src/visualize/shape.rs index 02b45ed59..de4599494 100644 --- a/library/src/visualize/shape.rs +++ b/library/src/visualize/shape.rs @@ -158,6 +158,7 @@ impl Layout for RectNode { self.inset(styles), self.outset(styles), self.radius(styles), + self.span(), ) } } @@ -267,6 +268,7 @@ impl Layout for SquareNode { self.inset(styles), self.outset(styles), self.radius(styles), + self.span(), ) } } @@ -348,6 +350,7 @@ impl Layout for EllipseNode { self.inset(styles), self.outset(styles), Corners::splat(Rel::zero()), + self.span(), ) } } @@ -454,6 +457,7 @@ impl Layout for CircleNode { self.inset(styles), self.outset(styles), Corners::splat(Rel::zero()), + self.span(), ) } } @@ -471,6 +475,7 @@ fn layout( mut inset: Sides>, outset: Sides>, radius: Corners>, + span: Span, ) -> SourceResult { let resolved = sizing .zip(regions.base()) @@ -524,9 +529,9 @@ fn layout( let size = frame.size() + outset.sum_by_axis(); let pos = Point::new(-outset.left, -outset.top); let shape = ellipse(size, fill, stroke.left); - frame.prepend(pos, Element::Shape(shape)); + frame.prepend(pos, Element::Shape(shape, span)); } else { - frame.fill_and_stroke(fill, stroke, outset, radius); + frame.fill_and_stroke(fill, stroke, outset, radius, span); } } diff --git a/src/doc.rs b/src/doc.rs index 6add64fc4..03885b03f 100644 --- a/src/doc.rs +++ b/src/doc.rs @@ -288,7 +288,7 @@ impl Frame { pub fn fill(&mut self, fill: Paint) { self.prepend( Point::zero(), - Element::Shape(Geometry::Rect(self.size()).filled(fill)), + Element::Shape(Geometry::Rect(self.size()).filled(fill), Span::detached()), ); } @@ -299,6 +299,7 @@ impl Frame { stroke: Sides>, outset: Sides>, radius: Corners>, + span: Span, ) { let outset = outset.relative_to(self.size()); let size = self.size() + outset.sum_by_axis(); @@ -307,7 +308,7 @@ impl Frame { self.prepend_multiple( rounded_rect(size, radius, fill, stroke) .into_iter() - .map(|x| (pos, Element::Shape(x))), + .map(|x| (pos, Element::Shape(x, span))), ) } @@ -349,6 +350,7 @@ impl Frame { Element::Shape( Geometry::Rect(self.size) .filled(RgbaColor { a: 100, ..Color::TEAL.to_rgba() }.into()), + Span::detached(), ), ); self.insert( @@ -359,6 +361,7 @@ impl Frame { paint: Color::RED.into(), thickness: Abs::pt(1.0), }), + Span::detached(), ), ); self @@ -369,11 +372,10 @@ impl Frame { let radius = Abs::pt(2.0); self.push( pos - Point::splat(radius), - Element::Shape(geom::ellipse( - Size::splat(2.0 * radius), - Some(Color::GREEN.into()), - None, - )), + Element::Shape( + geom::ellipse(Size::splat(2.0 * radius), Some(Color::GREEN.into()), None), + Span::detached(), + ), ); } @@ -381,10 +383,13 @@ impl Frame { pub fn mark_line(&mut self, y: Abs) { self.push( Point::with_y(y), - Element::Shape(Geometry::Line(Point::with_x(self.size.x)).stroked(Stroke { - paint: Color::GREEN.into(), - thickness: Abs::pt(1.0), - })), + Element::Shape( + Geometry::Line(Point::with_x(self.size.x)).stroked(Stroke { + paint: Color::GREEN.into(), + thickness: Abs::pt(1.0), + }), + Span::detached(), + ), ); } } @@ -406,9 +411,9 @@ pub enum Element { /// A run of shaped text. Text(Text), /// A geometric shape with optional fill and stroke. - Shape(Shape), + Shape(Shape, Span), /// An image and its size. - Image(Image, Size), + Image(Image, Size, Span), /// Meta information and the region it applies to. Meta(Meta, Size), } @@ -418,8 +423,8 @@ impl Debug for Element { match self { Self::Group(group) => group.fmt(f), Self::Text(text) => write!(f, "{text:?}"), - Self::Shape(shape) => write!(f, "{shape:?}"), - Self::Image(image, _) => write!(f, "{image:?}"), + Self::Shape(shape, _) => write!(f, "{shape:?}"), + Self::Image(image, _, _) => write!(f, "{image:?}"), Self::Meta(meta, _) => write!(f, "{meta:?}"), } } diff --git a/src/export/pdf/page.rs b/src/export/pdf/page.rs index 7f8c20ef8..df7b517f3 100644 --- a/src/export/pdf/page.rs +++ b/src/export/pdf/page.rs @@ -293,8 +293,8 @@ fn write_frame(ctx: &mut PageContext, frame: &Frame) { match element { Element::Group(group) => write_group(ctx, pos, group), Element::Text(text) => write_text(ctx, x, y, text), - Element::Shape(shape) => write_shape(ctx, x, y, shape), - Element::Image(image, size) => write_image(ctx, x, y, image, *size), + Element::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(link) => write_link(ctx, pos, link, *size), Meta::Node(_) => {} diff --git a/src/export/render.rs b/src/export/render.rs index bf183ebe0..58659b986 100644 --- a/src/export/render.rs +++ b/src/export/render.rs @@ -52,10 +52,10 @@ fn render_frame( Element::Text(text) => { render_text(canvas, ts, mask, text); } - Element::Shape(shape) => { + Element::Shape(shape, _) => { render_shape(canvas, ts, mask, shape); } - Element::Image(image, size) => { + Element::Image(image, size, _) => { render_image(canvas, ts, mask, image, *size); } Element::Meta(meta, _) => match meta { diff --git a/src/ide/jump.rs b/src/ide/jump.rs index 95f2fa029..0aa97b56a 100644 --- a/src/ide/jump.rs +++ b/src/ide/jump.rs @@ -1,7 +1,7 @@ use std::num::NonZeroUsize; use crate::doc::{Destination, Element, Frame, Location, Meta}; -use crate::geom::{Point, Size}; +use crate::geom::{Geometry, Point, Size}; use crate::model::Introspector; use crate::syntax::{LinkedNode, Source, SourceId, Span, SyntaxKind}; use crate::World; @@ -15,6 +15,14 @@ pub enum Jump { Dest(Destination), } +impl Jump { + fn from_span(world: &dyn World, span: Span) -> Self { + let source = world.source(span.source()); + let node = source.find(span); + Self::Source(source.id(), node.offset()) + } +} + /// Determine where to jump to based on a click in a frame. pub fn jump_from_click( world: &dyn World, @@ -24,48 +32,10 @@ pub fn jump_from_click( ) -> Option { 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, frames, &group.frame, click - pos) - { - return Some(span); - } - } - - if let Element::Text(text) = element { - for glyph in &text.glyphs { - if glyph.span.is_detached() { - continue; - } - - let width = glyph.x_advance.at(text.size); - if is_in_rect( - Point::new(pos.x, pos.y - text.size), - Size::new(width, text.size), - click, - ) { - let source = world.source(glyph.span.source()); - let node = source.find(glyph.span); - let pos = if node.kind() == SyntaxKind::Text { - let range = node.range(); - let mut offset = range.start + usize::from(glyph.offset); - if (click.x - pos.x) > width / 2.0 { - offset += glyph.c.len_utf8(); - } - offset.min(range.end) - } else { - node.offset() - }; - return Some(Jump::Source(source.id(), pos)); - } - - pos.x += width; - } - } - + // Prefer metadata. + for (pos, element) in frame.elements() { if let Element::Meta(Meta::Link(link), size) = element { - if is_in_rect(pos, *size, click) { + if is_in_rect(*pos, *size, click) { let dest = link.resolve(|| { introspector.get_or_insert_with(|| Introspector::new(frames)) }); @@ -76,6 +46,63 @@ pub fn jump_from_click( } } + for (mut pos, element) in frame.elements().rev() { + match element { + Element::Group(group) => { + // TODO: Handle transformation. + if let Some(span) = + jump_from_click(world, frames, &group.frame, click - pos) + { + return Some(span); + } + } + + Element::Text(text) => { + for glyph in &text.glyphs { + if glyph.span.is_detached() { + continue; + } + + let width = glyph.x_advance.at(text.size); + if is_in_rect( + Point::new(pos.x, pos.y - text.size), + Size::new(width, text.size), + click, + ) { + let source = world.source(glyph.span.source()); + let node = source.find(glyph.span); + let pos = if node.kind() == SyntaxKind::Text { + let range = node.range(); + let mut offset = range.start + usize::from(glyph.offset); + if (click.x - pos.x) > width / 2.0 { + offset += glyph.c.len_utf8(); + } + offset.min(range.end) + } else { + node.offset() + }; + return Some(Jump::Source(source.id(), pos)); + } + + pos.x += width; + } + } + + Element::Shape(shape, span) => { + let Geometry::Rect(size) = shape.geometry else { continue }; + if is_in_rect(pos, size, click) { + return Some(Jump::from_span(world, *span)); + } + } + + Element::Image(_, size, span) if is_in_rect(pos, *size, click) => { + return Some(Jump::from_span(world, *span)); + } + + _ => {} + } + } + None }