New block spacing model

This commit is contained in:
Laurenz 2022-11-09 18:16:59 +01:00
parent 12a59963b0
commit 010cc2effc
33 changed files with 315 additions and 489 deletions

View File

@ -12,9 +12,6 @@ pub trait ContentExt {
/// Underline this content. /// Underline this content.
fn underlined(self) -> Self; fn underlined(self) -> Self;
/// Add weak vertical spacing above and below the content.
fn spaced(self, above: Option<Abs>, below: Option<Abs>) -> Self;
/// Force a size for this content. /// Force a size for this content.
fn boxed(self, sizing: Axes<Option<Rel<Length>>>) -> Self; fn boxed(self, sizing: Axes<Option<Rel<Length>>>) -> Self;
@ -47,30 +44,6 @@ impl ContentExt for Content {
text::DecoNode::<{ text::UNDERLINE }>(self).pack() text::DecoNode::<{ text::UNDERLINE }>(self).pack()
} }
fn spaced(self, above: Option<Abs>, below: Option<Abs>) -> Self {
if above.is_none() && below.is_none() {
return self;
}
let mut seq = vec![];
if let Some(above) = above {
seq.push(
layout::VNode { amount: above.into(), weak: true, generated: true }
.pack(),
);
}
seq.push(self);
if let Some(below) = below {
seq.push(
layout::VNode { amount: below.into(), weak: true, generated: true }
.pack(),
);
}
Content::sequence(seq)
}
fn boxed(self, sizing: Axes<Option<Rel<Length>>>) -> Self { fn boxed(self, sizing: Axes<Option<Rel<Length>>>) -> Self {
layout::BoxNode { sizing, child: self }.pack() layout::BoxNode { sizing, child: self }.pack()
} }

View File

@ -1,3 +1,4 @@
use super::VNode;
use crate::prelude::*; use crate::prelude::*;
/// An inline-level container that sizes content. /// An inline-level container that sizes content.
@ -63,9 +64,22 @@ pub struct BlockNode(pub Content);
#[node(LayoutBlock)] #[node(LayoutBlock)]
impl BlockNode { 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> { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.eat()?.unwrap_or_default()).pack()) 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 { impl LayoutBlock for BlockNode {

View File

@ -1,6 +1,7 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use super::{AlignNode, PlaceNode, Spacing}; use super::{AlignNode, PlaceNode, Spacing, VNode};
use crate::layout::BlockNode;
use crate::prelude::*; use crate::prelude::*;
use crate::text::ParNode; use crate::text::ParNode;
@ -15,7 +16,7 @@ pub struct FlowNode(pub StyleVec<FlowChild>);
#[derive(Hash, PartialEq)] #[derive(Hash, PartialEq)]
pub enum FlowChild { pub enum FlowChild {
/// Vertical spacing between other children. /// Vertical spacing between other children.
Spacing(Spacing), Spacing(VNode),
/// Arbitrary block-level content. /// Arbitrary block-level content.
Block(Content), Block(Content),
/// A column / region break. /// A column / region break.
@ -32,13 +33,13 @@ impl LayoutBlock for FlowNode {
regions: &Regions, regions: &Regions,
styles: StyleChain, styles: StyleChain,
) -> SourceResult<Vec<Frame>> { ) -> SourceResult<Vec<Frame>> {
let mut layouter = FlowLayouter::new(regions); let mut layouter = FlowLayouter::new(regions, styles);
for (child, map) in self.0.iter() { for (child, map) in self.0.iter() {
let styles = map.chain(&styles); let styles = map.chain(&styles);
match child { match child {
FlowChild::Spacing(kind) => { FlowChild::Spacing(node) => {
layouter.layout_spacing(*kind, styles); layouter.layout_spacing(node, styles);
} }
FlowChild::Block(block) => { FlowChild::Block(block) => {
layouter.layout_block(world, block, styles)?; layouter.layout_block(world, block, styles)?;
@ -80,9 +81,11 @@ impl PartialOrd for FlowChild {
} }
/// Performs flow layout. /// Performs flow layout.
struct FlowLayouter { struct FlowLayouter<'a> {
/// The regions to layout children into. /// The regions to layout children into.
regions: Regions, regions: Regions,
/// The shared styles.
shared: StyleChain<'a>,
/// Whether the flow should expand to fill the region. /// Whether the flow should expand to fill the region.
expand: Axes<bool>, expand: Axes<bool>,
/// The full size of `regions.size` that was available before we started /// The full size of `regions.size` that was available before we started
@ -92,6 +95,8 @@ struct FlowLayouter {
used: Size, used: Size,
/// The sum of fractions in the current region. /// The sum of fractions in the current region.
fr: Fr, fr: Fr,
/// The spacing below the last block.
below: Option<VNode>,
/// Spacing and layouted blocks. /// Spacing and layouted blocks.
items: Vec<FlowItem>, items: Vec<FlowItem>,
/// Finished frames for previous regions. /// Finished frames for previous regions.
@ -110,9 +115,9 @@ enum FlowItem {
Placed(Frame), Placed(Frame),
} }
impl FlowLayouter { impl<'a> FlowLayouter<'a> {
/// Create a new flow layouter. /// Create a new flow layouter.
fn new(regions: &Regions) -> Self { fn new(regions: &Regions, shared: StyleChain<'a>) -> Self {
let expand = regions.expand; let expand = regions.expand;
let full = regions.first; let full = regions.first;
@ -122,18 +127,20 @@ impl FlowLayouter {
Self { Self {
regions, regions,
shared,
expand, expand,
full, full,
used: Size::zero(), used: Size::zero(),
fr: Fr::zero(), fr: Fr::zero(),
below: None,
items: vec![], items: vec![],
finished: vec![], finished: vec![],
} }
} }
/// Layout spacing. /// Layout spacing.
fn layout_spacing(&mut self, spacing: Spacing, styles: StyleChain) { fn layout_spacing(&mut self, node: &VNode, styles: StyleChain) {
match spacing { match node.amount {
Spacing::Relative(v) => { Spacing::Relative(v) => {
// Resolve the spacing and limit it to the remaining space. // Resolve the spacing and limit it to the remaining space.
let resolved = v.resolve(styles).relative_to(self.full.y); let resolved = v.resolve(styles).relative_to(self.full.y);
@ -147,6 +154,10 @@ impl FlowLayouter {
self.fr += v; self.fr += v;
} }
} }
if node.weak || node.amount.is_fractional() {
self.below = None;
}
} }
/// Layout a block. /// Layout a block.
@ -161,9 +172,19 @@ impl FlowLayouter {
self.finish_region(); 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 // Placed nodes that are out of flow produce placed items which aren't
// aligned later. // aligned later.
let mut is_placed = false;
if let Some(placed) = block.downcast::<PlaceNode>() { if let Some(placed) = block.downcast::<PlaceNode>() {
is_placed = true;
if placed.out_of_flow() { if placed.out_of_flow() {
let frame = block.layout_block(world, &self.regions, styles)?.remove(0); let frame = block.layout_block(world, &self.regions, styles)?.remove(0);
self.items.push(FlowItem::Placed(frame)); self.items.push(FlowItem::Placed(frame));
@ -202,6 +223,10 @@ impl FlowLayouter {
} }
} }
if !is_placed {
self.below = Some(styles.get(BlockNode::BELOW));
}
Ok(()) Ok(())
} }
@ -250,6 +275,7 @@ impl FlowLayouter {
self.full = self.regions.first; self.full = self.regions.first;
self.used = Size::zero(); self.used = Size::zero();
self.fr = Fr::zero(); self.fr = Fr::zero();
self.below = None;
self.finished.push(output); self.finished.push(output);
} }

View File

@ -1,5 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
use super::Spacing;
/// Arrange content in a grid. /// Arrange content in a grid.
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
pub struct GridNode { pub struct GridNode {
@ -66,6 +68,15 @@ pub enum TrackSizing {
Fractional(Fr), 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. /// Track sizing definitions.
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
pub struct TrackSizings(pub Vec<TrackSizing>); pub struct TrackSizings(pub Vec<TrackSizing>);

View File

@ -32,8 +32,8 @@ use typst::diag::SourceResult;
use typst::frame::Frame; use typst::frame::Frame;
use typst::geom::*; use typst::geom::*;
use typst::model::{ use typst::model::{
capability, Barrier, Content, Node, SequenceNode, Show, StyleChain, StyleEntry, capability, Content, Node, SequenceNode, Show, StyleChain, StyleEntry, StyleVec,
StyleVec, StyleVecBuilder, StyledNode, Target, StyleVecBuilder, StyledNode, Target,
}; };
use typst::World; use typst::World;
@ -85,11 +85,13 @@ impl LayoutBlock for Content {
regions: &Regions, regions: &Regions,
styles: StyleChain, styles: StyleChain,
) -> SourceResult<Vec<Frame>> { ) -> SourceResult<Vec<Frame>> {
if !self.has::<dyn Show>() || !styles.applicable(self) {
if let Some(node) = self.to::<dyn LayoutBlock>() { if let Some(node) = self.to::<dyn LayoutBlock>() {
let barrier = StyleEntry::Barrier(Barrier::new(self.id())); let barrier = StyleEntry::Barrier(self.id());
let styles = barrier.chain(&styles); let styles = barrier.chain(&styles);
return node.layout_block(world, regions, styles); return node.layout_block(world, regions, styles);
} }
}
let scratch = Scratch::default(); let scratch = Scratch::default();
let mut builder = Builder::new(world, &scratch, false); let mut builder = Builder::new(world, &scratch, false);
@ -119,17 +121,19 @@ impl LayoutInline for Content {
regions: &Regions, regions: &Regions,
styles: StyleChain, styles: StyleChain,
) -> SourceResult<Vec<Frame>> { ) -> SourceResult<Vec<Frame>> {
if !self.has::<dyn Show>() || !styles.applicable(self) {
if let Some(node) = self.to::<dyn LayoutInline>() { if let Some(node) = self.to::<dyn LayoutInline>() {
let barrier = StyleEntry::Barrier(Barrier::new(self.id())); let barrier = StyleEntry::Barrier(self.id());
let styles = barrier.chain(&styles); let styles = barrier.chain(&styles);
return node.layout_inline(world, regions, styles); return node.layout_inline(world, regions, styles);
} }
if let Some(node) = self.to::<dyn LayoutBlock>() { if let Some(node) = self.to::<dyn LayoutBlock>() {
let barrier = StyleEntry::Barrier(Barrier::new(self.id())); let barrier = StyleEntry::Barrier(self.id());
let styles = barrier.chain(&styles); let styles = barrier.chain(&styles);
return node.layout_block(world, regions, styles); return node.layout_block(world, regions, styles);
} }
}
let scratch = Scratch::default(); let scratch = Scratch::default();
let mut builder = Builder::new(world, &scratch, false); let mut builder = Builder::new(world, &scratch, false);
@ -253,8 +257,6 @@ struct Builder<'a> {
struct Scratch<'a> { struct Scratch<'a> {
/// An arena where intermediate style chains are stored. /// An arena where intermediate style chains are stored.
styles: Arena<StyleChain<'a>>, 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. /// An arena where intermediate content resulting from show rules is stored.
content: Arena<Content>, content: Arena<Content>,
} }
@ -354,18 +356,7 @@ impl<'a> Builder<'a> {
Ok(()) Ok(())
} }
fn show( fn show(&mut self, content: &Content, styles: StyleChain<'a>) -> SourceResult<bool> {
&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));
let Some(realized) = styles.apply(self.world, Target::Node(content))? else { let Some(realized) = styles.apply(self.world, Target::Node(content))? else {
return Ok(false); return Ok(false);
}; };
@ -457,7 +448,7 @@ struct DocBuilder<'a> {
} }
impl<'a> 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>() { if let Some(pagebreak) = content.downcast::<PagebreakNode>() {
self.keep_next = !pagebreak.weak; self.keep_next = !pagebreak.weak;
} }
@ -477,10 +468,10 @@ impl Default for DocBuilder<'_> {
/// Accepts flow content. /// Accepts flow content.
#[derive(Default)] #[derive(Default)]
struct FlowBuilder<'a>(CollapsingBuilder<'a, FlowChild>); struct FlowBuilder<'a>(CollapsingBuilder<'a, FlowChild>, bool);
impl<'a> FlowBuilder<'a> { 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: // Weak flow elements:
// Weakness | Element // Weakness | Element
// 0 | weak colbreak // 0 | weak colbreak
@ -490,8 +481,11 @@ impl<'a> FlowBuilder<'a> {
// 4 | generated weak fractional spacing // 4 | generated weak fractional spacing
// 5 | par spacing // 5 | par spacing
let last_was_parbreak = self.1;
self.1 = false;
if content.is::<ParbreakNode>() { if content.is::<ParbreakNode>() {
/* Nothing to do */ self.1 = true;
} else if let Some(colbreak) = content.downcast::<ColbreakNode>() { } else if let Some(colbreak) = content.downcast::<ColbreakNode>() {
if colbreak.weak { if colbreak.weak {
self.0.weak(FlowChild::Colbreak, styles, 0); self.0.weak(FlowChild::Colbreak, styles, 0);
@ -499,10 +493,10 @@ impl<'a> FlowBuilder<'a> {
self.0.destructive(FlowChild::Colbreak, styles); self.0.destructive(FlowChild::Colbreak, styles);
} }
} else if let Some(vertical) = content.downcast::<VNode>() { } 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(); let frac = vertical.amount.is_fractional();
if vertical.weak { 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); self.0.weak(child, styles, weakness);
} else if frac { } else if frac {
self.0.destructive(child, styles); self.0.destructive(child, styles);
@ -510,6 +504,24 @@ impl<'a> FlowBuilder<'a> {
self.0.ignorant(child, styles); self.0.ignorant(child, styles);
} }
} else if content.has::<dyn LayoutBlock>() { } 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()); let child = FlowChild::Block(content.clone());
if content.is::<PlaceNode>() { if content.is::<PlaceNode>() {
self.0.ignorant(child, styles); self.0.ignorant(child, styles);
@ -523,18 +535,6 @@ impl<'a> FlowBuilder<'a> {
true 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>) { fn finish(self, doc: &mut DocBuilder<'a>, styles: StyleChain<'a>) {
let (flow, shared) = self.0.finish(); let (flow, shared) = self.0.finish();
let styles = if flow.is_empty() { styles } else { shared }; let styles = if flow.is_empty() { styles } else { shared };
@ -552,7 +552,7 @@ impl<'a> FlowBuilder<'a> {
struct ParBuilder<'a>(CollapsingBuilder<'a, ParChild>); struct ParBuilder<'a>(CollapsingBuilder<'a, ParChild>);
impl<'a> ParBuilder<'a> { 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: // Weak par elements:
// Weakness | Element // Weakness | Element
// 0 | weak fractional spacing // 0 | weak fractional spacing
@ -621,7 +621,7 @@ impl<'a> ParBuilder<'a> {
children.push_front(ParChild::Spacing(indent.into())); 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 { fn is_empty(&self) -> bool {
@ -635,63 +635,54 @@ struct ListBuilder<'a> {
items: StyleVecBuilder<'a, ListItem>, items: StyleVecBuilder<'a, ListItem>,
/// Whether the list contains no paragraph breaks. /// Whether the list contains no paragraph breaks.
tight: bool, tight: bool,
/// Whether the list can be attached.
attachable: bool,
/// Trailing content for which it is unclear whether it is part of the list. /// Trailing content for which it is unclear whether it is part of the list.
staged: Vec<(&'a Content, StyleChain<'a>)>, staged: Vec<(&'a Content, StyleChain<'a>)>,
} }
impl<'a> ListBuilder<'a> { impl<'a> ListBuilder<'a> {
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
if self.items.is_empty() { if !self.items.is_empty()
if content.is::<ParbreakNode>() { && (content.is::<SpaceNode>() || content.is::<ParbreakNode>())
self.attachable = false; {
} else if !content.is::<SpaceNode>() && !content.is::<ListItem>() { self.staged.push((content, styles));
self.attachable = true; } else if let Some(item) = content.downcast::<ListItem>() {
}
}
if let Some(item) = content.downcast::<ListItem>() {
if self if self
.items .items
.items() .items()
.next() .next()
.map_or(true, |first| item.kind() == first.kind()) .map_or(false, |first| item.kind() != first.kind())
{ {
self.items.push(item.clone(), styles); return false;
self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::<ParbreakNode>());
return true;
}
} else if !self.items.is_empty()
&& (content.is::<SpaceNode>() || content.is::<ParbreakNode>())
{
self.staged.push((content, styles));
return true;
} }
false self.items.push(item.clone(), styles);
self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::<ParbreakNode>());
} else {
return false;
}
true
} }
fn finish(self, parent: &mut Builder<'a>) -> SourceResult<()> { fn finish(self, parent: &mut Builder<'a>) -> SourceResult<()> {
let (items, shared) = self.items.finish(); let (items, shared) = self.items.finish();
if let Some(item) = items.items().next() {
let Some(item) = items.items().next() else { return Ok(()) };
let tight = self.tight; let tight = self.tight;
let attached = tight && self.attachable;
let content = match item.kind() { let content = match item.kind() {
LIST => ListNode::<LIST> { tight, attached, items }.pack(), LIST => ListNode::<LIST> { tight, items }.pack(),
ENUM => ListNode::<ENUM> { tight, attached, items }.pack(), ENUM => ListNode::<ENUM> { tight, items }.pack(),
DESC | _ => ListNode::<DESC> { tight, attached, items }.pack(), DESC | _ => ListNode::<DESC> { tight, items }.pack(),
}; };
let stored = parent.scratch.content.alloc(content); let stored = parent.scratch.content.alloc(content);
parent.accept(stored, shared)?; parent.accept(stored, shared)?;
}
for (content, styles) in self.staged { for (content, styles) in self.staged {
parent.accept(content, styles)?; parent.accept(content, styles)?;
} }
parent.list.attachable = true; parent.list.tight = true;
Ok(()) Ok(())
} }
@ -706,7 +697,6 @@ impl Default for ListBuilder<'_> {
Self { Self {
items: StyleVecBuilder::default(), items: StyleVecBuilder::default(),
tight: true, tight: true,
attachable: true,
staged: vec![], staged: vec![],
} }
} }

View File

@ -1,10 +1,9 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use crate::prelude::*; use crate::prelude::*;
use crate::text::ParNode;
/// Horizontal spacing. /// Horizontal spacing.
#[derive(Debug, Clone, Hash)] #[derive(Debug, Copy, Clone, Hash)]
pub struct HNode { pub struct HNode {
pub amount: Spacing, pub amount: Spacing,
pub weak: bool, pub weak: bool,
@ -20,11 +19,22 @@ impl HNode {
} }
/// Vertical spacing. /// Vertical spacing.
#[derive(Debug, Clone, Hash)] #[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd)]
pub struct VNode { pub struct VNode {
pub amount: Spacing, pub amount: Spacing,
pub weak: bool, 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] #[node]
@ -32,7 +42,7 @@ impl VNode {
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
let amount = args.expect("spacing")?; let amount = args.expect("spacing")?;
let weak = args.named("weak")?.unwrap_or(false); 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 { impl PartialOrd for Spacing {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (self, other) { match (self, other) {
@ -77,24 +93,3 @@ castable! {
Value::Relative(v) => Self::Relative(v), Value::Relative(v) => Self::Relative(v),
Value::Fraction(v) => Self::Fractional(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())
}
}

View File

@ -5,9 +5,8 @@ mod tex;
use std::fmt::Write; use std::fmt::Write;
use self::tex::{layout_tex, Texify}; use self::tex::{layout_tex, Texify};
use crate::layout::BlockSpacing;
use crate::prelude::*; use crate::prelude::*;
use crate::text::{FallbackList, FontFamily, TextNode}; use crate::text::FontFamily;
/// A piece of a mathematical formula. /// A piece of a mathematical formula.
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, Hash)]
@ -18,15 +17,8 @@ pub struct MathNode {
pub display: bool, pub display: bool,
} }
#[node(Show, Finalize, LayoutInline, Texify)] #[node(Show, LayoutInline, Texify)]
impl MathNode { impl MathNode {
/// The spacing above display math.
#[property(resolve, shorthand(around))]
pub const ABOVE: Option<BlockSpacing> = Some(Ratio::one().into());
/// The spacing below display math.
#[property(resolve, shorthand(around))]
pub const BELOW: Option<BlockSpacing> = Some(Ratio::one().into());
fn field(&self, name: &str) -> Option<Value> { fn field(&self, name: &str) -> Option<Value> {
match name { match name {
"display" => Some(Value::Bool(self.display)), "display" => Some(Value::Bool(self.display)),
@ -40,27 +32,13 @@ impl Show for MathNode {
self.clone().pack() self.clone().pack()
} }
fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> { fn show(&self, _: Tracked<dyn World>, styles: StyleChain) -> SourceResult<Content> {
Ok(self.clone().pack()) let mut map = StyleMap::new();
} map.set_family(FontFamily::new("NewComputerModernMath"), styles);
}
impl Finalize for MathNode {
fn finalize(
&self,
_: Tracked<dyn World>,
styles: StyleChain,
mut realized: Content,
) -> SourceResult<Content> {
realized = realized.styled(
TextNode::FAMILY,
FallbackList(vec![FontFamily::new("NewComputerModernMath")]),
);
let mut realized = self.clone().pack().styled_with_map(map);
if self.display { if self.display {
realized = realized realized = realized.aligned(Axes::with_x(Some(Align::Center.into())))
.aligned(Axes::with_x(Some(Align::Center.into())))
.spaced(styles.get(Self::ABOVE), styles.get(Self::BELOW))
} }
Ok(realized) Ok(realized)

View File

@ -1,6 +1,6 @@
use typst::font::FontWeight; use typst::font::FontWeight;
use crate::layout::{BlockNode, BlockSpacing}; use crate::layout::{BlockNode, VNode};
use crate::prelude::*; use crate::prelude::*;
use crate::text::{TextNode, TextSize}; use crate::text::{TextNode, TextSize};
@ -16,25 +16,6 @@ pub struct HeadingNode {
#[node(Show, Finalize)] #[node(Show, Finalize)]
impl HeadingNode { impl HeadingNode {
/// Whether the heading appears in the outline.
pub const OUTLINED: bool = true;
/// Whether the heading is numbered.
pub const NUMBERED: bool = true;
/// The spacing above the heading.
#[property(referenced, shorthand(around))]
pub const ABOVE: Leveled<Option<BlockSpacing>> = Leveled::Mapping(|level| {
let ratio = match level.get() {
1 => 1.5,
_ => 1.2,
};
Some(Ratio::new(ratio).into())
});
/// The spacing below the heading.
#[property(referenced, shorthand(around))]
pub const BELOW: Leveled<Option<BlockSpacing>> =
Leveled::Value(Some(Ratio::new(0.55).into()));
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self { Ok(Self {
body: args.expect("body")?, body: args.expect("body")?,
@ -65,76 +46,25 @@ impl Show for HeadingNode {
impl Finalize for HeadingNode { impl Finalize for HeadingNode {
fn finalize( fn finalize(
&self, &self,
world: Tracked<dyn World>, _: Tracked<dyn World>,
styles: StyleChain, _: StyleChain,
mut realized: Content, realized: Content,
) -> SourceResult<Content> { ) -> SourceResult<Content> {
macro_rules! resolve { let size = Em::new(match self.level.get() {
($key:expr) => {
styles.get($key).resolve(world, self.level)?
};
}
let mut map = StyleMap::new();
map.set(TextNode::SIZE, {
let size = match self.level.get() {
1 => 1.4, 1 => 1.4,
2 => 1.2, 2 => 1.2,
_ => 1.0, _ => 1.0,
};
TextSize(Em::new(size).into())
}); });
let above = Em::new(if self.level.get() == 1 { 1.8 } else { 1.44 });
let below = Em::new(0.66);
let mut map = StyleMap::new();
map.set(TextNode::SIZE, TextSize(size.into()));
map.set(TextNode::WEIGHT, FontWeight::BOLD); map.set(TextNode::WEIGHT, FontWeight::BOLD);
map.set(BlockNode::ABOVE, VNode::strong(above.into()));
map.set(BlockNode::BELOW, VNode::strong(below.into()));
realized = realized.styled_with_map(map).spaced( Ok(realized.styled_with_map(map))
resolve!(Self::ABOVE).resolve(styles),
resolve!(Self::BELOW).resolve(styles),
);
Ok(realized)
}
}
/// Either the value or a closure mapping to the value.
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum Leveled<T> {
/// A bare value.
Value(T),
/// A simple mapping from a heading level to a value.
Mapping(fn(NonZeroUsize) -> T),
/// A closure mapping from a heading level to a value.
Func(Func, Span),
}
impl<T: Cast + Clone> Leveled<T> {
/// Resolve the value based on the level.
pub fn resolve(
&self,
world: Tracked<dyn World>,
level: NonZeroUsize,
) -> SourceResult<T> {
Ok(match self {
Self::Value(value) => value.clone(),
Self::Mapping(mapping) => mapping(level),
Self::Func(func, span) => {
let args = Args::new(*span, [Value::Int(level.get() as i64)]);
func.call_detached(world, args)?.cast().at(*span)?
}
})
}
}
impl<T: Cast> Cast<Spanned<Value>> for Leveled<T> {
fn is(value: &Spanned<Value>) -> bool {
matches!(&value.v, Value::Func(_)) || T::is(&value.v)
}
fn cast(value: Spanned<Value>) -> StrResult<Self> {
match value.v {
Value::Func(v) => Ok(Self::Func(v, value.span)),
v => T::cast(v)
.map(Self::Value)
.map_err(|msg| with_alternative(msg, "function")),
}
} }
} }

View File

@ -1,17 +1,15 @@
use unscanny::Scanner; use unscanny::Scanner;
use crate::base::Numbering; use crate::base::Numbering;
use crate::layout::{BlockSpacing, GridNode, HNode, TrackSizing}; use crate::layout::{BlockNode, GridNode, HNode, Spacing, TrackSizing};
use crate::prelude::*; use crate::prelude::*;
use crate::text::{ParNode, SpaceNode, TextNode}; use crate::text::{ParNode, SpaceNode, TextNode};
/// An unordered (bulleted) or ordered (numbered) list. /// An unordered (bulleted) or ordered (numbered) list.
#[derive(Debug, Hash)] #[derive(Debug, Clone, Hash)]
pub struct ListNode<const L: ListKind = LIST> { pub struct ListNode<const L: ListKind = LIST> {
/// If true, the items are separated by leading instead of list spacing. /// If true, the items are separated by leading instead of list spacing.
pub tight: bool, pub tight: bool,
/// If true, the spacing above the list is leading instead of above spacing.
pub attached: bool,
/// The individual bulleted or numbered items. /// The individual bulleted or numbered items.
pub items: StyleVec<ListItem>, pub items: StyleVec<ListItem>,
} }
@ -22,7 +20,7 @@ pub type EnumNode = ListNode<ENUM>;
/// A description list. /// A description list.
pub type DescNode = ListNode<DESC>; pub type DescNode = ListNode<DESC>;
#[node(Show, Finalize)] #[node(Show, LayoutBlock)]
impl<const L: ListKind> ListNode<L> { impl<const L: ListKind> ListNode<L> {
/// How the list is labelled. /// How the list is labelled.
#[property(referenced)] #[property(referenced)]
@ -37,16 +35,8 @@ impl<const L: ListKind> ListNode<L> {
DESC | _ => 1.0, DESC | _ => 1.0,
}) })
.into(); .into();
/// The spacing above the list.
#[property(resolve, shorthand(around))]
pub const ABOVE: Option<BlockSpacing> = Some(Ratio::one().into());
/// The spacing below the list.
#[property(resolve, shorthand(around))]
pub const BELOW: Option<BlockSpacing> = Some(Ratio::one().into());
/// The spacing between the items of a wide (non-tight) list. /// The spacing between the items of a wide (non-tight) list.
#[property(resolve)] pub const SPACING: Smart<Spacing> = Smart::Auto;
pub const SPACING: BlockSpacing = Ratio::one().into();
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
let items = match L { let items = match L {
@ -73,18 +63,12 @@ impl<const L: ListKind> ListNode<L> {
.collect(), .collect(),
}; };
Ok(Self { Ok(Self { tight: args.named("tight")?.unwrap_or(true), items }.pack())
tight: args.named("tight")?.unwrap_or(true),
attached: args.named("attached")?.unwrap_or(false),
items,
}
.pack())
} }
fn field(&self, name: &str) -> Option<Value> { fn field(&self, name: &str) -> Option<Value> {
match name { match name {
"tight" => Some(Value::Bool(self.tight)), "tight" => Some(Value::Bool(self.tight)),
"attached" => Some(Value::Bool(self.attached)),
"items" => { "items" => {
Some(Value::Array(self.items.items().map(|item| item.encode()).collect())) Some(Value::Array(self.items.items().map(|item| item.encode()).collect()))
} }
@ -102,11 +86,18 @@ impl<const L: ListKind> Show for ListNode<L> {
.pack() .pack()
} }
fn show( fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
Ok(self.clone().pack())
}
}
impl<const L: ListKind> LayoutBlock for ListNode<L> {
fn layout_block(
&self, &self,
world: Tracked<dyn World>, world: Tracked<dyn World>,
regions: &Regions,
styles: StyleChain, styles: StyleChain,
) -> SourceResult<Content> { ) -> SourceResult<Vec<Frame>> {
let mut cells = vec![]; let mut cells = vec![];
let mut number = 1; let mut number = 1;
@ -114,9 +105,11 @@ impl<const L: ListKind> Show for ListNode<L> {
let indent = styles.get(Self::INDENT); let indent = styles.get(Self::INDENT);
let body_indent = styles.get(Self::BODY_INDENT); let body_indent = styles.get(Self::BODY_INDENT);
let gutter = if self.tight { let gutter = if self.tight {
styles.get(ParNode::LEADING) styles.get(ParNode::LEADING).into()
} else { } else {
styles.get(Self::SPACING) styles
.get(Self::SPACING)
.unwrap_or_else(|| styles.get(BlockNode::BELOW).amount)
}; };
for (item, map) in self.items.iter() { for (item, map) in self.items.iter() {
@ -150,40 +143,17 @@ impl<const L: ListKind> Show for ListNode<L> {
number += 1; number += 1;
} }
Ok(GridNode { GridNode {
tracks: Axes::with_x(vec![ tracks: Axes::with_x(vec![
TrackSizing::Relative(indent.into()), TrackSizing::Relative(indent.into()),
TrackSizing::Auto, TrackSizing::Auto,
TrackSizing::Relative(body_indent.into()), TrackSizing::Relative(body_indent.into()),
TrackSizing::Auto, TrackSizing::Auto,
]), ]),
gutter: Axes::with_y(vec![TrackSizing::Relative(gutter.into())]), gutter: Axes::with_y(vec![gutter.into()]),
cells, cells,
} }
.pack()) .layout_block(world, regions, styles)
}
}
impl<const L: ListKind> Finalize for ListNode<L> {
fn finalize(
&self,
_: Tracked<dyn World>,
styles: StyleChain,
realized: Content,
) -> SourceResult<Content> {
let mut above = styles.get(Self::ABOVE);
let mut below = styles.get(Self::BELOW);
if self.attached {
if above.is_some() {
above = Some(styles.get(ParNode::LEADING));
}
if below.is_some() {
below = Some(styles.get(ParNode::SPACING));
}
}
Ok(realized.spaced(above, below))
} }
} }

View File

@ -1,8 +1,8 @@
use crate::layout::{BlockSpacing, GridNode, TrackSizing, TrackSizings}; use crate::layout::{GridNode, TrackSizing, TrackSizings};
use crate::prelude::*; use crate::prelude::*;
/// A table of items. /// A table of items.
#[derive(Debug, Hash)] #[derive(Debug, Clone, Hash)]
pub struct TableNode { pub struct TableNode {
/// Defines sizing for content rows and columns. /// Defines sizing for content rows and columns.
pub tracks: Axes<Vec<TrackSizing>>, pub tracks: Axes<Vec<TrackSizing>>,
@ -12,7 +12,7 @@ pub struct TableNode {
pub cells: Vec<Content>, pub cells: Vec<Content>,
} }
#[node(Show, Finalize)] #[node(Show, LayoutBlock)]
impl TableNode { impl TableNode {
/// How to fill the cells. /// How to fill the cells.
#[property(referenced)] #[property(referenced)]
@ -23,13 +23,6 @@ impl TableNode {
/// How much to pad the cells's content. /// How much to pad the cells's content.
pub const PADDING: Rel<Length> = Abs::pt(5.0).into(); pub const PADDING: Rel<Length> = Abs::pt(5.0).into();
/// The spacing above the table.
#[property(resolve, shorthand(around))]
pub const ABOVE: Option<BlockSpacing> = Some(Ratio::one().into());
/// The spacing below the table.
#[property(resolve, shorthand(around))]
pub const BELOW: Option<BlockSpacing> = Some(Ratio::one().into());
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
let TrackSizings(columns) = args.named("columns")?.unwrap_or_default(); let TrackSizings(columns) = args.named("columns")?.unwrap_or_default();
let TrackSizings(rows) = args.named("rows")?.unwrap_or_default(); let TrackSizings(rows) = args.named("rows")?.unwrap_or_default();
@ -67,11 +60,18 @@ impl Show for TableNode {
.pack() .pack()
} }
fn show( fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
Ok(self.clone().pack())
}
}
impl LayoutBlock for TableNode {
fn layout_block(
&self, &self,
world: Tracked<dyn World>, world: Tracked<dyn World>,
regions: &Regions,
styles: StyleChain, styles: StyleChain,
) -> SourceResult<Content> { ) -> SourceResult<Vec<Frame>> {
let fill = styles.get(Self::FILL); let fill = styles.get(Self::FILL);
let stroke = styles.get(Self::STROKE).map(PartialStroke::unwrap_or_default); let stroke = styles.get(Self::STROKE).map(PartialStroke::unwrap_or_default);
let padding = styles.get(Self::PADDING); let padding = styles.get(Self::PADDING);
@ -99,23 +99,12 @@ impl Show for TableNode {
}) })
.collect::<SourceResult<_>>()?; .collect::<SourceResult<_>>()?;
Ok(GridNode { GridNode {
tracks: self.tracks.clone(), tracks: self.tracks.clone(),
gutter: self.gutter.clone(), gutter: self.gutter.clone(),
cells, cells,
} }
.pack()) .layout_block(world, regions, styles)
}
}
impl Finalize for TableNode {
fn finalize(
&self,
_: Tracked<dyn World>,
styles: StyleChain,
realized: Content,
) -> SourceResult<Content> {
Ok(realized.spaced(styles.get(Self::ABOVE), styles.get(Self::BELOW)))
} }
} }

View File

@ -28,18 +28,12 @@ pub enum ParChild {
#[node(LayoutBlock)] #[node(LayoutBlock)]
impl ParNode { impl ParNode {
/// The spacing between lines.
#[property(resolve)]
pub const LEADING: Length = Em::new(0.65).into();
/// The extra spacing between paragraphs.
#[property(resolve)]
pub const SPACING: Length = Em::new(1.2).into();
/// The indent the first line of a consecutive paragraph should have. /// The indent the first line of a consecutive paragraph should have.
#[property(resolve)] #[property(resolve)]
pub const INDENT: Length = Length::zero(); pub const INDENT: Length = Length::zero();
/// Whether to allow paragraph spacing when there is paragraph indent. /// The spacing between lines.
pub const SPACING_AND_INDENT: bool = false; #[property(resolve)]
pub const LEADING: Length = Em::new(0.65).into();
/// How to align text and inline objects in their line. /// How to align text and inline objects in their line.
#[property(resolve)] #[property(resolve)]
pub const ALIGN: HorizontalAlign = HorizontalAlign(GenAlign::Start); pub const ALIGN: HorizontalAlign = HorizontalAlign(GenAlign::Start);

View File

@ -6,8 +6,8 @@ use syntect::highlighting::{
use syntect::parsing::SyntaxSet; use syntect::parsing::SyntaxSet;
use typst::syntax; use typst::syntax;
use super::{FallbackList, FontFamily, Hyphenate, LinebreakNode, TextNode}; use super::{FontFamily, Hyphenate, LinebreakNode, TextNode};
use crate::layout::{BlockNode, BlockSpacing}; use crate::layout::BlockNode;
use crate::prelude::*; use crate::prelude::*;
/// Monospaced text with optional syntax highlighting. /// Monospaced text with optional syntax highlighting.
@ -19,17 +19,11 @@ pub struct RawNode {
pub block: bool, pub block: bool,
} }
#[node(Show, Finalize)] #[node(Show)]
impl RawNode { impl RawNode {
/// The language to syntax-highlight in. /// The language to syntax-highlight in.
#[property(referenced)] #[property(referenced)]
pub const LANG: Option<EcoString> = None; pub const LANG: Option<EcoString> = None;
/// The spacing above block-level raw.
#[property(resolve, shorthand(around))]
pub const ABOVE: Option<BlockSpacing> = Some(Ratio::one().into());
/// The spacing below block-level raw.
#[property(resolve, shorthand(around))]
pub const BELOW: Option<BlockSpacing> = Some(Ratio::one().into());
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self { Ok(Self {
@ -104,31 +98,12 @@ impl Show for RawNode {
map.set(TextNode::OVERHANG, false); map.set(TextNode::OVERHANG, false);
map.set(TextNode::HYPHENATE, Hyphenate(Smart::Custom(false))); map.set(TextNode::HYPHENATE, Hyphenate(Smart::Custom(false)));
map.set(TextNode::SMART_QUOTES, false); map.set(TextNode::SMART_QUOTES, false);
map.set_family(FontFamily::new("IBM Plex Mono"), styles);
Ok(realized.styled_with_map(map)) Ok(realized.styled_with_map(map))
} }
} }
impl Finalize for RawNode {
fn finalize(
&self,
_: Tracked<dyn World>,
styles: StyleChain,
mut realized: Content,
) -> SourceResult<Content> {
realized = realized.styled(
TextNode::FAMILY,
FallbackList(vec![FontFamily::new("IBM Plex Mono")]),
);
if self.block {
realized = realized.spaced(styles.get(Self::ABOVE), styles.get(Self::BELOW));
}
Ok(realized)
}
}
/// Style a piece of text with a syntect style. /// Style a piece of text with a syntect style.
fn styled(piece: &str, foreground: Paint, style: Style) -> Content { fn styled(piece: &str, foreground: Paint, style: Style) -> Content {
let mut body = TextNode::packed(piece); let mut body = TextNode::packed(piece);

View File

@ -148,7 +148,7 @@ pub enum StyleEntry {
/// A show rule recipe. /// A show rule recipe.
Recipe(Recipe), Recipe(Recipe),
/// A barrier for scoped styles. /// A barrier for scoped styles.
Barrier(Barrier), Barrier(NodeId),
/// Guards against recursive show rules. /// Guards against recursive show rules.
Guard(RecipeId), Guard(RecipeId),
/// Allows recursive show rules again. /// Allows recursive show rules again.
@ -158,11 +158,11 @@ pub enum StyleEntry {
impl StyleEntry { impl StyleEntry {
/// Make this style the first link of the `tail` chain. /// Make this style the first link of the `tail` chain.
pub fn chain<'a>(&'a self, tail: &'a StyleChain) -> StyleChain<'a> { pub fn chain<'a>(&'a self, tail: &'a StyleChain) -> StyleChain<'a> {
if let StyleEntry::Barrier(barrier) = self { if let StyleEntry::Barrier(id) = self {
if !tail if !tail
.entries() .entries()
.filter_map(StyleEntry::property) .filter_map(StyleEntry::property)
.any(|p| p.scoped() && barrier.is_for(p.node())) .any(|p| p.scoped() && *id == p.node())
{ {
return *tail; return *tail;
} }
@ -203,7 +203,7 @@ impl Debug for StyleEntry {
match self { match self {
Self::Property(property) => property.fmt(f)?, Self::Property(property) => property.fmt(f)?,
Self::Recipe(recipe) => recipe.fmt(f)?, Self::Recipe(recipe) => recipe.fmt(f)?,
Self::Barrier(barrier) => barrier.fmt(f)?, Self::Barrier(id) => write!(f, "Barrier for {id:?}")?,
Self::Guard(sel) => write!(f, "Guard against {sel:?}")?, Self::Guard(sel) => write!(f, "Guard against {sel:?}")?,
Self::Unguard(sel) => write!(f, "Unguard against {sel:?}")?, Self::Unguard(sel) => write!(f, "Unguard against {sel:?}")?,
} }
@ -246,6 +246,34 @@ impl<'a> StyleChain<'a> {
K::get(self, self.values(key)) K::get(self, self.values(key))
} }
/// Whether the style chain has a matching recipe for the content.
pub fn applicable(self, content: &Content) -> bool {
let target = Target::Node(content);
// Find out how many recipes there any and whether any of them match.
let mut n = 0;
let mut any = true;
for recipe in self.entries().filter_map(StyleEntry::recipe) {
n += 1;
any |= recipe.applicable(target);
}
// Find an applicable recipe.
if any {
for recipe in self.entries().filter_map(StyleEntry::recipe) {
if recipe.applicable(target) {
let sel = RecipeId::Nth(n);
if !self.guarded(sel) {
return true;
}
}
n -= 1;
}
}
false
}
/// Apply show recipes in this style chain to a target. /// Apply show recipes in this style chain to a target.
pub fn apply( pub fn apply(
self, self,
@ -292,6 +320,7 @@ impl<'a> StyleChain<'a> {
.to::<dyn Show>() .to::<dyn Show>()
.unwrap() .unwrap()
.show(world, self)?; .show(world, self)?;
realized = Some(content.styled_with_entry(StyleEntry::Guard(sel))); realized = Some(content.styled_with_entry(StyleEntry::Guard(sel)));
} }
} }
@ -342,8 +371,9 @@ impl<'a> StyleChain<'a> {
fn values<K: Key<'a>>(self, _: K) -> Values<'a, K> { fn values<K: Key<'a>>(self, _: K) -> Values<'a, K> {
Values { Values {
entries: self.entries(), entries: self.entries(),
depth: 0,
key: PhantomData, key: PhantomData,
barriers: 0,
guarded: false,
} }
} }
@ -379,8 +409,9 @@ impl PartialEq for StyleChain<'_> {
/// An iterator over the values in a style chain. /// An iterator over the values in a style chain.
struct Values<'a, K> { struct Values<'a, K> {
entries: Entries<'a>, entries: Entries<'a>,
depth: usize,
key: PhantomData<K>, key: PhantomData<K>,
barriers: usize,
guarded: bool,
} }
impl<'a, K: Key<'a>> Iterator for Values<'a, K> { impl<'a, K: Key<'a>> Iterator for Values<'a, K> {
@ -391,13 +422,22 @@ impl<'a, K: Key<'a>> Iterator for Values<'a, K> {
match entry { match entry {
StyleEntry::Property(property) => { StyleEntry::Property(property) => {
if let Some(value) = property.downcast::<K>() { if let Some(value) = property.downcast::<K>() {
if !property.scoped() || self.depth <= 1 { if !property.scoped()
|| if self.guarded {
self.barriers == 1
} else {
self.barriers <= 1
}
{
return Some(value); return Some(value);
} }
} }
} }
StyleEntry::Barrier(barrier) => { StyleEntry::Barrier(id) => {
self.depth += barrier.is_for(K::node()) as usize; self.barriers += (*id == K::node()) as usize;
}
StyleEntry::Guard(RecipeId::Base(id)) => {
self.guarded |= *id == K::node();
} }
_ => {} _ => {}
} }
@ -444,7 +484,7 @@ impl<'a> Iterator for Links<'a> {
} }
/// A sequence of items with associated styles. /// A sequence of items with associated styles.
#[derive(Hash)] #[derive(Clone, Hash)]
pub struct StyleVec<T> { pub struct StyleVec<T> {
items: Vec<T>, items: Vec<T>,
maps: Vec<(StyleMap, usize)>, maps: Vec<(StyleMap, usize)>,
@ -758,32 +798,6 @@ impl Debug for KeyId {
} }
} }
/// A scoped property barrier.
///
/// Barriers interact with [scoped](super::StyleMap::scoped) styles: A scoped
/// style can still be read through a single barrier (the one of the node it
/// _should_ apply to), but a second barrier will make it invisible.
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct Barrier(NodeId);
impl Barrier {
/// Create a new barrier for the given node.
pub fn new(node: NodeId) -> Self {
Self(node)
}
/// Whether this barrier is for the node `T`.
pub fn is_for(&self, node: NodeId) -> bool {
self.0 == node
}
}
impl Debug for Barrier {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "Barrier for {:?}", self.0)
}
}
/// A property that is resolved with other properties from the style chain. /// A property that is resolved with other properties from the style chain.
pub trait Resolve { pub trait Resolve {
/// The type of the resolved output. /// The type of the resolved output.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -8,7 +8,6 @@
#eval("#let") #eval("#let")
--- ---
#set raw(around: none)
#show raw: it => text("IBM Plex Sans", eval(it.text)) #show raw: it => text("IBM Plex Sans", eval(it.text))
Interacting Interacting

View File

@ -42,9 +42,9 @@
--- ---
// Test aligning things in RTL stack with align function & fr units. // Test aligning things in RTL stack with align function & fr units.
#set page(width: 50pt, margins: 5pt) #set page(width: 50pt, margins: 5pt)
#set block(spacing: 5pt)
#set text(8pt) #set text(8pt)
#stack(dir: rtl, 1fr, [A], 1fr, [B], [C]) #stack(dir: rtl, 1fr, [A], 1fr, [B], [C])
#v(5pt)
#stack(dir: rtl, #stack(dir: rtl,
align(center, [A]), align(center, [A]),
align(left, [B]), align(left, [B]),

View File

@ -9,10 +9,8 @@ Attached to:
Next paragraph. Next paragraph.
--- ---
// Test attached list without parbreak after it. // Test that attached list isn't affected by block spacing.
// Ensures the par spacing is used below by setting #show list: it => { set block(above: 100pt); it }
// super high around spacing.
#set list(around: 100pt)
Hello Hello
- A - A
World World
@ -29,8 +27,8 @@ World
- B - B
--- ---
// Test not-attached tight list. // Test non-attached tight list.
#set list(around: 15pt) #set block(spacing: 15pt)
Hello Hello
- A - A
World World
@ -41,8 +39,8 @@ World
More. More.
--- ---
// Test that wide lists cannot be attached ... // Test that wide lists cannot be ...
#set list(around: 15pt, spacing: 15pt) #set block(spacing: 15pt)
Hello Hello
- A - A
@ -50,7 +48,7 @@ Hello
World World
--- ---
// ... unless really forced to. // ... even if forced to.
Hello Hello
#list(attached: true, tight: false)[A][B] #list(tight: false)[A][B]
World World

View File

@ -30,8 +30,8 @@ No: list \
--- ---
// Test style change. // Test style change.
#set text(8pt) #set text(8pt)
/ First list: #lorem(4) / First list: #lorem(4)
#set desc(body-indent: 30pt) #set desc(body-indent: 30pt)
/ Second list: #lorem(4) / Second list: #lorem(4)

View File

@ -43,9 +43,9 @@ multiline.
--- ---
// Test styling. // Test styling.
#show heading.where(level: 5): it => { #show heading.where(level: 5): it => block(
text(family: "Roboto", fill: eastern, it.body + [!]) text(family: "Roboto", fill: eastern, it.body + [!])
} )
= Heading = Heading
===== Heading 🌍 ===== Heading 🌍

View File

@ -6,7 +6,7 @@ No list
--- ---
_Shopping list_ _Shopping list_
#list(attached: true)[Apples][Potatoes][Juice] #list[Apples][Potatoes][Juice]
--- ---
- First level. - First level.

View File

@ -4,11 +4,7 @@
// Ensure that constructor styles aren't passed down the tree. // Ensure that constructor styles aren't passed down the tree.
// The inner list should have no extra indent. // The inner list should have no extra indent.
#set par(leading: 2pt) #set par(leading: 2pt)
#list( #list(body-indent: 20pt, [First], list[A][B])
body-indent: 20pt,
[First],
list([A], [B])
)
--- ---
// Ensure that constructor styles win, but not over outer styles. // Ensure that constructor styles win, but not over outer styles.
@ -29,5 +25,7 @@
A #rect(fill: yellow, inset: 5pt, rect()) B A #rect(fill: yellow, inset: 5pt, rect()) B
--- ---
// The inner list should not be indented extra. // The constructor property should still work
[#set text(1em);#list(indent: 20pt, list[A])] // when there are recursive show rules.
#show list: text.with(blue)
#list(label: "(a)", [A], list[B])

View File

@ -19,9 +19,9 @@ Hello *{x}*
- No more fruit - No more fruit
--- ---
// Test that that par spacing and text style are respected from // Test that that block spacing and text style are respected from
// the outside, but the more specific fill is respected. // the outside, but the more specific fill is respected.
#set par(spacing: 4pt) #set block(spacing: 4pt)
#set text(style: "italic", fill: eastern) #set text(style: "italic", fill: eastern)
#let x = [And the forest #parbreak() lay silent!] #let x = [And the forest #parbreak() lay silent!]
#text(fill: forest, x) #text(fill: forest, x)

View File

@ -2,7 +2,6 @@
--- ---
// Override lists. // Override lists.
#set list(around: none)
#show list: it => "(" + it.items.join(", ") + ")" #show list: it => "(" + it.items.join(", ") + ")"
- A - A
@ -13,7 +12,6 @@
--- ---
// Test full reset. // Test full reset.
#set heading(around: none)
#show heading: [B] #show heading: [B]
#show heading: text.with(size: 10pt, weight: 400) #show heading: text.with(size: 10pt, weight: 400)
A [= Heading] C A [= Heading] C
@ -21,7 +19,6 @@ A [= Heading] C
--- ---
// Test full removal. // Test full removal.
#show heading: none #show heading: none
#set heading(around: none)
Where is Where is
= There are no headings around here! = There are no headings around here!
@ -29,7 +26,7 @@ my heading?
--- ---
// Test integrated example. // Test integrated example.
#show heading: it => { #show heading: it => block({
set text(10pt) set text(10pt)
move(dy: -1pt)[📖] move(dy: -1pt)[📖]
h(5pt) h(5pt)
@ -38,7 +35,7 @@ my heading?
} else { } else {
text(red, it.body) text(red, it.body)
} }
} })
= Task 1 = Task 1
Some text. Some text.

View File

@ -15,52 +15,37 @@
- List - List
= Nope = Nope
---
// Test recursive base recipe. (Burn it with fire!)
#set list(label: [- Hey])
- Labelless
- List
--- ---
// Test show rule in function. // Test show rule in function.
#let starwars(body) = [ #let starwars(body) = {
#show list: it => { show list: it => block({
stack(dir: ltr, stack(dir: ltr,
text(red, it), text(red, it),
1fr, 1fr,
scale(x: -100%, text(blue, it)), scale(x: -100%, text(blue, it)),
) )
})
body
} }
#body
]
- Normal list - Normal list
#starwars[ #starwars[
- Star - Star
- Wars - Wars
- List - List
] ]
- Normal list - Normal list
--- ---
// Test multi-recursion with nested lists. // Test multi-recursion with nested lists.
#set rect(inset: 2pt) #set rect(inset: 3pt)
#show list: rect.with(stroke: blue) #show list: rect.with(stroke: blue)
#show list: rect.with(stroke: red) #show list: rect.with(stroke: red)
#show list: block
- List - List
- Nested - Nested
- List - List
- Recursive! - Recursive!
---
// Inner heading is not finalized. Bug?
#set heading(around: none)
#show heading: it => it.body
#show heading: [
= A [
= B
]
]
= Discarded

View File

@ -2,7 +2,7 @@
--- ---
#set par(indent: 12pt, leading: 5pt) #set par(indent: 12pt, leading: 5pt)
#set heading(above: 8pt) #set block(spacing: 5pt)
#show heading: text.with(size: 10pt) #show heading: text.with(size: 10pt)
The first paragraph has no indent. The first paragraph has no indent.
@ -31,7 +31,7 @@ starts a paragraph without indent.
--- ---
// This is madness. // This is madness.
#set par(indent: 12pt, spacing-and-indent: true) #set par(indent: 12pt)
Why would anybody ever ... Why would anybody ever ...
... want spacing and indent? ... want spacing and indent?

View File

@ -1,12 +1,8 @@
--- ---
#set page(width: 180pt) #set page(width: 180pt)
#set par( #set block(spacing: 5pt)
justify: true, #set par(justify: true, indent: 14pt, leading: 5pt)
indent: 14pt,
spacing: 0pt,
leading: 5pt,
)
This text is justified, meaning that spaces are stretched so that the text This text is justified, meaning that spaces are stretched so that the text
forms a "block" with flush edges at both sides. forms a "block" with flush edges at both sides.

View File

@ -7,33 +7,27 @@ To the right! Where the sunlight peeks behind the mountain.
--- ---
// Test changing leading and spacing. // Test changing leading and spacing.
#set par(spacing: 1em, leading: 2pt) #set block(spacing: 1em)
#set par(leading: 2pt)
But, soft! what light through yonder window breaks? But, soft! what light through yonder window breaks?
It is the east, and Juliet is the sun. It is the east, and Juliet is the sun.
---
// Test that largest paragraph spacing wins.
#set par(spacing: 2.5pt)
[#set par(spacing: 15pt);First]
[#set par(spacing: 7.5pt);Second]
Third
Fourth
--- ---
// Test that paragraph spacing loses against block spacing. // Test that paragraph spacing loses against block spacing.
#set par(spacing: 100pt) // TODO
#set table(around: 5pt) // #set block(spacing: 100pt)
// #show table: set block(spacing: 5pt)
#set block(spacing: 5pt)
Hello Hello
#table(columns: 4, fill: (x, y) => if odd(x + y) { silver })[A][B][C][D] #table(columns: 4, fill: (x, y) => if odd(x + y) { silver })[A][B][C][D]
--- ---
// While we're at it, test the larger block spacing wins. // While we're at it, test the larger block spacing wins.
#set raw(around: 15pt) #set block(spacing: 0pt)
#set math(around: 7.5pt) #show raw: it => { set block(spacing: 15pt); it }
#set list(around: 2.5pt) #show math: it => { set block(spacing: 7.5pt); it }
#set par(spacing: 0pt) #show list: it => { set block(spacing: 2.5pt); it }
```rust ```rust
fn main() {} fn main() {}