summaryrefslogtreecommitdiff
path: root/library/src/layout
diff options
context:
space:
mode:
Diffstat (limited to 'library/src/layout')
-rw-r--r--library/src/layout/container.rs14
-rw-r--r--library/src/layout/flow.rs46
-rw-r--r--library/src/layout/grid.rs11
-rw-r--r--library/src/layout/mod.rs156
-rw-r--r--library/src/layout/spacing.rs47
5 files changed, 155 insertions, 119 deletions
diff --git a/library/src/layout/container.rs b/library/src/layout/container.rs
index d65b78b6..22a9e02e 100644
--- a/library/src/layout/container.rs
+++ b/library/src/layout/container.rs
@@ -1,3 +1,4 @@
+use super::VNode;
use crate::prelude::*;
/// An inline-level container that sizes content.
@@ -63,9 +64,22 @@ pub struct BlockNode(pub Content);
#[node(LayoutBlock)]
impl BlockNode {
+ /// The spacing between the previous and this block.
+ #[property(skip)]
+ pub const ABOVE: VNode = VNode::weak(Em::new(1.2).into());
+ /// The spacing between this and the following block.
+ #[property(skip)]
+ pub const BELOW: VNode = VNode::weak(Em::new(1.2).into());
+
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.eat()?.unwrap_or_default()).pack())
}
+
+ fn set(...) {
+ let spacing = args.named("spacing")?.map(VNode::weak);
+ styles.set_opt(Self::ABOVE, args.named("above")?.map(VNode::strong).or(spacing));
+ styles.set_opt(Self::BELOW, args.named("below")?.map(VNode::strong).or(spacing));
+ }
}
impl LayoutBlock for BlockNode {
diff --git a/library/src/layout/flow.rs b/library/src/layout/flow.rs
index 347c1dd8..cc5dcd50 100644
--- a/library/src/layout/flow.rs
+++ b/library/src/layout/flow.rs
@@ -1,6 +1,7 @@
use std::cmp::Ordering;
-use super::{AlignNode, PlaceNode, Spacing};
+use super::{AlignNode, PlaceNode, Spacing, VNode};
+use crate::layout::BlockNode;
use crate::prelude::*;
use crate::text::ParNode;
@@ -15,7 +16,7 @@ pub struct FlowNode(pub StyleVec<FlowChild>);
#[derive(Hash, PartialEq)]
pub enum FlowChild {
/// Vertical spacing between other children.
- Spacing(Spacing),
+ Spacing(VNode),
/// Arbitrary block-level content.
Block(Content),
/// A column / region break.
@@ -32,13 +33,13 @@ impl LayoutBlock for FlowNode {
regions: &Regions,
styles: StyleChain,
) -> SourceResult<Vec<Frame>> {
- let mut layouter = FlowLayouter::new(regions);
+ let mut layouter = FlowLayouter::new(regions, styles);
for (child, map) in self.0.iter() {
let styles = map.chain(&styles);
match child {
- FlowChild::Spacing(kind) => {
- layouter.layout_spacing(*kind, styles);
+ FlowChild::Spacing(node) => {
+ layouter.layout_spacing(node, styles);
}
FlowChild::Block(block) => {
layouter.layout_block(world, block, styles)?;
@@ -80,9 +81,11 @@ impl PartialOrd for FlowChild {
}
/// Performs flow layout.
-struct FlowLayouter {
+struct FlowLayouter<'a> {
/// The regions to layout children into.
regions: Regions,
+ /// The shared styles.
+ shared: StyleChain<'a>,
/// Whether the flow should expand to fill the region.
expand: Axes<bool>,
/// The full size of `regions.size` that was available before we started
@@ -92,6 +95,8 @@ struct FlowLayouter {
used: Size,
/// The sum of fractions in the current region.
fr: Fr,
+ /// The spacing below the last block.
+ below: Option<VNode>,
/// Spacing and layouted blocks.
items: Vec<FlowItem>,
/// Finished frames for previous regions.
@@ -110,9 +115,9 @@ enum FlowItem {
Placed(Frame),
}
-impl FlowLayouter {
+impl<'a> FlowLayouter<'a> {
/// Create a new flow layouter.
- fn new(regions: &Regions) -> Self {
+ fn new(regions: &Regions, shared: StyleChain<'a>) -> Self {
let expand = regions.expand;
let full = regions.first;
@@ -122,18 +127,20 @@ impl FlowLayouter {
Self {
regions,
+ shared,
expand,
full,
used: Size::zero(),
fr: Fr::zero(),
+ below: None,
items: vec![],
finished: vec![],
}
}
/// Layout spacing.
- fn layout_spacing(&mut self, spacing: Spacing, styles: StyleChain) {
- match spacing {
+ fn layout_spacing(&mut self, node: &VNode, styles: StyleChain) {
+ match node.amount {
Spacing::Relative(v) => {
// Resolve the spacing and limit it to the remaining space.
let resolved = v.resolve(styles).relative_to(self.full.y);
@@ -147,6 +154,10 @@ impl FlowLayouter {
self.fr += v;
}
}
+
+ if node.weak || node.amount.is_fractional() {
+ self.below = None;
+ }
}
/// Layout a block.
@@ -161,9 +172,19 @@ impl FlowLayouter {
self.finish_region();
}
+ // Add spacing between the last block and this one.
+ if let Some(below) = self.below.take() {
+ let above = styles.get(BlockNode::ABOVE);
+ let pick_below = (above.weak && !below.weak) || (below.amount > above.amount);
+ let spacing = if pick_below { below } else { above };
+ self.layout_spacing(&spacing, self.shared);
+ }
+
// Placed nodes that are out of flow produce placed items which aren't
// aligned later.
+ let mut is_placed = false;
if let Some(placed) = block.downcast::<PlaceNode>() {
+ is_placed = true;
if placed.out_of_flow() {
let frame = block.layout_block(world, &self.regions, styles)?.remove(0);
self.items.push(FlowItem::Placed(frame));
@@ -202,6 +223,10 @@ impl FlowLayouter {
}
}
+ if !is_placed {
+ self.below = Some(styles.get(BlockNode::BELOW));
+ }
+
Ok(())
}
@@ -250,6 +275,7 @@ impl FlowLayouter {
self.full = self.regions.first;
self.used = Size::zero();
self.fr = Fr::zero();
+ self.below = None;
self.finished.push(output);
}
diff --git a/library/src/layout/grid.rs b/library/src/layout/grid.rs
index 8e1cb7a2..8af69b9a 100644
--- a/library/src/layout/grid.rs
+++ b/library/src/layout/grid.rs
@@ -1,5 +1,7 @@
use crate::prelude::*;
+use super::Spacing;
+
/// Arrange content in a grid.
#[derive(Debug, Hash)]
pub struct GridNode {
@@ -66,6 +68,15 @@ pub enum TrackSizing {
Fractional(Fr),
}
+impl From<Spacing> for TrackSizing {
+ fn from(spacing: Spacing) -> Self {
+ match spacing {
+ Spacing::Relative(rel) => Self::Relative(rel),
+ Spacing::Fractional(fr) => Self::Fractional(fr),
+ }
+ }
+}
+
/// Track sizing definitions.
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
pub struct TrackSizings(pub Vec<TrackSizing>);
diff --git a/library/src/layout/mod.rs b/library/src/layout/mod.rs
index 1032ba54..e0eb431c 100644
--- a/library/src/layout/mod.rs
+++ b/library/src/layout/mod.rs
@@ -32,8 +32,8 @@ use typst::diag::SourceResult;
use typst::frame::Frame;
use typst::geom::*;
use typst::model::{
- capability, Barrier, Content, Node, SequenceNode, Show, StyleChain, StyleEntry,
- StyleVec, StyleVecBuilder, StyledNode, Target,
+ capability, Content, Node, SequenceNode, Show, StyleChain, StyleEntry, StyleVec,
+ StyleVecBuilder, StyledNode, Target,
};
use typst::World;
@@ -85,10 +85,12 @@ impl LayoutBlock for Content {
regions: &Regions,
styles: StyleChain,
) -> SourceResult<Vec<Frame>> {
- if let Some(node) = self.to::<dyn LayoutBlock>() {
- let barrier = StyleEntry::Barrier(Barrier::new(self.id()));
- let styles = barrier.chain(&styles);
- return node.layout_block(world, regions, styles);
+ if !self.has::<dyn Show>() || !styles.applicable(self) {
+ if let Some(node) = self.to::<dyn LayoutBlock>() {
+ let barrier = StyleEntry::Barrier(self.id());
+ let styles = barrier.chain(&styles);
+ return node.layout_block(world, regions, styles);
+ }
}
let scratch = Scratch::default();
@@ -119,16 +121,18 @@ impl LayoutInline for Content {
regions: &Regions,
styles: StyleChain,
) -> SourceResult<Vec<Frame>> {
- if let Some(node) = self.to::<dyn LayoutInline>() {
- let barrier = StyleEntry::Barrier(Barrier::new(self.id()));
- let styles = barrier.chain(&styles);
- return node.layout_inline(world, regions, styles);
- }
+ if !self.has::<dyn Show>() || !styles.applicable(self) {
+ if let Some(node) = self.to::<dyn LayoutInline>() {
+ let barrier = StyleEntry::Barrier(self.id());
+ let styles = barrier.chain(&styles);
+ return node.layout_inline(world, regions, styles);
+ }
- if let Some(node) = self.to::<dyn LayoutBlock>() {
- let barrier = StyleEntry::Barrier(Barrier::new(self.id()));
- let styles = barrier.chain(&styles);
- return node.layout_block(world, regions, styles);
+ if let Some(node) = self.to::<dyn LayoutBlock>() {
+ let barrier = StyleEntry::Barrier(self.id());
+ let styles = barrier.chain(&styles);
+ return node.layout_block(world, regions, styles);
+ }
}
let scratch = Scratch::default();
@@ -253,8 +257,6 @@ struct Builder<'a> {
struct Scratch<'a> {
/// An arena where intermediate style chains are stored.
styles: Arena<StyleChain<'a>>,
- /// An arena for individual intermediate style entries.
- entries: Arena<StyleEntry>,
/// An arena where intermediate content resulting from show rules is stored.
content: Arena<Content>,
}
@@ -354,18 +356,7 @@ impl<'a> Builder<'a> {
Ok(())
}
- fn show(
- &mut self,
- content: &'a Content,
- styles: StyleChain<'a>,
- ) -> SourceResult<bool> {
- let barrier = StyleEntry::Barrier(Barrier::new(content.id()));
- let styles = self
- .scratch
- .entries
- .alloc(barrier)
- .chain(self.scratch.styles.alloc(styles));
-
+ fn show(&mut self, content: &Content, styles: StyleChain<'a>) -> SourceResult<bool> {
let Some(realized) = styles.apply(self.world, Target::Node(content))? else {
return Ok(false);
};
@@ -457,7 +448,7 @@ struct DocBuilder<'a> {
}
impl<'a> DocBuilder<'a> {
- fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) {
+ fn accept(&mut self, content: &Content, styles: StyleChain<'a>) {
if let Some(pagebreak) = content.downcast::<PagebreakNode>() {
self.keep_next = !pagebreak.weak;
}
@@ -477,10 +468,10 @@ impl Default for DocBuilder<'_> {
/// Accepts flow content.
#[derive(Default)]
-struct FlowBuilder<'a>(CollapsingBuilder<'a, FlowChild>);
+struct FlowBuilder<'a>(CollapsingBuilder<'a, FlowChild>, bool);
impl<'a> FlowBuilder<'a> {
- fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
+ fn accept(&mut self, content: &Content, styles: StyleChain<'a>) -> bool {
// Weak flow elements:
// Weakness | Element
// 0 | weak colbreak
@@ -490,8 +481,11 @@ impl<'a> FlowBuilder<'a> {
// 4 | generated weak fractional spacing
// 5 | par spacing
+ let last_was_parbreak = self.1;
+ self.1 = false;
+
if content.is::<ParbreakNode>() {
- /* Nothing to do */
+ self.1 = true;
} else if let Some(colbreak) = content.downcast::<ColbreakNode>() {
if colbreak.weak {
self.0.weak(FlowChild::Colbreak, styles, 0);
@@ -499,10 +493,10 @@ impl<'a> FlowBuilder<'a> {
self.0.destructive(FlowChild::Colbreak, styles);
}
} else if let Some(vertical) = content.downcast::<VNode>() {
- let child = FlowChild::Spacing(vertical.amount);
+ let child = FlowChild::Spacing(*vertical);
let frac = vertical.amount.is_fractional();
if vertical.weak {
- let weakness = 1 + u8::from(frac) + 2 * u8::from(vertical.generated);
+ let weakness = 1 + u8::from(frac);
self.0.weak(child, styles, weakness);
} else if frac {
self.0.destructive(child, styles);
@@ -510,6 +504,24 @@ impl<'a> FlowBuilder<'a> {
self.0.ignorant(child, styles);
}
} else if content.has::<dyn LayoutBlock>() {
+ if !last_was_parbreak {
+ let tight = if let Some(node) = content.downcast::<ListNode>() {
+ node.tight
+ } else if let Some(node) = content.downcast::<EnumNode>() {
+ node.tight
+ } else if let Some(node) = content.downcast::<DescNode>() {
+ node.tight
+ } else {
+ false
+ };
+
+ if tight {
+ let leading = styles.get(ParNode::LEADING);
+ let spacing = VNode::weak(leading.into());
+ self.0.weak(FlowChild::Spacing(spacing), styles, 1);
+ }
+ }
+
let child = FlowChild::Block(content.clone());
if content.is::<PlaceNode>() {
self.0.ignorant(child, styles);
@@ -523,18 +535,6 @@ impl<'a> FlowBuilder<'a> {
true
}
- fn par(&mut self, par: ParNode, styles: StyleChain<'a>, indent: bool) {
- let amount = if indent && !styles.get(ParNode::SPACING_AND_INDENT) {
- styles.get(ParNode::LEADING).into()
- } else {
- styles.get(ParNode::SPACING).into()
- };
-
- self.0.weak(FlowChild::Spacing(amount), styles, 5);
- self.0.supportive(FlowChild::Block(par.pack()), styles);
- self.0.weak(FlowChild::Spacing(amount), styles, 5);
- }
-
fn finish(self, doc: &mut DocBuilder<'a>, styles: StyleChain<'a>) {
let (flow, shared) = self.0.finish();
let styles = if flow.is_empty() { styles } else { shared };
@@ -552,7 +552,7 @@ impl<'a> FlowBuilder<'a> {
struct ParBuilder<'a>(CollapsingBuilder<'a, ParChild>);
impl<'a> ParBuilder<'a> {
- fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
+ fn accept(&mut self, content: &Content, styles: StyleChain<'a>) -> bool {
// Weak par elements:
// Weakness | Element
// 0 | weak fractional spacing
@@ -621,7 +621,7 @@ impl<'a> ParBuilder<'a> {
children.push_front(ParChild::Spacing(indent.into()));
}
- parent.flow.par(ParNode(children), shared, !indent.is_zero());
+ parent.flow.accept(&ParNode(children).pack(), shared);
}
fn is_empty(&self) -> bool {
@@ -635,63 +635,54 @@ struct ListBuilder<'a> {
items: StyleVecBuilder<'a, ListItem>,
/// Whether the list contains no paragraph breaks.
tight: bool,
- /// Whether the list can be attached.
- attachable: 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() {
- if content.is::<ParbreakNode>() {
- self.attachable = false;
- } else if !content.is::<SpaceNode>() && !content.is::<ListItem>() {
- self.attachable = true;
- }
- }
-
- if let Some(item) = content.downcast::<ListItem>() {
+ if !self.items.is_empty()
+ && (content.is::<SpaceNode>() || content.is::<ParbreakNode>())
+ {
+ self.staged.push((content, styles));
+ } else if let Some(item) = content.downcast::<ListItem>() {
if self
.items
.items()
.next()
- .map_or(true, |first| item.kind() == first.kind())
+ .map_or(false, |first| item.kind() != first.kind())
{
- self.items.push(item.clone(), styles);
- self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::<ParbreakNode>());
- return true;
+ return false;
}
- } else if !self.items.is_empty()
- && (content.is::<SpaceNode>() || content.is::<ParbreakNode>())
- {
- self.staged.push((content, styles));
- return true;
+
+ self.items.push(item.clone(), styles);
+ self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::<ParbreakNode>());
+ } else {
+ return false;
}
- false
+ true
}
fn finish(self, parent: &mut Builder<'a>) -> SourceResult<()> {
let (items, shared) = self.items.finish();
+ if let Some(item) = items.items().next() {
+ let tight = self.tight;
+ let content = match item.kind() {
+ LIST => ListNode::<LIST> { tight, items }.pack(),
+ ENUM => ListNode::<ENUM> { tight, items }.pack(),
+ DESC | _ => ListNode::<DESC> { tight, items }.pack(),
+ };
- let Some(item) = items.items().next() else { return Ok(()) };
- let tight = self.tight;
- let attached = tight && self.attachable;
- let content = match item.kind() {
- LIST => ListNode::<LIST> { tight, attached, items }.pack(),
- ENUM => ListNode::<ENUM> { tight, attached, items }.pack(),
- DESC | _ => ListNode::<DESC> { tight, attached, items }.pack(),
- };
-
- let stored = parent.scratch.content.alloc(content);
- parent.accept(stored, shared)?;
+ let stored = parent.scratch.content.alloc(content);
+ parent.accept(stored, shared)?;
+ }
for (content, styles) in self.staged {
parent.accept(content, styles)?;
}
- parent.list.attachable = true;
+ parent.list.tight = true;
Ok(())
}
@@ -706,7 +697,6 @@ impl Default for ListBuilder<'_> {
Self {
items: StyleVecBuilder::default(),
tight: true,
- attachable: true,
staged: vec![],
}
}
diff --git a/library/src/layout/spacing.rs b/library/src/layout/spacing.rs
index 67fff5db..66d71ed1 100644
--- a/library/src/layout/spacing.rs
+++ b/library/src/layout/spacing.rs
@@ -1,10 +1,9 @@
use std::cmp::Ordering;
use crate::prelude::*;
-use crate::text::ParNode;
/// Horizontal spacing.
-#[derive(Debug, Clone, Hash)]
+#[derive(Debug, Copy, Clone, Hash)]
pub struct HNode {
pub amount: Spacing,
pub weak: bool,
@@ -20,11 +19,22 @@ impl HNode {
}
/// Vertical spacing.
-#[derive(Debug, Clone, Hash)]
+#[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd)]
pub struct VNode {
pub amount: Spacing,
pub weak: bool,
- pub generated: bool,
+}
+
+impl VNode {
+ /// Create weak vertical spacing.
+ pub fn weak(amount: Spacing) -> Self {
+ Self { amount, weak: true }
+ }
+
+ /// Create strong vertical spacing.
+ pub fn strong(amount: Spacing) -> Self {
+ Self { amount, weak: false }
+ }
}
#[node]
@@ -32,7 +42,7 @@ impl VNode {
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
let amount = args.expect("spacing")?;
let weak = args.named("weak")?.unwrap_or(false);
- Ok(Self { amount, weak, generated: false }.pack())
+ Ok(Self { amount, weak }.pack())
}
}
@@ -59,6 +69,12 @@ impl From<Abs> for Spacing {
}
}
+impl From<Em> for Spacing {
+ fn from(em: Em) -> Self {
+ Self::Relative(Rel::new(Ratio::zero(), em.into()))
+ }
+}
+
impl PartialOrd for Spacing {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (self, other) {
@@ -77,24 +93,3 @@ castable! {
Value::Relative(v) => Self::Relative(v),
Value::Fraction(v) => Self::Fractional(v),
}
-
-/// Spacing around and between blocks, relative to paragraph spacing.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub struct BlockSpacing(Rel<Length>);
-
-castable!(BlockSpacing: Rel<Length>);
-
-impl Resolve for BlockSpacing {
- type Output = Abs;
-
- fn resolve(self, styles: StyleChain) -> Self::Output {
- let whole = styles.get(ParNode::SPACING);
- self.0.resolve(styles).relative_to(whole)
- }
-}
-
-impl From<Ratio> for BlockSpacing {
- fn from(ratio: Ratio) -> Self {
- Self(ratio.into())
- }
-}