diff options
Diffstat (limited to 'src/layout')
| -rw-r--r-- | src/layout/constraints.rs | 24 | ||||
| -rw-r--r-- | src/layout/deco.rs | 92 | ||||
| -rw-r--r-- | src/layout/frame.rs | 187 | ||||
| -rw-r--r-- | src/layout/grid.rs | 551 | ||||
| -rw-r--r-- | src/layout/image.rs | 44 | ||||
| -rw-r--r-- | src/layout/incremental.rs | 6 | ||||
| -rw-r--r-- | src/layout/levels.rs | 199 | ||||
| -rw-r--r-- | src/layout/mod.rs | 227 | ||||
| -rw-r--r-- | src/layout/pad.rs | 77 | ||||
| -rw-r--r-- | src/layout/par.rs | 620 | ||||
| -rw-r--r-- | src/layout/shape.rs | 106 | ||||
| -rw-r--r-- | src/layout/stack.rs | 226 | ||||
| -rw-r--r-- | src/layout/text.rs | 370 |
13 files changed, 230 insertions, 2499 deletions
diff --git a/src/layout/constraints.rs b/src/layout/constraints.rs index 11f4e5c2..fdcda276 100644 --- a/src/layout/constraints.rs +++ b/src/layout/constraints.rs @@ -1,4 +1,19 @@ -use super::*; +use std::rc::Rc; + +use crate::frame::Frame; +use crate::geom::{Length, Size, Spec}; + +/// Constrain a frame with constraints. +pub trait Constrain { + /// Reference-count the frame and wrap it with constraints. + fn constrain(self, cts: Constraints) -> Constrained<Rc<Frame>>; +} + +impl Constrain for Frame { + fn constrain(self, cts: Constraints) -> Constrained<Rc<Frame>> { + Constrained::new(Rc::new(self), cts) + } +} /// Carries an item that is only valid in certain regions and the constraints /// that describe these regions. @@ -10,6 +25,13 @@ pub struct Constrained<T> { pub cts: Constraints, } +impl<T> Constrained<T> { + /// Constrain an item with constraints. + pub fn new(item: T, cts: Constraints) -> Self { + Self { item, cts } + } +} + /// Describe regions that match them. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct Constraints { diff --git a/src/layout/deco.rs b/src/layout/deco.rs deleted file mode 100644 index 669bf404..00000000 --- a/src/layout/deco.rs +++ /dev/null @@ -1,92 +0,0 @@ -use super::*; -use crate::util::EcoString; - -/// A decoration for a frame. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub enum Decoration { - /// A link to an external resource. - Link(EcoString), - /// An underline/strikethrough/overline decoration. - Line(LineDecoration), -} - -impl Decoration { - /// Apply a decoration to a child's frame. - pub fn apply(&self, ctx: &LayoutContext, frame: &mut Frame) { - match self { - Decoration::Link(href) => { - let link = Element::Link(href.to_string(), frame.size); - frame.push(Point::zero(), link); - } - Decoration::Line(line) => { - line.apply(ctx, frame); - } - } - } -} - -/// Defines a line that is positioned over, under or on top of text. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct LineDecoration { - /// The kind of line. - pub kind: LineKind, - /// Stroke color of the line, defaults to the text color if `None`. - pub stroke: Option<Paint>, - /// Thickness of the line's strokes (dependent on scaled font size), read - /// from the font tables if `None`. - pub thickness: Option<Linear>, - /// Position of the line relative to the baseline (dependent on scaled font - /// size), read from the font tables if `None`. - pub offset: Option<Linear>, - /// Amount that the line will be longer or shorter than its associated text - /// (dependent on scaled font size). - pub extent: Linear, -} - -/// The kind of line decoration. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum LineKind { - /// A line under text. - Underline, - /// A line through text. - Strikethrough, - /// A line over text. - Overline, -} - -impl LineDecoration { - /// Apply a line decoration to a all text elements in a frame. - pub fn apply(&self, ctx: &LayoutContext, frame: &mut Frame) { - for i in 0 .. frame.children.len() { - let (pos, child) = &frame.children[i]; - if let FrameChild::Element(Element::Text(text)) = child { - let face = ctx.fonts.get(text.face_id); - let metrics = match self.kind { - LineKind::Underline => face.underline, - LineKind::Strikethrough => face.strikethrough, - LineKind::Overline => face.overline, - }; - - let stroke = self.stroke.unwrap_or(text.fill); - - let thickness = self - .thickness - .map(|s| s.resolve(text.size)) - .unwrap_or(metrics.strength.to_length(text.size)); - - let offset = self - .offset - .map(|s| s.resolve(text.size)) - .unwrap_or(-metrics.position.to_length(text.size)); - - let extent = self.extent.resolve(text.size); - - let subpos = Point::new(pos.x - extent, pos.y + offset); - let vector = Point::new(text.width + 2.0 * extent, Length::zero()); - let line = Geometry::Line(vector, thickness); - - frame.push(subpos, Element::Geometry(line, stroke)); - } - } - } -} diff --git a/src/layout/frame.rs b/src/layout/frame.rs deleted file mode 100644 index 82f60e22..00000000 --- a/src/layout/frame.rs +++ /dev/null @@ -1,187 +0,0 @@ -use std::fmt::{self, Debug, Formatter}; -use std::rc::Rc; - -use serde::{Deserialize, Serialize}; - -use super::{Constrained, Constraints}; -use crate::font::FaceId; -use crate::geom::{Em, Length, Paint, Path, Point, Size}; -use crate::image::ImageId; - -/// A finished layout with elements at fixed positions. -#[derive(Default, Clone, Eq, PartialEq, Serialize, Deserialize)] -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 children: Vec<(Point, FrameChild)>, -} - -impl Frame { - /// Create a new, empty frame. - #[track_caller] - pub fn new(size: Size, baseline: Length) -> Self { - assert!(size.is_finite()); - Self { size, baseline, children: vec![] } - } - - /// Add an element at a position in the foreground. - pub fn push(&mut self, pos: Point, 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, FrameChild::Element(element))); - } - - /// Add a frame element. - pub fn push_frame(&mut self, pos: Point, subframe: Rc<Self>) { - self.children.push((pos, FrameChild::Group(subframe))) - } - - /// Add all elements of another frame, placing them relative to the given - /// position. - pub fn merge_frame(&mut self, pos: Point, subframe: Self) { - if pos == Point::zero() && self.children.is_empty() { - self.children = subframe.children; - } else { - for (subpos, child) in subframe.children { - self.children.push((pos + subpos, child)); - } - } - } - - /// An iterator over all elements in the frame and its children. - pub fn elements(&self) -> Elements { - Elements { stack: vec![(0, Point::zero(), self)] } - } - - /// Wrap the frame with constraints. - pub fn constrain(self, cts: Constraints) -> Constrained<Rc<Self>> { - Constrained { item: Rc::new(self), cts } - } -} - -impl Debug for Frame { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - struct Children<'a>(&'a [(Point, FrameChild)]); - - impl Debug for Children<'_> { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.debug_map().entries(self.0.iter().map(|(k, v)| (k, v))).finish() - } - } - - f.debug_struct("Frame") - .field("size", &self.size) - .field("baseline", &self.baseline) - .field("children", &Children(&self.children)) - .finish() - } -} - -/// A frame can contain two different kinds of children: a leaf element or a -/// nested frame. -#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)] -pub enum FrameChild { - /// A leaf node in the frame tree. - Element(Element), - /// An interior group. - Group(Rc<Frame>), -} - -impl Debug for FrameChild { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Element(element) => element.fmt(f), - Self::Group(frame) => frame.fmt(f), - } - } -} - -/// An iterator over all elements in a frame, alongside with their positions. -pub struct Elements<'a> { - stack: Vec<(usize, Point, &'a Frame)>, -} - -impl<'a> Iterator for Elements<'a> { - type Item = (Point, &'a Element); - - fn next(&mut self) -> Option<Self::Item> { - let (cursor, offset, frame) = self.stack.last_mut()?; - match frame.children.get(*cursor) { - Some((pos, FrameChild::Group(f))) => { - let new_offset = *offset + *pos; - self.stack.push((0, new_offset, f.as_ref())); - self.next() - } - Some((pos, FrameChild::Element(e))) => { - *cursor += 1; - Some((*offset + *pos, e)) - } - None => { - self.stack.pop(); - if let Some((cursor, _, _)) = self.stack.last_mut() { - *cursor += 1; - } - self.next() - } - } - } -} - -/// The building block frames are composed of. -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] -pub enum Element { - /// Shaped text. - Text(Text), - /// A geometric shape and the paint which with it should be filled or - /// stroked (which one depends on the kind of geometry). - Geometry(Geometry, Paint), - /// A raster image. - Image(ImageId, Size), - /// A link to an external resource. - Link(String, Size), -} - -/// A run of shaped text. -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] -pub struct Text { - /// The font face the glyphs are contained in. - pub face_id: FaceId, - /// The font size. - pub size: Length, - /// The width of the text run. - pub width: Length, - /// Glyph color. - pub fill: Paint, - /// The glyphs. - pub glyphs: Vec<Glyph>, -} - -/// A glyph in a run of shaped text. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] -pub struct Glyph { - /// The glyph's index in the face. - pub id: u16, - /// The advance width of the glyph. - pub x_advance: Em, - /// The horizontal offset of the glyph. - pub x_offset: Em, -} - -/// A geometric shape. -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] -pub enum Geometry { - /// A filled rectangle with its origin in the topleft corner. - Rect(Size), - /// A filled ellipse with its origin in the center. - Ellipse(Size), - /// A stroked line to a point (relative to its position) with a thickness. - Line(Point, Length), - /// A filled bezier path. - Path(Path), -} diff --git a/src/layout/grid.rs b/src/layout/grid.rs deleted file mode 100644 index 57986b48..00000000 --- a/src/layout/grid.rs +++ /dev/null @@ -1,551 +0,0 @@ -use super::*; - -/// A node that arranges its children in a grid. -#[derive(Debug, Hash)] -pub struct GridNode { - /// Defines sizing for content rows and columns. - pub tracks: Spec<Vec<TrackSizing>>, - /// Defines sizing of gutter rows and columns between content. - pub gutter: Spec<Vec<TrackSizing>>, - /// The nodes to be arranged in a grid. - pub children: Vec<BlockNode>, -} - -/// Defines how to size a grid cell along an axis. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum TrackSizing { - /// Fit the cell to its contents. - Auto, - /// A length stated in absolute values and/or relative to the parent's size. - Linear(Linear), - /// A length that is the fraction of the remaining free space in the parent. - Fractional(Fractional), -} - -impl BlockLevel for GridNode { - fn layout( - &self, - ctx: &mut LayoutContext, - regions: &Regions, - ) -> Vec<Constrained<Rc<Frame>>> { - // Prepare grid layout by unifying content and gutter tracks. - let mut layouter = GridLayouter::new(self, regions.clone()); - - // Determine all column sizes. - layouter.measure_columns(ctx); - - // Layout the grid row-by-row. - layouter.layout(ctx) - } -} - -/// Performs grid layout. -struct GridLayouter<'a> { - /// The original expand state of the target region. - expand: Spec<bool>, - /// The column tracks including gutter tracks. - cols: Vec<TrackSizing>, - /// The row tracks including gutter tracks. - rows: Vec<TrackSizing>, - /// The children of the grid. - children: &'a [BlockNode], - /// The regions to layout into. - regions: Regions, - /// Resolved column sizes. - rcols: Vec<Length>, - /// The full block size of the current region. - full: Length, - /// The used-up size of the current region. The horizontal size is - /// determined once after columns are resolved and not touched again. - used: Size, - /// The sum of fractional ratios in the current region. - fr: Fractional, - /// Rows in the current region. - lrows: Vec<Row>, - /// Constraints for the active region. - cts: Constraints, - /// Frames for finished regions. - finished: Vec<Constrained<Rc<Frame>>>, -} - -/// Produced by initial row layout, auto and linear rows are already finished, -/// fractional rows not yet. -enum Row { - /// Finished row frame of auto or linear row. - Frame(Frame), - /// Ratio of a fractional row and y index of the track. - Fr(Fractional, usize), -} - -impl<'a> GridLayouter<'a> { - /// Prepare grid layout by unifying content and gutter tracks. - fn new(grid: &'a GridNode, mut regions: Regions) -> Self { - let mut cols = vec![]; - let mut rows = vec![]; - - // Number of content columns: Always at least one. - let c = grid.tracks.x.len().max(1); - - // Number of content rows: At least as many as given, but also at least - // as many as needed to place each item. - let r = { - let len = grid.children.len(); - let given = grid.tracks.y.len(); - let needed = len / c + (len % c).clamp(0, 1); - given.max(needed) - }; - - let auto = TrackSizing::Auto; - let zero = TrackSizing::Linear(Linear::zero()); - let get_or = |tracks: &[_], idx, default| { - tracks.get(idx).or(tracks.last()).copied().unwrap_or(default) - }; - - // Collect content and gutter columns. - for x in 0 .. c { - cols.push(get_or(&grid.tracks.x, x, auto)); - cols.push(get_or(&grid.gutter.x, x, zero)); - } - - // Collect content and gutter rows. - for y in 0 .. r { - rows.push(get_or(&grid.tracks.y, y, auto)); - rows.push(get_or(&grid.gutter.y, y, zero)); - } - - // Remove superfluous gutter tracks. - cols.pop(); - rows.pop(); - - // We use the regions only for auto row measurement and constraints. - let expand = regions.expand; - regions.expand = Spec::new(true, false); - - Self { - children: &grid.children, - cts: Constraints::new(expand), - full: regions.current.h, - expand, - rcols: vec![Length::zero(); cols.len()], - lrows: vec![], - used: Size::zero(), - fr: Fractional::zero(), - finished: vec![], - cols, - rows, - regions, - } - } - - /// Determine all column sizes. - fn measure_columns(&mut self, ctx: &mut LayoutContext) { - enum Case { - PurelyLinear, - Fitting, - Exact, - Overflowing, - } - - // The different cases affecting constraints. - let mut case = Case::PurelyLinear; - - // Sum of sizes of resolved linear tracks. - let mut linear = Length::zero(); - - // Sum of fractions of all fractional tracks. - let mut fr = Fractional::zero(); - - // Resolve the size of all linear columns and compute the sum of all - // fractional tracks. - for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) { - match col { - TrackSizing::Auto => { - case = Case::Fitting; - } - TrackSizing::Linear(v) => { - let resolved = v.resolve(self.regions.base.w); - *rcol = resolved; - linear += resolved; - } - TrackSizing::Fractional(v) => { - case = Case::Fitting; - fr += v; - } - } - } - - // Size that is not used by fixed-size columns. - let available = self.regions.current.w - linear; - if available >= Length::zero() { - // Determine size of auto columns. - let (auto, count) = self.measure_auto_columns(ctx, available); - - // If there is remaining space, distribute it to fractional columns, - // otherwise shrink auto columns. - let remaining = available - auto; - if remaining >= Length::zero() { - if !fr.is_zero() { - self.grow_fractional_columns(remaining, fr); - case = Case::Exact; - } - } else { - self.shrink_auto_columns(available, count); - case = Case::Exact; - } - } else if matches!(case, Case::Fitting) { - case = Case::Overflowing; - } - - // Children could depend on base. - self.cts.base = self.regions.base.to_spec().map(Some); - - // Set constraints depending on the case we hit. - match case { - Case::PurelyLinear => {} - Case::Fitting => self.cts.min.x = Some(self.used.w), - Case::Exact => self.cts.exact.x = Some(self.regions.current.w), - Case::Overflowing => self.cts.max.x = Some(linear), - } - - // Sum up the resolved column sizes once here. - self.used.w = self.rcols.iter().sum(); - } - - /// Measure the size that is available to auto columns. - fn measure_auto_columns( - &mut self, - ctx: &mut LayoutContext, - available: Length, - ) -> (Length, usize) { - let mut auto = Length::zero(); - let mut count = 0; - - // Determine size of auto columns by laying out all cells in those - // columns, measuring them and finding the largest one. - for (x, &col) in self.cols.iter().enumerate() { - if col != TrackSizing::Auto { - continue; - } - - let mut resolved = Length::zero(); - for y in 0 .. self.rows.len() { - if let Some(node) = self.cell(x, y) { - let size = Size::new(available, Length::inf()); - let mut regions = - Regions::one(size, self.regions.base, Spec::splat(false)); - - // For fractional rows, we can already resolve the correct - // base, for auto it's already correct and for fr we could - // only guess anyway. - if let TrackSizing::Linear(v) = self.rows[y] { - regions.base.h = v.resolve(self.regions.base.h); - } - - let frame = node.layout(ctx, ®ions).remove(0).item; - resolved.set_max(frame.size.w); - } - } - - self.rcols[x] = resolved; - auto += resolved; - count += 1; - } - - (auto, count) - } - - /// Distribute remaining space to fractional columns. - fn grow_fractional_columns(&mut self, remaining: Length, fr: Fractional) { - for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) { - if let TrackSizing::Fractional(v) = col { - let ratio = v / fr; - if ratio.is_finite() { - *rcol = ratio * remaining; - } - } - } - } - - /// Redistribute space to auto columns so that each gets a fair share. - fn shrink_auto_columns(&mut self, available: Length, count: usize) { - // The fair share each auto column may have. - let fair = available / count as f64; - - // The number of overlarge auto columns and the space that will be - // equally redistributed to them. - let mut overlarge: usize = 0; - let mut redistribute = available; - - // Find out the number of and space used by overlarge auto columns. - for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) { - if col == TrackSizing::Auto { - if *rcol > fair { - overlarge += 1; - } else { - redistribute -= *rcol; - } - } - } - - // Redistribute the space equally. - let share = redistribute / overlarge as f64; - for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) { - if col == TrackSizing::Auto && *rcol > fair { - *rcol = share; - } - } - } - - /// Layout the grid row-by-row. - fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Rc<Frame>>> { - for y in 0 .. self.rows.len() { - match self.rows[y] { - TrackSizing::Auto => self.layout_auto_row(ctx, y), - TrackSizing::Linear(v) => self.layout_linear_row(ctx, v, y), - TrackSizing::Fractional(v) => { - self.cts.exact.y = Some(self.full); - self.lrows.push(Row::Fr(v, y)); - self.fr += v; - } - } - } - - self.finish_region(ctx); - self.finished - } - - /// Layout a row with automatic size along the block axis. Such a row may - /// break across multiple regions. - fn layout_auto_row(&mut self, ctx: &mut LayoutContext, y: usize) { - let mut resolved: Vec<Length> = vec![]; - - // Determine the size for each region of the row. - for (x, &rcol) in self.rcols.iter().enumerate() { - if let Some(node) = self.cell(x, y) { - let mut regions = self.regions.clone(); - regions.mutate(|size| size.w = rcol); - - // Set the horizontal base back to the parent region's base for - // auto columns. - if self.cols[x] == TrackSizing::Auto { - regions.base.w = self.regions.base.w; - } - - let mut sizes = - node.layout(ctx, ®ions).into_iter().map(|frame| frame.item.size.h); - - for (target, size) in resolved.iter_mut().zip(&mut sizes) { - target.set_max(size); - } - - resolved.extend(sizes); - } - } - - // Nothing to layout. - if resolved.is_empty() { - return; - } - - // Layout into a single region. - if let &[first] = resolved.as_slice() { - let frame = self.layout_single_row(ctx, first, y); - self.push_row(frame); - return; - } - - // Expand all but the last region if the space is not - // eaten up by any fr rows. - if self.fr.is_zero() { - let len = resolved.len(); - for (target, (current, _)) in - resolved[.. len - 1].iter_mut().zip(self.regions.iter()) - { - target.set_max(current.h); - } - } - - // Layout into multiple regions. - let frames = self.layout_multi_row(ctx, &resolved, y); - let len = frames.len(); - for (i, frame) in frames.into_iter().enumerate() { - self.push_row(frame); - if i + 1 < len { - self.cts.exact.y = Some(self.full); - self.finish_region(ctx); - } - } - } - - /// Layout a row with linear sizing along the block axis. Such a row cannot - /// break across multiple regions, but it may force a region break. - fn layout_linear_row(&mut self, ctx: &mut LayoutContext, v: Linear, y: usize) { - let resolved = v.resolve(self.regions.base.h); - let frame = self.layout_single_row(ctx, resolved, y); - - // Skip to fitting region. - let length = frame.size.h; - while !self.regions.current.h.fits(length) && !self.regions.in_full_last() { - self.cts.max.y = Some(self.used.h + length); - self.finish_region(ctx); - - // Don't skip multiple regions for gutter and don't push a row. - if y % 2 == 1 { - return; - } - } - - self.push_row(frame); - } - - /// Layout a row with a fixed size along the block axis and return its frame. - fn layout_single_row( - &self, - ctx: &mut LayoutContext, - height: Length, - y: usize, - ) -> Frame { - let mut output = Frame::new(Size::new(self.used.w, height), height); - let mut pos = Point::zero(); - - for (x, &rcol) in self.rcols.iter().enumerate() { - if let Some(node) = self.cell(x, y) { - let size = Size::new(rcol, height); - - // Set the base to the size for non-auto rows. - let mut base = self.regions.base; - if self.cols[x] != TrackSizing::Auto { - base.w = size.w; - } - if self.rows[y] != TrackSizing::Auto { - base.h = size.h; - } - - let regions = Regions::one(size, base, Spec::splat(true)); - let frame = node.layout(ctx, ®ions).remove(0); - output.push_frame(pos, frame.item); - } - - pos.x += rcol; - } - - output - } - - /// Layout a row spanning multiple regions. - fn layout_multi_row( - &self, - ctx: &mut LayoutContext, - resolved: &[Length], - y: usize, - ) -> Vec<Frame> { - // Prepare frames. - let mut outputs: Vec<_> = resolved - .iter() - .map(|&h| Frame::new(Size::new(self.used.w, h), h)) - .collect(); - - // Prepare regions. - let size = Size::new(self.used.w, resolved[0]); - let mut regions = Regions::one(size, self.regions.base, Spec::splat(true)); - regions.backlog = resolved[1 ..] - .iter() - .map(|&h| Size::new(self.used.w, h)) - .collect::<Vec<_>>() - .into_iter(); - - // Layout the row. - let mut pos = Point::zero(); - for (x, &rcol) in self.rcols.iter().enumerate() { - if let Some(node) = self.cell(x, y) { - regions.mutate(|size| size.w = rcol); - - // Set the horizontal base back to the parent region's base for - // auto columns. - if self.cols[x] == TrackSizing::Auto { - regions.base.w = self.regions.base.w; - } - - // Push the layouted frames into the individual output frames. - let frames = node.layout(ctx, ®ions); - for (output, frame) in outputs.iter_mut().zip(frames) { - output.push_frame(pos, frame.item); - } - } - - pos.x += rcol; - } - - outputs - } - - /// Push a row frame into the current region. - fn push_row(&mut self, frame: Frame) { - self.regions.current.h -= frame.size.h; - self.used.h += frame.size.h; - self.lrows.push(Row::Frame(frame)); - } - - /// Finish rows for one region. - fn finish_region(&mut self, ctx: &mut LayoutContext) { - // Determine the size that remains for fractional rows. - let remaining = self.full - self.used.h; - - // Determine the size of the grid in this region, expanding fully if - // there are fr rows. - let mut size = self.used; - if !self.fr.is_zero() && self.full.is_finite() { - size.h = self.full; - } - - self.cts.min.y = Some(size.h); - - // The frame for the region. - let mut output = Frame::new(size, size.h); - let mut pos = Point::zero(); - - // Place finished rows and layout fractional rows. - for row in std::mem::take(&mut self.lrows) { - let frame = match row { - Row::Frame(frame) => frame, - Row::Fr(v, y) => { - let ratio = v / self.fr; - if remaining.is_finite() && ratio.is_finite() { - let resolved = ratio * remaining; - self.layout_single_row(ctx, resolved, y) - } else { - continue; - } - } - }; - - let height = frame.size.h; - output.merge_frame(pos, frame); - pos.y += height; - } - - self.regions.next(); - self.full = self.regions.current.h; - self.used.h = Length::zero(); - self.fr = Fractional::zero(); - self.finished.push(output.constrain(self.cts)); - self.cts = Constraints::new(self.expand); - } - - /// Get the node in the cell in column `x` and row `y`. - /// - /// Returns `None` if it's a gutter cell. - #[track_caller] - fn cell(&self, x: usize, y: usize) -> Option<&'a BlockNode> { - assert!(x < self.cols.len()); - assert!(y < self.rows.len()); - - // Even columns and rows are children, odd ones are gutter. - if x % 2 == 0 && y % 2 == 0 { - let c = 1 + self.cols.len() / 2; - self.children.get((y / 2) * c + x / 2) - } else { - None - } - } -} diff --git a/src/layout/image.rs b/src/layout/image.rs deleted file mode 100644 index b410895b..00000000 --- a/src/layout/image.rs +++ /dev/null @@ -1,44 +0,0 @@ -use super::*; -use crate::image::ImageId; - -/// An image node. -#[derive(Debug, Hash)] -pub struct ImageNode { - /// The id of the image file. - pub id: ImageId, - /// The fixed width, if any. - pub width: Option<Linear>, - /// The fixed height, if any. - pub height: Option<Linear>, -} - -impl InlineLevel for ImageNode { - fn layout(&self, ctx: &mut LayoutContext, space: Length, base: Size) -> Frame { - let img = ctx.images.get(self.id); - let pixel_size = Spec::new(img.width() as f64, img.height() as f64); - let pixel_ratio = pixel_size.x / pixel_size.y; - - let width = self.width.map(|w| w.resolve(base.w)); - let height = self.height.map(|w| w.resolve(base.h)); - - let size = match (width, height) { - (Some(width), Some(height)) => Size::new(width, height), - (Some(width), None) => Size::new(width, width / pixel_ratio), - (None, Some(height)) => Size::new(height * pixel_ratio, height), - (None, None) => { - if space.is_finite() { - // Fit to width. - Size::new(space, space / pixel_ratio) - } else { - // Unbounded width, we have to make up something, - // so it is 1pt per pixel. - pixel_size.map(Length::pt).to_size() - } - } - }; - - let mut frame = Frame::new(size, size.h); - frame.push(Point::zero(), Element::Image(self.id, size)); - frame - } -} diff --git a/src/layout/incremental.rs b/src/layout/incremental.rs index 2f6dccd0..a90bac1d 100644 --- a/src/layout/incremental.rs +++ b/src/layout/incremental.rs @@ -1,10 +1,12 @@ use std::cmp::Reverse; use std::collections::HashMap; +use std::rc::Rc; use decorum::N32; use itertools::Itertools; -use super::*; +use super::{Constrained, Regions}; +use crate::frame::Frame; const TEMP_LEN: usize = 5; const TEMP_LAST: usize = TEMP_LEN - 1; @@ -396,6 +398,8 @@ impl PatternProperties { #[cfg(test)] mod tests { use super::*; + use crate::geom::{Size, Spec}; + use crate::layout::Constraints; fn empty_frames() -> Vec<Constrained<Rc<Frame>>> { vec![Constrained { diff --git a/src/layout/levels.rs b/src/layout/levels.rs new file mode 100644 index 00000000..a6b8d050 --- /dev/null +++ b/src/layout/levels.rs @@ -0,0 +1,199 @@ +use std::fmt::{self, Debug, Formatter}; +use std::hash::{Hash, Hasher}; +use std::rc::Rc; + +use super::*; +use crate::geom::{Length, Size}; + +/// Page-level nodes directly produce frames representing pages. +/// +/// Such nodes create their own regions instead of being supplied with them from +/// some parent. +pub trait PageLevel: Debug { + /// Layout the node, producing one frame per page. + fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>>; +} + +/// Layouts its children onto one or multiple pages. +#[derive(Debug)] +pub struct PageNode { + /// The size of the page. + pub size: Size, + /// The node that produces the actual pages. + pub child: BlockNode, +} + +impl PageLevel for PageNode { + fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>> { + // When one of the lengths is infinite the page fits its content along + // that axis. + let expand = self.size.to_spec().map(Length::is_finite); + let regions = Regions::repeat(self.size, self.size, expand); + self.child.layout(ctx, ®ions).into_iter().map(|c| c.item).collect() + } +} + +impl<T> PageLevel for T +where + T: AsRef<[PageNode]> + Debug + ?Sized, +{ + fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>> { + self.as_ref().iter().flat_map(|node| node.layout(ctx)).collect() + } +} + +/// Block-level nodes can be layouted into a sequence of regions. +/// +/// They return one frame per used region alongside constraints that define +/// whether the result is reusable in other regions. +pub trait BlockLevel: Debug { + /// Layout the node into the given regions, producing constrained frames. + fn layout( + &self, + ctx: &mut LayoutContext, + regions: &Regions, + ) -> Vec<Constrained<Rc<Frame>>>; + + /// Convert to a packed block-level node. + fn pack(self) -> BlockNode + where + Self: Sized + Hash + 'static, + { + BlockNode { + #[cfg(feature = "layout-cache")] + hash: hash_node(&self), + node: Rc::new(self), + } + } +} + +/// A packed [block-level](BlockLevel) layouting node with precomputed hash. +#[derive(Clone)] +pub struct BlockNode { + node: Rc<dyn BlockLevel>, + #[cfg(feature = "layout-cache")] + hash: u64, +} + +impl BlockLevel for BlockNode { + fn layout( + &self, + ctx: &mut LayoutContext, + regions: &Regions, + ) -> Vec<Constrained<Rc<Frame>>> { + #[cfg(not(feature = "layout-cache"))] + return self.node.layout(ctx, regions); + + #[cfg(feature = "layout-cache")] + ctx.layouts.get(self.hash, regions).unwrap_or_else(|| { + ctx.level += 1; + let frames = self.node.layout(ctx, regions); + ctx.level -= 1; + + let entry = FramesEntry::new(frames.clone(), ctx.level); + + #[cfg(debug_assertions)] + if !entry.check(regions) { + eprintln!("node: {:#?}", self.node); + eprintln!("regions: {:#?}", regions); + eprintln!( + "constraints: {:#?}", + frames.iter().map(|c| c.cts).collect::<Vec<_>>() + ); + panic!("constraints did not match regions they were created for"); + } + + ctx.layouts.insert(self.hash, entry); + frames + }) + } + + fn pack(self) -> BlockNode + where + Self: Sized + Hash + 'static, + { + self + } +} + +impl Hash for BlockNode { + fn hash<H: Hasher>(&self, _state: &mut H) { + #[cfg(feature = "layout-cache")] + _state.write_u64(self.hash); + #[cfg(not(feature = "layout-cache"))] + unimplemented!() + } +} + +impl Debug for BlockNode { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.node.fmt(f) + } +} + +/// Inline-level nodes are layouted as part of paragraph layout. +/// +/// They only know the width and not the height of the paragraph's region and +/// return only a single frame. +pub trait InlineLevel: Debug { + /// Layout the node into a frame. + fn layout(&self, ctx: &mut LayoutContext, space: Length, base: Size) -> Frame; + + /// Convert to a packed inline-level node. + fn pack(self) -> InlineNode + where + Self: Sized + Hash + 'static, + { + InlineNode { + #[cfg(feature = "layout-cache")] + hash: hash_node(&self), + node: Rc::new(self), + } + } +} + +/// A packed [inline-level](InlineLevel) layouting node with precomputed hash. +#[derive(Clone)] +pub struct InlineNode { + node: Rc<dyn InlineLevel>, + #[cfg(feature = "layout-cache")] + hash: u64, +} + +impl InlineLevel for InlineNode { + fn layout(&self, ctx: &mut LayoutContext, space: Length, base: Size) -> Frame { + self.node.layout(ctx, space, base) + } + + fn pack(self) -> InlineNode + where + Self: Sized + Hash + 'static, + { + self + } +} + +impl Hash for InlineNode { + fn hash<H: Hasher>(&self, _state: &mut H) { + #[cfg(feature = "layout-cache")] + _state.write_u64(self.hash); + #[cfg(not(feature = "layout-cache"))] + unimplemented!() + } +} + +impl Debug for InlineNode { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.node.fmt(f) + } +} + +/// Hash a node alongside its type id. +#[cfg(feature = "layout-cache")] +fn hash_node(node: &(impl Hash + 'static)) -> u64 { + use std::any::Any; + let mut state = fxhash::FxHasher64::default(); + node.type_id().hash(&mut state); + node.hash(&mut state); + state.finish() +} diff --git a/src/layout/mod.rs b/src/layout/mod.rs index ffbf2668..49ceccf6 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -1,41 +1,22 @@ //! Layouting. mod constraints; -mod deco; -mod frame; -mod grid; -mod image; #[cfg(feature = "layout-cache")] mod incremental; -mod pad; -mod par; +mod levels; mod regions; -mod shape; -mod stack; -mod text; -pub use self::image::*; pub use constraints::*; -pub use deco::*; -pub use frame::*; -pub use grid::*; #[cfg(feature = "layout-cache")] pub use incremental::*; -pub use pad::*; -pub use par::*; +pub use levels::*; pub use regions::*; -pub use shape::*; -pub use stack::*; -pub use text::*; -use std::fmt::{self, Debug, Formatter}; -use std::hash::{Hash, Hasher}; use std::rc::Rc; use crate::font::FontStore; -use crate::geom::*; +use crate::frame::Frame; use crate::image::ImageStore; -use crate::util::OptionExt; use crate::Context; /// Layout a page-level node into a collection of frames. @@ -74,205 +55,3 @@ impl<'a> LayoutContext<'a> { } } } - -/// Page-level nodes directly produce frames representing pages. -/// -/// Such nodes create their own regions instead of being supplied with them from -/// some parent. -pub trait PageLevel: Debug { - /// Layout the node, producing one frame per page. - fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>>; -} - -/// Layouts its children onto one or multiple pages. -#[derive(Debug)] -pub struct PageNode { - /// The size of the page. - pub size: Size, - /// The node that produces the actual pages. - pub child: BlockNode, -} - -impl PageLevel for PageNode { - fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>> { - // When one of the lengths is infinite the page fits its content along - // that axis. - let expand = self.size.to_spec().map(Length::is_finite); - let regions = Regions::repeat(self.size, self.size, expand); - self.child.layout(ctx, ®ions).into_iter().map(|c| c.item).collect() - } -} - -impl<T> PageLevel for T -where - T: AsRef<[PageNode]> + Debug + ?Sized, -{ - fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>> { - self.as_ref().iter().flat_map(|node| node.layout(ctx)).collect() - } -} - -/// Block-level nodes can be layouted into a sequence of regions. -/// -/// They return one frame per used region alongside constraints that define -/// whether the result is reusable in other regions. -pub trait BlockLevel: Debug { - /// Layout the node into the given regions, producing constrained frames. - fn layout( - &self, - ctx: &mut LayoutContext, - regions: &Regions, - ) -> Vec<Constrained<Rc<Frame>>>; - - /// Convert to a packed block-level node. - fn pack(self) -> BlockNode - where - Self: Sized + Hash + 'static, - { - BlockNode { - #[cfg(feature = "layout-cache")] - hash: hash_node(&self), - node: Rc::new(self), - } - } -} - -/// A packed [block-level](BlockLevel) layouting node with precomputed hash. -#[derive(Clone)] -pub struct BlockNode { - node: Rc<dyn BlockLevel>, - #[cfg(feature = "layout-cache")] - hash: u64, -} - -impl BlockLevel for BlockNode { - fn layout( - &self, - ctx: &mut LayoutContext, - regions: &Regions, - ) -> Vec<Constrained<Rc<Frame>>> { - #[cfg(not(feature = "layout-cache"))] - return self.node.layout(ctx, regions); - - #[cfg(feature = "layout-cache")] - ctx.layouts.get(self.hash, regions).unwrap_or_else(|| { - ctx.level += 1; - let frames = self.node.layout(ctx, regions); - ctx.level -= 1; - - let entry = FramesEntry::new(frames.clone(), ctx.level); - - #[cfg(debug_assertions)] - if !entry.check(regions) { - eprintln!("node: {:#?}", self.node); - eprintln!("regions: {:#?}", regions); - eprintln!( - "constraints: {:#?}", - frames.iter().map(|c| c.cts).collect::<Vec<_>>() - ); - panic!("constraints did not match regions they were created for"); - } - - ctx.layouts.insert(self.hash, entry); - frames - }) - } - - fn pack(self) -> BlockNode - where - Self: Sized + Hash + 'static, - { - self - } -} - -impl Hash for BlockNode { - fn hash<H: Hasher>(&self, _state: &mut H) { - #[cfg(feature = "layout-cache")] - _state.write_u64(self.hash); - #[cfg(not(feature = "layout-cache"))] - unimplemented!() - } -} - -impl Debug for BlockNode { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.node.fmt(f) - } -} - -/// Inline-level nodes are layouted as part of paragraph layout. -/// -/// They only know the width and not the height of the paragraph's region and -/// return only a single frame. -pub trait InlineLevel: Debug { - /// Layout the node into a frame. - fn layout(&self, ctx: &mut LayoutContext, space: Length, base: Size) -> Frame; - - /// Convert to a packed inline-level node. - fn pack(self) -> InlineNode - where - Self: Sized + Hash + 'static, - { - InlineNode { - #[cfg(feature = "layout-cache")] - hash: hash_node(&self), - node: Rc::new(self), - } - } -} - -/// A packed [inline-level](InlineLevel) layouting node with precomputed hash. -#[derive(Clone)] -pub struct InlineNode { - node: Rc<dyn InlineLevel>, - #[cfg(feature = "layout-cache")] - hash: u64, -} - -impl InlineLevel for InlineNode { - fn layout(&self, ctx: &mut LayoutContext, space: Length, base: Size) -> Frame { - self.node.layout(ctx, space, base) - } - - fn pack(self) -> InlineNode - where - Self: Sized + Hash + 'static, - { - self - } -} - -impl Hash for InlineNode { - fn hash<H: Hasher>(&self, _state: &mut H) { - #[cfg(feature = "layout-cache")] - _state.write_u64(self.hash); - #[cfg(not(feature = "layout-cache"))] - unimplemented!() - } -} - -impl Debug for InlineNode { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.node.fmt(f) - } -} - -/// Hash a node alongside its type id. -#[cfg(feature = "layout-cache")] -fn hash_node(node: &(impl Hash + 'static)) -> u64 { - use std::any::Any; - let mut state = fxhash::FxHasher64::default(); - node.type_id().hash(&mut state); - node.hash(&mut state); - state.finish() -} - -/// Kinds of spacing. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum Spacing { - /// A length stated in absolute values and/or relative to the parent's size. - Linear(Linear), - /// A length that is the fraction of the remaining free space in the parent. - Fractional(Fractional), -} diff --git a/src/layout/pad.rs b/src/layout/pad.rs deleted file mode 100644 index 52766dfa..00000000 --- a/src/layout/pad.rs +++ /dev/null @@ -1,77 +0,0 @@ -use super::*; - -/// A node that adds padding to its child. -#[derive(Debug, Hash)] -pub struct PadNode { - /// The amount of padding. - pub padding: Sides<Linear>, - /// The child node whose sides to pad. - pub child: BlockNode, -} - -impl BlockLevel for PadNode { - fn layout( - &self, - ctx: &mut LayoutContext, - regions: &Regions, - ) -> Vec<Constrained<Rc<Frame>>> { - // Layout child into padded regions. - let mut frames = self.child.layout( - ctx, - ®ions.map(|size| size - self.padding.resolve(size).size()), - ); - - for (Constrained { item: frame, cts }, (current, base)) in - frames.iter_mut().zip(regions.iter()) - { - fn solve_axis(length: Length, padding: Linear) -> Length { - (length + padding.abs) - .div_finite(1.0 - padding.rel.get()) - .unwrap_or_default() - } - - // Solve for the size `padded` that satisfies (approximately): - // `padded - padding.resolve(padded).size() == size` - let padded = Size::new( - solve_axis(frame.size.w, self.padding.left + self.padding.right), - solve_axis(frame.size.h, self.padding.top + self.padding.bottom), - ); - - let padding = self.padding.resolve(padded); - let origin = Point::new(padding.left, padding.top); - - // Create a new larger frame and place the child's frame inside it. - let empty = Frame::new(padded, frame.baseline + origin.y); - let prev = std::mem::replace(frame, Rc::new(empty)); - let new = Rc::make_mut(frame); - new.push_frame(origin, prev); - - // Inflate min and max contraints by the padding. - for spec in [&mut cts.min, &mut cts.max] { - if let Some(x) = spec.x.as_mut() { - *x += padding.size().w; - } - if let Some(y) = spec.y.as_mut() { - *y += padding.size().h; - } - } - - // Set exact and base constraints if the child had them. - cts.exact.x.and_set(Some(current.w)); - cts.exact.y.and_set(Some(current.h)); - cts.base.x.and_set(Some(base.w)); - cts.base.y.and_set(Some(base.h)); - - // Also set base constraints if the padding is relative. - if self.padding.left.is_relative() || self.padding.right.is_relative() { - cts.base.x = Some(base.w); - } - - if self.padding.top.is_relative() || self.padding.bottom.is_relative() { - cts.base.y = Some(base.h); - } - } - - frames - } -} diff --git a/src/layout/par.rs b/src/layout/par.rs deleted file mode 100644 index a645eb07..00000000 --- a/src/layout/par.rs +++ /dev/null @@ -1,620 +0,0 @@ -use std::fmt::{self, Debug, Formatter}; -use std::rc::Rc; - -use itertools::Either; -use unicode_bidi::{BidiInfo, Level}; -use xi_unicode::LineBreakIterator; - -use super::*; -use crate::style::TextStyle; -use crate::util::{EcoString, RangeExt, SliceExt}; - -type Range = std::ops::Range<usize>; - -/// A node that arranges its children into a paragraph. -#[derive(Debug, Hash)] -pub struct ParNode { - /// The inline direction of this paragraph. - pub dir: Dir, - /// The spacing to insert between each line. - pub leading: Length, - /// The children to be arranged in a paragraph. - pub children: Vec<ParChild>, -} - -/// A child of a paragraph node. -#[derive(Hash)] -pub enum ParChild { - /// Spacing between other nodes. - Spacing(Spacing), - /// A run of text and how to align it in its line. - Text(EcoString, Align, Rc<TextStyle>), - /// Any child node and how to align it in its line. - Node(InlineNode, Align), - /// A decoration that applies until a matching `Undecorate`. - Decorate(Decoration), - /// The end of a decoration. - Undecorate, -} - -impl BlockLevel for ParNode { - fn layout( - &self, - ctx: &mut LayoutContext, - regions: &Regions, - ) -> Vec<Constrained<Rc<Frame>>> { - // Collect all text into one string used for BiDi analysis. - let text = self.collect_text(); - - // Find out the BiDi embedding levels. - let bidi = BidiInfo::new(&text, Level::from_dir(self.dir)); - - // Prepare paragraph layout by building a representation on which we can - // do line breaking without layouting each and every line from scratch. - let layouter = ParLayouter::new(self, ctx, regions, bidi); - - // Find suitable linebreaks. - layouter.layout(ctx, regions.clone()) - } -} - -impl ParNode { - /// Concatenate all text in the paragraph into one string, replacing spacing - /// with a space character and other non-text nodes with the object - /// replacement character. Returns the full text alongside the range each - /// child spans in the text. - fn collect_text(&self) -> String { - let mut text = String::new(); - for string in self.strings() { - text.push_str(string); - } - text - } - - /// The range of each item in the collected text. - fn ranges(&self) -> impl Iterator<Item = Range> + '_ { - let mut cursor = 0; - self.strings().map(move |string| { - let start = cursor; - cursor += string.len(); - start .. cursor - }) - } - - /// The string representation of each child. - fn strings(&self) -> impl Iterator<Item = &str> { - self.children.iter().map(|child| match child { - ParChild::Spacing(_) => " ", - ParChild::Text(ref piece, ..) => piece, - ParChild::Node(..) => "\u{FFFC}", - ParChild::Decorate(_) | ParChild::Undecorate => "", - }) - } -} - -impl Debug for ParChild { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Spacing(v) => write!(f, "Spacing({:?})", v), - Self::Text(text, ..) => write!(f, "Text({:?})", text), - Self::Node(node, ..) => node.fmt(f), - Self::Decorate(deco) => write!(f, "Decorate({:?})", deco), - Self::Undecorate => write!(f, "Undecorate"), - } - } -} - -/// A paragraph representation in which children are already layouted and text -/// is separated into shapable runs. -struct ParLayouter<'a> { - /// The top-level direction. - dir: Dir, - /// The line spacing. - leading: Length, - /// Bidirectional text embedding levels for the paragraph. - bidi: BidiInfo<'a>, - /// Spacing, separated text runs and layouted nodes. - items: Vec<ParItem<'a>>, - /// The ranges of the items in `bidi.text`. - ranges: Vec<Range>, - /// The decorations and the ranges they span. - decos: Vec<(Range, &'a Decoration)>, -} - -/// A prepared item in a paragraph layout. -enum ParItem<'a> { - /// Absolute spacing between other items. - Absolute(Length), - /// Fractional spacing between other items. - Fractional(Fractional), - /// A shaped text run with consistent direction. - Text(ShapedText<'a>, Align), - /// A layouted child node. - Frame(Frame, Align), -} - -impl<'a> ParLayouter<'a> { - /// Prepare initial shaped text and layouted children. - fn new( - par: &'a ParNode, - ctx: &mut LayoutContext, - regions: &Regions, - bidi: BidiInfo<'a>, - ) -> Self { - let mut items = vec![]; - let mut ranges = vec![]; - let mut starts = vec![]; - let mut decos = vec![]; - - // Layout the children and collect them into items. - for (range, child) in par.ranges().zip(&par.children) { - match *child { - ParChild::Spacing(Spacing::Linear(v)) => { - let resolved = v.resolve(regions.current.w); - items.push(ParItem::Absolute(resolved)); - ranges.push(range); - } - ParChild::Spacing(Spacing::Fractional(v)) => { - items.push(ParItem::Fractional(v)); - ranges.push(range); - } - ParChild::Text(_, align, ref style) => { - // TODO: Also split by language and script. - let mut cursor = range.start; - for (level, group) in bidi.levels[range].group_by_key(|&lvl| lvl) { - let start = cursor; - cursor += group.len(); - let subrange = start .. cursor; - let text = &bidi.text[subrange.clone()]; - let shaped = shape(ctx, text, style, level.dir()); - items.push(ParItem::Text(shaped, align)); - ranges.push(subrange); - } - } - ParChild::Node(ref node, align) => { - let frame = node.layout(ctx, regions.current.w, regions.base); - items.push(ParItem::Frame(frame, align)); - ranges.push(range); - } - ParChild::Decorate(ref deco) => { - starts.push((range.start, deco)); - } - ParChild::Undecorate => { - let (start, deco) = starts.pop().unwrap(); - decos.push((start .. range.end, deco)); - } - } - } - - Self { - dir: par.dir, - leading: par.leading, - bidi, - items, - ranges, - decos, - } - } - - /// Find first-fit line breaks and build the paragraph. - fn layout( - self, - ctx: &mut LayoutContext, - regions: Regions, - ) -> Vec<Constrained<Rc<Frame>>> { - let mut stack = LineStack::new(self.leading, regions); - - // The current line attempt. - // Invariant: Always fits into `stack.regions.current`. - let mut last = None; - - // The start of the line in `last`. - let mut start = 0; - - // Find suitable line breaks. - // TODO: Provide line break opportunities on alignment changes. - for (end, mandatory) in LineBreakIterator::new(self.bidi.text) { - // Compute the line and its size. - let mut line = LineLayout::new(ctx, &self, start .. end); - - // If the line doesn't fit anymore, we push the last fitting attempt - // into the stack and rebuild the line from its end. The resulting - // line cannot be broken up further. - if !stack.regions.current.fits(line.size) { - if let Some((last_line, last_end)) = last.take() { - // Since the new line try did not fit, no region that would - // fit the line will yield the same line break. Therefore, - // the width of the region must not fit the width of the - // tried line. - if !stack.regions.current.w.fits(line.size.w) { - stack.cts.max.x.set_min(line.size.w); - } - - // Same as above, but for height. - if !stack.regions.current.h.fits(line.size.h) { - let too_large = stack.size.h + self.leading + line.size.h; - stack.cts.max.y.set_min(too_large); - } - - stack.push(last_line); - - stack.cts.min.y = Some(stack.size.h); - start = last_end; - line = LineLayout::new(ctx, &self, start .. end); - } - } - - // If the line does not fit vertically, we start a new region. - while !stack.regions.current.h.fits(line.size.h) { - if stack.regions.in_full_last() { - stack.overflowing = true; - break; - } - - // Again, the line must not fit. It would if the space taken up - // plus the line height would fit, therefore the constraint - // below. - let too_large = stack.size.h + self.leading + line.size.h; - stack.cts.max.y.set_min(too_large); - - stack.finish_region(ctx); - } - - // If the line does not fit horizontally or we have a mandatory - // line break (i.e. due to "\n"), we push the line into the - // stack. - if mandatory || !stack.regions.current.w.fits(line.size.w) { - start = end; - last = None; - - stack.push(line); - - // If there is a trailing line break at the end of the - // paragraph, we want to force an empty line. - if mandatory && end == self.bidi.text.len() { - let line = LineLayout::new(ctx, &self, end .. end); - if stack.regions.current.h.fits(line.size.h) { - stack.push(line); - } - } - - stack.cts.min.y = Some(stack.size.h); - } else { - // Otherwise, the line fits both horizontally and vertically - // and we remember it. - stack.cts.min.x.set_max(line.size.w); - last = Some((line, end)); - } - } - - if let Some((line, _)) = last { - stack.push(line); - stack.cts.min.y = Some(stack.size.h); - } - - stack.finish(ctx) - } - - /// Find the index of the item whose range contains the `text_offset`. - fn find(&self, text_offset: usize) -> Option<usize> { - self.ranges.binary_search_by(|r| r.locate(text_offset)).ok() - } -} - -/// A lightweight representation of a line that spans a specific range in a -/// paragraph's text. This type enables you to cheaply measure the size of a -/// line in a range before comitting to building the line's frame. -struct LineLayout<'a> { - /// Bidi information about the paragraph. - par: &'a ParLayouter<'a>, - /// The range the line spans in the paragraph. - line: Range, - /// A reshaped text item if the line sliced up a text item at the start. - first: Option<ParItem<'a>>, - /// Middle items which don't need to be reprocessed. - items: &'a [ParItem<'a>], - /// A reshaped text item if the line sliced up a text item at the end. If - /// there is only one text item, this takes precedence over `first`. - last: Option<ParItem<'a>>, - /// The ranges, indexed as `[first, ..items, last]`. The ranges for `first` - /// and `last` aren't trimmed to the line, but it doesn't matter because - /// we're just checking which range an index falls into. - ranges: &'a [Range], - /// The size of the line. - size: Size, - /// The baseline of the line. - baseline: Length, - /// The sum of fractional ratios in the line. - fr: Fractional, -} - -impl<'a> LineLayout<'a> { - /// Create a line which spans the given range. - fn new(ctx: &mut LayoutContext, par: &'a ParLayouter<'a>, mut line: Range) -> Self { - // Find the items which bound the text range. - let last_idx = par.find(line.end.saturating_sub(1)).unwrap(); - let first_idx = if line.is_empty() { - last_idx - } else { - par.find(line.start).unwrap() - }; - - // Slice out the relevant items and ranges. - let mut items = &par.items[first_idx ..= last_idx]; - let ranges = &par.ranges[first_idx ..= last_idx]; - - // 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() { - // Compute the range we want to shape, trimming whitespace at the - // end of the line. - let base = par.ranges[last_idx].start; - let start = line.start.max(base); - let end = start + par.bidi.text[start .. line.end].trim_end().len(); - let range = start - base .. end - base; - - // Reshape if necessary. - if range.len() < shaped.text.len() { - // If start == end and the rest is empty, then we have an empty - // line. To make that line have the appropriate height, we shape the - // empty string. - if !range.is_empty() || rest.is_empty() { - // Reshape that part. - let reshaped = shaped.reshape(ctx, range); - last = Some(ParItem::Text(reshaped, *align)); - } - - items = rest; - line.end = end; - } - } - - // 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() { - // Compute the range we want to shape. - let Range { start: base, end: first_end } = par.ranges[first_idx]; - let start = line.start; - let end = line.end.min(first_end); - let range = start - base .. end - base; - - // Reshape if necessary. - if range.len() < shaped.text.len() { - if !range.is_empty() { - let reshaped = shaped.reshape(ctx, range); - first = Some(ParItem::Text(reshaped, *align)); - } - - items = rest; - } - } - - let mut width = Length::zero(); - let mut top = Length::zero(); - let mut bottom = Length::zero(); - let mut fr = Fractional::zero(); - - // Measure the size of the line. - for item in first.iter().chain(items).chain(&last) { - match *item { - ParItem::Absolute(v) => width += v, - ParItem::Fractional(v) => fr += v, - ParItem::Text(ShapedText { size, baseline, .. }, _) - | ParItem::Frame(Frame { size, baseline, .. }, _) => { - width += size.w; - top.set_max(baseline); - bottom.set_max(size.h - baseline); - } - } - } - - Self { - par, - line, - first, - items, - last, - ranges, - size: Size::new(width, top + bottom), - baseline: top, - fr, - } - } - - /// Build the line's frame. - fn build(&self, ctx: &LayoutContext, width: Length) -> Frame { - let size = Size::new(self.size.w.max(width), self.size.h); - let remaining = size.w - self.size.w; - - let mut output = Frame::new(size, self.baseline); - let mut offset = Length::zero(); - let mut ruler = Align::Start; - - for (range, item) in self.reordered() { - let mut position = |mut frame: Frame, align: Align| { - // Decorate. - for (deco_range, deco) in &self.par.decos { - if deco_range.contains(&range.start) { - deco.apply(ctx, &mut frame); - } - } - - // FIXME: Ruler alignment for RTL. - ruler = ruler.max(align); - let x = ruler.resolve(self.par.dir, offset .. remaining + offset); - let y = self.baseline - frame.baseline; - offset += frame.size.w; - - // Add to the line's frame. - output.merge_frame(Point::new(x, y), frame); - }; - - match *item { - ParItem::Absolute(v) => offset += v, - ParItem::Fractional(v) => { - let ratio = v / self.fr; - if remaining.is_finite() && ratio.is_finite() { - offset += ratio * remaining; - } - } - ParItem::Text(ref shaped, align) => position(shaped.build(), align), - ParItem::Frame(ref frame, align) => position(frame.clone(), align), - } - } - - output - } - - /// Iterate through the line's items in visual order. - fn reordered(&self) -> impl Iterator<Item = (Range, &ParItem<'a>)> { - // The bidi crate doesn't like empty lines. - let (levels, runs) = if !self.line.is_empty() { - // Find the paragraph that contains the line. - let para = self - .par - .bidi - .paragraphs - .iter() - .find(|para| para.range.contains(&self.line.start)) - .unwrap(); - - // Compute the reordered ranges in visual order (left to right). - self.par.bidi.visual_runs(para, self.line.clone()) - } else { - <_>::default() - }; - - runs.into_iter() - .flat_map(move |run| { - let first_idx = self.find(run.start).unwrap(); - let last_idx = self.find(run.end - 1).unwrap(); - let range = first_idx ..= last_idx; - - // Provide the items forwards or backwards depending on the run's - // direction. - if levels[run.start].is_ltr() { - Either::Left(range) - } else { - Either::Right(range.rev()) - } - }) - .map(move |idx| (self.ranges[idx].clone(), self.get(idx).unwrap())) - } - - /// Find the index of the item whose range contains the `text_offset`. - fn find(&self, text_offset: usize) -> Option<usize> { - self.ranges.binary_search_by(|r| r.locate(text_offset)).ok() - } - - /// Get the item at the index. - fn get(&self, index: usize) -> Option<&ParItem<'a>> { - self.first.iter().chain(self.items).chain(&self.last).nth(index) - } -} - -/// Stacks lines on top of each other. -struct LineStack<'a> { - leading: Length, - full: Size, - regions: Regions, - size: Size, - lines: Vec<LineLayout<'a>>, - finished: Vec<Constrained<Rc<Frame>>>, - cts: Constraints, - overflowing: bool, - fractional: bool, -} - -impl<'a> LineStack<'a> { - /// Create an empty line stack. - fn new(leading: Length, regions: Regions) -> Self { - Self { - leading, - full: regions.current, - cts: Constraints::new(regions.expand), - regions, - size: Size::zero(), - lines: vec![], - finished: vec![], - overflowing: false, - fractional: false, - } - } - - /// Push a new line into the stack. - fn push(&mut self, line: LineLayout<'a>) { - self.regions.current.h -= line.size.h + self.leading; - - self.size.w.set_max(line.size.w); - self.size.h += line.size.h; - if !self.lines.is_empty() { - self.size.h += self.leading; - } - - self.fractional |= !line.fr.is_zero(); - self.lines.push(line); - } - - /// Finish the frame for one region. - fn finish_region(&mut self, ctx: &LayoutContext) { - if self.regions.expand.x || self.fractional { - self.size.w = self.regions.current.w; - self.cts.exact.x = Some(self.regions.current.w); - } - - if self.overflowing { - self.cts.min.y = None; - self.cts.max.y = None; - self.cts.exact = self.full.to_spec().map(Some); - } - - let mut output = Frame::new(self.size, self.size.h); - let mut offset = Length::zero(); - let mut first = true; - - for line in self.lines.drain(..) { - let frame = line.build(ctx, self.size.w); - - let pos = Point::new(Length::zero(), offset); - if first { - output.baseline = pos.y + frame.baseline; - first = false; - } - - offset += frame.size.h + self.leading; - output.merge_frame(pos, frame); - } - - self.finished.push(output.constrain(self.cts)); - self.regions.next(); - self.full = self.regions.current; - self.cts = Constraints::new(self.regions.expand); - self.size = Size::zero(); - } - - /// Finish the last region and return the built frames. - fn finish(mut self, ctx: &LayoutContext) -> Vec<Constrained<Rc<Frame>>> { - self.finish_region(ctx); - self.finished - } -} - -/// Additional methods for BiDi levels. -trait LevelExt: Sized { - fn from_dir(dir: Dir) -> Option<Self>; - fn dir(self) -> Dir; -} - -impl LevelExt for Level { - fn from_dir(dir: Dir) -> Option<Self> { - match dir { - Dir::LTR => Some(Level::ltr()), - Dir::RTL => Some(Level::rtl()), - _ => None, - } - } - - fn dir(self) -> Dir { - if self.is_ltr() { Dir::LTR } else { Dir::RTL } - } -} diff --git a/src/layout/shape.rs b/src/layout/shape.rs deleted file mode 100644 index ed70dd95..00000000 --- a/src/layout/shape.rs +++ /dev/null @@ -1,106 +0,0 @@ -use std::f64::consts::SQRT_2; - -use super::*; -use crate::util::RcExt; - -/// Places its child into a sizable and fillable shape. -#[derive(Debug, Hash)] -pub struct ShapeNode { - /// Which shape to place the child into. - pub shape: ShapeKind, - /// The width, if any. - pub width: Option<Linear>, - /// The height, if any. - pub height: Option<Linear>, - /// How to fill the shape, if at all. - pub fill: Option<Paint>, - /// The child node to place into the shape, if any. - pub child: Option<BlockNode>, -} - -/// The type of a shape. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum ShapeKind { - /// A rectangle with equal side lengths. - Square, - /// A quadrilateral with four right angles. - Rect, - /// An ellipse with coinciding foci. - Circle, - /// A curve around two focal points. - Ellipse, -} - -impl InlineLevel for ShapeNode { - fn layout(&self, ctx: &mut LayoutContext, space: Length, base: Size) -> Frame { - // Resolve width and height relative to the region's base. - let width = self.width.map(|w| w.resolve(base.w)); - let height = self.height.map(|h| h.resolve(base.h)); - - // Layout. - let mut frame = if let Some(child) = &self.child { - let mut node: &dyn BlockLevel = child; - - let padded; - if matches!(self.shape, ShapeKind::Circle | ShapeKind::Ellipse) { - // Padding with this ratio ensures that a rectangular child fits - // perfectly into a circle / an ellipse. - padded = PadNode { - padding: Sides::splat(Relative::new(0.5 - SQRT_2 / 4.0).into()), - child: child.clone(), - }; - node = &padded; - } - - // The "pod" is the region into which the child will be layouted. - let mut pod = { - let size = Size::new(width.unwrap_or(space), height.unwrap_or(base.h)); - - let base = Size::new( - if width.is_some() { size.w } else { base.w }, - if height.is_some() { size.h } else { base.h }, - ); - - let expand = Spec::new(width.is_some(), height.is_some()); - Regions::one(size, base, expand) - }; - - // Now, layout the child. - let mut frames = node.layout(ctx, &pod); - - if matches!(self.shape, ShapeKind::Square | ShapeKind::Circle) { - // Relayout with full expansion into square region to make sure - // the result is really a square or circle. - let size = frames[0].item.size; - pod.current.w = size.w.max(size.h).min(pod.current.w); - pod.current.h = pod.current.w; - pod.expand = Spec::splat(true); - frames = node.layout(ctx, &pod); - } - - // Validate and set constraints. - assert_eq!(frames.len(), 1); - Rc::take(frames.into_iter().next().unwrap().item) - } else { - // Resolve shape size. - let size = Size::new(width.unwrap_or_default(), height.unwrap_or_default()); - Frame::new(size, size.h) - }; - - // Add background shape if desired. - if let Some(fill) = self.fill { - let (pos, geometry) = match self.shape { - ShapeKind::Square | ShapeKind::Rect => { - (Point::zero(), Geometry::Rect(frame.size)) - } - ShapeKind::Circle | ShapeKind::Ellipse => { - (frame.size.to_point() / 2.0, Geometry::Ellipse(frame.size)) - } - }; - - frame.prepend(pos, Element::Geometry(geometry, fill)); - } - - frame - } -} diff --git a/src/layout/stack.rs b/src/layout/stack.rs deleted file mode 100644 index 0fb3e3eb..00000000 --- a/src/layout/stack.rs +++ /dev/null @@ -1,226 +0,0 @@ -use std::fmt::{self, Debug, Formatter}; - -use super::*; - -/// A node that stacks its children. -#[derive(Debug, Hash)] -pub struct StackNode { - /// The stacking direction. - pub dir: Dir, - /// The children to be stacked. - pub children: Vec<StackChild>, -} - -/// A child of a stack node. -#[derive(Hash)] -pub enum StackChild { - /// Spacing between other nodes. - Spacing(Spacing), - /// Any block node and how to align it in the stack. - Node(BlockNode, Align), -} - -impl BlockLevel for StackNode { - fn layout( - &self, - ctx: &mut LayoutContext, - regions: &Regions, - ) -> Vec<Constrained<Rc<Frame>>> { - StackLayouter::new(self, regions.clone()).layout(ctx) - } -} - -impl Debug for StackChild { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Spacing(v) => write!(f, "Spacing({:?})", v), - Self::Node(node, _) => node.fmt(f), - } - } -} - -/// Performs stack layout. -struct StackLayouter<'a> { - /// The stack node to layout. - stack: &'a StackNode, - /// The axis of the block direction. - axis: SpecAxis, - /// Whether the stack should expand to fill the region. - expand: Spec<bool>, - /// The region to layout into. - regions: Regions, - /// The full size of `regions.current` that was available before we started - /// subtracting. - full: Size, - /// The generic size used by the frames for the current region. - used: Gen<Length>, - /// The sum of fractional ratios in the current region. - fr: Fractional, - /// Spacing and layouted nodes. - items: Vec<StackItem>, - /// Finished frames for previous regions. - finished: Vec<Constrained<Rc<Frame>>>, -} - -/// A prepared item in a stack layout. -enum StackItem { - /// Absolute spacing between other items. - Absolute(Length), - /// Fractional spacing between other items. - Fractional(Fractional), - /// A layouted child node. - Frame(Rc<Frame>, Align), -} - -impl<'a> StackLayouter<'a> { - /// Create a new stack layouter. - fn new(stack: &'a StackNode, mut regions: Regions) -> Self { - // Disable expansion along the block axis for children. - let axis = stack.dir.axis(); - let expand = regions.expand; - regions.expand.set(axis, false); - - Self { - stack, - axis, - expand, - full: regions.current, - regions, - used: Gen::zero(), - fr: Fractional::zero(), - items: vec![], - finished: vec![], - } - } - - /// Layout all children. - fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Rc<Frame>>> { - for child in &self.stack.children { - match *child { - StackChild::Spacing(Spacing::Linear(v)) => { - self.layout_absolute(v); - } - StackChild::Spacing(Spacing::Fractional(v)) => { - self.items.push(StackItem::Fractional(v)); - self.fr += v; - } - StackChild::Node(ref node, align) => { - self.layout_node(ctx, node, align); - } - } - } - - self.finish_region(); - self.finished - } - - /// Layout absolute spacing. - fn layout_absolute(&mut self, amount: Linear) { - // Resolve the linear, limiting it to the remaining available space. - let remaining = self.regions.current.get_mut(self.axis); - let resolved = amount.resolve(self.full.get(self.axis)); - let limited = resolved.min(*remaining); - *remaining -= limited; - self.used.block += limited; - self.items.push(StackItem::Absolute(resolved)); - } - - /// Layout a block node. - fn layout_node(&mut self, ctx: &mut LayoutContext, node: &BlockNode, align: Align) { - let frames = node.layout(ctx, &self.regions); - let len = frames.len(); - for (i, frame) in frames.into_iter().enumerate() { - // Grow our size. - let size = frame.item.size.to_gen(self.axis); - self.used.block += size.block; - self.used.inline.set_max(size.inline); - - // Remember the frame and shrink available space in the region for the - // following children. - self.items.push(StackItem::Frame(frame.item, align)); - *self.regions.current.get_mut(self.axis) -= size.block; - - if i + 1 < len { - self.finish_region(); - } - } - } - - /// Finish the frame for one region. - fn finish_region(&mut self) { - // Determine the size that remains for fractional spacing. - let remaining = self.full.get(self.axis) - self.used.block; - - // Determine the size of the stack in this region dependening on whether - // the region expands. - let used = self.used.to_size(self.axis); - let mut size = Size::new( - if self.expand.x { self.full.w } else { used.w }, - if self.expand.y { self.full.h } else { used.h }, - ); - - // Expand fully if there are fr spacings. - let full = self.full.get(self.axis); - if !self.fr.is_zero() && full.is_finite() { - size.set(self.axis, full); - } - - let mut output = Frame::new(size, size.h); - let mut before = Length::zero(); - let mut ruler = Align::Start; - let mut first = true; - - // Place all frames. - for item in self.items.drain(..) { - match item { - StackItem::Absolute(v) => before += v, - StackItem::Fractional(v) => { - let ratio = v / self.fr; - if remaining.is_finite() && ratio.is_finite() { - before += ratio * remaining; - } - } - StackItem::Frame(frame, align) => { - ruler = ruler.max(align); - - let parent = size.to_gen(self.axis); - let child = frame.size.to_gen(self.axis); - - // Align along the block axis. - let block = ruler.resolve( - self.stack.dir, - if self.stack.dir.is_positive() { - let after = self.used.block - before; - before .. parent.block - after - } else { - let before_with_self = before + child.block; - let after = self.used.block - before_with_self; - after .. parent.block - before_with_self - }, - ); - - let pos = Gen::new(Length::zero(), block).to_point(self.axis); - if first { - // The baseline of the stack is that of the first frame. - output.baseline = pos.y + frame.baseline; - first = false; - } - - output.push_frame(pos, frame); - before += child.block; - } - } - } - - // Generate tight constraints for now. - let mut cts = Constraints::new(self.expand); - cts.exact = self.full.to_spec().map(Some); - cts.base = self.regions.base.to_spec().map(Some); - - self.regions.next(); - self.full = self.regions.current; - self.used = Gen::zero(); - self.fr = Fractional::zero(); - self.finished.push(output.constrain(cts)); - } -} diff --git a/src/layout/text.rs b/src/layout/text.rs deleted file mode 100644 index a89d7e3b..00000000 --- a/src/layout/text.rs +++ /dev/null @@ -1,370 +0,0 @@ -use std::borrow::Cow; -use std::ops::Range; - -use rustybuzz::UnicodeBuffer; - -use super::*; -use crate::font::{Face, FaceId, FontVariant}; -use crate::geom::{Dir, Em, Length, Point, Size}; -use crate::style::TextStyle; -use crate::util::SliceExt; - -/// Shape text into [`ShapedText`]. -pub fn shape<'a>( - ctx: &mut LayoutContext, - text: &'a str, - style: &'a TextStyle, - dir: Dir, -) -> ShapedText<'a> { - let mut glyphs = vec![]; - if !text.is_empty() { - shape_segment( - ctx, - &mut glyphs, - 0, - text, - style.size, - style.variant(), - style.families(), - None, - dir, - ); - } - - let (size, baseline) = measure(ctx, &glyphs, style); - ShapedText { - text, - dir, - style, - size, - baseline, - glyphs: Cow::Owned(glyphs), - } -} - -/// The result of shaping text. -/// -/// This type contains owned or borrowed shaped text runs, which can be -/// measured, used to reshape substrings more quickly and converted into a -/// frame. -#[derive(Debug, Clone)] -pub struct ShapedText<'a> { - /// The text that was shaped. - pub text: &'a str, - /// The text direction. - pub dir: Dir, - /// The properties used for font selection. - pub style: &'a TextStyle, - /// The font size. - pub size: Size, - /// The baseline from the top of the frame. - pub baseline: Length, - /// The shaped glyphs. - pub glyphs: Cow<'a, [ShapedGlyph]>, -} - -/// A single glyph resulting from shaping. -#[derive(Debug, Copy, Clone)] -pub struct ShapedGlyph { - /// The font face the glyph is contained in. - pub face_id: FaceId, - /// The glyph's index in the face. - pub glyph_id: u16, - /// The advance width of the glyph. - pub x_advance: Em, - /// The horizontal offset of the glyph. - pub x_offset: Em, - /// The start index of the glyph in the source text. - pub text_index: usize, - /// Whether splitting the shaping result before this glyph would yield the - /// same results as shaping the parts to both sides of `text_index` - /// separately. - pub safe_to_break: bool, -} - -impl<'a> ShapedText<'a> { - /// Build the shaped text's frame. - pub fn build(&self) -> Frame { - let mut frame = Frame::new(self.size, self.baseline); - let mut offset = Length::zero(); - - for (face_id, group) in self.glyphs.as_ref().group_by_key(|g| g.face_id) { - let pos = Point::new(offset, self.baseline); - - let mut text = Text { - face_id, - size: self.style.size, - width: Length::zero(), - fill: self.style.fill, - glyphs: vec![], - }; - - for glyph in group { - text.glyphs.push(Glyph { - id: glyph.glyph_id, - x_advance: glyph.x_advance, - x_offset: glyph.x_offset, - }); - text.width += glyph.x_advance.to_length(text.size); - } - - offset += text.width; - frame.push(pos, Element::Text(text)); - } - - frame - } - - /// Reshape a range of the shaped text, reusing information from this - /// shaping process if possible. - pub fn reshape( - &'a self, - ctx: &mut LayoutContext, - text_range: Range<usize>, - ) -> ShapedText<'a> { - if let Some(glyphs) = self.slice_safe_to_break(text_range.clone()) { - let (size, baseline) = measure(ctx, glyphs, self.style); - Self { - text: &self.text[text_range], - dir: self.dir, - style: self.style, - size, - baseline, - glyphs: Cow::Borrowed(glyphs), - } - } else { - shape(ctx, &self.text[text_range], self.style, self.dir) - } - } - - /// Find the subslice of glyphs that represent the given text range if both - /// sides are safe to break. - fn slice_safe_to_break(&self, text_range: Range<usize>) -> Option<&[ShapedGlyph]> { - let Range { mut start, mut end } = text_range; - if !self.dir.is_positive() { - std::mem::swap(&mut start, &mut end); - } - - let left = self.find_safe_to_break(start, Side::Left)?; - let right = self.find_safe_to_break(end, Side::Right)?; - Some(&self.glyphs[left .. right]) - } - - /// Find the glyph offset matching the text index that is most towards the - /// given side and safe-to-break. - fn find_safe_to_break(&self, text_index: usize, towards: Side) -> Option<usize> { - let ltr = self.dir.is_positive(); - - // Handle edge cases. - let len = self.glyphs.len(); - if text_index == 0 { - return Some(if ltr { 0 } else { len }); - } else if text_index == self.text.len() { - return Some(if ltr { len } else { 0 }); - } - - // Find any glyph with the text index. - let mut idx = self - .glyphs - .binary_search_by(|g| { - let ordering = g.text_index.cmp(&text_index); - if ltr { ordering } else { ordering.reverse() } - }) - .ok()?; - - let next = match towards { - Side::Left => usize::checked_sub, - Side::Right => usize::checked_add, - }; - - // Search for the outermost glyph with the text index. - while let Some(next) = next(idx, 1) { - if self.glyphs.get(next).map_or(true, |g| g.text_index != text_index) { - break; - } - idx = next; - } - - // RTL needs offset one because the left side of the range should be - // exclusive and the right side inclusive, contrary to the normal - // behaviour of ranges. - if !ltr { - idx += 1; - } - - self.glyphs[idx].safe_to_break.then(|| idx) - } -} - -/// A visual side. -enum Side { - Left, - Right, -} - -/// Shape text with font fallback using the `families` iterator. -fn shape_segment<'a>( - ctx: &mut LayoutContext, - glyphs: &mut Vec<ShapedGlyph>, - base: usize, - text: &str, - size: Length, - variant: FontVariant, - mut families: impl Iterator<Item = &'a str> + Clone, - mut first_face: Option<FaceId>, - dir: Dir, -) { - // Select the font family. - let (face_id, fallback) = loop { - // Try to load the next available font family. - match families.next() { - Some(family) => { - if let Some(id) = ctx.fonts.select(family, variant) { - break (id, true); - } - } - // We're out of families, so we don't do any more fallback and just - // shape the tofus with the first face we originally used. - None => match first_face { - Some(id) => break (id, false), - None => return, - }, - } - }; - - // Remember the id if this the first available face since we use that one to - // shape tofus. - first_face.get_or_insert(face_id); - - // Fill the buffer with our text. - let mut buffer = UnicodeBuffer::new(); - buffer.push_str(text); - buffer.set_direction(match dir { - Dir::LTR => rustybuzz::Direction::LeftToRight, - Dir::RTL => rustybuzz::Direction::RightToLeft, - _ => unimplemented!(), - }); - - // Shape! - let mut face = ctx.fonts.get(face_id); - let buffer = rustybuzz::shape(face.ttf(), &[], buffer); - let infos = buffer.glyph_infos(); - let pos = buffer.glyph_positions(); - - // Collect the shaped glyphs, doing fallback and shaping parts again with - // the next font if necessary. - let mut i = 0; - while i < infos.len() { - let info = &infos[i]; - let cluster = info.cluster as usize; - - if info.glyph_id != 0 || !fallback { - // Add the glyph to the shaped output. - // TODO: Don't ignore y_advance and y_offset. - glyphs.push(ShapedGlyph { - face_id, - glyph_id: info.glyph_id as u16, - x_advance: face.to_em(pos[i].x_advance), - x_offset: face.to_em(pos[i].x_offset), - text_index: base + cluster, - safe_to_break: !info.unsafe_to_break(), - }); - } else { - // Determine the source text range for the tofu sequence. - let range = { - // First, search for the end of the tofu sequence. - let k = i; - while infos.get(i + 1).map_or(false, |info| info.glyph_id == 0) { - i += 1; - } - - // Then, determine the start and end text index. - // - // Examples: - // Everything is shown in visual order. Tofus are written as "_". - // We want to find out that the tofus span the text `2..6`. - // Note that the clusters are longer than 1 char. - // - // Left-to-right: - // Text: h a l i h a l l o - // Glyphs: A _ _ C E - // Clusters: 0 2 4 6 8 - // k=1 i=2 - // - // Right-to-left: - // Text: O L L A H I L A H - // Glyphs: E C _ _ A - // Clusters: 8 6 4 2 0 - // k=2 i=3 - - let ltr = dir.is_positive(); - let first = if ltr { k } else { i }; - let start = infos[first].cluster as usize; - - let last = if ltr { i.checked_add(1) } else { k.checked_sub(1) }; - let end = last - .and_then(|last| infos.get(last)) - .map_or(text.len(), |info| info.cluster as usize); - - start .. end - }; - - // Recursively shape the tofu sequence with the next family. - shape_segment( - ctx, - glyphs, - base + range.start, - &text[range], - size, - variant, - families.clone(), - first_face, - dir, - ); - - face = ctx.fonts.get(face_id); - } - - i += 1; - } -} - -/// Measure the size and baseline of a run of shaped glyphs with the given -/// properties. -fn measure( - ctx: &mut LayoutContext, - glyphs: &[ShapedGlyph], - style: &TextStyle, -) -> (Size, Length) { - let mut width = Length::zero(); - let mut top = Length::zero(); - let mut bottom = Length::zero(); - - // Expand top and bottom by reading the face's vertical metrics. - let mut expand = |face: &Face| { - top.set_max(face.vertical_metric(style.top_edge, style.size)); - bottom.set_max(-face.vertical_metric(style.bottom_edge, style.size)); - }; - - if glyphs.is_empty() { - // When there are no glyphs, we just use the vertical metrics of the - // first available font. - for family in style.families() { - if let Some(face_id) = ctx.fonts.select(family, style.variant) { - expand(ctx.fonts.get(face_id)); - break; - } - } - } else { - for (face_id, group) in glyphs.group_by_key(|g| g.face_id) { - let face = ctx.fonts.get(face_id); - expand(face); - - for glyph in group { - width += glyph.x_advance.to_length(style.size); - } - } - } - - (Size::new(width, top + bottom), top) -} |
