summaryrefslogtreecommitdiff
path: root/src/layout
diff options
context:
space:
mode:
Diffstat (limited to 'src/layout')
-rw-r--r--src/layout/constraints.rs24
-rw-r--r--src/layout/deco.rs92
-rw-r--r--src/layout/frame.rs187
-rw-r--r--src/layout/grid.rs551
-rw-r--r--src/layout/image.rs44
-rw-r--r--src/layout/incremental.rs6
-rw-r--r--src/layout/levels.rs199
-rw-r--r--src/layout/mod.rs227
-rw-r--r--src/layout/pad.rs77
-rw-r--r--src/layout/par.rs620
-rw-r--r--src/layout/shape.rs106
-rw-r--r--src/layout/stack.rs226
-rw-r--r--src/layout/text.rs370
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, &regions).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, &regions).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, &regions).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, &regions);
- 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, &regions).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, &regions).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,
- &regions.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)
-}