mirror of
https://github.com/typst/typst
synced 2025-05-18 11:05:28 +08:00
Move columns handling directly into FlowLayouter
(#4792)
This commit is contained in:
parent
eb0e0fec00
commit
a30b681251
@ -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(®ions.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(®ions.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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user