summaryrefslogtreecommitdiff
path: root/src/layout
diff options
context:
space:
mode:
authorMartin <mhaug@live.de>2021-08-23 23:56:33 +0200
committerGitHub <noreply@github.com>2021-08-23 23:56:33 +0200
commitd546453880721d7a12ea228e5c1ed6c65b653ca2 (patch)
tree6dee8615a0755f2376d557328eee070de2dcb744 /src/layout
parent0806af4aecc9414962b13894a2a3c4befd2ca3c8 (diff)
Links! (#43)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
Diffstat (limited to 'src/layout')
-rw-r--r--src/layout/frame.rs25
-rw-r--r--src/layout/par.rs89
2 files changed, 78 insertions, 36 deletions
diff --git a/src/layout/frame.rs b/src/layout/frame.rs
index e52e2751..15ef541b 100644
--- a/src/layout/frame.rs
+++ b/src/layout/frame.rs
@@ -16,17 +16,17 @@ pub struct Frame {
/// The baseline of the frame measured from the top.
pub baseline: Length,
/// The elements composing this layout.
- children: Vec<(Point, Child)>,
+ pub children: Vec<(Point, FrameChild)>,
}
/// A frame can contain two different kinds of children: a leaf element or a
/// nested frame.
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
-enum Child {
+pub enum FrameChild {
/// A leaf node in the frame tree.
Element(Element),
- /// An interior node.
- Frame(Rc<Frame>),
+ /// An interior node with an optional index.
+ Frame(Option<usize>, Rc<Frame>),
}
impl Frame {
@@ -38,17 +38,22 @@ impl Frame {
/// Add an element at a position in the foreground.
pub fn push(&mut self, pos: Point, element: Element) {
- self.children.push((pos, Child::Element(element)));
+ self.children.push((pos, FrameChild::Element(element)));
}
/// Add an element at a position in the background.
pub fn prepend(&mut self, pos: Point, element: Element) {
- self.children.insert(0, (pos, Child::Element(element)))
+ self.children.insert(0, (pos, FrameChild::Element(element)));
}
/// Add a frame element.
pub fn push_frame(&mut self, pos: Point, subframe: Rc<Self>) {
- self.children.push((pos, Child::Frame(subframe)))
+ self.children.push((pos, FrameChild::Frame(None, subframe)))
+ }
+
+ /// Add a frame element with an index of arbitrary use.
+ pub fn push_indexed_frame(&mut self, pos: Point, index: usize, subframe: Rc<Self>) {
+ self.children.push((pos, FrameChild::Frame(Some(index), subframe)));
}
/// Add all elements of another frame, placing them relative to the given
@@ -85,12 +90,12 @@ impl<'a> Iterator for Elements<'a> {
fn next(&mut self) -> Option<Self::Item> {
let (cursor, offset, frame) = self.stack.last_mut()?;
match frame.children.get(*cursor) {
- Some((pos, Child::Frame(f))) => {
+ Some((pos, FrameChild::Frame(_, f))) => {
let new_offset = *offset + *pos;
self.stack.push((0, new_offset, f.as_ref()));
self.next()
}
- Some((pos, Child::Element(e))) => {
+ Some((pos, FrameChild::Element(e))) => {
*cursor += 1;
Some((*offset + *pos, e))
}
@@ -115,6 +120,8 @@ pub enum Element {
Geometry(Geometry, Paint),
/// A raster image.
Image(ImageId, Size),
+ /// A link to an external resource.
+ Link(String, Size),
}
/// A run of shaped text.
diff --git a/src/layout/par.rs b/src/layout/par.rs
index 3df742a7..e92e5a18 100644
--- a/src/layout/par.rs
+++ b/src/layout/par.rs
@@ -4,7 +4,7 @@ use unicode_bidi::{BidiInfo, Level};
use xi_unicode::LineBreakIterator;
use super::*;
-use crate::eval::FontState;
+use crate::eval::{Decoration, FontState};
use crate::util::{EcoString, RangeExt, SliceExt};
type Range = std::ops::Range<usize>;
@@ -26,9 +26,9 @@ pub enum ParChild {
/// Spacing between other nodes.
Spacing(Linear),
/// A run of text and how to align it in its line.
- Text(EcoString, Align, Rc<FontState>),
+ Text(EcoString, Align, Rc<FontState>, Vec<Decoration>),
/// Any child node and how to align it in its line.
- Any(LayoutNode, Align),
+ Any(LayoutNode, Align, Vec<Decoration>),
}
impl Layout for ParNode {
@@ -48,7 +48,7 @@ impl Layout for ParNode {
let layouter = ParLayouter::new(self, ctx, regions, bidi);
// Find suitable linebreaks.
- layouter.layout(ctx, regions.clone())
+ layouter.layout(ctx, &self.children, regions.clone())
}
}
@@ -79,8 +79,8 @@ impl ParNode {
fn strings(&self) -> impl Iterator<Item = &str> {
self.children.iter().map(|child| match child {
ParChild::Spacing(_) => " ",
- ParChild::Text(ref piece, _, _) => piece,
- ParChild::Any(_, _) => "\u{FFFC}",
+ ParChild::Text(ref piece, ..) => piece,
+ ParChild::Any(..) => "\u{FFFC}",
})
}
}
@@ -119,25 +119,25 @@ impl<'a> ParLayouter<'a> {
let mut ranges = vec![];
// Layout the children and collect them into items.
- for (range, child) in par.ranges().zip(&par.children) {
+ for (i, (range, child)) in par.ranges().zip(&par.children).enumerate() {
match *child {
ParChild::Spacing(amount) => {
let resolved = amount.resolve(regions.current.w);
items.push(ParItem::Spacing(resolved));
ranges.push(range);
}
- ParChild::Text(_, align, ref state) => {
+ ParChild::Text(_, align, ref state, _) => {
// TODO: Also split by language and script.
for (subrange, dir) in split_runs(&bidi, range) {
let text = &bidi.text[subrange.clone()];
let shaped = shape(ctx, text, dir, state);
- items.push(ParItem::Text(shaped, align));
+ items.push(ParItem::Text(shaped, align, i));
ranges.push(subrange);
}
}
- ParChild::Any(ref node, align) => {
+ ParChild::Any(ref node, align, _) => {
let frame = node.layout(ctx, regions).remove(0);
- items.push(ParItem::Frame(frame.item, align));
+ items.push(ParItem::Frame(frame.item, align, i));
ranges.push(range);
}
}
@@ -156,9 +156,10 @@ impl<'a> ParLayouter<'a> {
fn layout(
self,
ctx: &mut LayoutContext,
+ children: &[ParChild],
regions: Regions,
) -> Vec<Constrained<Rc<Frame>>> {
- let mut stack = LineStack::new(self.line_spacing, regions);
+ let mut stack = LineStack::new(self.line_spacing, children, regions);
// The current line attempt.
// Invariant: Always fits into `stack.regions.current`.
@@ -273,9 +274,9 @@ enum ParItem<'a> {
/// Spacing between other items.
Spacing(Length),
/// A shaped text run with consistent direction.
- Text(ShapedText<'a>, Align),
+ Text(ShapedText<'a>, Align, usize),
/// A layouted child node.
- Frame(Rc<Frame>, Align),
+ Frame(Rc<Frame>, Align, usize),
}
impl ParItem<'_> {
@@ -283,8 +284,8 @@ impl ParItem<'_> {
pub fn size(&self) -> Size {
match self {
Self::Spacing(amount) => Size::new(*amount, Length::zero()),
- Self::Text(shaped, _) => shaped.size,
- Self::Frame(frame, _) => frame.size,
+ Self::Text(shaped, ..) => shaped.size,
+ Self::Frame(frame, ..) => frame.size,
}
}
@@ -292,8 +293,17 @@ impl ParItem<'_> {
pub fn baseline(&self) -> Length {
match self {
Self::Spacing(_) => Length::zero(),
- Self::Text(shaped, _) => shaped.baseline,
- Self::Frame(frame, _) => frame.baseline,
+ Self::Text(shaped, ..) => shaped.baseline,
+ Self::Frame(frame, ..) => frame.baseline,
+ }
+ }
+
+ /// The index of the `ParChild` that this item belongs to.
+ pub fn index(&self) -> Option<usize> {
+ match *self {
+ Self::Spacing(_) => None,
+ Self::Text(.., index) => Some(index),
+ Self::Frame(.., index) => Some(index),
}
}
}
@@ -301,6 +311,7 @@ impl ParItem<'_> {
/// Stacks lines on top of each other.
struct LineStack<'a> {
line_spacing: Length,
+ children: &'a [ParChild],
full: Size,
regions: Regions,
size: Size,
@@ -312,11 +323,12 @@ struct LineStack<'a> {
impl<'a> LineStack<'a> {
/// Create an empty line stack.
- fn new(line_spacing: Length, regions: Regions) -> Self {
+ fn new(line_spacing: Length, children: &'a [ParChild], regions: Regions) -> Self {
Self {
line_spacing,
- constraints: Constraints::new(regions.expand),
+ children,
full: regions.current,
+ constraints: Constraints::new(regions.expand),
regions,
size: Size::zero(),
lines: vec![],
@@ -368,6 +380,25 @@ impl<'a> LineStack<'a> {
output.merge_frame(pos, frame);
}
+ // For each frame, we look if any decorations apply.
+ for i in 0 .. output.children.len() {
+ let &(point, ref child) = &output.children[i];
+ if let &FrameChild::Frame(Some(frame_idx), ref frame) = child {
+ let size = frame.size;
+ for deco in match &self.children[frame_idx] {
+ ParChild::Spacing(_) => continue,
+ ParChild::Text(.., decos) => decos,
+ ParChild::Any(.., decos) => decos,
+ } {
+ match deco {
+ Decoration::Link(href) => {
+ output.push(point, Element::Link(href.to_string(), size));
+ }
+ }
+ }
+ }
+ }
+
self.finished.push(output.constrain(self.constraints));
self.regions.next();
self.full = self.regions.current;
@@ -426,7 +457,7 @@ impl<'a> LineLayout<'a> {
// Reshape the last item if it's split in half.
let mut last = None;
- if let Some((ParItem::Text(shaped, align), rest)) = items.split_last() {
+ if let Some((ParItem::Text(shaped, align, i), rest)) = items.split_last() {
// Compute the range we want to shape, trimming whitespace at the
// end of the line.
let base = par.ranges[last_idx].start;
@@ -442,7 +473,7 @@ impl<'a> LineLayout<'a> {
if !range.is_empty() || rest.is_empty() {
// Reshape that part.
let reshaped = shaped.reshape(ctx, range);
- last = Some(ParItem::Text(reshaped, *align));
+ last = Some(ParItem::Text(reshaped, *align, *i));
}
items = rest;
@@ -452,7 +483,7 @@ impl<'a> LineLayout<'a> {
// Reshape the start item if it's split in half.
let mut first = None;
- if let Some((ParItem::Text(shaped, align), rest)) = items.split_first() {
+ if let Some((ParItem::Text(shaped, align, i), rest)) = items.split_first() {
// Compute the range we want to shape.
let Range { start: base, end: first_end } = par.ranges[first_idx];
let start = line.start;
@@ -463,7 +494,7 @@ impl<'a> LineLayout<'a> {
if range.len() < shaped.text.len() {
if !range.is_empty() {
let reshaped = shaped.reshape(ctx, range);
- first = Some(ParItem::Text(reshaped, *align));
+ first = Some(ParItem::Text(reshaped, *align, *i));
}
items = rest;
@@ -511,11 +542,11 @@ impl<'a> LineLayout<'a> {
offset += amount;
return;
}
- ParItem::Text(ref shaped, align) => {
+ ParItem::Text(ref shaped, align, _) => {
ruler = ruler.max(align);
Rc::new(shaped.build(ctx))
}
- ParItem::Frame(ref frame, align) => {
+ ParItem::Frame(ref frame, align, _) => {
ruler = ruler.max(align);
frame.clone()
}
@@ -528,7 +559,11 @@ impl<'a> LineLayout<'a> {
);
offset += frame.size.w;
- output.push_frame(pos, frame);
+
+ match item.index() {
+ Some(idx) => output.push_indexed_frame(pos, idx, frame),
+ None => output.push_frame(pos, frame),
+ }
});
output