summaryrefslogtreecommitdiff
path: root/library/src/layout/mod.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-07-02 19:59:52 +0200
committerLaurenz <laurmaedje@gmail.com>2023-07-02 20:07:43 +0200
commitebfdb1dafa430786db10dad2ef7d5467c1bdbed1 (patch)
tree2bbc24ddb4124c4bb14dec0e536129d4de37b056 /library/src/layout/mod.rs
parent3ab19185093d7709f824b95b979060ce125389d8 (diff)
Move everything into `crates/` directory
Diffstat (limited to 'library/src/layout/mod.rs')
-rw-r--r--library/src/layout/mod.rs709
1 files changed, 0 insertions, 709 deletions
diff --git a/library/src/layout/mod.rs b/library/src/layout/mod.rs
deleted file mode 100644
index 41490eb8..00000000
--- a/library/src/layout/mod.rs
+++ /dev/null
@@ -1,709 +0,0 @@
-//! Composable layouts.
-
-mod align;
-mod columns;
-mod container;
-#[path = "enum.rs"]
-mod enum_;
-mod flow;
-mod fragment;
-mod grid;
-mod hide;
-mod list;
-mod measure;
-mod pad;
-mod page;
-mod par;
-mod place;
-mod regions;
-mod repeat;
-mod spacing;
-mod stack;
-mod table;
-mod terms;
-mod transform;
-
-pub use self::align::*;
-pub use self::columns::*;
-pub use self::container::*;
-pub use self::enum_::*;
-pub use self::flow::*;
-pub use self::fragment::*;
-pub use self::grid::*;
-pub use self::hide::*;
-pub use self::list::*;
-pub use self::measure::*;
-pub use self::pad::*;
-pub use self::page::*;
-pub use self::par::*;
-pub use self::place::*;
-pub use self::regions::*;
-pub use self::repeat::*;
-pub use self::spacing::*;
-pub use self::stack::*;
-pub use self::table::*;
-pub use self::terms::*;
-pub use self::transform::*;
-
-use std::mem;
-
-use typed_arena::Arena;
-use typst::diag::SourceResult;
-use typst::eval::Tracer;
-use typst::model::DelayedErrors;
-use typst::model::{applicable, realize, StyleVecBuilder};
-
-use crate::math::{EquationElem, LayoutMath};
-use crate::meta::DocumentElem;
-use crate::prelude::*;
-use crate::shared::BehavedBuilder;
-use crate::text::{LinebreakElem, SmartQuoteElem, SpaceElem, TextElem};
-use crate::visualize::{
- CircleElem, EllipseElem, ImageElem, LineElem, PathElem, PolygonElem, RectElem,
- SquareElem,
-};
-
-/// Hook up all layout definitions.
-pub(super) fn define(global: &mut Scope) {
- global.define("page", PageElem::func());
- global.define("pagebreak", PagebreakElem::func());
- global.define("v", VElem::func());
- global.define("par", ParElem::func());
- global.define("parbreak", ParbreakElem::func());
- global.define("h", HElem::func());
- global.define("box", BoxElem::func());
- global.define("block", BlockElem::func());
- global.define("list", ListElem::func());
- global.define("enum", EnumElem::func());
- global.define("terms", TermsElem::func());
- global.define("table", TableElem::func());
- global.define("stack", StackElem::func());
- global.define("grid", GridElem::func());
- global.define("columns", ColumnsElem::func());
- global.define("colbreak", ColbreakElem::func());
- global.define("place", PlaceElem::func());
- global.define("align", AlignElem::func());
- global.define("pad", PadElem::func());
- global.define("repeat", RepeatElem::func());
- global.define("move", MoveElem::func());
- global.define("scale", ScaleElem::func());
- global.define("rotate", RotateElem::func());
- global.define("hide", HideElem::func());
- global.define("measure", measure_func());
- global.define("ltr", Dir::LTR);
- global.define("rtl", Dir::RTL);
- global.define("ttb", Dir::TTB);
- global.define("btt", Dir::BTT);
- global.define("start", GenAlign::Start);
- global.define("end", GenAlign::End);
- global.define("left", GenAlign::Specific(Align::Left));
- global.define("center", GenAlign::Specific(Align::Center));
- global.define("right", GenAlign::Specific(Align::Right));
- global.define("top", GenAlign::Specific(Align::Top));
- global.define("horizon", GenAlign::Specific(Align::Horizon));
- global.define("bottom", GenAlign::Specific(Align::Bottom));
-}
-
-/// Root-level layout.
-pub trait LayoutRoot {
- /// Layout into one frame per page.
- fn layout_root(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Document>;
-}
-
-impl LayoutRoot for Content {
- #[tracing::instrument(name = "Content::layout_root", skip_all)]
- fn layout_root(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Document> {
- #[comemo::memoize]
- fn cached(
- content: &Content,
- world: Tracked<dyn World + '_>,
- introspector: Tracked<Introspector>,
- locator: Tracked<Locator>,
- delayed: TrackedMut<DelayedErrors>,
- tracer: TrackedMut<Tracer>,
- styles: StyleChain,
- ) -> SourceResult<Document> {
- let mut locator = Locator::chained(locator);
- let mut vt = Vt {
- world,
- introspector,
- locator: &mut locator,
- delayed,
- tracer,
- };
- let scratch = Scratch::default();
- let (realized, styles) = realize_root(&mut vt, &scratch, content, styles)?;
- realized
- .with::<dyn LayoutRoot>()
- .unwrap()
- .layout_root(&mut vt, styles)
- }
-
- tracing::info!("Starting layout");
- cached(
- self,
- vt.world,
- vt.introspector,
- vt.locator.track(),
- TrackedMut::reborrow_mut(&mut vt.delayed),
- TrackedMut::reborrow_mut(&mut vt.tracer),
- styles,
- )
- }
-}
-
-/// Layout into regions.
-pub trait Layout {
- /// Layout into one frame per region.
- fn layout(
- &self,
- vt: &mut Vt,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Fragment>;
-
- /// Layout without side effects.
- ///
- /// This element must be layouted again in the same order for the results to
- /// be valid.
- #[tracing::instrument(name = "Layout::measure", skip_all)]
- fn measure(
- &self,
- vt: &mut Vt,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Fragment> {
- let mut locator = Locator::chained(vt.locator.track());
- let mut vt = Vt {
- world: vt.world,
- introspector: vt.introspector,
- locator: &mut locator,
- tracer: TrackedMut::reborrow_mut(&mut vt.tracer),
- delayed: TrackedMut::reborrow_mut(&mut vt.delayed),
- };
- self.layout(&mut vt, styles, regions)
- }
-}
-
-impl Layout for Content {
- #[tracing::instrument(name = "Content::layout", skip_all)]
- fn layout(
- &self,
- vt: &mut Vt,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Fragment> {
- #[allow(clippy::too_many_arguments)]
- #[comemo::memoize]
- fn cached(
- content: &Content,
- world: Tracked<dyn World + '_>,
- introspector: Tracked<Introspector>,
- locator: Tracked<Locator>,
- delayed: TrackedMut<DelayedErrors>,
- tracer: TrackedMut<Tracer>,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Fragment> {
- let mut locator = Locator::chained(locator);
- let mut vt = Vt {
- world,
- introspector,
- locator: &mut locator,
- delayed,
- tracer,
- };
- let scratch = Scratch::default();
- let (realized, styles) = realize_block(&mut vt, &scratch, content, styles)?;
- realized
- .with::<dyn Layout>()
- .unwrap()
- .layout(&mut vt, styles, regions)
- }
-
- tracing::info!("Layouting `Content`");
-
- let fragment = cached(
- self,
- vt.world,
- vt.introspector,
- vt.locator.track(),
- TrackedMut::reborrow_mut(&mut vt.delayed),
- TrackedMut::reborrow_mut(&mut vt.tracer),
- styles,
- regions,
- )?;
-
- vt.locator.visit_frames(&fragment);
- Ok(fragment)
- }
-}
-
-/// Realize into an element that is capable of root-level layout.
-#[tracing::instrument(skip_all)]
-fn realize_root<'a>(
- vt: &mut Vt,
- scratch: &'a Scratch<'a>,
- content: &'a Content,
- styles: StyleChain<'a>,
-) -> SourceResult<(Content, StyleChain<'a>)> {
- if content.can::<dyn LayoutRoot>() && !applicable(content, styles) {
- return Ok((content.clone(), styles));
- }
-
- let mut builder = Builder::new(vt, scratch, true);
- builder.accept(content, styles)?;
- builder.interrupt_page(Some(styles))?;
- let (pages, shared) = builder.doc.unwrap().pages.finish();
- Ok((DocumentElem::new(pages.to_vec()).pack(), shared))
-}
-
-/// Realize into an element that is capable of block-level layout.
-#[tracing::instrument(skip_all)]
-fn realize_block<'a>(
- vt: &mut Vt,
- scratch: &'a Scratch<'a>,
- content: &'a Content,
- styles: StyleChain<'a>,
-) -> SourceResult<(Content, StyleChain<'a>)> {
- if content.can::<dyn Layout>()
- && !content.is::<LineElem>()
- && !content.is::<RectElem>()
- && !content.is::<SquareElem>()
- && !content.is::<EllipseElem>()
- && !content.is::<CircleElem>()
- && !content.is::<ImageElem>()
- && !content.is::<PolygonElem>()
- && !content.is::<PathElem>()
- && !applicable(content, styles)
- {
- return Ok((content.clone(), styles));
- }
-
- let mut builder = Builder::new(vt, scratch, false);
- builder.accept(content, styles)?;
- builder.interrupt_par()?;
- let (children, shared) = builder.flow.0.finish();
- Ok((FlowElem::new(children.to_vec()).pack(), shared))
-}
-
-/// Builds a document or a flow element from content.
-struct Builder<'a, 'v, 't> {
- /// The virtual typesetter.
- vt: &'v mut Vt<'t>,
- /// Scratch arenas for building.
- scratch: &'a Scratch<'a>,
- /// The current document building state.
- doc: Option<DocBuilder<'a>>,
- /// The current flow building state.
- flow: FlowBuilder<'a>,
- /// The current paragraph building state.
- par: ParBuilder<'a>,
- /// The current list building state.
- list: ListBuilder<'a>,
-}
-
-/// Temporary storage arenas for building.
-#[derive(Default)]
-struct Scratch<'a> {
- /// An arena where intermediate style chains are stored.
- styles: Arena<StyleChain<'a>>,
- /// An arena where intermediate content resulting from show rules is stored.
- content: Arena<Content>,
-}
-
-impl<'a, 'v, 't> Builder<'a, 'v, 't> {
- fn new(vt: &'v mut Vt<'t>, scratch: &'a Scratch<'a>, top: bool) -> Self {
- Self {
- vt,
- scratch,
- doc: top.then(DocBuilder::default),
- flow: FlowBuilder::default(),
- par: ParBuilder::default(),
- list: ListBuilder::default(),
- }
- }
-
- fn accept(
- &mut self,
- mut content: &'a Content,
- styles: StyleChain<'a>,
- ) -> SourceResult<()> {
- if content.can::<dyn LayoutMath>() && !content.is::<EquationElem>() {
- content =
- self.scratch.content.alloc(EquationElem::new(content.clone()).pack());
- }
-
- if let Some(realized) = realize(self.vt, content, styles)? {
- let stored = self.scratch.content.alloc(realized);
- return self.accept(stored, styles);
- }
-
- if let Some((elem, local)) = content.to_styled() {
- return self.styled(elem, local, styles);
- }
-
- if let Some(children) = content.to_sequence() {
- for elem in children {
- self.accept(elem, styles)?;
- }
- return Ok(());
- }
-
- if self.list.accept(content, styles) {
- return Ok(());
- }
-
- self.interrupt_list()?;
-
- if self.list.accept(content, styles) {
- return Ok(());
- }
-
- if self.par.accept(content, styles) {
- return Ok(());
- }
-
- self.interrupt_par()?;
-
- if self.flow.accept(content, styles) {
- return Ok(());
- }
-
- let keep = content
- .to::<PagebreakElem>()
- .map_or(false, |pagebreak| !pagebreak.weak(styles));
-
- self.interrupt_page(keep.then_some(styles))?;
-
- if let Some(doc) = &mut self.doc {
- if doc.accept(content, styles) {
- return Ok(());
- }
- }
-
- if content.is::<PagebreakElem>() {
- bail!(content.span(), "pagebreaks are not allowed inside of containers");
- } else {
- bail!(content.span(), "{} is not allowed here", content.func().name());
- }
- }
-
- fn styled(
- &mut self,
- elem: &'a Content,
- map: &'a Styles,
- styles: StyleChain<'a>,
- ) -> SourceResult<()> {
- let stored = self.scratch.styles.alloc(styles);
- let styles = stored.chain(map);
- self.interrupt_style(map, None)?;
- self.accept(elem, styles)?;
- self.interrupt_style(map, Some(styles))?;
- Ok(())
- }
-
- fn interrupt_style(
- &mut self,
- local: &Styles,
- outer: Option<StyleChain<'a>>,
- ) -> SourceResult<()> {
- if let Some(Some(span)) = local.interruption::<DocumentElem>() {
- if self.doc.is_none() {
- bail!(span, "document set rules are not allowed inside of containers");
- }
- if outer.is_none()
- && (!self.flow.0.is_empty()
- || !self.par.0.is_empty()
- || !self.list.items.is_empty())
- {
- bail!(span, "document set rules must appear before any content");
- }
- } else if let Some(Some(span)) = local.interruption::<PageElem>() {
- if self.doc.is_none() {
- bail!(span, "page configuration is not allowed inside of containers");
- }
- self.interrupt_page(outer)?;
- } else if local.interruption::<ParElem>().is_some()
- || local.interruption::<AlignElem>().is_some()
- {
- self.interrupt_par()?;
- } else if local.interruption::<ListElem>().is_some()
- || local.interruption::<EnumElem>().is_some()
- || local.interruption::<TermsElem>().is_some()
- {
- self.interrupt_list()?;
- }
- Ok(())
- }
-
- fn interrupt_list(&mut self) -> SourceResult<()> {
- if !self.list.items.is_empty() {
- let staged = mem::take(&mut self.list.staged);
- let (list, styles) = mem::take(&mut self.list).finish();
- let stored = self.scratch.content.alloc(list);
- self.accept(stored, styles)?;
- for (content, styles) in staged {
- self.accept(content, styles)?;
- }
- }
- Ok(())
- }
-
- fn interrupt_par(&mut self) -> SourceResult<()> {
- self.interrupt_list()?;
- if !self.par.0.is_empty() {
- let (par, styles) = mem::take(&mut self.par).finish();
- let stored = self.scratch.content.alloc(par);
- self.accept(stored, styles)?;
- }
-
- Ok(())
- }
-
- fn interrupt_page(&mut self, styles: Option<StyleChain<'a>>) -> SourceResult<()> {
- self.interrupt_par()?;
- let Some(doc) = &mut self.doc else { return Ok(()) };
- if !self.flow.0.is_empty() || (doc.keep_next && styles.is_some()) {
- let (flow, shared) = mem::take(&mut self.flow).0.finish();
- let styles = if shared == StyleChain::default() {
- styles.unwrap_or_default()
- } else {
- shared
- };
- let page = PageElem::new(FlowElem::new(flow.to_vec()).pack());
- let stored = self.scratch.content.alloc(page.pack());
- self.accept(stored, styles)?;
- }
- Ok(())
- }
-}
-
-/// Accepts pagebreaks and pages.
-struct DocBuilder<'a> {
- /// The page runs built so far.
- pages: StyleVecBuilder<'a, Content>,
- /// Whether to keep a following page even if it is empty.
- keep_next: bool,
- /// Whether the next page should be cleared to an even or odd number.
- clear_next: Option<Parity>,
-}
-
-impl<'a> DocBuilder<'a> {
- fn accept(&mut self, content: &Content, styles: StyleChain<'a>) -> bool {
- if let Some(pagebreak) = content.to::<PagebreakElem>() {
- self.keep_next = !pagebreak.weak(styles);
- self.clear_next = pagebreak.to(styles);
- return true;
- }
-
- if let Some(page) = content.to::<PageElem>() {
- let elem = if let Some(clear_to) = self.clear_next.take() {
- let mut page = page.clone();
- page.push_clear_to(Some(clear_to));
- page.pack()
- } else {
- content.clone()
- };
-
- self.pages.push(elem, styles);
- self.keep_next = false;
- return true;
- }
-
- false
- }
-}
-
-impl Default for DocBuilder<'_> {
- fn default() -> Self {
- Self {
- pages: StyleVecBuilder::new(),
- keep_next: true,
- clear_next: None,
- }
- }
-}
-
-/// Accepts flow content.
-#[derive(Default)]
-struct FlowBuilder<'a>(BehavedBuilder<'a>, bool);
-
-impl<'a> FlowBuilder<'a> {
- fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
- if content.is::<ParbreakElem>() {
- self.1 = true;
- return true;
- }
-
- let last_was_parbreak = self.1;
- self.1 = false;
-
- if content.is::<VElem>()
- || content.is::<ColbreakElem>()
- || content.is::<MetaElem>()
- {
- self.0.push(content.clone(), styles);
- return true;
- }
-
- if content.can::<dyn Layout>() || content.is::<ParElem>() {
- let is_tight_list = if let Some(elem) = content.to::<ListElem>() {
- elem.tight(styles)
- } else if let Some(elem) = content.to::<EnumElem>() {
- elem.tight(styles)
- } else if let Some(elem) = content.to::<TermsElem>() {
- elem.tight(styles)
- } else {
- false
- };
-
- if !last_was_parbreak && is_tight_list {
- let leading = ParElem::leading_in(styles);
- let spacing = VElem::list_attach(leading.into());
- self.0.push(spacing.pack(), styles);
- }
-
- let (above, below) = if let Some(block) = content.to::<BlockElem>() {
- (block.above(styles), block.below(styles))
- } else {
- (BlockElem::above_in(styles), BlockElem::below_in(styles))
- };
-
- self.0.push(above.pack(), styles);
- self.0.push(content.clone(), styles);
- self.0.push(below.pack(), styles);
- return true;
- }
-
- false
- }
-}
-
-/// Accepts paragraph content.
-#[derive(Default)]
-struct ParBuilder<'a>(BehavedBuilder<'a>);
-
-impl<'a> ParBuilder<'a> {
- fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
- if content.is::<MetaElem>() {
- if !self.0.is_basically_empty() {
- self.0.push(content.clone(), styles);
- return true;
- }
- } else if content.is::<SpaceElem>()
- || content.is::<TextElem>()
- || content.is::<HElem>()
- || content.is::<LinebreakElem>()
- || content.is::<SmartQuoteElem>()
- || content.to::<EquationElem>().map_or(false, |elem| !elem.block(styles))
- || content.is::<BoxElem>()
- {
- self.0.push(content.clone(), styles);
- return true;
- }
-
- false
- }
-
- fn finish(self) -> (Content, StyleChain<'a>) {
- let (children, shared) = self.0.finish();
- (ParElem::new(children.to_vec()).pack(), shared)
- }
-}
-
-/// Accepts list / enum items, spaces, paragraph breaks.
-struct ListBuilder<'a> {
- /// The list items collected so far.
- items: StyleVecBuilder<'a, Content>,
- /// Whether the list contains no paragraph breaks.
- tight: bool,
- /// Trailing content for which it is unclear whether it is part of the list.
- staged: Vec<(&'a Content, StyleChain<'a>)>,
-}
-
-impl<'a> ListBuilder<'a> {
- fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
- if !self.items.is_empty()
- && (content.is::<SpaceElem>() || content.is::<ParbreakElem>())
- {
- self.staged.push((content, styles));
- return true;
- }
-
- if (content.is::<ListItem>()
- || content.is::<EnumItem>()
- || content.is::<TermItem>())
- && self
- .items
- .elems()
- .next()
- .map_or(true, |first| first.func() == content.func())
- {
- self.items.push(content.clone(), styles);
- self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::<ParbreakElem>());
- return true;
- }
-
- false
- }
-
- fn finish(self) -> (Content, StyleChain<'a>) {
- let (items, shared) = self.items.finish();
- let item = items.items().next().unwrap();
- let output = if item.is::<ListItem>() {
- ListElem::new(
- items
- .iter()
- .map(|(item, local)| {
- let item = item.to::<ListItem>().unwrap();
- item.clone().with_body(item.body().styled_with_map(local.clone()))
- })
- .collect::<Vec<_>>(),
- )
- .with_tight(self.tight)
- .pack()
- } else if item.is::<EnumItem>() {
- EnumElem::new(
- items
- .iter()
- .map(|(item, local)| {
- let item = item.to::<EnumItem>().unwrap();
- item.clone().with_body(item.body().styled_with_map(local.clone()))
- })
- .collect::<Vec<_>>(),
- )
- .with_tight(self.tight)
- .pack()
- } else if item.is::<TermItem>() {
- TermsElem::new(
- items
- .iter()
- .map(|(item, local)| {
- let item = item.to::<TermItem>().unwrap();
- item.clone()
- .with_term(item.term().styled_with_map(local.clone()))
- .with_description(
- item.description().styled_with_map(local.clone()),
- )
- })
- .collect::<Vec<_>>(),
- )
- .with_tight(self.tight)
- .pack()
- } else {
- unreachable!()
- };
- (output, shared)
- }
-}
-
-impl Default for ListBuilder<'_> {
- fn default() -> Self {
- Self {
- items: StyleVecBuilder::default(),
- tight: true,
- staged: vec![],
- }
- }
-}