Move columns handling directly into FlowLayouter (#4792)

This commit is contained in:
Laurenz 2024-08-19 17:06:59 +02:00 committed by GitHub
parent eb0e0fec00
commit a30b681251
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -28,7 +28,7 @@ use crate::model::{FootnoteElem, FootnoteEntry, ParElem};
use crate::realize::StyleVec; use crate::realize::StyleVec;
use crate::realize::{realize_flow, realize_root, Arenas}; use crate::realize::{realize_flow, realize_root, Arenas};
use crate::text::TextElem; use crate::text::TextElem;
use crate::utils::Numeric; use crate::utils::{NonZeroExt, Numeric};
use crate::World; use crate::World;
/// Layout content into a document. /// Layout content into a document.
@ -368,6 +368,8 @@ pub fn layout_fragment(
locator.track(), locator.track(),
styles, styles,
regions, regions,
NonZeroUsize::ONE,
Rel::zero(),
) )
} }
@ -386,68 +388,19 @@ pub fn layout_fragment_with_columns(
count: NonZeroUsize, count: NonZeroUsize,
gutter: Rel<Abs>, gutter: Rel<Abs>,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
// Separating the infinite space into infinite columns does not make layout_fragment_impl(
// much sense. engine.world,
if !regions.size.x.is_finite() { engine.introspector,
return layout_fragment(engine, content, locator, styles, regions); engine.traced,
} TrackedMut::reborrow_mut(&mut engine.sink),
engine.route.track(),
// Determine the width of the gutter and each column. content,
let count = count.get(); locator.track(),
let gutter = gutter.relative_to(regions.base().x); styles,
let width = (regions.size.x - gutter * (count - 1) as f64) / count as f64; regions,
count,
let backlog: Vec<_> = std::iter::once(&regions.size.y) gutter,
.chain(regions.backlog) )
.flat_map(|&height| std::iter::repeat(height).take(count))
.skip(1)
.collect();
// Create the pod regions.
let pod = Regions {
size: Size::new(width, regions.size.y),
full: regions.full,
backlog: &backlog,
last: regions.last,
expand: Axes::new(true, regions.expand.y),
root: regions.root,
};
// Layout the children.
let mut frames = layout_fragment(engine, content, locator, styles, pod)?.into_iter();
let mut finished = vec![];
let dir = TextElem::dir_in(styles);
let total_regions = (frames.len() as f32 / count as f32).ceil() as usize;
// Stitch together the column for each region.
for region in regions.iter().take(total_regions) {
// The height should be the parent height if we should expand.
// Otherwise its the maximum column height for the frame. In that
// case, the frame is first created with zero height and then
// resized.
let height = if regions.expand.y { region.y } else { Abs::zero() };
let mut output = Frame::hard(Size::new(regions.size.x, height));
let mut cursor = Abs::zero();
for _ in 0..count {
let Some(frame) = frames.next() else { break };
if !regions.expand.y {
output.size_mut().y.set_max(frame.height());
}
let width = frame.width();
let x =
if dir == Dir::LTR { cursor } else { regions.size.x - cursor - width };
output.push_frame(Point::with_x(x), frame);
cursor += width + gutter;
}
finished.push(output);
}
Ok(Fragment::frames(finished))
} }
/// The internal implementation of [`layout_fragment`]. /// The internal implementation of [`layout_fragment`].
@ -461,8 +414,10 @@ fn layout_fragment_impl(
route: Tracked<Route>, route: Tracked<Route>,
content: &Content, content: &Content,
locator: Tracked<Locator>, locator: Tracked<Locator>,
styles: StyleChain, mut styles: StyleChain,
regions: Regions, regions: Regions,
columns: NonZeroUsize,
column_gutter: Rel<Abs>,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
let link = LocatorLink::new(locator); let link = LocatorLink::new(locator);
let mut locator = Locator::link(&link).split(); let mut locator = Locator::link(&link).split();
@ -480,19 +435,31 @@ fn layout_fragment_impl(
hint: "try to reduce the amount of nesting in your layout", hint: "try to reduce the amount of nesting in your layout",
); );
} }
// If we are in a `PageElem`, this might already be a realized flow.
if let Some(flow) = content.to_packed::<FlowElem>() {
return FlowLayouter::new(&mut engine, flow, locator, &styles, regions).layout();
}
// Layout the content by first turning it into a `FlowElem` and then // Layout the content by first turning it into a `FlowElem` and then
// layouting that. // layouting that.
let arenas = Arenas::default(); let arenas = Arenas::default();
let (flow, styles) =
realize_flow(&mut engine, &mut locator, &arenas, content, styles)?;
FlowLayouter::new(&mut engine, &flow, locator, &styles, regions).layout() // If we are in a `PageElem`, this might already be a realized flow.
let stored;
let flow = if let Some(flow) = content.to_packed::<FlowElem>() {
flow
} else {
(stored, styles) =
realize_flow(&mut engine, &mut locator, &arenas, content, styles)?;
&stored
};
FlowLayouter::new(
&mut engine,
flow,
locator,
&styles,
regions,
columns,
column_gutter,
&mut vec![],
)
.layout(regions)
} }
/// A collection of block-level layoutable elements. This is analogous to a /// A collection of block-level layoutable elements. This is analogous to a
@ -533,7 +500,12 @@ struct FlowLayouter<'a, 'e> {
locator: SplitLocator<'a>, locator: SplitLocator<'a>,
/// The shared styles. /// The shared styles.
styles: &'a StyleChain<'a>, styles: &'a StyleChain<'a>,
/// The regions to layout children into. /// The number of columns.
columns: usize,
/// The gutter between columns.
column_gutter: Abs,
/// The regions to layout children into. These already incorporate the
/// columns.
regions: Regions<'a>, regions: Regions<'a>,
/// Whether the flow should expand to fill the region. /// Whether the flow should expand to fill the region.
expand: Axes<bool>, expand: Axes<bool>,
@ -627,13 +599,48 @@ impl FlowItem {
impl<'a, 'e> FlowLayouter<'a, 'e> { impl<'a, 'e> FlowLayouter<'a, 'e> {
/// Create a new flow layouter. /// Create a new flow layouter.
#[allow(clippy::too_many_arguments)]
fn new( fn new(
engine: &'a mut Engine<'e>, engine: &'a mut Engine<'e>,
flow: &'a Packed<FlowElem>, flow: &'a Packed<FlowElem>,
locator: SplitLocator<'a>, locator: SplitLocator<'a>,
styles: &'a StyleChain<'a>, styles: &'a StyleChain<'a>,
mut regions: Regions<'a>, mut regions: Regions<'a>,
columns: NonZeroUsize,
column_gutter: Rel<Abs>,
backlog: &'a mut Vec<Abs>,
) -> Self { ) -> Self {
// Separating the infinite space into infinite columns does not make
// much sense.
let mut columns = columns.get();
if !regions.size.x.is_finite() {
columns = 1;
}
// Determine the width of the gutter and each column.
let column_gutter = column_gutter.relative_to(regions.base().x);
if columns > 1 {
*backlog = std::iter::once(&regions.size.y)
.chain(regions.backlog)
.flat_map(|&height| std::iter::repeat(height).take(columns))
.skip(1)
.collect();
let width =
(regions.size.x - column_gutter * (columns - 1) as f64) / columns as f64;
// Create the pod regions.
regions = Regions {
size: Size::new(width, regions.size.y),
full: regions.full,
backlog,
last: regions.last,
expand: Axes::new(true, regions.expand.y),
root: regions.root,
};
}
// Check whether we have just a single multiple-layoutable element. In // Check whether we have just a single multiple-layoutable element. In
// that case, we do not set `expand.y` to `false`, but rather keep it at // that case, we do not set `expand.y` to `false`, but rather keep it at
// its original value (since that element can take the full space). // its original value (since that element can take the full space).
@ -663,6 +670,8 @@ impl<'a, 'e> FlowLayouter<'a, 'e> {
root, root,
locator, locator,
styles, styles,
columns,
column_gutter,
regions, regions,
expand, expand,
initial: regions.size, initial: regions.size,
@ -681,7 +690,7 @@ impl<'a, 'e> FlowLayouter<'a, 'e> {
} }
/// Layout the flow. /// Layout the flow.
fn layout(mut self) -> SourceResult<Fragment> { fn layout(mut self, regions: Regions) -> SourceResult<Fragment> {
for (child, styles) in self.flow.children.chain(self.styles) { for (child, styles) in self.flow.children.chain(self.styles) {
if let Some(elem) = child.to_packed::<TagElem>() { if let Some(elem) = child.to_packed::<TagElem>() {
self.handle_tag(elem); self.handle_tag(elem);
@ -702,7 +711,7 @@ impl<'a, 'e> FlowLayouter<'a, 'e> {
} }
} }
self.finish() self.finish(regions)
} }
/// Place explicit metadata into the flow. /// Place explicit metadata into the flow.
@ -1229,7 +1238,7 @@ impl<'a, 'e> FlowLayouter<'a, 'e> {
} }
/// Finish layouting and return the resulting fragment. /// Finish layouting and return the resulting fragment.
fn finish(mut self) -> SourceResult<Fragment> { fn finish(mut self, regions: Regions) -> SourceResult<Fragment> {
if self.expand.y { if self.expand.y {
while !self.regions.backlog.is_empty() { while !self.regions.backlog.is_empty() {
self.finish_region(true)?; self.finish_region(true)?;
@ -1241,7 +1250,46 @@ impl<'a, 'e> FlowLayouter<'a, 'e> {
self.finish_region(true)?; self.finish_region(true)?;
} }
Ok(Fragment::frames(self.finished)) if self.columns == 1 {
return Ok(Fragment::frames(self.finished));
}
// Stitch together the column for each region.
let dir = TextElem::dir_in(*self.styles);
let total = (self.finished.len() as f32 / self.columns as f32).ceil() as usize;
let mut collected = vec![];
let mut iter = self.finished.into_iter();
for region in regions.iter().take(total) {
// The height should be the parent height if we should expand.
// Otherwise its the maximum column height for the frame. In that
// case, the frame is first created with zero height and then
// resized.
let height = if regions.expand.y { region.y } else { Abs::zero() };
let mut output = Frame::hard(Size::new(regions.size.x, height));
let mut cursor = Abs::zero();
for _ in 0..self.columns {
let Some(frame) = iter.next() else { break };
if !regions.expand.y {
output.size_mut().y.set_max(frame.height());
}
let width = frame.width();
let x = if dir == Dir::LTR {
cursor
} else {
regions.size.x - cursor - width
};
output.push_frame(Point::with_x(x), frame);
cursor += width + self.column_gutter;
}
collected.push(output);
}
Ok(Fragment::frames(collected))
} }
/// Tries to process all footnotes in the frame, placing them /// Tries to process all footnotes in the frame, placing them