Layout API docs (#4103)

This commit is contained in:
bluebear94 2024-05-13 05:06:21 -04:00 committed by GitHub
parent 36040d93ef
commit d859218b90
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 134 additions and 23 deletions

View File

@ -17,7 +17,7 @@ pub struct Engine<'a> {
/// Provides access to information about the document.
pub introspector: Tracked<'a, Introspector>,
/// The route the engine took during compilation. This is used to detect
/// cyclic imports and too much nesting.
/// cyclic imports and excessive nesting.
pub route: Route<'a>,
/// Provides stable identities to elements.
pub locator: &'a mut Locator<'a>,
@ -45,8 +45,11 @@ impl Engine<'_> {
}
/// The route the engine took during compilation. This is used to detect
/// cyclic imports and too much nesting.
/// cyclic imports and excessive nesting.
pub struct Route<'a> {
/// The parent route segment, if present.
///
/// This is used when an engine is created from another engine.
// We need to override the constraint's lifetime here so that `Tracked` is
// covariant over the constraint. If it becomes invariant, we're in for a
// world of lifetime pain.
@ -60,9 +63,10 @@ pub struct Route<'a> {
/// route exceeds `MAX_DEPTH`, then we throw a "maximum ... depth exceeded"
/// error.
len: usize,
/// The upper bound we've established for the parent chain length. We don't
/// know the exact length (that would defeat the whole purpose because it
/// would prevent cache reuse of some computation at different,
/// The upper bound we've established for the parent chain length.
///
/// We don't know the exact length (that would defeat the whole purpose
/// because it would prevent cache reuse of some computation at different,
/// non-exceeding depths).
upper: AtomicUsize,
}

View File

@ -354,9 +354,13 @@ impl Hash for dyn Blockable {
/// A show rule recipe.
#[derive(Clone, PartialEq, Hash)]
pub struct Recipe {
/// The span errors are reported with.
/// The span that errors are reported with.
pub span: Span,
/// Determines whether the recipe applies to an element.
///
/// If this is `None`, then this recipe is from a show rule with
/// no selector (`show: rest => ...`), which is [eagerly applied][Content::styled_with_recipe]
/// to the rest of the content in the scope.
pub selector: Option<Selector>,
/// The transformation to perform on the match.
pub transform: Transformation,

View File

@ -1,3 +1,9 @@
//! Layout flows.
//!
//! A *flow* is a collection of block-level layoutable elements.
//! This is analogous to a paragraph, which is a collection of
//! inline-level layoutable elements.
use std::fmt::{self, Debug, Formatter};
use crate::diag::{bail, SourceResult};
@ -20,7 +26,7 @@ use crate::util::Numeric;
/// and the contents of boxes.
#[elem(Debug, LayoutMultiple)]
pub struct FlowElem {
/// The children that will be arranges into a flow.
/// The children that will be arranged into a flow.
#[variadic]
pub children: Vec<Content>,
}
@ -96,10 +102,12 @@ struct FlowLayouter<'a> {
/// subtracting.
initial: Size,
/// Whether the last block was a paragraph.
///
/// Used for indenting paragraphs after the first in a block.
last_was_par: bool,
/// Spacing and layouted blocks for the current region.
items: Vec<FlowItem>,
/// A queue of floats.
/// A queue of floating elements.
pending_floats: Vec<FlowItem>,
/// Whether we have any footnotes in the current region.
has_footnotes: bool,
@ -123,10 +131,19 @@ enum FlowItem {
Absolute(Abs, bool),
/// Fractional spacing between other items.
Fractional(Fr),
/// A frame for a layouted block, how to align it, whether it sticks to the
/// item after it (for orphan prevention), and whether it is movable
/// (to keep it together with its footnotes).
Frame { frame: Frame, align: Axes<FixedAlignment>, sticky: bool, movable: bool },
/// A frame for a layouted block.
Frame {
/// The frame itself.
frame: Frame,
/// How to align the frame.
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.
///
/// This is true for frames created by paragraphs and [`LayoutSingle`] elements.
movable: bool,
},
/// An absolutely placed frame.
Placed {
frame: Frame,
@ -143,7 +160,7 @@ enum FlowItem {
impl FlowItem {
/// Whether this item is out-of-flow.
///
/// Out-of-flow items are guaranteed to have a [`Size::zero()`].
/// Out-of-flow items are guaranteed to have a [zero size][Size::zero()].
fn is_out_of_flow(&self) -> bool {
match self {
Self::Placed { float: false, .. } => true,
@ -235,6 +252,8 @@ impl<'a> FlowLayouter<'a> {
)?
.into_frames();
// If the first line doesnt fit in this region, then defer any
// 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();
@ -641,6 +660,9 @@ impl<'a> FlowLayouter<'a> {
}
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,
@ -663,6 +685,9 @@ impl FlowLayouter<'_> {
}
/// Processes all footnotes in the frame.
///
/// Returns true if the footnote entries fit in the allotted
/// regions.
fn handle_footnotes(
&mut self,
engine: &mut Engine,

View File

@ -285,6 +285,10 @@ impl Frame {
}
/// Attach the metadata from this style chain to the frame.
///
/// If `force` is true, then the metadata is attached even when
/// the frame is empty.
// TODO: when would you want to pass true to `force` as opposed to false?
pub fn meta(&mut self, styles: StyleChain, force: bool) {
if force || !self.is_empty() {
self.meta_iter(MetaElem::data_in(styles));
@ -456,6 +460,8 @@ pub enum FrameKind {
#[default]
Soft,
/// A container which uses its own size.
///
/// This is used for page, block, box, column, grid, and stack elements.
Hard,
}

View File

@ -119,6 +119,11 @@ pub fn define(global: &mut Scope) {
}
/// Root-level layout.
///
/// This produces a complete document and is implemented for
/// [`DocumentElem`][crate::model::DocumentElem]. Any [`Content`]
/// can also be laid out at root level, in which case it is
/// wrapped inside a document element.
pub trait LayoutRoot {
/// Layout into a document with one frame per page.
fn layout_root(
@ -128,7 +133,10 @@ pub trait LayoutRoot {
) -> SourceResult<Document>;
}
/// Layout into multiple regions.
/// Layout into multiple [regions][Regions].
///
/// This is more appropriate for elements that, for example, can be
/// laid out across multiple pages or columns.
pub trait LayoutMultiple {
/// Layout into one frame per region.
fn layout(
@ -160,7 +168,10 @@ pub trait LayoutMultiple {
}
}
/// Layout into a single region.
/// Layout into a single [region][Regions].
///
/// This is more appropriate for elements that don't make sense to
/// layout across multiple pages or columns, such as shapes.
pub trait LayoutSingle {
/// Layout into one frame per region.
fn layout(

View File

@ -3,6 +3,12 @@ use std::fmt::{self, Debug, Formatter};
use crate::layout::{Abs, Axes, Size};
/// A sequence of regions to layout into.
///
/// A *region* is a contiguous rectangular space in which elements
/// can be laid out. All regions within a `Regions` object have the
/// same width, namely `self.size.x`. This means that it is not
/// currently possible to, for instance, have content wrap to the
/// side of a floating element.
#[derive(Copy, Clone, Hash)]
pub struct Regions<'a> {
/// The remaining size of the first region.

View File

@ -8,7 +8,7 @@ use crate::layout::{
/// Moves content without affecting layout.
///
/// The `move` function allows you to move content while th layout still 'sees'
/// The `move` function allows you to move content while the layout still 'sees'
/// it at the original positions. Containers will still be sized as if the
/// content was not moved.
///

View File

@ -1,4 +1,10 @@
//! Realization of content.
//!
//! *Realization* is the process of applying show rules to produce
//! something that can be laid out directly.
//!
//! Currently, there are issues with the realization process, and
//! it is subject to changes in the future.
mod arenas;
mod behaviour;
@ -98,11 +104,13 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
}
}
/// Adds a piece of content to this builder.
fn accept(
&mut self,
mut content: &'a Content,
styles: StyleChain<'a>,
) -> SourceResult<()> {
// Implicitly wrap math content in an equation if needed
if content.can::<dyn LayoutMath>() && !content.is::<EquationElem>() {
content = self
.arenas
@ -133,6 +141,8 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
return Ok(());
}
// Try to merge `content` with an element under construction
if self.cites.accept(content, styles) {
return Ok(());
}
@ -227,6 +237,7 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
Ok(())
}
/// Interrupts citation grouping and adds the resulting citation group to the builder.
fn interrupt_cites(&mut self) -> SourceResult<()> {
if !self.cites.items.is_empty() {
let staged = mem::take(&mut self.cites.staged);
@ -239,6 +250,7 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
Ok(())
}
/// Interrupts list building and adds the resulting list element to the builder.
fn interrupt_list(&mut self) -> SourceResult<()> {
self.interrupt_cites()?;
if !self.list.items.is_empty() {
@ -252,6 +264,7 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
Ok(())
}
/// Interrupts paragraph building and adds the resulting paragraph element to the builder.
fn interrupt_par(&mut self) -> SourceResult<()> {
self.interrupt_list()?;
if !self.par.0.is_empty() {
@ -262,6 +275,7 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
Ok(())
}
/// Interrupts page building and adds the resulting page element to the builder.
fn interrupt_page(
&mut self,
styles: Option<StyleChain<'a>>,
@ -284,7 +298,7 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
}
}
/// Accepts pagebreaks and pages.
/// Builds a [document][DocumentElem] from pagebreaks and pages.
struct DocBuilder<'a> {
/// The page runs built so far.
pages: BehavedBuilder<'a>,
@ -295,6 +309,12 @@ struct DocBuilder<'a> {
}
impl<'a> DocBuilder<'a> {
/// Tries to accept a piece of content.
///
/// Returns true if this content could be merged into the document.
/// If this function returns false, then the
/// content could not be merged, and document building should be
/// interrupted so that the content can be added elsewhere.
fn accept(
&mut self,
arenas: &'a Arenas<'a>,
@ -324,6 +344,8 @@ impl<'a> DocBuilder<'a> {
false
}
/// Turns this builder into the resulting document, along with
/// its [style chain][StyleChain].
fn finish(self) -> (Packed<DocumentElem>, StyleChain<'a>) {
let (children, trunk, span) = self.pages.finish();
(Packed::new(DocumentElem::new(children)).spanned(span), trunk)
@ -340,11 +362,17 @@ impl Default for DocBuilder<'_> {
}
}
/// Accepts flow content.
/// Builds a [flow][FlowElem] from flow content.
#[derive(Default)]
struct FlowBuilder<'a>(BehavedBuilder<'a>, bool);
impl<'a> FlowBuilder<'a> {
/// Tries to accept a piece of content.
///
/// Returns true if this content could be merged into the flow.
/// If this function returns false, then the
/// content could not be merged, and flow building should be
/// interrupted so that the content can be added elsewhere.
fn accept(
&mut self,
arenas: &'a Arenas<'a>,
@ -403,17 +431,25 @@ impl<'a> FlowBuilder<'a> {
false
}
/// Turns this builder into the resulting flow, along with
/// its [style chain][StyleChain].
fn finish(self) -> (Packed<FlowElem>, StyleChain<'a>) {
let (children, trunk, span) = self.0.finish();
(Packed::new(FlowElem::new(children)).spanned(span), trunk)
}
}
/// Accepts paragraph content.
/// Builds a [paragraph][ParElem] from paragraph content.
#[derive(Default)]
struct ParBuilder<'a>(BehavedBuilder<'a>);
impl<'a> ParBuilder<'a> {
/// Tries to accept a piece of content.
///
/// Returns true if this content could be merged into the paragraph.
/// If this function returns false, then the
/// content could not be merged, and paragraph building should be
/// interrupted so that the content can be added elsewhere.
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
if content.is::<MetaElem>() {
if !self.0.is_empty() {
@ -437,13 +473,16 @@ impl<'a> ParBuilder<'a> {
false
}
/// Turns this builder into the resulting paragraph, along with
/// its [style chain][StyleChain].
fn finish(self) -> (Packed<ParElem>, StyleChain<'a>) {
let (children, trunk, span) = self.0.finish();
(Packed::new(ParElem::new(children)).spanned(span), trunk)
}
}
/// Accepts list / enum items, spaces, paragraph breaks.
/// Builds a list (either [`ListElem`], [`EnumElem`], or [`TermsElem`])
/// from list or enum items, spaces, and paragraph breaks.
struct ListBuilder<'a> {
/// The list items collected so far.
items: BehavedBuilder<'a>,
@ -454,6 +493,12 @@ struct ListBuilder<'a> {
}
impl<'a> ListBuilder<'a> {
/// Tries to accept a piece of content.
///
/// Returns true if this content could be merged into the list.
/// If this function returns false, then the
/// content could not be merged, and list building should be
/// interrupted so that the content can be added elsewhere.
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
if !self.items.is_empty()
&& (content.is::<SpaceElem>() || content.is::<ParbreakElem>())
@ -479,6 +524,8 @@ impl<'a> ListBuilder<'a> {
false
}
/// Turns this builder into the resulting list, along with
/// its [style chain][StyleChain].
fn finish(self) -> (Content, StyleChain<'a>) {
let (items, trunk, span) = self.items.finish_iter();
let mut items = items.peekable();
@ -545,7 +592,7 @@ impl Default for ListBuilder<'_> {
}
}
/// Accepts citations.
/// Builds a [citation group][CiteGroup] from citations.
#[derive(Default)]
struct CiteGroupBuilder<'a> {
/// The styles.
@ -557,6 +604,12 @@ struct CiteGroupBuilder<'a> {
}
impl<'a> CiteGroupBuilder<'a> {
/// Tries to accept a piece of content.
///
/// Returns true if this content could be merged into the citation
/// group. If this function returns false, then the
/// content could not be merged, and citation grouping should be
/// interrupted so that the content can be added elsewhere.
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
if !self.items.is_empty()
&& (content.is::<SpaceElem>() || content.is::<MetaElem>())
@ -577,6 +630,8 @@ impl<'a> CiteGroupBuilder<'a> {
false
}
/// Turns this builder into the resulting citation group, along with
/// its [style chain][StyleChain].
fn finish(self) -> (Packed<CiteGroup>, StyleChain<'a>) {
let span = self.items.first().map(|cite| cite.span()).unwrap_or(Span::detached());
(Packed::new(CiteGroup::new(self.items)).spanned(span), self.styles)

View File

@ -15,7 +15,7 @@ use crate::util::{hash128, BitSet};
/// What to do with an element when encountering it during realization.
struct Verdict<'a> {
/// Whether the element is already prepated (i.e. things that should only
/// Whether the element is already prepared (i.e. things that should only
/// happen once have happened).
prepared: bool,
/// A map of styles to apply to the element.
@ -32,7 +32,7 @@ enum ShowStep<'a> {
Builtin,
}
/// Whether the `target` element needs processing.
/// Returns whether the `target` element needs processing.
pub fn processable<'a>(
engine: &mut Engine,
target: &'a Content,