More jump targets

This commit is contained in:
Laurenz 2023-03-16 17:36:04 +01:00
parent ecb5543985
commit e8435df5ec
13 changed files with 122 additions and 75 deletions

View File

@ -141,7 +141,7 @@ impl Layout for BoxNode {
if fill.is_some() || stroke.iter().any(Option::is_some) { if fill.is_some() || stroke.iter().any(Option::is_some) {
let outset = self.outset(styles); let outset = self.outset(styles);
let radius = self.radius(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. // Apply metadata.
@ -383,7 +383,7 @@ impl Layout for BlockNode {
let outset = self.outset(styles); let outset = self.outset(styles);
let radius = self.radius(styles); let radius = self.radius(styles);
for frame in frames.iter_mut().skip(skip as usize) { 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());
} }
} }

View File

@ -166,14 +166,20 @@ impl Layout for TableNode {
for offset in points(rows.iter().map(|piece| piece.height)) { for offset in points(rows.iter().map(|piece| piece.height)) {
let target = Point::with_x(frame.width() + thickness); let target = Point::with_x(frame.width() + thickness);
let hline = Geometry::Line(target).stroked(stroke); 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. // Render vertical lines.
for offset in points(layout.cols.iter().copied()) { for offset in points(layout.cols.iter().copied()) {
let target = Point::with_y(frame.height() + thickness); let target = Point::with_y(frame.height() + thickness);
let vline = Geometry::Line(target).stroked(stroke); 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 pos = Point::new(dx, dy);
let size = Size::new(col, row.height); let size = Size::new(col, row.height);
let rect = Geometry::Rect(size).filled(fill); let rect = Geometry::Rect(size).filled(fill);
frame.prepend(pos, Element::Shape(rect)); frame.prepend(pos, Element::Shape(rect, self.span()));
} }
dy += row.height; dy += row.height;
} }

View File

@ -137,6 +137,7 @@ fn layout(
paint: TextNode::fill_in(ctx.styles()), paint: TextNode::fill_in(ctx.styles()),
thickness, thickness,
}), }),
span,
), ),
); );
ctx.push(FrameFragment::new(ctx, frame)); ctx.push(FrameFragment::new(ctx, frame));

View File

@ -127,6 +127,7 @@ fn layout(
Element::Shape( Element::Shape(
Geometry::Line(Point::with_x(radicand.width())) Geometry::Line(Point::with_x(radicand.width()))
.stroked(Stroke { paint: TextNode::fill_in(ctx.styles()), thickness }), .stroked(Stroke { paint: TextNode::fill_in(ctx.styles()), thickness }),
span,
), ),
); );

View File

@ -285,6 +285,7 @@ fn create(
bibliography: &BibliographyNode, bibliography: &BibliographyNode,
citations: Vec<&CiteNode>, citations: Vec<&CiteNode>,
) -> Arc<Works> { ) -> Arc<Works> {
let span = bibliography.span();
let entries = load(world, &bibliography.path()).unwrap(); let entries = load(world, &bibliography.path()).unwrap();
let style = bibliography.style(StyleChain::default()); let style = bibliography.style(StyleChain::default());
let bib_id = bibliography.0.stable_id().unwrap(); let bib_id = bibliography.0.stable_id().unwrap();
@ -369,7 +370,7 @@ fn create(
} }
// Format and link to the reference entry. // 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))); .linked(Link::Node(ref_id(entry)));
} }
@ -410,12 +411,12 @@ fn create(
let prefix = reference.prefix.map(|prefix| { let prefix = reference.prefix.map(|prefix| {
// Format and link to first citation. // Format and link to first citation.
let bracketed = prefix.with_default_brackets(&*citation_style); 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()])) .linked(Link::Node(ids[reference.entry.key()]))
.styled(backlink.clone()) .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() { if prefix.is_none() {
reference = reference.styled(backlink); reference = reference.styled(backlink);
} }
@ -471,6 +472,7 @@ const SUPPLEMENT: &str = "cdc579c45cf3d648905c142c7082683f";
fn format_display_string( fn format_display_string(
string: &DisplayString, string: &DisplayString,
mut supplement: Option<Content>, mut supplement: Option<Content>,
span: Span,
) -> Content { ) -> Content {
let mut stops: Vec<_> = string let mut stops: Vec<_> = string
.formatting .formatting
@ -498,7 +500,7 @@ fn format_display_string(
let mut content = if segment == SUPPLEMENT && supplement.is_some() { let mut content = if segment == SUPPLEMENT && supplement.is_some() {
supplement.take().unwrap_or_default() supplement.take().unwrap_or_default()
} else { } else {
TextNode::packed(segment) TextNode::packed(segment).spanned(span)
}; };
for (range, fmt) in &string.formatting { for (range, fmt) in &string.formatting {

View File

@ -285,7 +285,7 @@ pub(super) fn decorate(
if target.x >= min_width || !deco.evade { if target.x >= min_width || !deco.evade {
let shape = Geometry::Line(target).stroked(stroke); let shape = Geometry::Line(target).stroked(stroke);
frame.push(origin, Element::Shape(shape)); frame.push(origin, Element::Shape(shape, Span::detached()));
} }
}; };

View File

@ -97,7 +97,7 @@ impl Layout for ImageNode {
// the frame to the target size, center aligning the image in the // the frame to the target size, center aligning the image in the
// process. // process.
let mut frame = Frame::new(fitted); 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); frame.resize(target, Align::CENTER_HORIZON);
// Create a clipping group if only part of the image should be visible. // Create a clipping group if only part of the image should be visible.

View File

@ -76,7 +76,7 @@ impl Layout for LineNode {
let mut frame = Frame::new(target); let mut frame = Frame::new(target);
let shape = Geometry::Line(delta.to_point()).stroked(stroke); 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)) Ok(Fragment::frame(frame))
} }
} }

View File

@ -158,6 +158,7 @@ impl Layout for RectNode {
self.inset(styles), self.inset(styles),
self.outset(styles), self.outset(styles),
self.radius(styles), self.radius(styles),
self.span(),
) )
} }
} }
@ -267,6 +268,7 @@ impl Layout for SquareNode {
self.inset(styles), self.inset(styles),
self.outset(styles), self.outset(styles),
self.radius(styles), self.radius(styles),
self.span(),
) )
} }
} }
@ -348,6 +350,7 @@ impl Layout for EllipseNode {
self.inset(styles), self.inset(styles),
self.outset(styles), self.outset(styles),
Corners::splat(Rel::zero()), Corners::splat(Rel::zero()),
self.span(),
) )
} }
} }
@ -454,6 +457,7 @@ impl Layout for CircleNode {
self.inset(styles), self.inset(styles),
self.outset(styles), self.outset(styles),
Corners::splat(Rel::zero()), Corners::splat(Rel::zero()),
self.span(),
) )
} }
} }
@ -471,6 +475,7 @@ fn layout(
mut inset: Sides<Rel<Abs>>, mut inset: Sides<Rel<Abs>>,
outset: Sides<Rel<Abs>>, outset: Sides<Rel<Abs>>,
radius: Corners<Rel<Abs>>, radius: Corners<Rel<Abs>>,
span: Span,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
let resolved = sizing let resolved = sizing
.zip(regions.base()) .zip(regions.base())
@ -524,9 +529,9 @@ fn layout(
let size = frame.size() + outset.sum_by_axis(); let size = frame.size() + outset.sum_by_axis();
let pos = Point::new(-outset.left, -outset.top); let pos = Point::new(-outset.left, -outset.top);
let shape = ellipse(size, fill, stroke.left); let shape = ellipse(size, fill, stroke.left);
frame.prepend(pos, Element::Shape(shape)); frame.prepend(pos, Element::Shape(shape, span));
} else { } else {
frame.fill_and_stroke(fill, stroke, outset, radius); frame.fill_and_stroke(fill, stroke, outset, radius, span);
} }
} }

View File

@ -288,7 +288,7 @@ impl Frame {
pub fn fill(&mut self, fill: Paint) { pub fn fill(&mut self, fill: Paint) {
self.prepend( self.prepend(
Point::zero(), 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<Option<Stroke>>, stroke: Sides<Option<Stroke>>,
outset: Sides<Rel<Abs>>, outset: Sides<Rel<Abs>>,
radius: Corners<Rel<Abs>>, radius: Corners<Rel<Abs>>,
span: Span,
) { ) {
let outset = outset.relative_to(self.size()); let outset = outset.relative_to(self.size());
let size = self.size() + outset.sum_by_axis(); let size = self.size() + outset.sum_by_axis();
@ -307,7 +308,7 @@ impl Frame {
self.prepend_multiple( self.prepend_multiple(
rounded_rect(size, radius, fill, stroke) rounded_rect(size, radius, fill, stroke)
.into_iter() .into_iter()
.map(|x| (pos, Element::Shape(x))), .map(|x| (pos, Element::Shape(x, span))),
) )
} }
@ -349,6 +350,7 @@ impl Frame {
Element::Shape( Element::Shape(
Geometry::Rect(self.size) Geometry::Rect(self.size)
.filled(RgbaColor { a: 100, ..Color::TEAL.to_rgba() }.into()), .filled(RgbaColor { a: 100, ..Color::TEAL.to_rgba() }.into()),
Span::detached(),
), ),
); );
self.insert( self.insert(
@ -359,6 +361,7 @@ impl Frame {
paint: Color::RED.into(), paint: Color::RED.into(),
thickness: Abs::pt(1.0), thickness: Abs::pt(1.0),
}), }),
Span::detached(),
), ),
); );
self self
@ -369,11 +372,10 @@ impl Frame {
let radius = Abs::pt(2.0); let radius = Abs::pt(2.0);
self.push( self.push(
pos - Point::splat(radius), pos - Point::splat(radius),
Element::Shape(geom::ellipse( Element::Shape(
Size::splat(2.0 * radius), geom::ellipse(Size::splat(2.0 * radius), Some(Color::GREEN.into()), None),
Some(Color::GREEN.into()), Span::detached(),
None, ),
)),
); );
} }
@ -381,10 +383,13 @@ impl Frame {
pub fn mark_line(&mut self, y: Abs) { pub fn mark_line(&mut self, y: Abs) {
self.push( self.push(
Point::with_y(y), Point::with_y(y),
Element::Shape(Geometry::Line(Point::with_x(self.size.x)).stroked(Stroke { Element::Shape(
Geometry::Line(Point::with_x(self.size.x)).stroked(Stroke {
paint: Color::GREEN.into(), paint: Color::GREEN.into(),
thickness: Abs::pt(1.0), thickness: Abs::pt(1.0),
})), }),
Span::detached(),
),
); );
} }
} }
@ -406,9 +411,9 @@ pub enum Element {
/// A run of shaped text. /// A run of shaped text.
Text(Text), Text(Text),
/// A geometric shape with optional fill and stroke. /// A geometric shape with optional fill and stroke.
Shape(Shape), Shape(Shape, Span),
/// An image and its size. /// An image and its size.
Image(Image, Size), Image(Image, Size, Span),
/// Meta information and the region it applies to. /// Meta information and the region it applies to.
Meta(Meta, Size), Meta(Meta, Size),
} }
@ -418,8 +423,8 @@ impl Debug for Element {
match self { match self {
Self::Group(group) => group.fmt(f), Self::Group(group) => group.fmt(f),
Self::Text(text) => write!(f, "{text:?}"), Self::Text(text) => write!(f, "{text:?}"),
Self::Shape(shape) => write!(f, "{shape:?}"), Self::Shape(shape, _) => write!(f, "{shape:?}"),
Self::Image(image, _) => write!(f, "{image:?}"), Self::Image(image, _, _) => write!(f, "{image:?}"),
Self::Meta(meta, _) => write!(f, "{meta:?}"), Self::Meta(meta, _) => write!(f, "{meta:?}"),
} }
} }

View File

@ -293,8 +293,8 @@ fn write_frame(ctx: &mut PageContext, frame: &Frame) {
match element { match element {
Element::Group(group) => write_group(ctx, pos, group), Element::Group(group) => write_group(ctx, pos, group),
Element::Text(text) => write_text(ctx, x, y, text), Element::Text(text) => write_text(ctx, x, y, text),
Element::Shape(shape) => write_shape(ctx, x, y, shape), Element::Shape(shape, _) => write_shape(ctx, x, y, shape),
Element::Image(image, size) => write_image(ctx, x, y, image, *size), Element::Image(image, size, _) => write_image(ctx, x, y, image, *size),
Element::Meta(meta, size) => match meta { Element::Meta(meta, size) => match meta {
Meta::Link(link) => write_link(ctx, pos, link, *size), Meta::Link(link) => write_link(ctx, pos, link, *size),
Meta::Node(_) => {} Meta::Node(_) => {}

View File

@ -52,10 +52,10 @@ fn render_frame(
Element::Text(text) => { Element::Text(text) => {
render_text(canvas, ts, mask, text); render_text(canvas, ts, mask, text);
} }
Element::Shape(shape) => { Element::Shape(shape, _) => {
render_shape(canvas, ts, mask, shape); render_shape(canvas, ts, mask, shape);
} }
Element::Image(image, size) => { Element::Image(image, size, _) => {
render_image(canvas, ts, mask, image, *size); render_image(canvas, ts, mask, image, *size);
} }
Element::Meta(meta, _) => match meta { Element::Meta(meta, _) => match meta {

View File

@ -1,7 +1,7 @@
use std::num::NonZeroUsize; use std::num::NonZeroUsize;
use crate::doc::{Destination, Element, Frame, Location, Meta}; 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::model::Introspector;
use crate::syntax::{LinkedNode, Source, SourceId, Span, SyntaxKind}; use crate::syntax::{LinkedNode, Source, SourceId, Span, SyntaxKind};
use crate::World; use crate::World;
@ -15,6 +15,14 @@ pub enum Jump {
Dest(Destination), 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. /// Determine where to jump to based on a click in a frame.
pub fn jump_from_click( pub fn jump_from_click(
world: &dyn World, world: &dyn World,
@ -24,16 +32,32 @@ pub fn jump_from_click(
) -> Option<Jump> { ) -> Option<Jump> {
let mut introspector = None; let mut introspector = None;
for (mut pos, element) in frame.elements() { // Prefer metadata.
if let Element::Group(group) = element { for (pos, element) in frame.elements() {
if let Element::Meta(Meta::Link(link), size) = element {
if is_in_rect(*pos, *size, click) {
let dest = link.resolve(|| {
introspector.get_or_insert_with(|| Introspector::new(frames))
});
let Some(dest) = dest else { continue };
return Some(Jump::Dest(dest));
}
}
}
for (mut pos, element) in frame.elements().rev() {
match element {
Element::Group(group) => {
// TODO: Handle transformation. // TODO: Handle transformation.
if let Some(span) = jump_from_click(world, frames, &group.frame, click - pos) if let Some(span) =
jump_from_click(world, frames, &group.frame, click - pos)
{ {
return Some(span); return Some(span);
} }
} }
if let Element::Text(text) = element { Element::Text(text) => {
for glyph in &text.glyphs { for glyph in &text.glyphs {
if glyph.span.is_detached() { if glyph.span.is_detached() {
continue; continue;
@ -64,16 +88,19 @@ pub fn jump_from_click(
} }
} }
if let Element::Meta(Meta::Link(link), size) = element { Element::Shape(shape, span) => {
if is_in_rect(pos, *size, click) { let Geometry::Rect(size) = shape.geometry else { continue };
let dest = link.resolve(|| { if is_in_rect(pos, size, click) {
introspector.get_or_insert_with(|| Introspector::new(frames)) return Some(Jump::from_span(world, *span));
});
let Some(dest) = dest else { continue };
return Some(Jump::Dest(dest));
} }
} }
Element::Image(_, size, span) if is_in_rect(pos, *size, click) => {
return Some(Jump::from_span(world, *span));
}
_ => {}
}
} }
None None