mirror of
https://github.com/typst/typst
synced 2025-05-19 11:35:27 +08:00
Consolidate flow layout (#4772)
This commit is contained in:
parent
d97d71948e
commit
b0687198b1
@ -5,7 +5,8 @@ use crate::engine::Engine;
|
|||||||
use crate::foundations::{elem, Content, NativeElement, Packed, Show, StyleChain};
|
use crate::foundations::{elem, Content, NativeElement, Packed, Show, StyleChain};
|
||||||
use crate::introspection::Locator;
|
use crate::introspection::Locator;
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
Abs, Axes, BlockElem, Dir, Fragment, Frame, Length, Point, Ratio, Regions, Rel, Size,
|
layout_fragment, Abs, Axes, BlockElem, Dir, Fragment, Frame, Length, Point, Ratio,
|
||||||
|
Regions, Rel, Size,
|
||||||
};
|
};
|
||||||
use crate::realize::{Behave, Behaviour};
|
use crate::realize::{Behave, Behaviour};
|
||||||
use crate::text::TextElem;
|
use crate::text::TextElem;
|
||||||
@ -77,12 +78,12 @@ fn layout_columns(
|
|||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
let body = elem.body();
|
let body = &elem.body;
|
||||||
|
|
||||||
// Separating the infinite space into infinite columns does not make
|
// Separating the infinite space into infinite columns does not make
|
||||||
// much sense.
|
// much sense.
|
||||||
if !regions.size.x.is_finite() {
|
if !regions.size.x.is_finite() {
|
||||||
return body.layout(engine, locator, styles, regions);
|
return layout_fragment(engine, body, locator, styles, regions);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the width of the gutter and each column.
|
// Determine the width of the gutter and each column.
|
||||||
@ -107,7 +108,7 @@ fn layout_columns(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Layout the children.
|
// Layout the children.
|
||||||
let mut frames = body.layout(engine, locator, styles, pod)?.into_iter();
|
let mut frames = layout_fragment(engine, body, locator, styles, pod)?.into_iter();
|
||||||
let mut finished = vec![];
|
let mut finished = vec![];
|
||||||
|
|
||||||
let dir = TextElem::dir_in(styles);
|
let dir = TextElem::dir_in(styles);
|
||||||
|
@ -9,8 +9,8 @@ use crate::foundations::{
|
|||||||
};
|
};
|
||||||
use crate::introspection::Locator;
|
use crate::introspection::Locator;
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
Abs, Axes, Corners, Em, Fr, Fragment, Frame, FrameKind, Length, Region, Regions, Rel,
|
layout_fragment, layout_frame, Abs, Axes, Corners, Em, Fr, Fragment, Frame,
|
||||||
Sides, Size, Spacing,
|
FrameKind, Length, Region, Regions, Rel, Sides, Size, Spacing,
|
||||||
};
|
};
|
||||||
use crate::utils::Numeric;
|
use crate::utils::Numeric;
|
||||||
use crate::visualize::{clip_rect, Paint, Stroke};
|
use crate::visualize::{clip_rect, Paint, Stroke};
|
||||||
@ -141,9 +141,7 @@ impl Packed<BoxElem> {
|
|||||||
|
|
||||||
// If we have a child, layout it into the body. Boxes are boundaries
|
// If we have a child, layout it into the body. Boxes are boundaries
|
||||||
// for gradient relativeness, so we set the `FrameKind` to `Hard`.
|
// for gradient relativeness, so we set the `FrameKind` to `Hard`.
|
||||||
Some(body) => body
|
Some(body) => layout_frame(engine, body, locator, styles, pod)?
|
||||||
.layout(engine, locator, styles, pod.into_regions())?
|
|
||||||
.into_frame()
|
|
||||||
.with_kind(FrameKind::Hard),
|
.with_kind(FrameKind::Hard),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -531,7 +529,7 @@ impl Packed<BlockElem> {
|
|||||||
// If we have content as our body, just layout it.
|
// If we have content as our body, just layout it.
|
||||||
Some(BlockChild::Content(body)) => {
|
Some(BlockChild::Content(body)) => {
|
||||||
let mut fragment =
|
let mut fragment =
|
||||||
body.layout(engine, locator.relayout(), styles, pod)?;
|
layout_fragment(engine, body, locator.relayout(), styles, pod)?;
|
||||||
|
|
||||||
// If the body is automatically sized and produced more than one
|
// If the body is automatically sized and produced more than one
|
||||||
// fragment, ensure that the width was consistent across all
|
// fragment, ensure that the width was consistent across all
|
||||||
@ -552,7 +550,7 @@ impl Packed<BlockElem> {
|
|||||||
expand: Axes::new(true, pod.expand.y),
|
expand: Axes::new(true, pod.expand.y),
|
||||||
..pod
|
..pod
|
||||||
};
|
};
|
||||||
fragment = body.layout(engine, locator, styles, pod)?;
|
fragment = layout_fragment(engine, body, locator, styles, pod)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment
|
fragment
|
||||||
|
@ -1,31 +1,423 @@
|
|||||||
//! Layout flows.
|
//! Layout of content
|
||||||
//!
|
//! - at the top-level, into a [`Document`].
|
||||||
//! A *flow* is a collection of block-level layoutable elements.
|
//! - inside of a container, into a [`Frame`] or [`Fragment`].
|
||||||
//! This is analogous to a paragraph, which is a collection of
|
|
||||||
//! inline-level layoutable elements.
|
|
||||||
|
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
use std::ptr;
|
||||||
|
|
||||||
|
use comemo::{Track, Tracked, TrackedMut};
|
||||||
|
|
||||||
use crate::diag::{bail, SourceResult};
|
use crate::diag::{bail, SourceResult};
|
||||||
use crate::engine::Engine;
|
use crate::engine::{Engine, Route, Sink, Traced};
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
elem, Args, Construct, Content, NativeElement, Packed, Resolve, Smart, StyleChain,
|
elem, Args, Construct, Content, NativeElement, Packed, Resolve, Smart, StyleChain,
|
||||||
};
|
};
|
||||||
use crate::introspection::{Locator, SplitLocator, Tag, TagElem};
|
use crate::introspection::{
|
||||||
use crate::layout::{
|
Counter, CounterDisplayElem, CounterKey, Introspector, Locator, LocatorLink,
|
||||||
Abs, AlignElem, Axes, BlockElem, ColbreakElem, FixedAlignment, FlushElem, Fr,
|
ManualPageCounter, SplitLocator, Tag, TagElem,
|
||||||
Fragment, Frame, FrameItem, PlaceElem, Point, Ratio, Regions, Rel, Size, Spacing,
|
|
||||||
VElem,
|
|
||||||
};
|
};
|
||||||
|
use crate::layout::{
|
||||||
|
Abs, AlignElem, Alignment, Axes, Binding, BlockElem, ColbreakElem, ColumnsElem, Dir,
|
||||||
|
FixedAlignment, FlushElem, Fr, Fragment, Frame, FrameItem, HAlignment, Length,
|
||||||
|
OuterVAlignment, Page, PageElem, Paper, Parity, PlaceElem, Point, Ratio, Region,
|
||||||
|
Regions, Rel, Sides, Size, Spacing, VAlignment, VElem,
|
||||||
|
};
|
||||||
|
use crate::model::{Document, Numbering};
|
||||||
use crate::model::{FootnoteElem, FootnoteEntry, ParElem};
|
use crate::model::{FootnoteElem, FootnoteEntry, ParElem};
|
||||||
use crate::realize::StyleVec;
|
use crate::realize::StyleVec;
|
||||||
|
use crate::realize::{realize_flow, realize_root, Arenas};
|
||||||
use crate::text::TextElem;
|
use crate::text::TextElem;
|
||||||
use crate::utils::Numeric;
|
use crate::utils::Numeric;
|
||||||
|
use crate::World;
|
||||||
|
|
||||||
/// Arranges spacing, paragraphs and block-level elements into a flow.
|
/// Layout content into a document.
|
||||||
|
///
|
||||||
|
/// This first performs root-level realization and then lays out the resulting
|
||||||
|
/// elements. In contrast to [`layout_fragment`], this does not take regions
|
||||||
|
/// since the regions are defined by the page configuration in the content and
|
||||||
|
/// style chain.
|
||||||
|
#[typst_macros::time(name = "document")]
|
||||||
|
pub fn layout_document(
|
||||||
|
engine: &mut Engine,
|
||||||
|
content: &Content,
|
||||||
|
styles: StyleChain,
|
||||||
|
) -> SourceResult<Document> {
|
||||||
|
layout_document_impl(
|
||||||
|
engine.world,
|
||||||
|
engine.introspector,
|
||||||
|
engine.traced,
|
||||||
|
TrackedMut::reborrow_mut(&mut engine.sink),
|
||||||
|
engine.route.track(),
|
||||||
|
content,
|
||||||
|
styles,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The internal implementation of `layout_document`.
|
||||||
|
#[comemo::memoize]
|
||||||
|
fn layout_document_impl(
|
||||||
|
world: Tracked<dyn World + '_>,
|
||||||
|
introspector: Tracked<Introspector>,
|
||||||
|
traced: Tracked<Traced>,
|
||||||
|
sink: TrackedMut<Sink>,
|
||||||
|
route: Tracked<Route>,
|
||||||
|
content: &Content,
|
||||||
|
styles: StyleChain,
|
||||||
|
) -> SourceResult<Document> {
|
||||||
|
let mut locator = Locator::root().split();
|
||||||
|
let mut engine = Engine {
|
||||||
|
world,
|
||||||
|
introspector,
|
||||||
|
traced,
|
||||||
|
sink,
|
||||||
|
route: Route::extend(route).unnested(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let arenas = Arenas::default();
|
||||||
|
let (children, styles, info) =
|
||||||
|
realize_root(&mut engine, &mut locator, &arenas, content, styles)?;
|
||||||
|
|
||||||
|
let mut peekable = children.chain(&styles).peekable();
|
||||||
|
let iter = std::iter::from_fn(|| {
|
||||||
|
let (child, styles) = peekable.next()?;
|
||||||
|
let extend_to = peekable
|
||||||
|
.peek()
|
||||||
|
.and_then(|(next, _)| *next.to_packed::<PageElem>()?.clear_to()?);
|
||||||
|
let locator = locator.next(&child.span());
|
||||||
|
Some((child, styles, extend_to, locator))
|
||||||
|
});
|
||||||
|
|
||||||
|
let layouts =
|
||||||
|
engine.parallelize(iter, |engine, (child, styles, extend_to, locator)| {
|
||||||
|
if let Some(page) = child.to_packed::<PageElem>() {
|
||||||
|
layout_page_run(engine, page, locator, styles, extend_to)
|
||||||
|
} else {
|
||||||
|
bail!(child.span(), "expected page element");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut page_counter = ManualPageCounter::new();
|
||||||
|
let mut pages = Vec::with_capacity(children.len());
|
||||||
|
for result in layouts {
|
||||||
|
let layout = result?;
|
||||||
|
pages.extend(finalize_page_run(&mut engine, layout, &mut page_counter)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Document { pages, info, introspector: Introspector::default() })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A prepared layout of a page run that can be finalized with access to the
|
||||||
|
/// page counter.
|
||||||
|
struct PageRunLayout<'a> {
|
||||||
|
page: &'a Packed<PageElem>,
|
||||||
|
locator: SplitLocator<'a>,
|
||||||
|
styles: StyleChain<'a>,
|
||||||
|
extend_to: Option<Parity>,
|
||||||
|
area: Size,
|
||||||
|
margin: Sides<Abs>,
|
||||||
|
two_sided: bool,
|
||||||
|
frames: Vec<Frame>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A document can consist of multiple `PageElem`s, one per run of pages
|
||||||
|
/// with equal properties (not one per actual output page!). The `number` is
|
||||||
|
/// the physical page number of the first page of this run. It is mutated
|
||||||
|
/// while we post-process the pages in this function. This function returns
|
||||||
|
/// a fragment consisting of multiple frames, one per output page of this
|
||||||
|
/// page run.
|
||||||
|
#[typst_macros::time(name = "pages", span = page.span())]
|
||||||
|
fn layout_page_run<'a>(
|
||||||
|
engine: &mut Engine,
|
||||||
|
page: &'a Packed<PageElem>,
|
||||||
|
locator: Locator<'a>,
|
||||||
|
styles: StyleChain<'a>,
|
||||||
|
extend_to: Option<Parity>,
|
||||||
|
) -> SourceResult<PageRunLayout<'a>> {
|
||||||
|
let mut locator = locator.split();
|
||||||
|
|
||||||
|
// When one of the lengths is infinite the page fits its content along
|
||||||
|
// that axis.
|
||||||
|
let width = page.width(styles).unwrap_or(Abs::inf());
|
||||||
|
let height = page.height(styles).unwrap_or(Abs::inf());
|
||||||
|
let mut size = Size::new(width, height);
|
||||||
|
if page.flipped(styles) {
|
||||||
|
std::mem::swap(&mut size.x, &mut size.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut min = width.min(height);
|
||||||
|
if !min.is_finite() {
|
||||||
|
min = Paper::A4.width();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the margins.
|
||||||
|
let default = Rel::<Length>::from((2.5 / 21.0) * min);
|
||||||
|
let margin = page.margin(styles);
|
||||||
|
let two_sided = margin.two_sided.unwrap_or(false);
|
||||||
|
let margin = margin
|
||||||
|
.sides
|
||||||
|
.map(|side| side.and_then(Smart::custom).unwrap_or(default))
|
||||||
|
.resolve(styles)
|
||||||
|
.relative_to(size);
|
||||||
|
|
||||||
|
// Realize columns.
|
||||||
|
let mut child = page.body().clone();
|
||||||
|
let columns = page.columns(styles);
|
||||||
|
if columns.get() > 1 {
|
||||||
|
child = ColumnsElem::new(child)
|
||||||
|
.with_count(columns)
|
||||||
|
.pack()
|
||||||
|
.spanned(page.span());
|
||||||
|
}
|
||||||
|
|
||||||
|
let area = size - margin.sum_by_axis();
|
||||||
|
let mut regions = Regions::repeat(area, area.map(Abs::is_finite));
|
||||||
|
regions.root = true;
|
||||||
|
|
||||||
|
// Layout the child.
|
||||||
|
let frames =
|
||||||
|
layout_fragment(engine, &child, locator.next(&page.span()), styles, regions)?
|
||||||
|
.into_frames();
|
||||||
|
|
||||||
|
Ok(PageRunLayout {
|
||||||
|
page,
|
||||||
|
locator,
|
||||||
|
styles,
|
||||||
|
extend_to,
|
||||||
|
area,
|
||||||
|
margin,
|
||||||
|
two_sided,
|
||||||
|
frames,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finalize the layout with access to the next page counter.
|
||||||
|
#[typst_macros::time(name = "finalize pages", span = page.span())]
|
||||||
|
fn finalize_page_run(
|
||||||
|
engine: &mut Engine,
|
||||||
|
PageRunLayout {
|
||||||
|
page,
|
||||||
|
mut locator,
|
||||||
|
styles,
|
||||||
|
extend_to,
|
||||||
|
area,
|
||||||
|
margin,
|
||||||
|
two_sided,
|
||||||
|
mut frames,
|
||||||
|
}: PageRunLayout<'_>,
|
||||||
|
page_counter: &mut ManualPageCounter,
|
||||||
|
) -> SourceResult<Vec<Page>> {
|
||||||
|
// Align the child to the pagebreak's parity.
|
||||||
|
// Check for page count after adding the pending frames
|
||||||
|
if extend_to.is_some_and(|p| !p.matches(page_counter.physical().get() + frames.len()))
|
||||||
|
{
|
||||||
|
// Insert empty page after the current pages.
|
||||||
|
let size = area.map(Abs::is_finite).select(area, Size::zero());
|
||||||
|
frames.push(Frame::hard(size));
|
||||||
|
}
|
||||||
|
|
||||||
|
let fill = page.fill(styles);
|
||||||
|
let foreground = page.foreground(styles);
|
||||||
|
let background = page.background(styles);
|
||||||
|
let header_ascent = page.header_ascent(styles);
|
||||||
|
let footer_descent = page.footer_descent(styles);
|
||||||
|
let numbering = page.numbering(styles);
|
||||||
|
let number_align = page.number_align(styles);
|
||||||
|
let binding =
|
||||||
|
page.binding(styles)
|
||||||
|
.unwrap_or_else(|| match TextElem::dir_in(styles) {
|
||||||
|
Dir::LTR => Binding::Left,
|
||||||
|
_ => Binding::Right,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Construct the numbering (for header or footer).
|
||||||
|
let numbering_marginal = numbering.as_ref().map(|numbering| {
|
||||||
|
let both = match numbering {
|
||||||
|
Numbering::Pattern(pattern) => pattern.pieces() >= 2,
|
||||||
|
Numbering::Func(_) => true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut counter = CounterDisplayElem::new(
|
||||||
|
Counter::new(CounterKey::Page),
|
||||||
|
Smart::Custom(numbering.clone()),
|
||||||
|
both,
|
||||||
|
)
|
||||||
|
.pack()
|
||||||
|
.spanned(page.span());
|
||||||
|
|
||||||
|
// We interpret the Y alignment as selecting header or footer
|
||||||
|
// and then ignore it for aligning the actual number.
|
||||||
|
if let Some(x) = number_align.x() {
|
||||||
|
counter = counter.aligned(x.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
counter
|
||||||
|
});
|
||||||
|
|
||||||
|
let header = page.header(styles);
|
||||||
|
let footer = page.footer(styles);
|
||||||
|
let (header, footer) = if matches!(number_align.y(), Some(OuterVAlignment::Top)) {
|
||||||
|
(header.as_ref().unwrap_or(&numbering_marginal), footer.as_ref().unwrap_or(&None))
|
||||||
|
} else {
|
||||||
|
(header.as_ref().unwrap_or(&None), footer.as_ref().unwrap_or(&numbering_marginal))
|
||||||
|
};
|
||||||
|
|
||||||
|
// Post-process pages.
|
||||||
|
let mut pages = Vec::with_capacity(frames.len());
|
||||||
|
for mut frame in frames {
|
||||||
|
// The padded width of the page's content without margins.
|
||||||
|
let pw = frame.width();
|
||||||
|
|
||||||
|
// If two sided, left becomes inside and right becomes outside.
|
||||||
|
// Thus, for left-bound pages, we want to swap on even pages and
|
||||||
|
// for right-bound pages, we want to swap on odd pages.
|
||||||
|
let mut margin = margin;
|
||||||
|
if two_sided && binding.swap(page_counter.physical()) {
|
||||||
|
std::mem::swap(&mut margin.left, &mut margin.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Realize margins.
|
||||||
|
frame.set_size(frame.size() + margin.sum_by_axis());
|
||||||
|
frame.translate(Point::new(margin.left, margin.top));
|
||||||
|
|
||||||
|
// The page size with margins.
|
||||||
|
let size = frame.size();
|
||||||
|
|
||||||
|
// Realize overlays.
|
||||||
|
for marginal in [header, footer, background, foreground] {
|
||||||
|
let Some(content) = marginal.as_ref() else { continue };
|
||||||
|
|
||||||
|
let (pos, area, align);
|
||||||
|
if ptr::eq(marginal, header) {
|
||||||
|
let ascent = header_ascent.relative_to(margin.top);
|
||||||
|
pos = Point::with_x(margin.left);
|
||||||
|
area = Size::new(pw, margin.top - ascent);
|
||||||
|
align = Alignment::BOTTOM;
|
||||||
|
} else if ptr::eq(marginal, footer) {
|
||||||
|
let descent = footer_descent.relative_to(margin.bottom);
|
||||||
|
pos = Point::new(margin.left, size.y - margin.bottom + descent);
|
||||||
|
area = Size::new(pw, margin.bottom - descent);
|
||||||
|
align = Alignment::TOP;
|
||||||
|
} else {
|
||||||
|
pos = Point::zero();
|
||||||
|
area = size;
|
||||||
|
align = HAlignment::Center + VAlignment::Horizon;
|
||||||
|
};
|
||||||
|
|
||||||
|
let aligned = content.clone().styled(AlignElem::set_alignment(align));
|
||||||
|
let sub = layout_frame(
|
||||||
|
engine,
|
||||||
|
&aligned,
|
||||||
|
locator.next(&content.span()),
|
||||||
|
styles,
|
||||||
|
Region::new(area, Axes::splat(true)),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if ptr::eq(marginal, header) || ptr::eq(marginal, background) {
|
||||||
|
frame.prepend_frame(pos, sub);
|
||||||
|
} else {
|
||||||
|
frame.push_frame(pos, sub);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
page_counter.visit(engine, &frame)?;
|
||||||
|
pages.push(Page {
|
||||||
|
frame,
|
||||||
|
fill: fill.clone(),
|
||||||
|
numbering: numbering.clone(),
|
||||||
|
number: page_counter.logical(),
|
||||||
|
});
|
||||||
|
|
||||||
|
page_counter.step();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(pages)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Layout content into a single region.
|
||||||
|
pub fn layout_frame(
|
||||||
|
engine: &mut Engine,
|
||||||
|
content: &Content,
|
||||||
|
locator: Locator,
|
||||||
|
styles: StyleChain,
|
||||||
|
region: Region,
|
||||||
|
) -> SourceResult<Frame> {
|
||||||
|
layout_fragment(engine, content, locator, styles, region.into())
|
||||||
|
.map(Fragment::into_frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Layout content into multiple regions.
|
||||||
|
///
|
||||||
|
/// When just layouting into a single region, prefer [`layout_frame`].
|
||||||
|
pub fn layout_fragment(
|
||||||
|
engine: &mut Engine,
|
||||||
|
content: &Content,
|
||||||
|
locator: Locator,
|
||||||
|
styles: StyleChain,
|
||||||
|
regions: Regions,
|
||||||
|
) -> SourceResult<Fragment> {
|
||||||
|
layout_fragment_impl(
|
||||||
|
engine.world,
|
||||||
|
engine.introspector,
|
||||||
|
engine.traced,
|
||||||
|
TrackedMut::reborrow_mut(&mut engine.sink),
|
||||||
|
engine.route.track(),
|
||||||
|
content,
|
||||||
|
locator.track(),
|
||||||
|
styles,
|
||||||
|
regions,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The internal implementation of [`layout_fragment`].
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
#[comemo::memoize]
|
||||||
|
fn layout_fragment_impl(
|
||||||
|
world: Tracked<dyn World + '_>,
|
||||||
|
introspector: Tracked<Introspector>,
|
||||||
|
traced: Tracked<Traced>,
|
||||||
|
sink: TrackedMut<Sink>,
|
||||||
|
route: Tracked<Route>,
|
||||||
|
content: &Content,
|
||||||
|
locator: Tracked<Locator>,
|
||||||
|
styles: StyleChain,
|
||||||
|
regions: Regions,
|
||||||
|
) -> SourceResult<Fragment> {
|
||||||
|
let link = LocatorLink::new(locator);
|
||||||
|
let mut locator = Locator::link(&link).split();
|
||||||
|
let mut engine = Engine {
|
||||||
|
world,
|
||||||
|
introspector,
|
||||||
|
traced,
|
||||||
|
sink,
|
||||||
|
route: Route::extend(route),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !engine.route.within(Route::MAX_LAYOUT_DEPTH) {
|
||||||
|
bail!(
|
||||||
|
content.span(), "maximum layout depth exceeded";
|
||||||
|
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
|
||||||
|
// layouting that.
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A collection of block-level layoutable elements. This is analogous to a
|
||||||
|
/// paragraph, which is a collection of inline-level layoutable elements.
|
||||||
///
|
///
|
||||||
/// This element is responsible for layouting both the top-level content flow
|
/// This element is responsible for layouting both the top-level content flow
|
||||||
/// and the contents of boxes.
|
/// and the contents of any containers.
|
||||||
#[elem(Debug, Construct)]
|
#[elem(Debug, Construct)]
|
||||||
pub struct FlowElem {
|
pub struct FlowElem {
|
||||||
/// The children that will be arranged into a flow.
|
/// The children that will be arranged into a flow.
|
||||||
@ -40,19 +432,6 @@ impl Construct for FlowElem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Packed<FlowElem> {
|
|
||||||
#[typst_macros::time(name = "flow", span = self.span())]
|
|
||||||
pub fn layout(
|
|
||||||
&self,
|
|
||||||
engine: &mut Engine,
|
|
||||||
locator: Locator,
|
|
||||||
styles: StyleChain,
|
|
||||||
regions: Regions,
|
|
||||||
) -> SourceResult<Fragment> {
|
|
||||||
FlowLayouter::new(engine, self, locator, &styles, regions).layout()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for FlowElem {
|
impl Debug for FlowElem {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
write!(f, "Flow ")?;
|
write!(f, "Flow ")?;
|
||||||
@ -169,7 +548,7 @@ impl<'a, 'e> FlowLayouter<'a, 'e> {
|
|||||||
fn new(
|
fn new(
|
||||||
engine: &'a mut Engine<'e>,
|
engine: &'a mut Engine<'e>,
|
||||||
flow: &'a Packed<FlowElem>,
|
flow: &'a Packed<FlowElem>,
|
||||||
locator: Locator<'a>,
|
locator: SplitLocator<'a>,
|
||||||
styles: &'a StyleChain<'a>,
|
styles: &'a StyleChain<'a>,
|
||||||
mut regions: Regions<'a>,
|
mut regions: Regions<'a>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@ -200,7 +579,7 @@ impl<'a, 'e> FlowLayouter<'a, 'e> {
|
|||||||
engine,
|
engine,
|
||||||
flow,
|
flow,
|
||||||
root,
|
root,
|
||||||
locator: locator.split(),
|
locator,
|
||||||
styles,
|
styles,
|
||||||
regions,
|
regions,
|
||||||
expand,
|
expand,
|
||||||
@ -272,6 +651,7 @@ impl<'a, 'e> FlowLayouter<'a, 'e> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Layout a paragraph.
|
/// Layout a paragraph.
|
||||||
|
#[typst_macros::time(name = "par", span = par.span())]
|
||||||
fn handle_par(
|
fn handle_par(
|
||||||
&mut self,
|
&mut self,
|
||||||
par: &'a Packed<ParElem>,
|
par: &'a Packed<ParElem>,
|
||||||
@ -286,16 +666,16 @@ impl<'a, 'e> FlowLayouter<'a, 'e> {
|
|||||||
// not on the Y position.
|
// not on the Y position.
|
||||||
let consecutive = self.last_was_par;
|
let consecutive = self.last_was_par;
|
||||||
let locator = self.locator.next(&par.span());
|
let locator = self.locator.next(&par.span());
|
||||||
let lines = par
|
let lines = crate::layout::layout_inline(
|
||||||
.layout(
|
self.engine,
|
||||||
self.engine,
|
&par.children,
|
||||||
locator,
|
locator,
|
||||||
styles,
|
styles,
|
||||||
consecutive,
|
consecutive,
|
||||||
self.regions.base(),
|
self.regions.base(),
|
||||||
self.regions.expand.x,
|
self.regions.expand.x,
|
||||||
)?
|
)?
|
||||||
.into_frames();
|
.into_frames();
|
||||||
|
|
||||||
// If the first line doesn’t fit in this region, then defer any
|
// If the first line doesn’t fit in this region, then defer any
|
||||||
// previous sticky frame to the next region (if available)
|
// previous sticky frame to the next region (if available)
|
||||||
@ -440,14 +820,12 @@ impl<'a, 'e> FlowLayouter<'a, 'e> {
|
|||||||
});
|
});
|
||||||
let y_align = alignment.map(|align| align.y().map(|y| y.resolve(styles)));
|
let y_align = alignment.map(|align| align.y().map(|y| y.resolve(styles)));
|
||||||
|
|
||||||
let mut frame = placed
|
let mut frame = placed.layout(
|
||||||
.layout(
|
self.engine,
|
||||||
self.engine,
|
self.locator.next(&placed.span()),
|
||||||
self.locator.next(&placed.span()),
|
styles,
|
||||||
styles,
|
self.regions.base(),
|
||||||
self.regions.base(),
|
)?;
|
||||||
)?
|
|
||||||
.into_frame();
|
|
||||||
|
|
||||||
frame.post_process(styles);
|
frame.post_process(styles);
|
||||||
|
|
||||||
@ -830,15 +1208,14 @@ impl<'a, 'e> FlowLayouter<'a, 'e> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.regions.size.y -= self.footnote_config.gap;
|
self.regions.size.y -= self.footnote_config.gap;
|
||||||
let frames = FootnoteEntry::new(notes[k].clone())
|
let frames = layout_fragment(
|
||||||
.pack()
|
self.engine,
|
||||||
.layout(
|
&FootnoteEntry::new(notes[k].clone()).pack(),
|
||||||
self.engine,
|
Locator::synthesize(notes[k].location().unwrap()),
|
||||||
Locator::synthesize(notes[k].location().unwrap()),
|
*self.styles,
|
||||||
*self.styles,
|
self.regions.with_root(false),
|
||||||
self.regions.with_root(false),
|
)?
|
||||||
)?
|
.into_frames();
|
||||||
.into_frames();
|
|
||||||
|
|
||||||
// If the entries didn't fit, abort (to keep footnote and entry
|
// If the entries didn't fit, abort (to keep footnote and entry
|
||||||
// together).
|
// together).
|
||||||
@ -882,13 +1259,12 @@ impl<'a, 'e> FlowLayouter<'a, 'e> {
|
|||||||
/// Layout and save the footnote separator, typically a line.
|
/// Layout and save the footnote separator, typically a line.
|
||||||
fn layout_footnote_separator(&mut self) -> SourceResult<()> {
|
fn layout_footnote_separator(&mut self) -> SourceResult<()> {
|
||||||
let expand = Axes::new(self.regions.expand.x, false);
|
let expand = Axes::new(self.regions.expand.x, false);
|
||||||
let pod = Regions::one(self.regions.base(), expand);
|
let pod = Region::new(self.regions.base(), expand);
|
||||||
let separator = &self.footnote_config.separator;
|
let separator = &self.footnote_config.separator;
|
||||||
|
|
||||||
// FIXME: Shouldn't use `root()` here.
|
// FIXME: Shouldn't use `root()` here.
|
||||||
let mut frame = separator
|
let mut frame =
|
||||||
.layout(self.engine, Locator::root(), *self.styles, pod)?
|
layout_frame(self.engine, separator, Locator::root(), *self.styles, pod)?;
|
||||||
.into_frame();
|
|
||||||
frame.size_mut().y += self.footnote_config.clearance;
|
frame.size_mut().y += self.footnote_config.clearance;
|
||||||
frame.translate(Point::with_y(self.footnote_config.clearance));
|
frame.translate(Point::with_y(self.footnote_config.clearance));
|
||||||
|
|
||||||
|
@ -14,7 +14,8 @@ use crate::foundations::{
|
|||||||
};
|
};
|
||||||
use crate::introspection::Locator;
|
use crate::introspection::Locator;
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
Abs, Alignment, Axes, Fragment, Length, LinePosition, Regions, Rel, Sides, Sizing,
|
layout_fragment, Abs, Alignment, Axes, Fragment, Length, LinePosition, Regions, Rel,
|
||||||
|
Sides, Sizing,
|
||||||
};
|
};
|
||||||
use crate::syntax::Span;
|
use crate::syntax::Span;
|
||||||
use crate::utils::NonZeroExt;
|
use crate::utils::NonZeroExt;
|
||||||
@ -220,7 +221,7 @@ impl<'a> Cell<'a> {
|
|||||||
if disambiguator > 0 {
|
if disambiguator > 0 {
|
||||||
locator = locator.split().next_inner(disambiguator as u128);
|
locator = locator.split().next_inner(disambiguator as u128);
|
||||||
}
|
}
|
||||||
self.body.layout(engine, locator, styles, regions)
|
layout_fragment(engine, &self.body, locator, styles, regions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ use crate::engine::Engine;
|
|||||||
use crate::foundations::{Resolve, StyleChain};
|
use crate::foundations::{Resolve, StyleChain};
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
Abs, Axes, Cell, CellGrid, Dir, Fr, Fragment, Frame, FrameItem, Length, Point,
|
Abs, Axes, Cell, CellGrid, Dir, Fr, Fragment, Frame, FrameItem, Length, Point,
|
||||||
Regions, Rel, Size, Sizing,
|
Region, Regions, Rel, Size, Sizing,
|
||||||
};
|
};
|
||||||
use crate::syntax::Span;
|
use crate::syntax::Span;
|
||||||
use crate::text::TextElem;
|
use crate::text::TextElem;
|
||||||
@ -843,8 +843,8 @@ impl<'a> GridLayouter<'a> {
|
|||||||
let already_covered_width = self.cell_spanned_width(cell, parent.x);
|
let already_covered_width = self.cell_spanned_width(cell, parent.x);
|
||||||
|
|
||||||
let size = Size::new(available, height);
|
let size = Size::new(available, height);
|
||||||
let pod = Regions::one(size, Axes::splat(false));
|
let pod = Region::new(size, Axes::splat(false));
|
||||||
let frame = cell.layout(engine, 0, self.styles, pod)?.into_frame();
|
let frame = cell.layout(engine, 0, self.styles, pod.into())?.into_frame();
|
||||||
resolved.set_max(frame.width() - already_covered_width);
|
resolved.set_max(frame.width() - already_covered_width);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1062,7 +1062,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
// Force cell to fit into a single region when the row is
|
// Force cell to fit into a single region when the row is
|
||||||
// unbreakable, even when it is a breakable rowspan, as a best
|
// unbreakable, even when it is a breakable rowspan, as a best
|
||||||
// effort.
|
// effort.
|
||||||
let mut pod = Regions::one(size, self.regions.expand);
|
let mut pod: Regions = Region::new(size, self.regions.expand).into();
|
||||||
pod.full = measurement_data.full;
|
pod.full = measurement_data.full;
|
||||||
|
|
||||||
if measurement_data.frames_in_previous_regions > 0 {
|
if measurement_data.frames_in_previous_regions > 0 {
|
||||||
@ -1244,7 +1244,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
if cell.rowspan.get() == 1 {
|
if cell.rowspan.get() == 1 {
|
||||||
let width = self.cell_spanned_width(cell, x);
|
let width = self.cell_spanned_width(cell, x);
|
||||||
let size = Size::new(width, height);
|
let size = Size::new(width, height);
|
||||||
let mut pod = Regions::one(size, Axes::splat(true));
|
let mut pod: Regions = Region::new(size, Axes::splat(true)).into();
|
||||||
if self.grid.rows[y] == Sizing::Auto
|
if self.grid.rows[y] == Sizing::Auto
|
||||||
&& self.unbreakable_rows_left == 0
|
&& self.unbreakable_rows_left == 0
|
||||||
{
|
{
|
||||||
@ -1296,7 +1296,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
|
|
||||||
// Prepare regions.
|
// Prepare regions.
|
||||||
let size = Size::new(self.width, heights[0]);
|
let size = Size::new(self.width, heights[0]);
|
||||||
let mut pod = Regions::one(size, Axes::splat(true));
|
let mut pod: Regions = Region::new(size, Axes::splat(true)).into();
|
||||||
pod.full = self.regions.full;
|
pod.full = self.regions.full;
|
||||||
pod.backlog = &heights[1..];
|
pod.backlog = &heights[1..];
|
||||||
|
|
||||||
|
@ -967,7 +967,7 @@ impl From<Content> for GridCell {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Function with common code to display a grid cell or table cell.
|
/// Function with common code to display a grid cell or table cell.
|
||||||
pub fn show_grid_cell(
|
pub(crate) fn show_grid_cell(
|
||||||
mut body: Content,
|
mut body: Content,
|
||||||
inset: Smart<Sides<Option<Rel<Length>>>>,
|
inset: Smart<Sides<Option<Rel<Length>>>>,
|
||||||
align: Smart<Alignment>,
|
align: Smart<Alignment>,
|
||||||
|
@ -3,7 +3,9 @@ use super::repeated::Repeatable;
|
|||||||
use crate::diag::SourceResult;
|
use crate::diag::SourceResult;
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::Resolve;
|
use crate::foundations::Resolve;
|
||||||
use crate::layout::{Abs, Axes, Cell, Frame, GridLayouter, Point, Regions, Size, Sizing};
|
use crate::layout::{
|
||||||
|
Abs, Axes, Cell, Frame, GridLayouter, Point, Region, Regions, Size, Sizing,
|
||||||
|
};
|
||||||
use crate::utils::MaybeReverseIter;
|
use crate::utils::MaybeReverseIter;
|
||||||
|
|
||||||
/// All information needed to layout a single rowspan.
|
/// All information needed to layout a single rowspan.
|
||||||
@ -123,7 +125,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
|
|
||||||
// Prepare regions.
|
// Prepare regions.
|
||||||
let size = Size::new(width, *first_height);
|
let size = Size::new(width, *first_height);
|
||||||
let mut pod = Regions::one(size, Axes::splat(true));
|
let mut pod: Regions = Region::new(size, Axes::splat(true)).into();
|
||||||
pod.backlog = backlog;
|
pod.backlog = backlog;
|
||||||
|
|
||||||
if !is_effectively_unbreakable
|
if !is_effectively_unbreakable
|
||||||
|
@ -30,54 +30,15 @@ type Range = std::ops::Range<usize>;
|
|||||||
|
|
||||||
/// Layouts content inline.
|
/// Layouts content inline.
|
||||||
pub(crate) fn layout_inline(
|
pub(crate) fn layout_inline(
|
||||||
children: &StyleVec,
|
|
||||||
engine: &mut Engine,
|
engine: &mut Engine,
|
||||||
|
children: &StyleVec,
|
||||||
locator: Locator,
|
locator: Locator,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
consecutive: bool,
|
consecutive: bool,
|
||||||
region: Size,
|
region: Size,
|
||||||
expand: bool,
|
expand: bool,
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
#[comemo::memoize]
|
layout_inline_impl(
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
fn cached(
|
|
||||||
children: &StyleVec,
|
|
||||||
world: Tracked<dyn World + '_>,
|
|
||||||
introspector: Tracked<Introspector>,
|
|
||||||
traced: Tracked<Traced>,
|
|
||||||
sink: TrackedMut<Sink>,
|
|
||||||
route: Tracked<Route>,
|
|
||||||
locator: Tracked<Locator>,
|
|
||||||
styles: StyleChain,
|
|
||||||
consecutive: bool,
|
|
||||||
region: Size,
|
|
||||||
expand: bool,
|
|
||||||
) -> SourceResult<Fragment> {
|
|
||||||
let link = LocatorLink::new(locator);
|
|
||||||
let locator = Locator::link(&link);
|
|
||||||
let mut engine = Engine {
|
|
||||||
world,
|
|
||||||
introspector,
|
|
||||||
traced,
|
|
||||||
sink,
|
|
||||||
route: Route::extend(route),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Collect all text into one string for BiDi analysis.
|
|
||||||
let (text, segments, spans) =
|
|
||||||
collect(children, &mut engine, locator, &styles, region, consecutive)?;
|
|
||||||
|
|
||||||
// Perform BiDi analysis and then prepares paragraph layout.
|
|
||||||
let p = prepare(&mut engine, children, &text, segments, spans, styles)?;
|
|
||||||
|
|
||||||
// Break the paragraph into lines.
|
|
||||||
let lines = linebreak(&engine, &p, region.x - p.hang);
|
|
||||||
|
|
||||||
// Turn the selected lines into frames.
|
|
||||||
finalize(&mut engine, &p, &lines, styles, region, expand)
|
|
||||||
}
|
|
||||||
|
|
||||||
cached(
|
|
||||||
children,
|
children,
|
||||||
engine.world,
|
engine.world,
|
||||||
engine.introspector,
|
engine.introspector,
|
||||||
@ -91,3 +52,43 @@ pub(crate) fn layout_inline(
|
|||||||
expand,
|
expand,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The internal, memoized implementation of `layout_inline`.
|
||||||
|
#[comemo::memoize]
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn layout_inline_impl(
|
||||||
|
children: &StyleVec,
|
||||||
|
world: Tracked<dyn World + '_>,
|
||||||
|
introspector: Tracked<Introspector>,
|
||||||
|
traced: Tracked<Traced>,
|
||||||
|
sink: TrackedMut<Sink>,
|
||||||
|
route: Tracked<Route>,
|
||||||
|
locator: Tracked<Locator>,
|
||||||
|
styles: StyleChain,
|
||||||
|
consecutive: bool,
|
||||||
|
region: Size,
|
||||||
|
expand: bool,
|
||||||
|
) -> SourceResult<Fragment> {
|
||||||
|
let link = LocatorLink::new(locator);
|
||||||
|
let locator = Locator::link(&link);
|
||||||
|
let mut engine = Engine {
|
||||||
|
world,
|
||||||
|
introspector,
|
||||||
|
traced,
|
||||||
|
sink,
|
||||||
|
route: Route::extend(route),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Collect all text into one string for BiDi analysis.
|
||||||
|
let (text, segments, spans) =
|
||||||
|
collect(children, &mut engine, locator, &styles, region, consecutive)?;
|
||||||
|
|
||||||
|
// Perform BiDi analysis and then prepares paragraph layout.
|
||||||
|
let p = prepare(&mut engine, children, &text, segments, spans, styles)?;
|
||||||
|
|
||||||
|
// Break the paragraph into lines.
|
||||||
|
let lines = linebreak(&engine, &p, region.x - p.hang);
|
||||||
|
|
||||||
|
// Turn the selected lines into frames.
|
||||||
|
finalize(&mut engine, &p, &lines, styles, region, expand)
|
||||||
|
}
|
||||||
|
@ -6,7 +6,7 @@ use crate::foundations::{
|
|||||||
dict, elem, func, Content, Context, Func, NativeElement, Packed, Show, StyleChain,
|
dict, elem, func, Content, Context, Func, NativeElement, Packed, Show, StyleChain,
|
||||||
};
|
};
|
||||||
use crate::introspection::Locatable;
|
use crate::introspection::Locatable;
|
||||||
use crate::layout::{BlockElem, Size};
|
use crate::layout::{layout_fragment, BlockElem, Size};
|
||||||
use crate::syntax::Span;
|
use crate::syntax::Span;
|
||||||
|
|
||||||
/// Provides access to the current outer container's (or page's, if none)
|
/// Provides access to the current outer container's (or page's, if none)
|
||||||
@ -92,7 +92,7 @@ impl Show for Packed<LayoutElem> {
|
|||||||
[dict! { "width" => x, "height" => y }],
|
[dict! { "width" => x, "height" => y }],
|
||||||
)?
|
)?
|
||||||
.display();
|
.display();
|
||||||
result.layout(engine, locator, styles, regions)
|
layout_fragment(engine, &result, locator, styles, regions)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.pack()
|
.pack()
|
||||||
|
@ -6,7 +6,7 @@ use crate::foundations::{
|
|||||||
dict, func, Content, Context, Dict, Resolve, Smart, StyleChain, Styles,
|
dict, func, Content, Context, Dict, Resolve, Smart, StyleChain, Styles,
|
||||||
};
|
};
|
||||||
use crate::introspection::{Locator, LocatorLink};
|
use crate::introspection::{Locator, LocatorLink};
|
||||||
use crate::layout::{Abs, Axes, Length, Regions, Size};
|
use crate::layout::{layout_frame, Abs, Axes, Length, Region, Size};
|
||||||
use crate::syntax::Span;
|
use crate::syntax::Span;
|
||||||
|
|
||||||
/// Measures the layouted size of content.
|
/// Measures the layouted size of content.
|
||||||
@ -93,7 +93,7 @@ pub fn measure(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Create a pod region with the available space.
|
// Create a pod region with the available space.
|
||||||
let pod = Regions::one(
|
let pod = Region::new(
|
||||||
Axes::new(
|
Axes::new(
|
||||||
width.resolve(styles).unwrap_or(Abs::inf()),
|
width.resolve(styles).unwrap_or(Abs::inf()),
|
||||||
height.resolve(styles).unwrap_or(Abs::inf()),
|
height.resolve(styles).unwrap_or(Abs::inf()),
|
||||||
@ -109,7 +109,7 @@ pub fn measure(
|
|||||||
let link = LocatorLink::measure(here);
|
let link = LocatorLink::measure(here);
|
||||||
let locator = Locator::link(&link);
|
let locator = Locator::link(&link);
|
||||||
|
|
||||||
let frame = content.layout(engine, locator, styles, pod)?.into_frame();
|
let frame = layout_frame(engine, &content, locator, styles, pod)?;
|
||||||
let Size { x, y } = frame.size();
|
let Size { x, y } = frame.size();
|
||||||
Ok(dict! { "width" => x, "height" => y })
|
Ok(dict! { "width" => x, "height" => y })
|
||||||
}
|
}
|
||||||
|
@ -67,17 +67,9 @@ pub use self::spacing::*;
|
|||||||
pub use self::stack::*;
|
pub use self::stack::*;
|
||||||
pub use self::transform::*;
|
pub use self::transform::*;
|
||||||
|
|
||||||
pub(crate) use self::inline::*;
|
pub(crate) use self::inline::layout_inline;
|
||||||
|
|
||||||
use comemo::{Track, Tracked, TrackedMut};
|
use crate::foundations::{category, Category, Scope};
|
||||||
|
|
||||||
use crate::diag::{bail, SourceResult};
|
|
||||||
use crate::engine::{Engine, Route, Sink, Traced};
|
|
||||||
use crate::foundations::{category, Category, Content, Scope, StyleChain};
|
|
||||||
use crate::introspection::{Introspector, Locator, LocatorLink};
|
|
||||||
use crate::model::Document;
|
|
||||||
use crate::realize::{realize_doc, realize_flow, Arenas};
|
|
||||||
use crate::World;
|
|
||||||
|
|
||||||
/// Arranging elements on the page in different ways.
|
/// Arranging elements on the page in different ways.
|
||||||
///
|
///
|
||||||
@ -116,117 +108,3 @@ pub fn define(global: &mut Scope) {
|
|||||||
global.define_func::<measure>();
|
global.define_func::<measure>();
|
||||||
global.define_func::<layout>();
|
global.define_func::<layout>();
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Content {
|
|
||||||
/// Layout the content into a document.
|
|
||||||
///
|
|
||||||
/// This first realizes the content into a
|
|
||||||
/// [`DocumentElem`][crate::model::DocumentElem], which is then laid out. In
|
|
||||||
/// contrast to [`layout`](Self::layout()), this does not take regions since
|
|
||||||
/// the regions are defined by the page configuration in the content and
|
|
||||||
/// style chain.
|
|
||||||
pub fn layout_document(
|
|
||||||
&self,
|
|
||||||
engine: &mut Engine,
|
|
||||||
styles: StyleChain,
|
|
||||||
) -> SourceResult<Document> {
|
|
||||||
#[comemo::memoize]
|
|
||||||
fn cached(
|
|
||||||
content: &Content,
|
|
||||||
world: Tracked<dyn World + '_>,
|
|
||||||
introspector: Tracked<Introspector>,
|
|
||||||
traced: Tracked<Traced>,
|
|
||||||
sink: TrackedMut<Sink>,
|
|
||||||
route: Tracked<Route>,
|
|
||||||
styles: StyleChain,
|
|
||||||
) -> SourceResult<Document> {
|
|
||||||
let mut locator = Locator::root().split();
|
|
||||||
let mut engine = Engine {
|
|
||||||
world,
|
|
||||||
introspector,
|
|
||||||
traced,
|
|
||||||
sink,
|
|
||||||
route: Route::extend(route).unnested(),
|
|
||||||
};
|
|
||||||
let arenas = Arenas::default();
|
|
||||||
let (document, styles, info) =
|
|
||||||
realize_doc(&mut engine, locator.next(&()), &arenas, content, styles)?;
|
|
||||||
document.layout(&mut engine, locator.next(&()), styles, info)
|
|
||||||
}
|
|
||||||
|
|
||||||
cached(
|
|
||||||
self,
|
|
||||||
engine.world,
|
|
||||||
engine.introspector,
|
|
||||||
engine.traced,
|
|
||||||
TrackedMut::reborrow_mut(&mut engine.sink),
|
|
||||||
engine.route.track(),
|
|
||||||
styles,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Layout the content into the given regions.
|
|
||||||
pub fn layout(
|
|
||||||
&self,
|
|
||||||
engine: &mut Engine,
|
|
||||||
locator: Locator,
|
|
||||||
styles: StyleChain,
|
|
||||||
regions: Regions,
|
|
||||||
) -> SourceResult<Fragment> {
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
#[comemo::memoize]
|
|
||||||
fn cached(
|
|
||||||
content: &Content,
|
|
||||||
world: Tracked<dyn World + '_>,
|
|
||||||
introspector: Tracked<Introspector>,
|
|
||||||
traced: Tracked<Traced>,
|
|
||||||
sink: TrackedMut<Sink>,
|
|
||||||
route: Tracked<Route>,
|
|
||||||
locator: Tracked<Locator>,
|
|
||||||
styles: StyleChain,
|
|
||||||
regions: Regions,
|
|
||||||
) -> SourceResult<Fragment> {
|
|
||||||
let link = LocatorLink::new(locator);
|
|
||||||
let locator = Locator::link(&link);
|
|
||||||
let mut engine = Engine {
|
|
||||||
world,
|
|
||||||
introspector,
|
|
||||||
traced,
|
|
||||||
sink,
|
|
||||||
route: Route::extend(route),
|
|
||||||
};
|
|
||||||
|
|
||||||
if !engine.route.within(Route::MAX_LAYOUT_DEPTH) {
|
|
||||||
bail!(
|
|
||||||
content.span(), "maximum layout depth exceeded";
|
|
||||||
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 flow.layout(&mut engine, locator, styles, regions);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Layout the content by first turning it into a `FlowElem` and then
|
|
||||||
// layouting that.
|
|
||||||
let mut locator = locator.split();
|
|
||||||
let arenas = Arenas::default();
|
|
||||||
let (flow, styles) =
|
|
||||||
realize_flow(&mut engine, locator.next(&()), &arenas, content, styles)?;
|
|
||||||
flow.layout(&mut engine, locator.next(&()), styles, regions)
|
|
||||||
}
|
|
||||||
|
|
||||||
cached(
|
|
||||||
self,
|
|
||||||
engine.world,
|
|
||||||
engine.introspector,
|
|
||||||
engine.traced,
|
|
||||||
TrackedMut::reborrow_mut(&mut engine.sink),
|
|
||||||
engine.route.track(),
|
|
||||||
locator.track(),
|
|
||||||
styles,
|
|
||||||
regions,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -5,7 +5,8 @@ use crate::foundations::{
|
|||||||
};
|
};
|
||||||
use crate::introspection::Locator;
|
use crate::introspection::Locator;
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
Abs, BlockElem, Fragment, Frame, Length, Point, Regions, Rel, Sides, Size,
|
layout_fragment, Abs, BlockElem, Fragment, Frame, Length, Point, Regions, Rel, Sides,
|
||||||
|
Size,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Adds spacing around content.
|
/// Adds spacing around content.
|
||||||
@ -91,7 +92,7 @@ fn layout_pad(
|
|||||||
let pod = regions.map(&mut backlog, |size| shrink(size, &padding));
|
let pod = regions.map(&mut backlog, |size| shrink(size, &padding));
|
||||||
|
|
||||||
// Layout child into padded regions.
|
// Layout child into padded regions.
|
||||||
let mut fragment = elem.body().layout(engine, locator, styles, pod)?;
|
let mut fragment = layout_fragment(engine, &elem.body, locator, styles, pod)?;
|
||||||
|
|
||||||
for frame in &mut fragment {
|
for frame in &mut fragment {
|
||||||
grow(frame, &padding);
|
grow(frame, &padding);
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
use std::ptr;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use comemo::Track;
|
use comemo::Track;
|
||||||
@ -9,21 +8,15 @@ use comemo::Track;
|
|||||||
use crate::diag::{bail, SourceResult};
|
use crate::diag::{bail, SourceResult};
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
cast, elem, AutoValue, Cast, Content, Context, Dict, Fold, Func, NativeElement,
|
cast, elem, AutoValue, Cast, Content, Context, Dict, Fold, Func, Smart, StyleChain,
|
||||||
Packed, Resolve, Smart, StyleChain, Value,
|
Value,
|
||||||
};
|
|
||||||
use crate::introspection::{
|
|
||||||
Counter, CounterDisplayElem, CounterKey, Locator, ManualPageCounter, SplitLocator,
|
|
||||||
};
|
};
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
Abs, AlignElem, Alignment, Axes, ColumnsElem, Dir, Frame, HAlignment, Length,
|
Abs, Alignment, Frame, HAlignment, Length, OuterVAlignment, Ratio, Rel, Sides,
|
||||||
OuterVAlignment, Point, Ratio, Regions, Rel, Sides, Size, SpecificAlignment,
|
SpecificAlignment,
|
||||||
VAlignment,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::model::Numbering;
|
use crate::model::Numbering;
|
||||||
use crate::text::TextElem;
|
use crate::utils::{NonZeroExt, Scalar};
|
||||||
use crate::utils::{NonZeroExt, Numeric, Scalar};
|
|
||||||
use crate::visualize::{Color, Paint};
|
use crate::visualize::{Color, Paint};
|
||||||
|
|
||||||
/// Layouts its child onto one or multiple pages.
|
/// Layouts its child onto one or multiple pages.
|
||||||
@ -348,236 +341,6 @@ pub struct PageElem {
|
|||||||
pub clear_to: Option<Parity>,
|
pub clear_to: Option<Parity>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Packed<PageElem> {
|
|
||||||
/// A document can consist of multiple `PageElem`s, one per run of pages
|
|
||||||
/// with equal properties (not one per actual output page!). The `number` is
|
|
||||||
/// the physical page number of the first page of this run. It is mutated
|
|
||||||
/// while we post-process the pages in this function. This function returns
|
|
||||||
/// a fragment consisting of multiple frames, one per output page of this
|
|
||||||
/// page run.
|
|
||||||
#[typst_macros::time(name = "page", span = self.span())]
|
|
||||||
pub fn layout<'a>(
|
|
||||||
&'a self,
|
|
||||||
engine: &mut Engine,
|
|
||||||
locator: Locator<'a>,
|
|
||||||
styles: StyleChain<'a>,
|
|
||||||
extend_to: Option<Parity>,
|
|
||||||
) -> SourceResult<PageLayout<'a>> {
|
|
||||||
let mut locator = locator.split();
|
|
||||||
|
|
||||||
// When one of the lengths is infinite the page fits its content along
|
|
||||||
// that axis.
|
|
||||||
let width = self.width(styles).unwrap_or(Abs::inf());
|
|
||||||
let height = self.height(styles).unwrap_or(Abs::inf());
|
|
||||||
let mut size = Size::new(width, height);
|
|
||||||
if self.flipped(styles) {
|
|
||||||
std::mem::swap(&mut size.x, &mut size.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut min = width.min(height);
|
|
||||||
if !min.is_finite() {
|
|
||||||
min = Paper::A4.width();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine the margins.
|
|
||||||
let default = Rel::<Length>::from((2.5 / 21.0) * min);
|
|
||||||
let margin = self.margin(styles);
|
|
||||||
let two_sided = margin.two_sided.unwrap_or(false);
|
|
||||||
let margin = margin
|
|
||||||
.sides
|
|
||||||
.map(|side| side.and_then(Smart::custom).unwrap_or(default))
|
|
||||||
.resolve(styles)
|
|
||||||
.relative_to(size);
|
|
||||||
|
|
||||||
// Realize columns.
|
|
||||||
let mut child = self.body().clone();
|
|
||||||
let columns = self.columns(styles);
|
|
||||||
if columns.get() > 1 {
|
|
||||||
child = ColumnsElem::new(child)
|
|
||||||
.with_count(columns)
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span());
|
|
||||||
}
|
|
||||||
|
|
||||||
let area = size - margin.sum_by_axis();
|
|
||||||
let mut regions = Regions::repeat(area, area.map(Abs::is_finite));
|
|
||||||
regions.root = true;
|
|
||||||
|
|
||||||
// Layout the child.
|
|
||||||
let frames = child
|
|
||||||
.layout(engine, locator.next(&self.span()), styles, regions)?
|
|
||||||
.into_frames();
|
|
||||||
|
|
||||||
Ok(PageLayout {
|
|
||||||
page: self,
|
|
||||||
locator,
|
|
||||||
styles,
|
|
||||||
extend_to,
|
|
||||||
area,
|
|
||||||
margin,
|
|
||||||
two_sided,
|
|
||||||
frames,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A prepared layout of a page run that can be finalized with access to the
|
|
||||||
/// page counter.
|
|
||||||
pub struct PageLayout<'a> {
|
|
||||||
page: &'a Packed<PageElem>,
|
|
||||||
locator: SplitLocator<'a>,
|
|
||||||
styles: StyleChain<'a>,
|
|
||||||
extend_to: Option<Parity>,
|
|
||||||
area: Size,
|
|
||||||
margin: Sides<Abs>,
|
|
||||||
two_sided: bool,
|
|
||||||
frames: Vec<Frame>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PageLayout<'_> {
|
|
||||||
/// Finalize the layout with access to the next page counter.
|
|
||||||
#[typst_macros::time(name = "finalize page", span = self.page.span())]
|
|
||||||
pub fn finalize(
|
|
||||||
mut self,
|
|
||||||
engine: &mut Engine,
|
|
||||||
page_counter: &mut ManualPageCounter,
|
|
||||||
) -> SourceResult<Vec<Page>> {
|
|
||||||
let styles = self.styles;
|
|
||||||
|
|
||||||
// Align the child to the pagebreak's parity.
|
|
||||||
// Check for page count after adding the pending frames
|
|
||||||
if self.extend_to.is_some_and(|p| {
|
|
||||||
!p.matches(page_counter.physical().get() + self.frames.len())
|
|
||||||
}) {
|
|
||||||
// Insert empty page after the current pages.
|
|
||||||
let size = self.area.map(Abs::is_finite).select(self.area, Size::zero());
|
|
||||||
self.frames.push(Frame::hard(size));
|
|
||||||
}
|
|
||||||
|
|
||||||
let fill = self.page.fill(styles);
|
|
||||||
let foreground = self.page.foreground(styles);
|
|
||||||
let background = self.page.background(styles);
|
|
||||||
let header_ascent = self.page.header_ascent(styles);
|
|
||||||
let footer_descent = self.page.footer_descent(styles);
|
|
||||||
let numbering = self.page.numbering(styles);
|
|
||||||
let number_align = self.page.number_align(styles);
|
|
||||||
let binding =
|
|
||||||
self.page
|
|
||||||
.binding(styles)
|
|
||||||
.unwrap_or_else(|| match TextElem::dir_in(styles) {
|
|
||||||
Dir::LTR => Binding::Left,
|
|
||||||
_ => Binding::Right,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Construct the numbering (for header or footer).
|
|
||||||
let numbering_marginal = numbering.as_ref().map(|numbering| {
|
|
||||||
let both = match numbering {
|
|
||||||
Numbering::Pattern(pattern) => pattern.pieces() >= 2,
|
|
||||||
Numbering::Func(_) => true,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut counter = CounterDisplayElem::new(
|
|
||||||
Counter::new(CounterKey::Page),
|
|
||||||
Smart::Custom(numbering.clone()),
|
|
||||||
both,
|
|
||||||
)
|
|
||||||
.pack()
|
|
||||||
.spanned(self.page.span());
|
|
||||||
|
|
||||||
// We interpret the Y alignment as selecting header or footer
|
|
||||||
// and then ignore it for aligning the actual number.
|
|
||||||
if let Some(x) = number_align.x() {
|
|
||||||
counter = counter.aligned(x.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
counter
|
|
||||||
});
|
|
||||||
|
|
||||||
let header = self.page.header(styles);
|
|
||||||
let footer = self.page.footer(styles);
|
|
||||||
let (header, footer) = if matches!(number_align.y(), Some(OuterVAlignment::Top)) {
|
|
||||||
(
|
|
||||||
header.as_ref().unwrap_or(&numbering_marginal),
|
|
||||||
footer.as_ref().unwrap_or(&None),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
header.as_ref().unwrap_or(&None),
|
|
||||||
footer.as_ref().unwrap_or(&numbering_marginal),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Post-process pages.
|
|
||||||
let mut pages = Vec::with_capacity(self.frames.len());
|
|
||||||
for mut frame in self.frames {
|
|
||||||
// The padded width of the page's content without margins.
|
|
||||||
let pw = frame.width();
|
|
||||||
|
|
||||||
// If two sided, left becomes inside and right becomes outside.
|
|
||||||
// Thus, for left-bound pages, we want to swap on even pages and
|
|
||||||
// for right-bound pages, we want to swap on odd pages.
|
|
||||||
let mut margin = self.margin;
|
|
||||||
if self.two_sided && binding.swap(page_counter.physical()) {
|
|
||||||
std::mem::swap(&mut margin.left, &mut margin.right);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Realize margins.
|
|
||||||
frame.set_size(frame.size() + margin.sum_by_axis());
|
|
||||||
frame.translate(Point::new(margin.left, margin.top));
|
|
||||||
|
|
||||||
// The page size with margins.
|
|
||||||
let size = frame.size();
|
|
||||||
|
|
||||||
// Realize overlays.
|
|
||||||
for marginal in [header, footer, background, foreground] {
|
|
||||||
let Some(content) = marginal.as_ref() else { continue };
|
|
||||||
|
|
||||||
let (pos, area, align);
|
|
||||||
if ptr::eq(marginal, header) {
|
|
||||||
let ascent = header_ascent.relative_to(margin.top);
|
|
||||||
pos = Point::with_x(margin.left);
|
|
||||||
area = Size::new(pw, margin.top - ascent);
|
|
||||||
align = Alignment::BOTTOM;
|
|
||||||
} else if ptr::eq(marginal, footer) {
|
|
||||||
let descent = footer_descent.relative_to(margin.bottom);
|
|
||||||
pos = Point::new(margin.left, size.y - margin.bottom + descent);
|
|
||||||
area = Size::new(pw, margin.bottom - descent);
|
|
||||||
align = Alignment::TOP;
|
|
||||||
} else {
|
|
||||||
pos = Point::zero();
|
|
||||||
area = size;
|
|
||||||
align = HAlignment::Center + VAlignment::Horizon;
|
|
||||||
};
|
|
||||||
|
|
||||||
let pod = Regions::one(area, Axes::splat(true));
|
|
||||||
let sub = content
|
|
||||||
.clone()
|
|
||||||
.styled(AlignElem::set_alignment(align))
|
|
||||||
.layout(engine, self.locator.next(&content.span()), styles, pod)?
|
|
||||||
.into_frame();
|
|
||||||
|
|
||||||
if ptr::eq(marginal, header) || ptr::eq(marginal, background) {
|
|
||||||
frame.prepend_frame(pos, sub);
|
|
||||||
} else {
|
|
||||||
frame.push_frame(pos, sub);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
page_counter.visit(engine, &frame)?;
|
|
||||||
pages.push(Page {
|
|
||||||
frame,
|
|
||||||
fill: fill.clone(),
|
|
||||||
numbering: numbering.clone(),
|
|
||||||
number: page_counter.logical(),
|
|
||||||
});
|
|
||||||
|
|
||||||
page_counter.step();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(pages)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A finished page.
|
/// A finished page.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Page {
|
pub struct Page {
|
||||||
@ -736,7 +499,7 @@ pub enum Binding {
|
|||||||
|
|
||||||
impl Binding {
|
impl Binding {
|
||||||
/// Whether to swap left and right margin for the page with this number.
|
/// Whether to swap left and right margin for the page with this number.
|
||||||
fn swap(self, number: NonZeroUsize) -> bool {
|
pub fn swap(self, number: NonZeroUsize) -> bool {
|
||||||
match self {
|
match self {
|
||||||
// Left-bound must swap on even pages
|
// Left-bound must swap on even pages
|
||||||
// (because it is correct on the first page).
|
// (because it is correct on the first page).
|
||||||
@ -798,14 +561,18 @@ cast! {
|
|||||||
v: Func => Self::Func(v),
|
v: Func => Self::Func(v),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A list of page ranges to be exported. The ranges are one-indexed.
|
/// A list of page ranges to be exported.
|
||||||
/// For example, `1..=3` indicates the first, second and third pages should be
|
|
||||||
/// exported.
|
|
||||||
pub struct PageRanges(Vec<PageRange>);
|
pub struct PageRanges(Vec<PageRange>);
|
||||||
|
|
||||||
|
/// A range of pages to export.
|
||||||
|
///
|
||||||
|
/// The range is one-indexed. For example, `1..=3` indicates the first, second
|
||||||
|
/// and third pages should be exported.
|
||||||
pub type PageRange = RangeInclusive<Option<NonZeroUsize>>;
|
pub type PageRange = RangeInclusive<Option<NonZeroUsize>>;
|
||||||
|
|
||||||
impl PageRanges {
|
impl PageRanges {
|
||||||
|
/// Create new page ranges.
|
||||||
pub fn new(ranges: Vec<PageRange>) -> Self {
|
pub fn new(ranges: Vec<PageRange>) -> Self {
|
||||||
Self(ranges)
|
Self(ranges)
|
||||||
}
|
}
|
||||||
@ -875,7 +642,7 @@ pub enum Parity {
|
|||||||
|
|
||||||
impl Parity {
|
impl Parity {
|
||||||
/// Whether the given number matches the parity.
|
/// Whether the given number matches the parity.
|
||||||
fn matches(self, number: usize) -> bool {
|
pub fn matches(self, number: usize) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Even => number % 2 == 0,
|
Self::Even => number % 2 == 0,
|
||||||
Self::Odd => number % 2 == 1,
|
Self::Odd => number % 2 == 1,
|
||||||
|
@ -3,7 +3,7 @@ use crate::engine::Engine;
|
|||||||
use crate::foundations::{elem, scope, Content, Packed, Smart, StyleChain, Unlabellable};
|
use crate::foundations::{elem, scope, Content, Packed, Smart, StyleChain, Unlabellable};
|
||||||
use crate::introspection::Locator;
|
use crate::introspection::Locator;
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
Alignment, Axes, Em, Fragment, Length, Regions, Rel, Size, VAlignment,
|
layout_frame, Alignment, Axes, Em, Frame, Length, Region, Rel, Size, VAlignment,
|
||||||
};
|
};
|
||||||
use crate::realize::{Behave, Behaviour};
|
use crate::realize::{Behave, Behaviour};
|
||||||
|
|
||||||
@ -112,7 +112,7 @@ impl Packed<PlaceElem> {
|
|||||||
locator: Locator,
|
locator: Locator,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
base: Size,
|
base: Size,
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Frame> {
|
||||||
// The pod is the base area of the region because for absolute
|
// The pod is the base area of the region because for absolute
|
||||||
// placement we don't really care about the already used area.
|
// placement we don't really care about the already used area.
|
||||||
let float = self.float(styles);
|
let float = self.float(styles);
|
||||||
@ -135,9 +135,8 @@ impl Packed<PlaceElem> {
|
|||||||
.clone()
|
.clone()
|
||||||
.aligned(alignment.unwrap_or_else(|| Alignment::CENTER));
|
.aligned(alignment.unwrap_or_else(|| Alignment::CENTER));
|
||||||
|
|
||||||
let pod = Regions::one(base, Axes::splat(false));
|
let pod = Region::new(base, Axes::splat(false));
|
||||||
let frame = child.layout(engine, locator, styles, pod)?.into_frame();
|
layout_frame(engine, &child, locator, styles, pod)
|
||||||
Ok(Fragment::frame(frame))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,10 +17,18 @@ impl Region {
|
|||||||
pub fn new(size: Size, expand: Axes<bool>) -> Self {
|
pub fn new(size: Size, expand: Axes<bool>) -> Self {
|
||||||
Self { size, expand }
|
Self { size, expand }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Turns this into a region sequence.
|
impl From<Region> for Regions<'_> {
|
||||||
pub fn into_regions(self) -> Regions<'static> {
|
fn from(region: Region) -> Self {
|
||||||
Regions::one(self.size, self.expand)
|
Regions {
|
||||||
|
size: region.size,
|
||||||
|
expand: region.expand,
|
||||||
|
full: region.size.y,
|
||||||
|
backlog: &[],
|
||||||
|
last: None,
|
||||||
|
root: false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,6 +43,9 @@ impl Region {
|
|||||||
pub struct Regions<'a> {
|
pub struct Regions<'a> {
|
||||||
/// The remaining size of the first region.
|
/// The remaining size of the first region.
|
||||||
pub size: Size,
|
pub size: Size,
|
||||||
|
/// Whether elements should expand to fill the regions instead of shrinking
|
||||||
|
/// to fit the content.
|
||||||
|
pub expand: Axes<bool>,
|
||||||
/// The full height of the region for relative sizing.
|
/// The full height of the region for relative sizing.
|
||||||
pub full: Abs,
|
pub full: Abs,
|
||||||
/// The height of followup regions. The width is the same for all regions.
|
/// The height of followup regions. The width is the same for all regions.
|
||||||
@ -42,9 +53,6 @@ pub struct Regions<'a> {
|
|||||||
/// The height of the final region that is repeated once the backlog is
|
/// The height of the final region that is repeated once the backlog is
|
||||||
/// drained. The width is the same for all regions.
|
/// drained. The width is the same for all regions.
|
||||||
pub last: Option<Abs>,
|
pub last: Option<Abs>,
|
||||||
/// Whether elements should expand to fill the regions instead of shrinking
|
|
||||||
/// to fit the content.
|
|
||||||
pub expand: Axes<bool>,
|
|
||||||
/// Whether these are the root regions or direct descendants.
|
/// Whether these are the root regions or direct descendants.
|
||||||
///
|
///
|
||||||
/// True for the padded page regions and columns directly in the page,
|
/// True for the padded page regions and columns directly in the page,
|
||||||
@ -53,18 +61,6 @@ pub struct Regions<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Regions<'_> {
|
impl Regions<'_> {
|
||||||
/// Create a new region sequence with exactly one region.
|
|
||||||
pub fn one(size: Size, expand: Axes<bool>) -> Self {
|
|
||||||
Self {
|
|
||||||
size,
|
|
||||||
full: size.y,
|
|
||||||
backlog: &[],
|
|
||||||
last: None,
|
|
||||||
expand,
|
|
||||||
root: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new sequence of same-size regions that repeats indefinitely.
|
/// Create a new sequence of same-size regions that repeats indefinitely.
|
||||||
pub fn repeat(size: Size, expand: Axes<bool>) -> Self {
|
pub fn repeat(size: Size, expand: Axes<bool>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -5,7 +5,7 @@ use crate::foundations::{
|
|||||||
};
|
};
|
||||||
use crate::introspection::Locator;
|
use crate::introspection::Locator;
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
Abs, AlignElem, Axes, BlockElem, Frame, Length, Point, Region, Regions, Size,
|
layout_frame, Abs, AlignElem, Axes, BlockElem, Frame, Length, Point, Region, Size,
|
||||||
};
|
};
|
||||||
use crate::utils::Numeric;
|
use crate::utils::Numeric;
|
||||||
|
|
||||||
@ -63,8 +63,8 @@ fn layout_repeat(
|
|||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
region: Region,
|
region: Region,
|
||||||
) -> SourceResult<Frame> {
|
) -> SourceResult<Frame> {
|
||||||
let pod = Regions::one(region.size, Axes::new(false, false));
|
let pod = Region::new(region.size, Axes::new(false, false));
|
||||||
let piece = elem.body().layout(engine, locator, styles, pod)?.into_frame();
|
let piece = layout_frame(engine, &elem.body, locator, styles, pod)?;
|
||||||
let size = Size::new(region.size.x, piece.height());
|
let size = Size::new(region.size.x, piece.height());
|
||||||
|
|
||||||
if !size.is_finite() {
|
if !size.is_finite() {
|
||||||
|
@ -8,8 +8,8 @@ use crate::foundations::{
|
|||||||
};
|
};
|
||||||
use crate::introspection::{Locator, SplitLocator};
|
use crate::introspection::{Locator, SplitLocator};
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
Abs, AlignElem, Axes, Axis, BlockElem, Dir, FixedAlignment, Fr, Fragment, Frame,
|
layout_fragment, Abs, AlignElem, Axes, Axis, BlockElem, Dir, FixedAlignment, Fr,
|
||||||
HElem, Point, Regions, Size, Spacing, VElem,
|
Fragment, Frame, HElem, Point, Regions, Size, Spacing, VElem,
|
||||||
};
|
};
|
||||||
use crate::utils::{Get, Numeric};
|
use crate::utils::{Get, Numeric};
|
||||||
|
|
||||||
@ -257,8 +257,9 @@ impl<'a> StackLayouter<'a> {
|
|||||||
}
|
}
|
||||||
.resolve(styles);
|
.resolve(styles);
|
||||||
|
|
||||||
let fragment = block.layout(
|
let fragment = layout_fragment(
|
||||||
engine,
|
engine,
|
||||||
|
block,
|
||||||
self.locator.next(&block.span()),
|
self.locator.next(&block.span()),
|
||||||
styles,
|
styles,
|
||||||
self.regions,
|
self.regions,
|
||||||
|
@ -9,8 +9,8 @@ use crate::foundations::{
|
|||||||
};
|
};
|
||||||
use crate::introspection::Locator;
|
use crate::introspection::Locator;
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
Abs, Alignment, Angle, Axes, BlockElem, FixedAlignment, Frame, HAlignment, Length,
|
layout_frame, Abs, Alignment, Angle, Axes, BlockElem, FixedAlignment, Frame,
|
||||||
Point, Ratio, Region, Regions, Rel, Size, VAlignment,
|
HAlignment, Length, Point, Ratio, Region, Rel, Size, VAlignment,
|
||||||
};
|
};
|
||||||
use crate::utils::Numeric;
|
use crate::utils::Numeric;
|
||||||
|
|
||||||
@ -62,10 +62,7 @@ fn layout_move(
|
|||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
region: Region,
|
region: Region,
|
||||||
) -> SourceResult<Frame> {
|
) -> SourceResult<Frame> {
|
||||||
let mut frame = elem
|
let mut frame = layout_frame(engine, &elem.body, locator, styles, region)?;
|
||||||
.body()
|
|
||||||
.layout(engine, locator, styles, region.into_regions())?
|
|
||||||
.into_frame();
|
|
||||||
let delta = Axes::new(elem.dx(styles), elem.dy(styles)).resolve(styles);
|
let delta = Axes::new(elem.dx(styles), elem.dy(styles)).resolve(styles);
|
||||||
let delta = delta.zip_map(region.size, Rel::relative_to);
|
let delta = delta.zip_map(region.size, Rel::relative_to);
|
||||||
frame.translate(delta.to_point());
|
frame.translate(delta.to_point());
|
||||||
@ -299,8 +296,8 @@ impl Packed<ScaleElem> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let size = Lazy::new(|| {
|
let size = Lazy::new(|| {
|
||||||
let pod = Regions::one(container, Axes::splat(false));
|
let pod = Region::new(container, Axes::splat(false));
|
||||||
let frame = self.body().layout(engine, locator, styles, pod)?.into_frame();
|
let frame = layout_frame(engine, &self.body, locator, styles, pod)?;
|
||||||
SourceResult::Ok(frame.size())
|
SourceResult::Ok(frame.size())
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -478,12 +475,12 @@ fn measure_and_layout(
|
|||||||
) -> SourceResult<Frame> {
|
) -> SourceResult<Frame> {
|
||||||
if reflow {
|
if reflow {
|
||||||
// Measure the size of the body.
|
// Measure the size of the body.
|
||||||
let pod = Regions::one(size, Axes::splat(false));
|
let pod = Region::new(size, Axes::splat(false));
|
||||||
let frame = body.layout(engine, locator.relayout(), styles, pod)?.into_frame();
|
let frame = layout_frame(engine, body, locator.relayout(), styles, pod)?;
|
||||||
|
|
||||||
// Actually perform the layout.
|
// Actually perform the layout.
|
||||||
let pod = Regions::one(frame.size(), Axes::splat(true));
|
let pod = Region::new(frame.size(), Axes::splat(true));
|
||||||
let mut frame = body.layout(engine, locator, styles, pod)?.into_frame();
|
let mut frame = layout_frame(engine, body, locator, styles, pod)?;
|
||||||
let Axes { x, y } = align.zip_map(frame.size(), FixedAlignment::position);
|
let Axes { x, y } = align.zip_map(frame.size(), FixedAlignment::position);
|
||||||
|
|
||||||
// Compute the transform.
|
// Compute the transform.
|
||||||
@ -499,9 +496,7 @@ fn measure_and_layout(
|
|||||||
Ok(frame)
|
Ok(frame)
|
||||||
} else {
|
} else {
|
||||||
// Layout the body.
|
// Layout the body.
|
||||||
let mut frame = body
|
let mut frame = layout_frame(engine, body, locator, styles, region)?;
|
||||||
.layout(engine, locator, styles, region.into_regions())?
|
|
||||||
.into_frame();
|
|
||||||
let Axes { x, y } = align.zip_map(frame.size(), FixedAlignment::position);
|
let Axes { x, y } = align.zip_map(frame.size(), FixedAlignment::position);
|
||||||
|
|
||||||
// Compute the transform.
|
// Compute the transform.
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
//! [evaluate]: eval::eval
|
//! [evaluate]: eval::eval
|
||||||
//! [module]: foundations::Module
|
//! [module]: foundations::Module
|
||||||
//! [content]: foundations::Content
|
//! [content]: foundations::Content
|
||||||
//! [layouted]: foundations::Content::layout_document
|
//! [layouted]: crate::layout::layout_document
|
||||||
//! [document]: model::Document
|
//! [document]: model::Document
|
||||||
//! [frame]: layout::Frame
|
//! [frame]: layout::Frame
|
||||||
|
|
||||||
@ -86,7 +86,7 @@ use crate::visualize::Color;
|
|||||||
#[typst_macros::time]
|
#[typst_macros::time]
|
||||||
pub fn compile(world: &dyn World) -> Warned<SourceResult<Document>> {
|
pub fn compile(world: &dyn World) -> Warned<SourceResult<Document>> {
|
||||||
let mut sink = Sink::new();
|
let mut sink = Sink::new();
|
||||||
let output = compile_inner(world.track(), Traced::default().track(), &mut sink)
|
let output = compile_impl(world.track(), Traced::default().track(), &mut sink)
|
||||||
.map_err(deduplicate);
|
.map_err(deduplicate);
|
||||||
Warned { output, warnings: sink.warnings() }
|
Warned { output, warnings: sink.warnings() }
|
||||||
}
|
}
|
||||||
@ -97,12 +97,13 @@ pub fn compile(world: &dyn World) -> Warned<SourceResult<Document>> {
|
|||||||
pub fn trace(world: &dyn World, span: Span) -> EcoVec<(Value, Option<Styles>)> {
|
pub fn trace(world: &dyn World, span: Span) -> EcoVec<(Value, Option<Styles>)> {
|
||||||
let mut sink = Sink::new();
|
let mut sink = Sink::new();
|
||||||
let traced = Traced::new(span);
|
let traced = Traced::new(span);
|
||||||
compile_inner(world.track(), traced.track(), &mut sink).ok();
|
compile_impl(world.track(), traced.track(), &mut sink).ok();
|
||||||
sink.values()
|
sink.values()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Relayout until introspection converges.
|
/// The internal implementation of `compile` with a bit lower-level interface
|
||||||
fn compile_inner(
|
/// that is also used by `trace`.
|
||||||
|
fn compile_impl(
|
||||||
world: Tracked<dyn World + '_>,
|
world: Tracked<dyn World + '_>,
|
||||||
traced: Tracked<Traced>,
|
traced: Tracked<Traced>,
|
||||||
sink: &mut Sink,
|
sink: &mut Sink,
|
||||||
@ -150,7 +151,7 @@ fn compile_inner(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Layout!
|
// Layout!
|
||||||
document = content.layout_document(&mut engine, styles)?;
|
document = crate::layout::layout_document(&mut engine, &content, styles)?;
|
||||||
document.introspector.rebuild(&document.pages);
|
document.introspector.rebuild(&document.pages);
|
||||||
iter += 1;
|
iter += 1;
|
||||||
|
|
||||||
|
@ -13,12 +13,11 @@ use crate::diag::SourceResult;
|
|||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{Content, Packed, StyleChain};
|
use crate::foundations::{Content, Packed, StyleChain};
|
||||||
use crate::introspection::{Locator, SplitLocator};
|
use crate::introspection::{Locator, SplitLocator};
|
||||||
use crate::layout::{Abs, Axes, BoxElem, Em, Frame, Regions, Size};
|
use crate::layout::{layout_frame, Abs, Axes, BoxElem, Em, Frame, Region, Size};
|
||||||
use crate::math::{
|
use crate::math::{
|
||||||
scaled_font_size, styled_char, EquationElem, FrameFragment, GlyphFragment,
|
scaled_font_size, styled_char, EquationElem, FrameFragment, GlyphFragment,
|
||||||
LayoutMath, MathFragment, MathRun, MathSize, THICK,
|
LayoutMath, MathFragment, MathRun, MathSize, THICK,
|
||||||
};
|
};
|
||||||
use crate::model::ParElem;
|
|
||||||
use crate::realize::StyleVec;
|
use crate::realize::StyleVec;
|
||||||
use crate::syntax::{is_newline, Span};
|
use crate::syntax::{is_newline, Span};
|
||||||
use crate::text::{
|
use crate::text::{
|
||||||
@ -51,7 +50,7 @@ pub struct MathContext<'a, 'b, 'v> {
|
|||||||
// External.
|
// External.
|
||||||
pub engine: &'v mut Engine<'b>,
|
pub engine: &'v mut Engine<'b>,
|
||||||
pub locator: SplitLocator<'v>,
|
pub locator: SplitLocator<'v>,
|
||||||
pub regions: Regions<'static>,
|
pub region: Region,
|
||||||
// Font-related.
|
// Font-related.
|
||||||
pub font: &'a Font,
|
pub font: &'a Font,
|
||||||
pub ttf: &'a ttf_parser::Face<'a>,
|
pub ttf: &'a ttf_parser::Face<'a>,
|
||||||
@ -107,7 +106,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
|
|||||||
Self {
|
Self {
|
||||||
engine,
|
engine,
|
||||||
locator: locator.split(),
|
locator: locator.split(),
|
||||||
regions: Regions::one(base, Axes::splat(false)),
|
region: Region::new(base, Axes::splat(false)),
|
||||||
font,
|
font,
|
||||||
ttf: font.ttf(),
|
ttf: font.ttf(),
|
||||||
table: math_table,
|
table: math_table,
|
||||||
@ -182,7 +181,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
|
|||||||
self.engine,
|
self.engine,
|
||||||
self.locator.next(&boxed.span()),
|
self.locator.next(&boxed.span()),
|
||||||
styles.chain(&local),
|
styles.chain(&local),
|
||||||
self.regions.base(),
|
self.region.size,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,14 +193,13 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
|
|||||||
) -> SourceResult<Frame> {
|
) -> SourceResult<Frame> {
|
||||||
let local =
|
let local =
|
||||||
TextElem::set_size(TextSize(scaled_font_size(self, styles).into())).wrap();
|
TextElem::set_size(TextSize(scaled_font_size(self, styles).into())).wrap();
|
||||||
Ok(content
|
layout_frame(
|
||||||
.layout(
|
self.engine,
|
||||||
self.engine,
|
content,
|
||||||
self.locator.next(&content.span()),
|
self.locator.next(&content.span()),
|
||||||
styles.chain(&local),
|
styles.chain(&local),
|
||||||
self.regions,
|
self.region,
|
||||||
)?
|
)
|
||||||
.into_frame())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout the given [`TextElem`] into a [`MathFragment`].
|
/// Layout the given [`TextElem`] into a [`MathFragment`].
|
||||||
@ -300,19 +298,17 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
|
|||||||
// it will overflow. So emulate an `hbox` instead and allow the paragraph
|
// it will overflow. So emulate an `hbox` instead and allow the paragraph
|
||||||
// to extend as far as needed.
|
// to extend as far as needed.
|
||||||
let spaced = text.graphemes(true).nth(1).is_some();
|
let spaced = text.graphemes(true).nth(1).is_some();
|
||||||
let text = TextElem::packed(text).spanned(span);
|
let elem = TextElem::packed(text).spanned(span);
|
||||||
let par = ParElem::new(StyleVec::wrap(eco_vec![text]));
|
let frame = crate::layout::layout_inline(
|
||||||
let frame = Packed::new(par)
|
self.engine,
|
||||||
.spanned(span)
|
&StyleVec::wrap(eco_vec![elem]),
|
||||||
.layout(
|
self.locator.next(&span),
|
||||||
self.engine,
|
styles,
|
||||||
self.locator.next(&span),
|
false,
|
||||||
styles,
|
Size::splat(Abs::inf()),
|
||||||
false,
|
false,
|
||||||
Size::splat(Abs::inf()),
|
)?
|
||||||
false,
|
.into_frame();
|
||||||
)?
|
|
||||||
.into_frame();
|
|
||||||
|
|
||||||
Ok(FrameFragment::new(self, styles, frame)
|
Ok(FrameFragment::new(self, styles, frame)
|
||||||
.with_class(MathClass::Alphabetic)
|
.with_class(MathClass::Alphabetic)
|
||||||
|
@ -10,9 +10,9 @@ use crate::foundations::{
|
|||||||
};
|
};
|
||||||
use crate::introspection::{Count, Counter, CounterUpdate, Locatable, Locator};
|
use crate::introspection::{Count, Counter, CounterUpdate, Locatable, Locator};
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
Abs, AlignElem, Alignment, Axes, BlockElem, Em, FixedAlignment, Fragment, Frame,
|
layout_frame, Abs, AlignElem, Alignment, Axes, BlockElem, Em, FixedAlignment,
|
||||||
InlineElem, InlineItem, OuterHAlignment, Point, Regions, Size, SpecificAlignment,
|
Fragment, Frame, InlineElem, InlineItem, OuterHAlignment, Point, Region, Regions,
|
||||||
VAlignment,
|
Size, SpecificAlignment, VAlignment,
|
||||||
};
|
};
|
||||||
use crate::math::{
|
use crate::math::{
|
||||||
scaled_font_size, LayoutMath, MathContext, MathRunFrameBuilder, MathSize, MathVariant,
|
scaled_font_size, LayoutMath, MathContext, MathRunFrameBuilder, MathSize, MathVariant,
|
||||||
@ -399,12 +399,11 @@ fn layout_equation_block(
|
|||||||
return Ok(Fragment::frames(frames));
|
return Ok(Fragment::frames(frames));
|
||||||
};
|
};
|
||||||
|
|
||||||
let pod = Regions::one(regions.base(), Axes::splat(false));
|
let pod = Region::new(regions.base(), Axes::splat(false));
|
||||||
let number = Counter::of(EquationElem::elem())
|
let counter = Counter::of(EquationElem::elem())
|
||||||
.display_at_loc(engine, elem.location().unwrap(), styles, numbering)?
|
.display_at_loc(engine, elem.location().unwrap(), styles, numbering)?
|
||||||
.spanned(span)
|
.spanned(span);
|
||||||
.layout(engine, locator.next(&()), styles, pod)?
|
let number = layout_frame(engine, &counter, locator.next(&()), styles, pod)?;
|
||||||
.into_frame();
|
|
||||||
|
|
||||||
static NUMBER_GUTTER: Em = Em::new(0.5);
|
static NUMBER_GUTTER: Em = Em::new(0.5);
|
||||||
let full_number_width = number.width() + NUMBER_GUTTER.resolve(styles);
|
let full_number_width = number.width() + NUMBER_GUTTER.resolve(styles);
|
||||||
|
@ -434,7 +434,7 @@ fn layout_vec_body(
|
|||||||
row_gap: Rel<Abs>,
|
row_gap: Rel<Abs>,
|
||||||
alternator: LeftRightAlternator,
|
alternator: LeftRightAlternator,
|
||||||
) -> SourceResult<Frame> {
|
) -> SourceResult<Frame> {
|
||||||
let gap = row_gap.relative_to(ctx.regions.base().y);
|
let gap = row_gap.relative_to(ctx.region.size.y);
|
||||||
|
|
||||||
let denom_style = style_for_denominator(styles);
|
let denom_style = style_for_denominator(styles);
|
||||||
let mut flat = vec![];
|
let mut flat = vec![];
|
||||||
@ -464,7 +464,7 @@ fn layout_mat_body(
|
|||||||
return Ok(Frame::soft(Size::zero()));
|
return Ok(Frame::soft(Size::zero()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let gap = gap.zip_map(ctx.regions.base(), Rel::relative_to);
|
let gap = gap.zip_map(ctx.region.size, Rel::relative_to);
|
||||||
let half_gap = gap * 0.5;
|
let half_gap = gap * 0.5;
|
||||||
|
|
||||||
// We provide a default stroke thickness that scales
|
// We provide a default stroke thickness that scales
|
||||||
|
@ -3,12 +3,11 @@ use ecow::EcoString;
|
|||||||
use crate::diag::{bail, HintedStrResult, SourceResult};
|
use crate::diag::{bail, HintedStrResult, SourceResult};
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
cast, elem, Args, Array, Construct, Content, Datetime, Fields, Packed, Smart,
|
cast, elem, Args, Array, Construct, Content, Datetime, Fields, Smart, StyleChain,
|
||||||
StyleChain, Styles, Value,
|
Styles, Value,
|
||||||
};
|
};
|
||||||
use crate::introspection::{Introspector, Locator, ManualPageCounter};
|
use crate::introspection::Introspector;
|
||||||
use crate::layout::{Page, PageElem};
|
use crate::layout::Page;
|
||||||
use crate::realize::StyleVec;
|
|
||||||
|
|
||||||
/// The root element of a document and its metadata.
|
/// The root element of a document and its metadata.
|
||||||
///
|
///
|
||||||
@ -57,11 +56,6 @@ pub struct DocumentElem {
|
|||||||
/// something other than `{auto}`.
|
/// something other than `{auto}`.
|
||||||
#[ghost]
|
#[ghost]
|
||||||
pub date: Smart<Option<Datetime>>,
|
pub date: Smart<Option<Datetime>>,
|
||||||
|
|
||||||
/// The page runs.
|
|
||||||
#[internal]
|
|
||||||
#[variadic]
|
|
||||||
pub children: StyleVec,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Construct for DocumentElem {
|
impl Construct for DocumentElem {
|
||||||
@ -70,48 +64,6 @@ impl Construct for DocumentElem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Packed<DocumentElem> {
|
|
||||||
/// Layout this document.
|
|
||||||
#[typst_macros::time(name = "document", span = self.span())]
|
|
||||||
pub fn layout(
|
|
||||||
&self,
|
|
||||||
engine: &mut Engine,
|
|
||||||
locator: Locator,
|
|
||||||
styles: StyleChain,
|
|
||||||
info: DocumentInfo,
|
|
||||||
) -> SourceResult<Document> {
|
|
||||||
let children = self.children();
|
|
||||||
let mut peekable = children.chain(&styles).peekable();
|
|
||||||
let mut locator = locator.split();
|
|
||||||
|
|
||||||
let iter = std::iter::from_fn(|| {
|
|
||||||
let (child, styles) = peekable.next()?;
|
|
||||||
let extend_to = peekable
|
|
||||||
.peek()
|
|
||||||
.and_then(|(next, _)| *next.to_packed::<PageElem>()?.clear_to()?);
|
|
||||||
let locator = locator.next(&child.span());
|
|
||||||
Some((child, styles, extend_to, locator))
|
|
||||||
});
|
|
||||||
|
|
||||||
let layouts =
|
|
||||||
engine.parallelize(iter, |engine, (child, styles, extend_to, locator)| {
|
|
||||||
if let Some(page) = child.to_packed::<PageElem>() {
|
|
||||||
page.layout(engine, locator, styles, extend_to)
|
|
||||||
} else {
|
|
||||||
bail!(child.span(), "unexpected document child");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut page_counter = ManualPageCounter::new();
|
|
||||||
let mut pages = Vec::with_capacity(self.children().len());
|
|
||||||
for result in layouts {
|
|
||||||
pages.extend(result?.finalize(engine, &mut page_counter)?);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Document { pages, info, introspector: Introspector::default() })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A list of authors.
|
/// A list of authors.
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Hash)]
|
#[derive(Debug, Default, Clone, PartialEq, Hash)]
|
||||||
pub struct Author(Vec<EcoString>);
|
pub struct Author(Vec<EcoString>);
|
||||||
|
@ -9,7 +9,9 @@ use crate::foundations::{
|
|||||||
use crate::introspection::{
|
use crate::introspection::{
|
||||||
Count, Counter, CounterUpdate, Locatable, Locator, LocatorLink,
|
Count, Counter, CounterUpdate, Locatable, Locator, LocatorLink,
|
||||||
};
|
};
|
||||||
use crate::layout::{Abs, Axes, BlockChild, BlockElem, Em, HElem, Length, Regions};
|
use crate::layout::{
|
||||||
|
layout_frame, Abs, Axes, BlockChild, BlockElem, Em, HElem, Length, Region,
|
||||||
|
};
|
||||||
use crate::model::{Numbering, Outlinable, ParElem, Refable, Supplement};
|
use crate::model::{Numbering, Outlinable, ParElem, Refable, Supplement};
|
||||||
use crate::text::{FontWeight, LocalName, SpaceElem, TextElem, TextSize};
|
use crate::text::{FontWeight, LocalName, SpaceElem, TextElem, TextSize};
|
||||||
use crate::utils::NonZeroExt;
|
use crate::utils::NonZeroExt;
|
||||||
@ -233,15 +235,14 @@ impl Show for Packed<HeadingElem> {
|
|||||||
.spanned(span);
|
.spanned(span);
|
||||||
|
|
||||||
if hanging_indent.is_auto() {
|
if hanging_indent.is_auto() {
|
||||||
let pod = Regions::one(Axes::splat(Abs::inf()), Axes::splat(false));
|
let pod = Region::new(Axes::splat(Abs::inf()), Axes::splat(false));
|
||||||
|
|
||||||
// We don't have a locator for the numbering here, so we just
|
// We don't have a locator for the numbering here, so we just
|
||||||
// use the measurement infrastructure for now.
|
// use the measurement infrastructure for now.
|
||||||
let link = LocatorLink::measure(location);
|
let link = LocatorLink::measure(location);
|
||||||
let size = numbering
|
let size =
|
||||||
.layout(engine, Locator::link(&link), styles, pod)?
|
layout_frame(engine, &numbering, Locator::link(&link), styles, pod)?
|
||||||
.into_frame()
|
.size();
|
||||||
.size();
|
|
||||||
|
|
||||||
indent = size.x + SPACING_TO_NUMBERING.resolve(styles);
|
indent = size.x + SPACING_TO_NUMBERING.resolve(styles);
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,9 @@ use std::fmt::{self, Debug, Formatter};
|
|||||||
use crate::diag::SourceResult;
|
use crate::diag::SourceResult;
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
elem, Args, Cast, Construct, Content, NativeElement, Packed, Set, Smart, StyleChain,
|
elem, Args, Cast, Construct, Content, NativeElement, Packed, Set, Smart, Unlabellable,
|
||||||
Unlabellable,
|
|
||||||
};
|
};
|
||||||
use crate::introspection::Locator;
|
use crate::layout::{Em, Length};
|
||||||
use crate::layout::{Em, Fragment, Length, Size};
|
|
||||||
use crate::realize::StyleVec;
|
use crate::realize::StyleVec;
|
||||||
|
|
||||||
/// Arranges text, spacing and inline-level elements into a paragraph.
|
/// Arranges text, spacing and inline-level elements into a paragraph.
|
||||||
@ -159,30 +157,6 @@ impl Construct for ParElem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Packed<ParElem> {
|
|
||||||
/// Layout the paragraph into a collection of lines.
|
|
||||||
#[typst_macros::time(name = "par", span = self.span())]
|
|
||||||
pub fn layout(
|
|
||||||
&self,
|
|
||||||
engine: &mut Engine,
|
|
||||||
locator: Locator,
|
|
||||||
styles: StyleChain,
|
|
||||||
consecutive: bool,
|
|
||||||
region: Size,
|
|
||||||
expand: bool,
|
|
||||||
) -> SourceResult<Fragment> {
|
|
||||||
crate::layout::layout_inline(
|
|
||||||
&self.children,
|
|
||||||
engine,
|
|
||||||
locator,
|
|
||||||
styles,
|
|
||||||
consecutive,
|
|
||||||
region,
|
|
||||||
expand,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for ParElem {
|
impl Debug for ParElem {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
write!(f, "Par ")?;
|
write!(f, "Par ")?;
|
||||||
|
@ -23,7 +23,7 @@ use crate::engine::{Engine, Route};
|
|||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
Content, NativeElement, Packed, SequenceElem, Smart, StyleChain, StyledElem, Styles,
|
Content, NativeElement, Packed, SequenceElem, Smart, StyleChain, StyledElem, Styles,
|
||||||
};
|
};
|
||||||
use crate::introspection::{Locator, SplitLocator, TagElem};
|
use crate::introspection::{SplitLocator, TagElem};
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
AlignElem, BlockElem, BoxElem, ColbreakElem, FlowElem, FlushElem, HElem, InlineElem,
|
AlignElem, BlockElem, BoxElem, ColbreakElem, FlowElem, FlushElem, HElem, InlineElem,
|
||||||
PageElem, PagebreakElem, Parity, PlaceElem, VElem,
|
PageElem, PagebreakElem, Parity, PlaceElem, VElem,
|
||||||
@ -36,16 +36,15 @@ use crate::model::{
|
|||||||
use crate::syntax::Span;
|
use crate::syntax::Span;
|
||||||
use crate::text::{LinebreakElem, SmartQuoteElem, SpaceElem, TextElem};
|
use crate::text::{LinebreakElem, SmartQuoteElem, SpaceElem, TextElem};
|
||||||
|
|
||||||
/// Realize into a `DocumentElem`, an element that is capable of root-level
|
/// Realize at the root-level.
|
||||||
/// layout.
|
#[typst_macros::time(name = "realize root")]
|
||||||
#[typst_macros::time(name = "realize doc")]
|
pub fn realize_root<'a>(
|
||||||
pub fn realize_doc<'a>(
|
engine: &mut Engine<'a>,
|
||||||
engine: &mut Engine,
|
locator: &mut SplitLocator<'a>,
|
||||||
locator: Locator,
|
|
||||||
arenas: &'a Arenas<'a>,
|
arenas: &'a Arenas<'a>,
|
||||||
content: &'a Content,
|
content: &'a Content,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
) -> SourceResult<(Packed<DocumentElem>, StyleChain<'a>, DocumentInfo)> {
|
) -> SourceResult<(StyleVec, StyleChain<'a>, DocumentInfo)> {
|
||||||
let mut builder = Builder::new(engine, locator, arenas, true);
|
let mut builder = Builder::new(engine, locator, arenas, true);
|
||||||
builder.accept(content, styles)?;
|
builder.accept(content, styles)?;
|
||||||
builder.interrupt_page(Some(styles), true)?;
|
builder.interrupt_page(Some(styles), true)?;
|
||||||
@ -55,8 +54,8 @@ pub fn realize_doc<'a>(
|
|||||||
/// Realize into a `FlowElem`, an element that is capable of block-level layout.
|
/// Realize into a `FlowElem`, an element that is capable of block-level layout.
|
||||||
#[typst_macros::time(name = "realize flow")]
|
#[typst_macros::time(name = "realize flow")]
|
||||||
pub fn realize_flow<'a>(
|
pub fn realize_flow<'a>(
|
||||||
engine: &mut Engine,
|
engine: &mut Engine<'a>,
|
||||||
locator: Locator,
|
locator: &mut SplitLocator<'a>,
|
||||||
arenas: &'a Arenas<'a>,
|
arenas: &'a Arenas<'a>,
|
||||||
content: &'a Content,
|
content: &'a Content,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
@ -68,11 +67,11 @@ pub fn realize_flow<'a>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Builds a document or a flow element from content.
|
/// Builds a document or a flow element from content.
|
||||||
struct Builder<'a, 'v, 't> {
|
struct Builder<'a, 'v> {
|
||||||
/// The engine.
|
/// The engine.
|
||||||
engine: &'v mut Engine<'t>,
|
engine: &'v mut Engine<'a>,
|
||||||
/// Assigns unique locations to elements.
|
/// Assigns unique locations to elements.
|
||||||
locator: SplitLocator<'v>,
|
locator: &'v mut SplitLocator<'a>,
|
||||||
/// Scratch arenas for building.
|
/// Scratch arenas for building.
|
||||||
arenas: &'a Arenas<'a>,
|
arenas: &'a Arenas<'a>,
|
||||||
/// The current document building state.
|
/// The current document building state.
|
||||||
@ -87,16 +86,16 @@ struct Builder<'a, 'v, 't> {
|
|||||||
cites: CiteGroupBuilder<'a>,
|
cites: CiteGroupBuilder<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'v, 't> Builder<'a, 'v, 't> {
|
impl<'a, 'v> Builder<'a, 'v> {
|
||||||
fn new(
|
fn new(
|
||||||
engine: &'v mut Engine<'t>,
|
engine: &'v mut Engine<'a>,
|
||||||
locator: Locator<'v>,
|
locator: &'v mut SplitLocator<'a>,
|
||||||
arenas: &'a Arenas<'a>,
|
arenas: &'a Arenas<'a>,
|
||||||
top: bool,
|
top: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
engine,
|
engine,
|
||||||
locator: locator.split(),
|
locator,
|
||||||
arenas,
|
arenas,
|
||||||
doc: top.then(DocBuilder::default),
|
doc: top.then(DocBuilder::default),
|
||||||
flow: FlowBuilder::default(),
|
flow: FlowBuilder::default(),
|
||||||
@ -121,8 +120,7 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
|
|||||||
|
|
||||||
// Styled elements and sequences can (at least currently) also have
|
// Styled elements and sequences can (at least currently) also have
|
||||||
// labels, so this needs to happen before they are handled.
|
// labels, so this needs to happen before they are handled.
|
||||||
if let Some(realized) = process(self.engine, &mut self.locator, content, styles)?
|
if let Some(realized) = process(self.engine, self.locator, content, styles)? {
|
||||||
{
|
|
||||||
self.engine.route.increase();
|
self.engine.route.increase();
|
||||||
if !self.engine.route.within(Route::MAX_SHOW_RULE_DEPTH) {
|
if !self.engine.route.within(Route::MAX_SHOW_RULE_DEPTH) {
|
||||||
bail!(
|
bail!(
|
||||||
@ -350,11 +348,11 @@ impl<'a> DocBuilder<'a> {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Turns this builder into the resulting document, along with
|
/// Turns this builder into the resulting page runs, along with
|
||||||
/// its [style chain][StyleChain].
|
/// its [style chain][StyleChain].
|
||||||
fn finish(self) -> (Packed<DocumentElem>, StyleChain<'a>, DocumentInfo) {
|
fn finish(self) -> (StyleVec, StyleChain<'a>, DocumentInfo) {
|
||||||
let (children, trunk, span) = self.pages.finish();
|
let (children, trunk, _) = self.pages.finish();
|
||||||
(Packed::new(DocumentElem::new(children)).spanned(span), trunk, self.info)
|
(children, trunk, self.info)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ use crate::diag::{bail, SourceResult};
|
|||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{func, repr, scope, ty, Content, Smart, StyleChain};
|
use crate::foundations::{func, repr, scope, ty, Content, Smart, StyleChain};
|
||||||
use crate::introspection::Locator;
|
use crate::introspection::Locator;
|
||||||
use crate::layout::{Abs, Axes, Frame, Length, Regions, Size};
|
use crate::layout::{layout_frame, Abs, Axes, Frame, Length, Region, Size};
|
||||||
use crate::syntax::{Span, Spanned};
|
use crate::syntax::{Span, Spanned};
|
||||||
use crate::utils::{LazyHash, Numeric};
|
use crate::utils::{LazyHash, Numeric};
|
||||||
use crate::visualize::RelativeTo;
|
use crate::visualize::RelativeTo;
|
||||||
@ -192,8 +192,8 @@ impl Pattern {
|
|||||||
let library = world.library();
|
let library = world.library();
|
||||||
let locator = Locator::root();
|
let locator = Locator::root();
|
||||||
let styles = StyleChain::new(&library.styles);
|
let styles = StyleChain::new(&library.styles);
|
||||||
let pod = Regions::one(region, Axes::splat(false));
|
let pod = Region::new(region, Axes::splat(false));
|
||||||
let mut frame = body.layout(engine, locator, styles, pod)?.into_frame();
|
let mut frame = layout_frame(engine, &body, locator, styles, pod)?;
|
||||||
|
|
||||||
// Set the size of the frame if the size is enforced.
|
// Set the size of the frame if the size is enforced.
|
||||||
if let Smart::Custom(size) = size {
|
if let Smart::Custom(size) = size {
|
||||||
|
@ -7,8 +7,8 @@ use crate::foundations::{
|
|||||||
};
|
};
|
||||||
use crate::introspection::Locator;
|
use crate::introspection::Locator;
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
Abs, Axes, BlockElem, Corner, Corners, Frame, FrameItem, Length, Point, Ratio,
|
layout_frame, Abs, Axes, BlockElem, Corner, Corners, Frame, FrameItem, Length, Point,
|
||||||
Region, Regions, Rel, Sides, Size,
|
Ratio, Region, Rel, Sides, Size,
|
||||||
};
|
};
|
||||||
use crate::syntax::Span;
|
use crate::syntax::Span;
|
||||||
use crate::utils::Get;
|
use crate::utils::Get;
|
||||||
@ -495,16 +495,14 @@ fn layout_shape(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Layout the child.
|
// Layout the child.
|
||||||
frame = child
|
frame = layout_frame(engine, child, locator.relayout(), styles, pod)?;
|
||||||
.layout(engine, locator.relayout(), styles, pod.into_regions())?
|
|
||||||
.into_frame();
|
|
||||||
|
|
||||||
// If the child is a square or circle, relayout with full expansion into
|
// If the child is a square or circle, relayout with full expansion into
|
||||||
// square region to make sure the result is really quadratic.
|
// square region to make sure the result is really quadratic.
|
||||||
if kind.is_quadratic() {
|
if kind.is_quadratic() {
|
||||||
let length = frame.size().max_by_side().min(pod.size.min_by_side());
|
let length = frame.size().max_by_side().min(pod.size.min_by_side());
|
||||||
let quad_pod = Regions::one(Size::splat(length), Axes::splat(true));
|
let quad_pod = Region::new(Size::splat(length), Axes::splat(true));
|
||||||
frame = child.layout(engine, locator, styles, quad_pod)?.into_frame();
|
frame = layout_frame(engine, child, locator, styles, quad_pod)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply the inset.
|
// Apply the inset.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user