summaryrefslogtreecommitdiff
path: root/src/layout
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-10-31 15:52:16 +0100
committerLaurenz <laurmaedje@gmail.com>2021-10-31 15:52:35 +0100
commit5b344b663a3d224134923eea0d67ebf44c069b07 (patch)
tree34a5fb464a38b9d4cb11294379b3ddf351dfce21 /src/layout
parentfeff013abb17f31bc5305fe77fe67cf615c19ff2 (diff)
Reorganize modules
Instead of separating functionality into layout and library, everything lives in the library now. This way, related things live side by side and there are no duplicate file names in the two directories.
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)
-}