mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Clean up flow a bit (#4505)
This commit is contained in:
parent
b847cccba4
commit
906de589ce
@ -47,51 +47,7 @@ impl Packed<FlowElem> {
|
||||
styles: StyleChain,
|
||||
regions: Regions,
|
||||
) -> SourceResult<Fragment> {
|
||||
if !regions.size.x.is_finite() && regions.expand.x {
|
||||
bail!(self.span(), "cannot expand into infinite width");
|
||||
}
|
||||
if !regions.size.y.is_finite() && regions.expand.y {
|
||||
bail!(self.span(), "cannot expand into infinite height");
|
||||
}
|
||||
|
||||
// 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
|
||||
// its original value (since that element can take the full space).
|
||||
//
|
||||
// Consider the following code: `block(height: 5cm, pad(10pt, align(bottom, ..)))`
|
||||
// Thanks to the code below, the expansion will be passed all the way
|
||||
// through the block & pad and reach the innermost flow, so that things
|
||||
// are properly bottom-aligned.
|
||||
let mut alone = false;
|
||||
if let [child] = self.children().elements() {
|
||||
alone = child.is::<BlockElem>();
|
||||
}
|
||||
|
||||
let mut layouter = FlowLayouter::new(locator, styles, regions, alone);
|
||||
for (child, styles) in self.children().chain(&styles) {
|
||||
if let Some(elem) = child.to_packed::<TagElem>() {
|
||||
layouter.layout_tag(elem);
|
||||
} else if child.is::<FlushElem>() {
|
||||
layouter.flush(engine)?;
|
||||
} else if let Some(elem) = child.to_packed::<VElem>() {
|
||||
layouter.layout_spacing(engine, elem, styles)?;
|
||||
} else if let Some(elem) = child.to_packed::<ParElem>() {
|
||||
layouter.layout_par(engine, elem, styles)?;
|
||||
} else if let Some(elem) = child.to_packed::<BlockElem>() {
|
||||
layouter.layout_block(engine, elem, styles)?;
|
||||
} else if let Some(placed) = child.to_packed::<PlaceElem>() {
|
||||
layouter.layout_placed(engine, placed, styles)?;
|
||||
} else if child.is::<ColbreakElem>() {
|
||||
if !layouter.regions.backlog.is_empty() || layouter.regions.last.is_some()
|
||||
{
|
||||
layouter.finish_region(engine, true)?;
|
||||
}
|
||||
} else {
|
||||
bail!(child.span(), "unexpected flow child");
|
||||
}
|
||||
}
|
||||
|
||||
layouter.finish(engine)
|
||||
FlowLayouter::new(engine, self, locator, &styles, regions).layout()
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,13 +59,17 @@ impl Debug for FlowElem {
|
||||
}
|
||||
|
||||
/// Performs flow layout.
|
||||
struct FlowLayouter<'a> {
|
||||
struct FlowLayouter<'a, 'e> {
|
||||
/// The engine.
|
||||
engine: &'a mut Engine<'e>,
|
||||
/// The children that will be arranged into a flow.
|
||||
flow: &'a Packed<FlowElem>,
|
||||
/// Whether this is the root flow.
|
||||
root: bool,
|
||||
/// Provides unique locations to the flow's children.
|
||||
locator: SplitLocator<'a>,
|
||||
/// The shared styles.
|
||||
styles: StyleChain<'a>,
|
||||
styles: &'a StyleChain<'a>,
|
||||
/// The regions to layout children into.
|
||||
regions: Regions<'a>,
|
||||
/// Whether the flow should expand to fill the region.
|
||||
@ -124,7 +84,7 @@ struct FlowLayouter<'a> {
|
||||
/// Spacing and layouted blocks for the current region.
|
||||
items: Vec<FlowItem>,
|
||||
/// A queue of tags that will be attached to the next frame.
|
||||
pending_tags: Vec<Tag>,
|
||||
pending_tags: Vec<&'a Tag>,
|
||||
/// A queue of floating elements.
|
||||
pending_floats: Vec<FlowItem>,
|
||||
/// Whether we have any footnotes in the current region.
|
||||
@ -157,18 +117,27 @@ enum FlowItem {
|
||||
align: Axes<FixedAlignment>,
|
||||
/// Whether the frame sticks to the item after it (for orphan prevention).
|
||||
sticky: bool,
|
||||
/// Whether the frame is movable; that is, kept together with its footnotes.
|
||||
/// Whether the frame is movable; that is, kept together with its
|
||||
/// footnotes.
|
||||
///
|
||||
/// This is true for frames created by paragraphs and [`LayoutSingle`] elements.
|
||||
/// This is true for frames created by paragraphs and
|
||||
/// [`BlockElem::single_layouter`] elements.
|
||||
movable: bool,
|
||||
},
|
||||
/// An absolutely placed frame.
|
||||
Placed {
|
||||
/// The layouted content.
|
||||
frame: Frame,
|
||||
/// Where to place the content horizontally.
|
||||
x_align: FixedAlignment,
|
||||
/// Where to place the content vertically.
|
||||
y_align: Smart<Option<FixedAlignment>>,
|
||||
/// A translation to apply to the content.
|
||||
delta: Axes<Rel<Abs>>,
|
||||
/// Whether the content floats --- i.e. collides with in-flow content.
|
||||
float: bool,
|
||||
/// The amount of space that needs to be kept between the placed content
|
||||
/// and in-flow content. Only relevant if `float` is `true`.
|
||||
clearance: Abs,
|
||||
},
|
||||
/// A footnote frame (can also be the separator).
|
||||
@ -193,24 +162,41 @@ impl FlowItem {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FlowLayouter<'a> {
|
||||
impl<'a, 'e> FlowLayouter<'a, 'e> {
|
||||
/// Create a new flow layouter.
|
||||
fn new(
|
||||
engine: &'a mut Engine<'e>,
|
||||
flow: &'a Packed<FlowElem>,
|
||||
locator: Locator<'a>,
|
||||
styles: StyleChain<'a>,
|
||||
styles: &'a StyleChain<'a>,
|
||||
mut regions: Regions<'a>,
|
||||
alone: bool,
|
||||
) -> Self {
|
||||
let expand = regions.expand;
|
||||
let root = std::mem::replace(&mut regions.root, false);
|
||||
// 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
|
||||
// its original value (since that element can take the full space).
|
||||
//
|
||||
// Consider the following code: `block(height: 5cm, pad(10pt,
|
||||
// align(bottom, ..)))`. Thanks to the code below, the expansion will be
|
||||
// passed all the way through the block & pad and reach the innermost
|
||||
// flow, so that things are properly bottom-aligned.
|
||||
let mut alone = false;
|
||||
if let [child] = flow.children.elements() {
|
||||
alone = child.is::<BlockElem>();
|
||||
}
|
||||
|
||||
// Disable vertical expansion when there are multiple or not directly
|
||||
// layoutable children.
|
||||
let expand = regions.expand;
|
||||
if !alone {
|
||||
regions.expand.y = false;
|
||||
}
|
||||
|
||||
// The children aren't root.
|
||||
let root = std::mem::replace(&mut regions.root, false);
|
||||
|
||||
Self {
|
||||
engine,
|
||||
flow,
|
||||
root,
|
||||
locator: locator.split(),
|
||||
styles,
|
||||
@ -223,52 +209,84 @@ impl<'a> FlowLayouter<'a> {
|
||||
pending_floats: vec![],
|
||||
has_footnotes: false,
|
||||
footnote_config: FootnoteConfig {
|
||||
separator: FootnoteEntry::separator_in(styles),
|
||||
clearance: FootnoteEntry::clearance_in(styles),
|
||||
gap: FootnoteEntry::gap_in(styles),
|
||||
separator: FootnoteEntry::separator_in(*styles),
|
||||
clearance: FootnoteEntry::clearance_in(*styles),
|
||||
gap: FootnoteEntry::gap_in(*styles),
|
||||
},
|
||||
finished: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Layout the flow.
|
||||
fn layout(mut self) -> SourceResult<Fragment> {
|
||||
for (child, styles) in self.flow.children.chain(self.styles) {
|
||||
if let Some(elem) = child.to_packed::<TagElem>() {
|
||||
self.handle_tag(elem);
|
||||
} else if let Some(elem) = child.to_packed::<VElem>() {
|
||||
self.handle_v(elem, styles)?;
|
||||
} else if let Some(elem) = child.to_packed::<ColbreakElem>() {
|
||||
self.handle_colbreak(elem)?;
|
||||
} else if let Some(elem) = child.to_packed::<ParElem>() {
|
||||
self.handle_par(elem, styles)?;
|
||||
} else if let Some(elem) = child.to_packed::<BlockElem>() {
|
||||
self.handle_block(elem, styles)?;
|
||||
} else if let Some(elem) = child.to_packed::<PlaceElem>() {
|
||||
self.handle_place(elem, styles)?;
|
||||
} else if let Some(elem) = child.to_packed::<FlushElem>() {
|
||||
self.handle_flush(elem)?;
|
||||
} else {
|
||||
bail!(child.span(), "unexpected flow child");
|
||||
}
|
||||
}
|
||||
|
||||
self.finish()
|
||||
}
|
||||
|
||||
/// Place explicit metadata into the flow.
|
||||
fn layout_tag(&mut self, elem: &Packed<TagElem>) {
|
||||
self.pending_tags.push(elem.tag.clone());
|
||||
fn handle_tag(&mut self, elem: &'a Packed<TagElem>) {
|
||||
self.pending_tags.push(&elem.tag);
|
||||
}
|
||||
|
||||
/// Layout vertical spacing.
|
||||
fn layout_spacing(
|
||||
&mut self,
|
||||
engine: &mut Engine,
|
||||
v: &Packed<VElem>,
|
||||
styles: StyleChain,
|
||||
) -> SourceResult<()> {
|
||||
self.layout_item(
|
||||
engine,
|
||||
match v.amount() {
|
||||
Spacing::Rel(rel) => FlowItem::Absolute(
|
||||
rel.resolve(styles).relative_to(self.initial.y),
|
||||
v.weakness(styles) > 0,
|
||||
),
|
||||
Spacing::Fr(fr) => FlowItem::Fractional(*fr),
|
||||
},
|
||||
)
|
||||
fn handle_v(&mut self, v: &'a Packed<VElem>, styles: StyleChain) -> SourceResult<()> {
|
||||
self.handle_item(match v.amount {
|
||||
Spacing::Rel(rel) => FlowItem::Absolute(
|
||||
// Resolve the spacing relative to the current base height.
|
||||
rel.resolve(styles).relative_to(self.initial.y),
|
||||
v.weakness(styles) > 0,
|
||||
),
|
||||
Spacing::Fr(fr) => FlowItem::Fractional(fr),
|
||||
})
|
||||
}
|
||||
|
||||
/// Layout a column break.
|
||||
fn handle_colbreak(&mut self, _: &'a Packed<ColbreakElem>) -> SourceResult<()> {
|
||||
// If there is still an available region, skip to it.
|
||||
// TODO: Turn this into a region abstraction.
|
||||
if !self.regions.backlog.is_empty() || self.regions.last.is_some() {
|
||||
self.finish_region(true)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Layout a paragraph.
|
||||
fn layout_par(
|
||||
fn handle_par(
|
||||
&mut self,
|
||||
engine: &mut Engine,
|
||||
par: &Packed<ParElem>,
|
||||
par: &'a Packed<ParElem>,
|
||||
styles: StyleChain,
|
||||
) -> SourceResult<()> {
|
||||
// Fetch properties.
|
||||
let align = AlignElem::alignment_in(styles).resolve(styles);
|
||||
let leading = ParElem::leading_in(styles);
|
||||
|
||||
// Layout the paragraph into lines. This only depends on the base size,
|
||||
// not on the Y position.
|
||||
let consecutive = self.last_was_par;
|
||||
let locator = self.locator.next(&par.span());
|
||||
let lines = par
|
||||
.layout(
|
||||
engine,
|
||||
self.locator.next(&par.span()),
|
||||
self.engine,
|
||||
locator,
|
||||
styles,
|
||||
consecutive,
|
||||
self.regions.base(),
|
||||
@ -280,39 +298,26 @@ impl<'a> FlowLayouter<'a> {
|
||||
// previous sticky frame to the next region (if available)
|
||||
if let Some(first) = lines.first() {
|
||||
while !self.regions.size.y.fits(first.height()) && !self.regions.in_last() {
|
||||
let mut sticky = self.items.len();
|
||||
for (i, item) in self.items.iter().enumerate().rev() {
|
||||
match *item {
|
||||
FlowItem::Absolute(_, _) => {}
|
||||
FlowItem::Frame { sticky: true, .. } => sticky = i,
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
|
||||
let carry: Vec<_> = self.items.drain(sticky..).collect();
|
||||
self.finish_region(engine, false)?;
|
||||
let in_last = self.regions.in_last();
|
||||
|
||||
for item in carry {
|
||||
self.layout_item(engine, item)?;
|
||||
}
|
||||
|
||||
let in_last = self.finish_region_with_migration()?;
|
||||
if in_last {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Layout the lines.
|
||||
for (i, mut frame) in lines.into_iter().enumerate() {
|
||||
if i > 0 {
|
||||
self.layout_item(engine, FlowItem::Absolute(leading, true))?;
|
||||
self.handle_item(FlowItem::Absolute(leading, true))?;
|
||||
}
|
||||
|
||||
self.drain_tag(&mut frame);
|
||||
self.layout_item(
|
||||
engine,
|
||||
FlowItem::Frame { frame, align, sticky: false, movable: true },
|
||||
)?;
|
||||
self.handle_item(FlowItem::Frame {
|
||||
frame,
|
||||
align,
|
||||
sticky: false,
|
||||
movable: true,
|
||||
})?;
|
||||
}
|
||||
|
||||
self.last_was_par = true;
|
||||
@ -320,56 +325,54 @@ impl<'a> FlowLayouter<'a> {
|
||||
}
|
||||
|
||||
/// Layout into multiple regions.
|
||||
fn layout_block(
|
||||
fn handle_block(
|
||||
&mut self,
|
||||
engine: &mut Engine,
|
||||
block: &'a Packed<BlockElem>,
|
||||
styles: StyleChain<'a>,
|
||||
) -> SourceResult<()> {
|
||||
// Temporarily delegate rootness to the columns.
|
||||
// Fetch properties.
|
||||
let sticky = block.sticky(styles);
|
||||
let align = AlignElem::alignment_in(styles).resolve(styles);
|
||||
|
||||
// If the block is "rootable" it may host footnotes. In that case, we
|
||||
// defer rootness to it temporarily. We disable our own rootness to
|
||||
// prevent duplicate footnotes.
|
||||
let is_root = self.root;
|
||||
if is_root && block.rootable(styles) {
|
||||
self.root = false;
|
||||
self.regions.root = true;
|
||||
}
|
||||
|
||||
// Skip directly if region is already full.
|
||||
if self.regions.is_full() {
|
||||
// Skip directly if region is already full.
|
||||
self.finish_region(engine, false)?;
|
||||
self.finish_region(false)?;
|
||||
}
|
||||
|
||||
// Layout the block itself.
|
||||
let sticky = block.sticky(styles);
|
||||
let fragment = block.layout(
|
||||
engine,
|
||||
self.engine,
|
||||
self.locator.next(&block.span()),
|
||||
styles,
|
||||
self.regions,
|
||||
)?;
|
||||
|
||||
// How to align the block.
|
||||
let align = AlignElem::alignment_in(styles).resolve(styles);
|
||||
|
||||
let mut notes = Vec::new();
|
||||
for (i, mut frame) in fragment.into_iter().enumerate() {
|
||||
// Find footnotes in the frame.
|
||||
if self.root {
|
||||
find_footnotes(&mut notes, &frame);
|
||||
collect_footnotes(&mut notes, &frame);
|
||||
}
|
||||
|
||||
if i > 0 {
|
||||
self.finish_region(engine, false)?;
|
||||
self.finish_region(false)?;
|
||||
}
|
||||
|
||||
self.drain_tag(&mut frame);
|
||||
frame.post_process(styles);
|
||||
self.layout_item(
|
||||
engine,
|
||||
FlowItem::Frame { frame, align, sticky, movable: false },
|
||||
)?;
|
||||
self.handle_item(FlowItem::Frame { frame, align, sticky, movable: false })?;
|
||||
}
|
||||
|
||||
self.try_handle_footnotes(engine, notes)?;
|
||||
self.try_handle_footnotes(notes)?;
|
||||
|
||||
self.root = is_root;
|
||||
self.regions.root = false;
|
||||
@ -379,50 +382,56 @@ impl<'a> FlowLayouter<'a> {
|
||||
}
|
||||
|
||||
/// Layout a placed element.
|
||||
fn layout_placed(
|
||||
fn handle_place(
|
||||
&mut self,
|
||||
engine: &mut Engine,
|
||||
placed: &Packed<PlaceElem>,
|
||||
placed: &'a Packed<PlaceElem>,
|
||||
styles: StyleChain,
|
||||
) -> SourceResult<()> {
|
||||
// Fetch properties.
|
||||
let float = placed.float(styles);
|
||||
let clearance = placed.clearance(styles);
|
||||
let alignment = placed.alignment(styles);
|
||||
let delta = Axes::new(placed.dx(styles), placed.dy(styles)).resolve(styles);
|
||||
|
||||
let x_align = alignment.map_or(FixedAlignment::Center, |align| {
|
||||
align.x().unwrap_or_default().resolve(styles)
|
||||
});
|
||||
let y_align = alignment.map(|align| align.y().map(|y| y.resolve(styles)));
|
||||
|
||||
let mut frame = placed
|
||||
.layout(
|
||||
engine,
|
||||
self.engine,
|
||||
self.locator.next(&placed.span()),
|
||||
styles,
|
||||
self.regions.base(),
|
||||
)?
|
||||
.into_frame();
|
||||
|
||||
frame.post_process(styles);
|
||||
let item = FlowItem::Placed { frame, x_align, y_align, delta, float, clearance };
|
||||
self.layout_item(engine, item)
|
||||
|
||||
self.handle_item(FlowItem::Placed {
|
||||
frame,
|
||||
x_align,
|
||||
y_align,
|
||||
delta,
|
||||
float,
|
||||
clearance,
|
||||
})
|
||||
}
|
||||
|
||||
/// Attach currently pending metadata to the frame.
|
||||
fn drain_tag(&mut self, frame: &mut Frame) {
|
||||
if !self.pending_tags.is_empty() && !frame.is_empty() {
|
||||
frame.prepend_multiple(
|
||||
self.pending_tags
|
||||
.drain(..)
|
||||
.map(|tag| (Point::zero(), FrameItem::Tag(tag))),
|
||||
);
|
||||
/// Lays out all floating elements before continuing with other content.
|
||||
fn handle_flush(&mut self, _: &'a Packed<FlushElem>) -> SourceResult<()> {
|
||||
for item in std::mem::take(&mut self.pending_floats) {
|
||||
self.handle_item(item)?;
|
||||
}
|
||||
while !self.pending_floats.is_empty() {
|
||||
self.finish_region(false)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Layout a finished frame.
|
||||
fn layout_item(
|
||||
&mut self,
|
||||
engine: &mut Engine,
|
||||
mut item: FlowItem,
|
||||
) -> SourceResult<()> {
|
||||
fn handle_item(&mut self, mut item: FlowItem) -> SourceResult<()> {
|
||||
match item {
|
||||
FlowItem::Absolute(v, weak) => {
|
||||
if weak
|
||||
@ -439,24 +448,24 @@ impl<'a> FlowLayouter<'a> {
|
||||
FlowItem::Frame { ref frame, movable, .. } => {
|
||||
let height = frame.height();
|
||||
while !self.regions.size.y.fits(height) && !self.regions.in_last() {
|
||||
self.finish_region(engine, false)?;
|
||||
self.finish_region(false)?;
|
||||
}
|
||||
|
||||
let in_last = self.regions.in_last();
|
||||
self.regions.size.y -= height;
|
||||
if self.root && movable {
|
||||
let mut notes = Vec::new();
|
||||
find_footnotes(&mut notes, frame);
|
||||
collect_footnotes(&mut notes, frame);
|
||||
self.items.push(item);
|
||||
|
||||
// When we are already in_last, we can directly force the
|
||||
// footnotes.
|
||||
if !self.handle_footnotes(engine, &mut notes, true, in_last)? {
|
||||
if !self.handle_footnotes(&mut notes, true, in_last)? {
|
||||
let item = self.items.pop();
|
||||
self.finish_region(engine, false)?;
|
||||
self.finish_region(false)?;
|
||||
self.items.extend(item);
|
||||
self.regions.size.y -= height;
|
||||
self.handle_footnotes(engine, &mut notes, true, true)?;
|
||||
self.handle_footnotes(&mut notes, true, true)?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
@ -504,8 +513,8 @@ impl<'a> FlowLayouter<'a> {
|
||||
// Find footnotes in the frame.
|
||||
if self.root {
|
||||
let mut notes = vec![];
|
||||
find_footnotes(&mut notes, frame);
|
||||
self.try_handle_footnotes(engine, notes)?;
|
||||
collect_footnotes(&mut notes, frame);
|
||||
self.try_handle_footnotes(notes)?;
|
||||
}
|
||||
}
|
||||
FlowItem::Footnote(_) => {}
|
||||
@ -515,12 +524,49 @@ impl<'a> FlowLayouter<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Attach currently pending metadata to the frame.
|
||||
fn drain_tag(&mut self, frame: &mut Frame) {
|
||||
if !self.pending_tags.is_empty() && !frame.is_empty() {
|
||||
frame.prepend_multiple(
|
||||
self.pending_tags
|
||||
.drain(..)
|
||||
.map(|tag| (Point::zero(), FrameItem::Tag(tag.clone()))),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Finisht the region, migrating all sticky items to the next one.
|
||||
///
|
||||
/// Returns whether we migrated into a last region.
|
||||
fn finish_region_with_migration(&mut self) -> SourceResult<bool> {
|
||||
// Find the suffix of sticky items.
|
||||
let mut sticky = self.items.len();
|
||||
for (i, item) in self.items.iter().enumerate().rev() {
|
||||
match *item {
|
||||
FlowItem::Absolute(_, _) => {}
|
||||
FlowItem::Frame { sticky: true, .. } => sticky = i,
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
|
||||
let carry: Vec<_> = self.items.drain(sticky..).collect();
|
||||
self.finish_region(false)?;
|
||||
|
||||
let in_last = self.regions.in_last();
|
||||
for item in carry {
|
||||
self.handle_item(item)?;
|
||||
}
|
||||
|
||||
Ok(in_last)
|
||||
}
|
||||
|
||||
/// Finish the frame for one region.
|
||||
///
|
||||
/// Set `force` to `true` to allow creating a frame for out-of-flow elements
|
||||
/// only (this is used to force the creation of a frame in case the
|
||||
/// remaining elements are all out-of-flow).
|
||||
fn finish_region(&mut self, engine: &mut Engine, force: bool) -> SourceResult<()> {
|
||||
fn finish_region(&mut self, force: bool) -> SourceResult<()> {
|
||||
// Early return if we don't have any relevant items.
|
||||
if !force
|
||||
&& !self.items.is_empty()
|
||||
&& self.items.iter().all(FlowItem::is_out_of_flow)
|
||||
@ -585,6 +631,13 @@ impl<'a> FlowLayouter<'a> {
|
||||
size.y = self.initial.y;
|
||||
}
|
||||
|
||||
if !self.regions.size.x.is_finite() && self.expand.x {
|
||||
bail!(self.flow.span(), "cannot expand into infinite width");
|
||||
}
|
||||
if !self.regions.size.y.is_finite() && self.expand.y {
|
||||
bail!(self.flow.span(), "cannot expand into infinite height");
|
||||
}
|
||||
|
||||
let mut output = Frame::soft(size);
|
||||
let mut ruler = FixedAlignment::Start;
|
||||
let mut float_top_offset = Abs::zero();
|
||||
@ -653,7 +706,9 @@ impl<'a> FlowLayouter<'a> {
|
||||
if force && !self.pending_tags.is_empty() {
|
||||
let pos = Point::with_y(offset);
|
||||
output.push_multiple(
|
||||
self.pending_tags.drain(..).map(|tag| (pos, FrameItem::Tag(tag))),
|
||||
self.pending_tags
|
||||
.drain(..)
|
||||
.map(|tag| (pos, FrameItem::Tag(tag.clone()))),
|
||||
);
|
||||
}
|
||||
|
||||
@ -665,62 +720,42 @@ impl<'a> FlowLayouter<'a> {
|
||||
|
||||
// Try to place floats into the next region.
|
||||
for item in std::mem::take(&mut self.pending_floats) {
|
||||
self.layout_item(engine, item)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Lays out all floating elements before continuing with other content.
|
||||
fn flush(&mut self, engine: &mut Engine) -> SourceResult<()> {
|
||||
for item in std::mem::take(&mut self.pending_floats) {
|
||||
self.layout_item(engine, item)?;
|
||||
}
|
||||
while !self.pending_floats.is_empty() {
|
||||
self.finish_region(engine, false)?;
|
||||
self.handle_item(item)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Finish layouting and return the resulting fragment.
|
||||
fn finish(mut self, engine: &mut Engine) -> SourceResult<Fragment> {
|
||||
fn finish(mut self) -> SourceResult<Fragment> {
|
||||
if self.expand.y {
|
||||
while !self.regions.backlog.is_empty() {
|
||||
self.finish_region(engine, true)?;
|
||||
self.finish_region(true)?;
|
||||
}
|
||||
}
|
||||
|
||||
self.finish_region(engine, true)?;
|
||||
self.finish_region(true)?;
|
||||
while !self.items.is_empty() {
|
||||
self.finish_region(engine, true)?;
|
||||
self.finish_region(true)?;
|
||||
}
|
||||
|
||||
Ok(Fragment::frames(self.finished))
|
||||
}
|
||||
}
|
||||
|
||||
impl FlowLayouter<'_> {
|
||||
/// Tries to process all footnotes in the frame, placing them
|
||||
/// in the next region if they could not be placed in the current
|
||||
/// one.
|
||||
fn try_handle_footnotes(
|
||||
&mut self,
|
||||
engine: &mut Engine,
|
||||
mut notes: Vec<Packed<FootnoteElem>>,
|
||||
) -> SourceResult<()> {
|
||||
// When we are already in_last, we can directly force the
|
||||
// footnotes.
|
||||
if self.root
|
||||
&& !self.handle_footnotes(
|
||||
engine,
|
||||
&mut notes,
|
||||
false,
|
||||
self.regions.in_last(),
|
||||
)?
|
||||
&& !self.handle_footnotes(&mut notes, false, self.regions.in_last())?
|
||||
{
|
||||
self.finish_region(engine, false)?;
|
||||
self.handle_footnotes(engine, &mut notes, false, true)?;
|
||||
self.finish_region(false)?;
|
||||
self.handle_footnotes(&mut notes, false, true)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -731,7 +766,6 @@ impl FlowLayouter<'_> {
|
||||
/// regions.
|
||||
fn handle_footnotes(
|
||||
&mut self,
|
||||
engine: &mut Engine,
|
||||
notes: &mut Vec<Packed<FootnoteElem>>,
|
||||
movable: bool,
|
||||
force: bool,
|
||||
@ -750,16 +784,16 @@ impl FlowLayouter<'_> {
|
||||
}
|
||||
|
||||
if !self.has_footnotes {
|
||||
self.layout_footnote_separator(engine)?;
|
||||
self.layout_footnote_separator()?;
|
||||
}
|
||||
|
||||
self.regions.size.y -= self.footnote_config.gap;
|
||||
let frames = FootnoteEntry::new(notes[k].clone())
|
||||
.pack()
|
||||
.layout(
|
||||
engine,
|
||||
self.engine,
|
||||
Locator::synthesize(notes[k].location().unwrap()),
|
||||
self.styles,
|
||||
*self.styles,
|
||||
self.regions.with_root(false),
|
||||
)?
|
||||
.into_frames();
|
||||
@ -780,10 +814,10 @@ impl FlowLayouter<'_> {
|
||||
|
||||
let prev = notes.len();
|
||||
for (i, frame) in frames.into_iter().enumerate() {
|
||||
find_footnotes(notes, &frame);
|
||||
collect_footnotes(notes, &frame);
|
||||
if i > 0 {
|
||||
self.finish_region(engine, false)?;
|
||||
self.layout_footnote_separator(engine)?;
|
||||
self.finish_region(false)?;
|
||||
self.layout_footnote_separator()?;
|
||||
self.regions.size.y -= self.footnote_config.gap;
|
||||
}
|
||||
self.regions.size.y -= frame.height();
|
||||
@ -804,14 +838,14 @@ impl FlowLayouter<'_> {
|
||||
}
|
||||
|
||||
/// Layout and save the footnote separator, typically a line.
|
||||
fn layout_footnote_separator(&mut self, engine: &mut Engine) -> SourceResult<()> {
|
||||
fn layout_footnote_separator(&mut self) -> SourceResult<()> {
|
||||
let expand = Axes::new(self.regions.expand.x, false);
|
||||
let pod = Regions::one(self.regions.base(), expand);
|
||||
let separator = &self.footnote_config.separator;
|
||||
|
||||
// FIXME: Shouldn't use `root()` here.
|
||||
let mut frame = separator
|
||||
.layout(engine, Locator::root(), self.styles, pod)?
|
||||
.layout(self.engine, Locator::root(), *self.styles, pod)?
|
||||
.into_frame();
|
||||
frame.size_mut().y += self.footnote_config.clearance;
|
||||
frame.translate(Point::with_y(self.footnote_config.clearance));
|
||||
@ -824,11 +858,11 @@ impl FlowLayouter<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds all footnotes in the frame.
|
||||
fn find_footnotes(notes: &mut Vec<Packed<FootnoteElem>>, frame: &Frame) {
|
||||
/// Collect all footnotes in a frame.
|
||||
fn collect_footnotes(notes: &mut Vec<Packed<FootnoteElem>>, frame: &Frame) {
|
||||
for (_, item) in frame.items() {
|
||||
match item {
|
||||
FrameItem::Group(group) => find_footnotes(notes, &group.frame),
|
||||
FrameItem::Group(group) => collect_footnotes(notes, &group.frame),
|
||||
FrameItem::Tag(tag)
|
||||
if !notes.iter().any(|note| note.location() == tag.elem.location()) =>
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user