mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Layout API docs (#4103)
This commit is contained in:
parent
36040d93ef
commit
d859218b90
@ -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,
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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 doesn’t 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,
|
||||
|
@ -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,
|
||||
}
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
///
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user