diff --git a/src/layout/frame.rs b/src/layout/frame.rs index d3276e996..b6100b83e 100644 --- a/src/layout/frame.rs +++ b/src/layout/frame.rs @@ -10,14 +10,16 @@ use crate::geom::{Length, Path, Point, Size}; pub struct Frame { /// The size of the frame. pub size: Size, + /// The baseline of the frame measured from the top. + pub baseline: Length, /// The elements composing this layout. pub elements: Vec<(Point, Element)>, } impl Frame { /// Create a new, empty frame. - pub fn new(size: Size) -> Self { - Self { size, elements: vec![] } + pub fn new(size: Size, baseline: Length) -> Self { + Self { size, baseline, elements: vec![] } } /// Add an element at a position. diff --git a/src/layout/par.rs b/src/layout/par.rs index 5646d41b4..ffa36fd5a 100644 --- a/src/layout/par.rs +++ b/src/layout/par.rs @@ -102,7 +102,9 @@ struct ParLayouter<'a> { struct Line { items: Vec, - size: Size, + width: Length, + top: Length, + bottom: Length, ruler: Align, hard: bool, } @@ -123,22 +125,17 @@ impl<'a> ParLayouter<'a> { finished: vec![], stack: vec![], stack_size: Size::ZERO, - line: Line { - items: vec![], - size: Size::ZERO, - ruler: Align::Start, - hard: true, - }, + line: Line::new(true), } } /// Push horizontal spacing. fn push_spacing(&mut self, range: Range, amount: Length) { - let amount = amount.min(self.areas.current.width - self.line.size.width); - self.line.size.width += amount; + let amount = amount.min(self.areas.current.width - self.line.width); + self.line.width += amount; self.line.items.push(LineItem { range, - frame: Frame::new(Size::new(amount, Length::ZERO)), + frame: Frame::new(Size::new(amount, Length::ZERO), Length::ZERO), align: Align::default(), }) } @@ -266,7 +263,7 @@ impl<'a> ParLayouter<'a> { } // Find out whether the area still has enough space for this frame. - if !self.usable().fits(frame.size) && self.line.size.width > Length::ZERO { + if !self.usable().fits(frame.size) && self.line.width > Length::ZERO { self.finish_line(false); // Here, we can directly check whether the frame fits into @@ -285,10 +282,11 @@ impl<'a> ParLayouter<'a> { // A line can contain frames with different alignments. Their exact // positions are calculated later depending on the alignments. - let size = frame.size; + let Frame { size, baseline, .. } = frame; self.line.items.push(LineItem { range, frame, align }); - self.line.size.width += size.width; - self.line.size.height = self.line.size.height.max(size.height); + self.line.width += size.width; + self.line.top = self.line.top.max(baseline); + self.line.bottom = self.line.bottom.max(size.height - baseline); self.line.ruler = align; } @@ -297,41 +295,39 @@ impl<'a> ParLayouter<'a> { // `areas.current`, but the width of the current line needs to be // subtracted to make sure the frame fits. let mut usable = self.areas.current; - usable.width -= self.line.size.width; + usable.width -= self.line.width; usable } fn finish_line(&mut self, hard: bool) { - if !mem::replace(&mut self.line.hard, hard) && self.line.items.is_empty() { + let mut line = mem::replace(&mut self.line, Line::new(hard)); + if !line.hard && line.items.is_empty() { return; } // BiDi reordering. - self.reorder_line(); + line.reorder(&self.bidi); let full_size = { let expand = self.areas.expand.horizontal; let full = self.areas.full.width; - Size::new( - expand.resolve(self.line.size.width, full), - self.line.size.height, - ) + Size::new(expand.resolve(line.width, full), line.top + line.bottom) }; - let mut output = Frame::new(full_size); + let mut output = Frame::new(full_size, line.top + line.bottom); let mut offset = Length::ZERO; - for item in mem::take(&mut self.line.items) { + for item in line.items { // Align along the x axis. let x = item.align.resolve(if self.dir.is_positive() { - offset .. full_size.width - self.line.size.width + offset + offset .. full_size.width - line.width + offset } else { - full_size.width - self.line.size.width + offset .. offset + full_size.width - line.width + offset .. offset }); offset += item.frame.size.width; - let pos = Point::new(x, Length::ZERO); + let pos = Point::new(x, line.top - item.frame.baseline); output.push_frame(pos, item.frame); } @@ -341,31 +337,73 @@ impl<'a> ParLayouter<'a> { self.areas.current.height -= self.line_spacing; } - self.stack.push((self.stack_size.height, output, self.line.ruler)); + self.stack.push((self.stack_size.height, output, line.ruler)); self.stack_size.height += full_size.height; self.stack_size.width = self.stack_size.width.max(full_size.width); self.areas.current.height -= full_size.height; - self.line.size = Size::ZERO; - self.line.ruler = Align::Start; } - fn reorder_line(&mut self) { - let items = &mut self.line.items; + fn finish_area(&mut self) { + let mut output = Frame::new(self.stack_size, Length::ZERO); + let mut baseline = None; + + for (before, line, align) in mem::take(&mut self.stack) { + // Align along the x axis. + let x = align.resolve(if self.dir.is_positive() { + Length::ZERO .. self.stack_size.width - line.size.width + } else { + self.stack_size.width - line.size.width .. Length::ZERO + }); + + let pos = Point::new(x, before); + baseline.get_or_insert(pos.y + line.baseline); + output.push_frame(pos, line); + } + + if let Some(baseline) = baseline { + output.baseline = baseline; + } + + self.finished.push(output); + self.areas.next(); + self.stack_size = Size::ZERO; + } + + fn finish(mut self) -> Vec { + self.finish_line(false); + self.finish_area(); + self.finished + } +} + +impl Line { + fn new(hard: bool) -> Self { + Self { + items: vec![], + width: Length::ZERO, + top: Length::ZERO, + bottom: Length::ZERO, + ruler: Align::Start, + hard, + } + } + + fn reorder(&mut self, bidi: &BidiInfo) { + let items = &mut self.items; let line_range = match (items.first(), items.last()) { (Some(first), Some(last)) => first.range.start .. last.range.end, _ => return, }; // Find the paragraph that contains the frame. - let para = self - .bidi + let para = bidi .paragraphs .iter() .find(|para| para.range.contains(&line_range.start)) .unwrap(); // Compute the reordered ranges in visual order (left to right). - let (levels, ranges) = self.bidi.visual_runs(para, line_range); + let (levels, ranges) = bidi.visual_runs(para, line_range); // Reorder the items. items.sort_by_key(|item| { @@ -387,31 +425,6 @@ impl<'a> ParLayouter<'a> { (idx, dist) }); } - - fn finish_area(&mut self) { - let mut output = Frame::new(self.stack_size); - for (before, line, align) in mem::take(&mut self.stack) { - // Align along the x axis. - let x = align.resolve(if self.dir.is_positive() { - Length::ZERO .. self.stack_size.width - line.size.width - } else { - self.stack_size.width - line.size.width .. Length::ZERO - }); - - let pos = Point::new(x, before); - output.push_frame(pos, line); - } - - self.finished.push(output); - self.areas.next(); - self.stack_size = Size::ZERO; - } - - fn finish(mut self) -> Vec { - self.finish_line(false); - self.finish_area(); - self.finished - } } trait LevelExt: Sized { diff --git a/src/layout/shaping.rs b/src/layout/shaping.rs index 6e4b2b410..ca30ce3e7 100644 --- a/src/layout/shaping.rs +++ b/src/layout/shaping.rs @@ -5,19 +5,34 @@ use ttf_parser::GlyphId; use super::{Element, Frame, ShapedText}; use crate::env::FontLoader; use crate::exec::FontProps; -use crate::geom::{Dir, Point, Size}; +use crate::geom::{Dir, Length, Point, Size}; /// Shape text into a frame containing [`ShapedText`] runs. pub fn shape(text: &str, dir: Dir, loader: &mut FontLoader, props: &FontProps) -> Frame { - let mut frame = Frame::new(Size::ZERO); let iter = props.families.iter(); - shape_segment(&mut frame, text, dir, loader, props, iter, None); + let mut results = vec![]; + shape_segment(&mut results, text, dir, loader, props, iter, None); + + let mut top = Length::ZERO; + let mut bottom = Length::ZERO; + for result in &results { + top = top.max(result.top); + bottom = bottom.max(result.bottom); + } + + let mut frame = Frame::new(Size::new(Length::ZERO, top + bottom), top); + for shaped in results { + let offset = frame.size.width; + frame.size.width += shaped.width; + frame.push(Point::new(offset, top), Element::Text(shaped)); + } + frame } /// Shape text into a frame with font fallback using the `families` iterator. fn shape_segment<'a>( - frame: &mut Frame, + results: &mut Vec, text: &str, dir: Dir, loader: &mut FontLoader, @@ -53,7 +68,7 @@ fn shape_segment<'a>( let units_per_em = f64::from(ttf.units_per_em().unwrap_or(1000)); let convert = |units| f64::from(units) / units_per_em * props.size; let top = convert(i32::from(props.top_edge.lookup(ttf))); - let bottom = convert(i32::from(props.bottom_edge.lookup(ttf))); + let bottom = convert(i32::from(-props.bottom_edge.lookup(ttf))); let mut shaped = ShapedText::new(id, props.size, top, bottom, props.color); // Fill the buffer with our text. @@ -76,7 +91,7 @@ fn shape_segment<'a>( if info.codepoint == 0 && fallback { // Flush what we have so far. if !shaped.glyphs.is_empty() { - place(frame, shaped); + results.push(shaped); shaped = ShapedText::new(id, props.size, top, bottom, props.color); } @@ -107,7 +122,7 @@ fn shape_segment<'a>( let part = &text[range]; // Recursively shape the tofu sequence with the next family. - shape_segment(frame, part, dir, loader, props, families.clone(), first); + shape_segment(results, part, dir, loader, props, families.clone(), first); } else { // Add the glyph to the shaped output. // TODO: Don't ignore y_advance and y_offset. @@ -119,14 +134,6 @@ fn shape_segment<'a>( } if !shaped.glyphs.is_empty() { - place(frame, shaped) + results.push(shaped); } } - -/// Place shaped text into a frame. -fn place(frame: &mut Frame, shaped: ShapedText) { - let offset = frame.size.width; - frame.size.width += shaped.width; - frame.size.height = frame.size.height.max(shaped.top - shaped.bottom); - frame.push(Point::new(offset, shaped.top), Element::Text(shaped)); -} diff --git a/src/layout/stack.rs b/src/layout/stack.rs index 79fde72d1..b9de1bbcb 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -119,7 +119,8 @@ impl StackLayouter { size.switch(self.main) }; - let mut output = Frame::new(full_size.switch(self.main).to_size()); + let mut output = Frame::new(full_size.switch(self.main).to_size(), Length::ZERO); + let mut baseline = None; for (before, frame, aligns) in std::mem::take(&mut self.frames) { let child_size = frame.size.switch(self.main); @@ -142,9 +143,14 @@ impl StackLayouter { }); let pos = Gen::new(main, cross).switch(self.main).to_point(); + baseline.get_or_insert(pos.y + frame.baseline); output.push_frame(pos, frame); } + if let Some(baseline) = baseline { + output.baseline = baseline; + } + self.finished.push(output); self.areas.next(); self.ruler = Align::Start; diff --git a/src/library/image.rs b/src/library/image.rs index ed7b2268a..09b563360 100644 --- a/src/library/image.rs +++ b/src/library/image.rs @@ -73,7 +73,7 @@ impl Layout for ImageNode { } }; - let mut frame = Frame::new(size); + let mut frame = Frame::new(size, size.height); frame.push(Point::ZERO, Element::Image(Image { res: self.res, size })); vec![frame]