New block spacing model
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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>);
|
||||||
|
@ -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![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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)
|
||||||
|
@ -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")),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
@ -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.
|
||||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 25 KiB |
@ -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
|
||||||
|
@ -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]),
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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 🌍
|
||||||
|
@ -6,7 +6,7 @@ No list
|
|||||||
|
|
||||||
---
|
---
|
||||||
_Shopping list_
|
_Shopping list_
|
||||||
#list(attached: true)[Apples][Potatoes][Juice]
|
#list[Apples][Potatoes][Juice]
|
||||||
|
|
||||||
---
|
---
|
||||||
- First level.
|
- First level.
|
||||||
|
@ -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])
|
||||||
|
@ -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)
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
|
||||||
|
@ -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?
|
||||||
|
@ -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.
|
||||||
|
@ -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() {}
|
||||||
|