mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Refactor flow, stack and grid layouters a bit
This commit is contained in:
parent
f9d3802492
commit
bdc7127adf
@ -39,6 +39,14 @@ impl<T> Spec<T> {
|
|||||||
Spec { x: &self.x, y: &self.y }
|
Spec { x: &self.x, y: &self.y }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert from `&Spec<T>` to `Spec<&<T as Deref>::Target>`.
|
||||||
|
pub fn as_deref(&self) -> Spec<&T::Target>
|
||||||
|
where
|
||||||
|
T: Deref,
|
||||||
|
{
|
||||||
|
Spec { x: &self.x, y: &self.y }
|
||||||
|
}
|
||||||
|
|
||||||
/// Convert from `&mut Spec<T>` to `Spec<&mut T>`.
|
/// Convert from `&mut Spec<T>` to `Spec<&mut T>`.
|
||||||
pub fn as_mut(&mut self) -> Spec<&mut T> {
|
pub fn as_mut(&mut self) -> Spec<&mut T> {
|
||||||
Spec { x: &mut self.x, y: &mut self.y }
|
Spec { x: &mut self.x, y: &mut self.y }
|
||||||
|
@ -190,11 +190,7 @@ impl<'a> FlowLayouter<'a> {
|
|||||||
let aligns = Spec::new(
|
let aligns = Spec::new(
|
||||||
// For non-expanding paragraphs it is crucial that we align the
|
// For non-expanding paragraphs it is crucial that we align the
|
||||||
// whole paragraph as it is itself aligned.
|
// whole paragraph as it is itself aligned.
|
||||||
if node.is::<ParNode>() {
|
styles.get(ParNode::ALIGN),
|
||||||
styles.get(ParNode::ALIGN)
|
|
||||||
} else {
|
|
||||||
Align::Left
|
|
||||||
},
|
|
||||||
// Vertical align node alignment is respected by the flow node.
|
// Vertical align node alignment is respected by the flow node.
|
||||||
node.downcast::<AlignNode>()
|
node.downcast::<AlignNode>()
|
||||||
.and_then(|aligned| aligned.aligns.y)
|
.and_then(|aligned| aligned.aligns.y)
|
||||||
|
@ -40,12 +40,15 @@ impl Layout for GridNode {
|
|||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> Vec<Constrained<Arc<Frame>>> {
|
) -> Vec<Constrained<Arc<Frame>>> {
|
||||||
// Prepare grid layout by unifying content and gutter tracks.
|
// Prepare grid layout by unifying content and gutter tracks.
|
||||||
let mut layouter = GridLayouter::new(self, regions.clone(), styles);
|
let layouter = GridLayouter::new(
|
||||||
|
self.tracks.as_deref(),
|
||||||
|
self.gutter.as_deref(),
|
||||||
|
&self.children,
|
||||||
|
regions,
|
||||||
|
styles,
|
||||||
|
);
|
||||||
|
|
||||||
// Determine all column sizes.
|
// Measure the columsna nd layout the grid row-by-row.
|
||||||
layouter.measure_columns(ctx);
|
|
||||||
|
|
||||||
// Layout the grid row-by-row.
|
|
||||||
layouter.layout(ctx)
|
layouter.layout(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -87,9 +90,9 @@ castable! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Performs grid layout.
|
/// Performs grid layout.
|
||||||
struct GridLayouter<'a> {
|
pub struct GridLayouter<'a> {
|
||||||
/// The children of the grid.
|
/// The grid cells.
|
||||||
children: &'a [PackedNode],
|
cells: &'a [PackedNode],
|
||||||
/// The column tracks including gutter tracks.
|
/// The column tracks including gutter tracks.
|
||||||
cols: Vec<TrackSizing>,
|
cols: Vec<TrackSizing>,
|
||||||
/// The row tracks including gutter tracks.
|
/// The row tracks including gutter tracks.
|
||||||
@ -102,7 +105,7 @@ struct GridLayouter<'a> {
|
|||||||
rcols: Vec<Length>,
|
rcols: Vec<Length>,
|
||||||
/// Rows in the current region.
|
/// Rows in the current region.
|
||||||
lrows: Vec<Row>,
|
lrows: Vec<Row>,
|
||||||
/// Whether the grid should expand to fill the region.
|
/// Whether the grid itself should expand to fill the region.
|
||||||
expand: Spec<bool>,
|
expand: Spec<bool>,
|
||||||
/// The full height of the current region.
|
/// The full height of the current region.
|
||||||
full: Length,
|
full: Length,
|
||||||
@ -127,19 +130,27 @@ enum Row {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> GridLayouter<'a> {
|
impl<'a> GridLayouter<'a> {
|
||||||
/// Prepare grid layout by unifying content and gutter tracks.
|
/// Create a new grid layouter.
|
||||||
fn new(grid: &'a GridNode, mut regions: Regions, styles: StyleChain<'a>) -> Self {
|
///
|
||||||
|
/// This prepares grid layout by unifying content and gutter tracks.
|
||||||
|
pub fn new(
|
||||||
|
tracks: Spec<&[TrackSizing]>,
|
||||||
|
gutter: Spec<&[TrackSizing]>,
|
||||||
|
cells: &'a [PackedNode],
|
||||||
|
regions: &Regions,
|
||||||
|
styles: StyleChain<'a>,
|
||||||
|
) -> Self {
|
||||||
let mut cols = vec![];
|
let mut cols = vec![];
|
||||||
let mut rows = vec![];
|
let mut rows = vec![];
|
||||||
|
|
||||||
// Number of content columns: Always at least one.
|
// Number of content columns: Always at least one.
|
||||||
let c = grid.tracks.x.len().max(1);
|
let c = tracks.x.len().max(1);
|
||||||
|
|
||||||
// Number of content rows: At least as many as given, but also at least
|
// Number of content rows: At least as many as given, but also at least
|
||||||
// as many as needed to place each item.
|
// as many as needed to place each item.
|
||||||
let r = {
|
let r = {
|
||||||
let len = grid.children.len();
|
let len = cells.len();
|
||||||
let given = grid.tracks.y.len();
|
let given = tracks.y.len();
|
||||||
let needed = len / c + (len % c).clamp(0, 1);
|
let needed = len / c + (len % c).clamp(0, 1);
|
||||||
given.max(needed)
|
given.max(needed)
|
||||||
};
|
};
|
||||||
@ -152,14 +163,14 @@ impl<'a> GridLayouter<'a> {
|
|||||||
|
|
||||||
// Collect content and gutter columns.
|
// Collect content and gutter columns.
|
||||||
for x in 0 .. c {
|
for x in 0 .. c {
|
||||||
cols.push(get_or(&grid.tracks.x, x, auto));
|
cols.push(get_or(tracks.x, x, auto));
|
||||||
cols.push(get_or(&grid.gutter.x, x, zero));
|
cols.push(get_or(gutter.x, x, zero));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect content and gutter rows.
|
// Collect content and gutter rows.
|
||||||
for y in 0 .. r {
|
for y in 0 .. r {
|
||||||
rows.push(get_or(&grid.tracks.y, y, auto));
|
rows.push(get_or(tracks.y, y, auto));
|
||||||
rows.push(get_or(&grid.gutter.y, y, zero));
|
rows.push(get_or(gutter.y, y, zero));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove superfluous gutter tracks.
|
// Remove superfluous gutter tracks.
|
||||||
@ -173,10 +184,11 @@ impl<'a> GridLayouter<'a> {
|
|||||||
|
|
||||||
// We use the regions for auto row measurement. Since at that moment,
|
// We use the regions for auto row measurement. Since at that moment,
|
||||||
// columns are already sized, we can enable horizontal expansion.
|
// columns are already sized, we can enable horizontal expansion.
|
||||||
|
let mut regions = regions.clone();
|
||||||
regions.expand = Spec::new(true, false);
|
regions.expand = Spec::new(true, false);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
children: &grid.children,
|
cells,
|
||||||
cols,
|
cols,
|
||||||
rows,
|
rows,
|
||||||
regions,
|
regions,
|
||||||
@ -192,6 +204,32 @@ impl<'a> GridLayouter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Determines the columns sizes and then layouts the grid row-by-row.
|
||||||
|
pub fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Arc<Frame>>> {
|
||||||
|
self.measure_columns(ctx);
|
||||||
|
|
||||||
|
for y in 0 .. self.rows.len() {
|
||||||
|
// Skip to next region if current one is full, but only for content
|
||||||
|
// rows, not for gutter rows.
|
||||||
|
if y % 2 == 0 && self.regions.is_full() {
|
||||||
|
self.finish_region(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
/// Determine all column sizes.
|
/// Determine all column sizes.
|
||||||
fn measure_columns(&mut self, ctx: &mut LayoutContext) {
|
fn measure_columns(&mut self, ctx: &mut LayoutContext) {
|
||||||
enum Case {
|
enum Case {
|
||||||
@ -354,30 +392,6 @@ impl<'a> GridLayouter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout the grid row-by-row.
|
|
||||||
fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Arc<Frame>>> {
|
|
||||||
for y in 0 .. self.rows.len() {
|
|
||||||
// Skip to next region if current one is full, but only for content
|
|
||||||
// rows, not for gutter rows.
|
|
||||||
if y % 2 == 0 && self.regions.is_full() {
|
|
||||||
self.finish_region(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 height. Such a row may break across multiple
|
/// Layout a row with automatic height. Such a row may break across multiple
|
||||||
/// regions.
|
/// regions.
|
||||||
fn layout_auto_row(&mut self, ctx: &mut LayoutContext, y: usize) {
|
fn layout_auto_row(&mut self, ctx: &mut LayoutContext, y: usize) {
|
||||||
@ -599,7 +613,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
// Even columns and rows are children, odd ones are gutter.
|
// Even columns and rows are children, odd ones are gutter.
|
||||||
if x % 2 == 0 && y % 2 == 0 {
|
if x % 2 == 0 && y % 2 == 0 {
|
||||||
let c = 1 + self.cols.len() / 2;
|
let c = 1 + self.cols.len() / 2;
|
||||||
self.children.get((y / 2) * c + x / 2)
|
self.cells.get((y / 2) * c + x / 2)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,29 @@ impl Layout for StackNode {
|
|||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> Vec<Constrained<Arc<Frame>>> {
|
) -> Vec<Constrained<Arc<Frame>>> {
|
||||||
StackLayouter::new(self, regions.clone(), styles).layout(ctx)
|
let mut layouter = StackLayouter::new(self.dir, regions);
|
||||||
|
|
||||||
|
// Spacing to insert before the next node.
|
||||||
|
let mut deferred = None;
|
||||||
|
|
||||||
|
for child in &self.children {
|
||||||
|
match child {
|
||||||
|
StackChild::Spacing(kind) => {
|
||||||
|
layouter.layout_spacing(*kind);
|
||||||
|
deferred = None;
|
||||||
|
}
|
||||||
|
StackChild::Node(node) => {
|
||||||
|
if let Some(kind) = deferred {
|
||||||
|
layouter.layout_spacing(kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
layouter.layout_node(ctx, node, styles);
|
||||||
|
deferred = self.spacing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
layouter.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,29 +87,23 @@ castable! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Performs stack layout.
|
/// Performs stack layout.
|
||||||
struct StackLayouter<'a> {
|
pub struct StackLayouter {
|
||||||
/// The children of the stack.
|
|
||||||
children: &'a [StackChild],
|
|
||||||
/// The stacking direction.
|
/// The stacking direction.
|
||||||
dir: Dir,
|
dir: Dir,
|
||||||
/// The axis of the stacking direction.
|
/// The axis of the stacking direction.
|
||||||
axis: SpecAxis,
|
axis: SpecAxis,
|
||||||
/// The spacing between non-spacing children.
|
|
||||||
spacing: Option<SpacingKind>,
|
|
||||||
/// The regions to layout children into.
|
/// The regions to layout children into.
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
/// The inherited styles.
|
/// Whether the stack itself should expand to fill the region.
|
||||||
styles: StyleChain<'a>,
|
|
||||||
/// Whether the stack should expand to fill the region.
|
|
||||||
expand: Spec<bool>,
|
expand: Spec<bool>,
|
||||||
/// The full size of `regions.current` that was available before we started
|
/// The full size of the current region that was available at the start.
|
||||||
/// subtracting.
|
|
||||||
full: Size,
|
full: Size,
|
||||||
/// The generic size used by the frames for the current region.
|
/// The generic size used by the frames for the current region.
|
||||||
used: Gen<Length>,
|
used: Gen<Length>,
|
||||||
/// The sum of fractional ratios in the current region.
|
/// The sum of fractional ratios in the current region.
|
||||||
fr: Fractional,
|
fr: Fractional,
|
||||||
/// Spacing and layouted nodes.
|
/// Already layouted items whose exact positions are not yet known due to
|
||||||
|
/// fractional spacing.
|
||||||
items: Vec<StackItem>,
|
items: Vec<StackItem>,
|
||||||
/// Finished frames for previous regions.
|
/// Finished frames for previous regions.
|
||||||
finished: Vec<Constrained<Arc<Frame>>>,
|
finished: Vec<Constrained<Arc<Frame>>>,
|
||||||
@ -103,24 +119,21 @@ enum StackItem {
|
|||||||
Frame(Arc<Frame>, Align),
|
Frame(Arc<Frame>, Align),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> StackLayouter<'a> {
|
impl StackLayouter {
|
||||||
/// Create a new stack layouter.
|
/// Create a new stack layouter.
|
||||||
fn new(stack: &'a StackNode, mut regions: Regions, styles: StyleChain<'a>) -> Self {
|
pub fn new(dir: Dir, regions: &Regions) -> Self {
|
||||||
let dir = stack.dir;
|
|
||||||
let axis = dir.axis();
|
let axis = dir.axis();
|
||||||
let expand = regions.expand;
|
let expand = regions.expand;
|
||||||
let full = regions.current;
|
let full = regions.current;
|
||||||
|
|
||||||
// Disable expansion along the block axis for children.
|
// Disable expansion along the block axis for children.
|
||||||
|
let mut regions = regions.clone();
|
||||||
regions.expand.set(axis, false);
|
regions.expand.set(axis, false);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
children: &stack.children,
|
|
||||||
dir,
|
dir,
|
||||||
axis,
|
axis,
|
||||||
spacing: stack.spacing,
|
|
||||||
regions,
|
regions,
|
||||||
styles,
|
|
||||||
expand,
|
expand,
|
||||||
full,
|
full,
|
||||||
used: Gen::zero(),
|
used: Gen::zero(),
|
||||||
@ -130,67 +143,43 @@ impl<'a> StackLayouter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout all children.
|
/// Add spacing along the spacing direction.
|
||||||
fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Arc<Frame>>> {
|
pub fn layout_spacing(&mut self, spacing: SpacingKind) {
|
||||||
// Spacing to insert before the next node.
|
|
||||||
let mut deferred = None;
|
|
||||||
|
|
||||||
for child in self.children {
|
|
||||||
match *child {
|
|
||||||
StackChild::Spacing(kind) => {
|
|
||||||
self.layout_spacing(kind);
|
|
||||||
deferred = None;
|
|
||||||
}
|
|
||||||
StackChild::Node(ref node) => {
|
|
||||||
if let Some(kind) = deferred {
|
|
||||||
self.layout_spacing(kind);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.regions.is_full() {
|
|
||||||
self.finish_region();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.layout_node(ctx, node);
|
|
||||||
deferred = self.spacing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.finish_region();
|
|
||||||
self.finished
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Layout spacing.
|
|
||||||
fn layout_spacing(&mut self, spacing: SpacingKind) {
|
|
||||||
match spacing {
|
match spacing {
|
||||||
SpacingKind::Linear(v) => self.layout_absolute(v),
|
SpacingKind::Linear(v) => {
|
||||||
SpacingKind::Fractional(v) => {
|
// Resolve the linear and limit it to the remaining space.
|
||||||
self.items.push(StackItem::Fractional(v));
|
let resolved = v.resolve(self.regions.base.get(self.axis));
|
||||||
self.fr += v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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 remaining = self.regions.current.get_mut(self.axis);
|
||||||
let resolved = amount.resolve(self.regions.base.get(self.axis));
|
|
||||||
let limited = resolved.min(*remaining);
|
let limited = resolved.min(*remaining);
|
||||||
*remaining -= limited;
|
*remaining -= limited;
|
||||||
self.used.main += limited;
|
self.used.main += limited;
|
||||||
self.items.push(StackItem::Absolute(resolved));
|
self.items.push(StackItem::Absolute(resolved));
|
||||||
}
|
}
|
||||||
|
SpacingKind::Fractional(v) => {
|
||||||
|
self.fr += v;
|
||||||
|
self.items.push(StackItem::Fractional(v));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Layout an arbitrary node.
|
||||||
|
pub fn layout_node(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut LayoutContext,
|
||||||
|
node: &PackedNode,
|
||||||
|
styles: StyleChain,
|
||||||
|
) {
|
||||||
|
if self.regions.is_full() {
|
||||||
|
self.finish_region();
|
||||||
|
}
|
||||||
|
|
||||||
/// Layout a node.
|
|
||||||
fn layout_node(&mut self, ctx: &mut LayoutContext, node: &PackedNode) {
|
|
||||||
// Align nodes' block-axis alignment is respected by the stack node.
|
// Align nodes' block-axis alignment is respected by the stack node.
|
||||||
let align = node
|
let align = node
|
||||||
.downcast::<AlignNode>()
|
.downcast::<AlignNode>()
|
||||||
.and_then(|node| node.aligns.get(self.axis))
|
.and_then(|node| node.aligns.get(self.axis))
|
||||||
.unwrap_or(self.dir.start().into());
|
.unwrap_or(self.dir.start().into());
|
||||||
|
|
||||||
let frames = node.layout(ctx, &self.regions, self.styles);
|
let frames = node.layout(ctx, &self.regions, styles);
|
||||||
let len = frames.len();
|
let len = frames.len();
|
||||||
for (i, frame) in frames.into_iter().enumerate() {
|
for (i, frame) in frames.into_iter().enumerate() {
|
||||||
// Grow our size, shrink the region and save the frame for later.
|
// Grow our size, shrink the region and save the frame for later.
|
||||||
@ -206,8 +195,8 @@ impl<'a> StackLayouter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish the frame for one region.
|
/// Advance to the next region.
|
||||||
fn finish_region(&mut self) {
|
pub fn finish_region(&mut self) {
|
||||||
// Determine the size of the stack in this region dependening on whether
|
// Determine the size of the stack in this region dependening on whether
|
||||||
// the region expands.
|
// the region expands.
|
||||||
let used = self.used.to_spec(self.axis);
|
let used = self.used.to_spec(self.axis);
|
||||||
@ -228,12 +217,8 @@ impl<'a> StackLayouter<'a> {
|
|||||||
// Place all frames.
|
// Place all frames.
|
||||||
for item in self.items.drain(..) {
|
for item in self.items.drain(..) {
|
||||||
match item {
|
match item {
|
||||||
StackItem::Absolute(v) => {
|
StackItem::Absolute(v) => cursor += v,
|
||||||
cursor += v;
|
StackItem::Fractional(v) => cursor += v.resolve(self.fr, remaining),
|
||||||
}
|
|
||||||
StackItem::Fractional(v) => {
|
|
||||||
cursor += v.resolve(self.fr, remaining);
|
|
||||||
}
|
|
||||||
StackItem::Frame(frame, align) => {
|
StackItem::Frame(frame, align) => {
|
||||||
if self.dir.is_positive() {
|
if self.dir.is_positive() {
|
||||||
ruler = ruler.max(align);
|
ruler = ruler.max(align);
|
||||||
@ -270,4 +255,10 @@ impl<'a> StackLayouter<'a> {
|
|||||||
self.fr = Fractional::zero();
|
self.fr = Fractional::zero();
|
||||||
self.finished.push(output.constrain(cts));
|
self.finished.push(output.constrain(cts));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Finish layouting and return the resulting frames.
|
||||||
|
pub fn finish(mut self) -> Vec<Constrained<Arc<Frame>>> {
|
||||||
|
self.finish_region();
|
||||||
|
self.finished
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user