diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-04-30 14:12:28 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-04-30 14:12:28 +0200 |
| commit | f9e115daf54c29358f890b137f50a33a781af680 (patch) | |
| tree | 496de52246629ea8039db6beea94eb779ed2851d /src/library | |
| parent | f7c67cde72e6a67f45180856b332bae9863243bd (diff) | |
New block spacing model
Diffstat (limited to 'src/library')
| -rw-r--r-- | src/library/graphics/shape.rs | 1 | ||||
| -rw-r--r-- | src/library/graphics/transform.rs | 4 | ||||
| -rw-r--r-- | src/library/layout/align.rs | 12 | ||||
| -rw-r--r-- | src/library/layout/columns.rs | 5 | ||||
| -rw-r--r-- | src/library/layout/flow.rs | 41 | ||||
| -rw-r--r-- | src/library/layout/pad.rs | 12 | ||||
| -rw-r--r-- | src/library/layout/page.rs | 4 | ||||
| -rw-r--r-- | src/library/layout/spacing.rs | 45 | ||||
| -rw-r--r-- | src/library/layout/stack.rs | 12 | ||||
| -rw-r--r-- | src/library/math/mod.rs | 20 | ||||
| -rw-r--r-- | src/library/mod.rs | 2 | ||||
| -rw-r--r-- | src/library/structure/heading.rs | 51 | ||||
| -rw-r--r-- | src/library/structure/list.rs | 48 | ||||
| -rw-r--r-- | src/library/structure/table.rs | 31 | ||||
| -rw-r--r-- | src/library/text/deco.rs | 2 | ||||
| -rw-r--r-- | src/library/text/mod.rs | 12 | ||||
| -rw-r--r-- | src/library/text/par.rs | 39 | ||||
| -rw-r--r-- | src/library/text/raw.rs | 36 | ||||
| -rw-r--r-- | src/library/utility/blind.rs | 2 |
19 files changed, 239 insertions, 140 deletions
diff --git a/src/library/graphics/shape.rs b/src/library/graphics/shape.rs index e4c832f0..49c74c2f 100644 --- a/src/library/graphics/shape.rs +++ b/src/library/graphics/shape.rs @@ -26,7 +26,6 @@ impl<const S: ShapeKind> ShapeNode<S> { /// How to stroke the shape. #[property(resolve, fold)] pub const STROKE: Smart<Option<RawStroke>> = Smart::Auto; - /// How much to pad the shape's content. pub const PADDING: Relative<RawLength> = Relative::zero(); diff --git a/src/library/graphics/transform.rs b/src/library/graphics/transform.rs index ea021cc1..a4aa20db 100644 --- a/src/library/graphics/transform.rs +++ b/src/library/graphics/transform.rs @@ -13,8 +13,8 @@ pub struct MoveNode { #[node] impl MoveNode { fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { - let dx = args.named("x")?.unwrap_or_default(); - let dy = args.named("y")?.unwrap_or_default(); + let dx = args.named("dx")?.unwrap_or_default(); + let dy = args.named("dy")?.unwrap_or_default(); Ok(Content::inline(Self { delta: Spec::new(dx, dy), child: args.expect("body")?, diff --git a/src/library/layout/align.rs b/src/library/layout/align.rs index 2a4d039e..c050d2a4 100644 --- a/src/library/layout/align.rs +++ b/src/library/layout/align.rs @@ -13,9 +13,15 @@ pub struct AlignNode { #[node] impl AlignNode { fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { - let aligns: Spec<_> = args.find()?.unwrap_or_default(); - let body: LayoutNode = args.expect("body")?; - Ok(Content::block(body.aligned(aligns))) + let aligns: Spec<Option<RawAlign>> = args.find()?.unwrap_or_default(); + let body: Content = args.expect("body")?; + Ok(match (body, aligns) { + (Content::Block(node), _) => Content::Block(node.aligned(aligns)), + (other, Spec { x: Some(x), y: None }) => { + other.styled(ParNode::ALIGN, HorizontalAlign(x)) + } + (other, _) => Content::Block(other.pack().aligned(aligns)), + }) } } diff --git a/src/library/layout/columns.rs b/src/library/layout/columns.rs index 4963043e..8e523694 100644 --- a/src/library/layout/columns.rs +++ b/src/library/layout/columns.rs @@ -106,7 +106,8 @@ pub struct ColbreakNode; #[node] impl ColbreakNode { - fn construct(_: &mut Context, _: &mut Args) -> TypResult<Content> { - Ok(Content::Colbreak) + fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { + let weak = args.named("weak")?.unwrap_or(false); + Ok(Content::Colbreak { weak }) } } diff --git a/src/library/layout/flow.rs b/src/library/layout/flow.rs index 6b43c8b7..6193a68f 100644 --- a/src/library/layout/flow.rs +++ b/src/library/layout/flow.rs @@ -1,3 +1,5 @@ +use std::cmp::Ordering; + use super::{AlignNode, PlaceNode, Spacing}; use crate::library::prelude::*; use crate::library::text::ParNode; @@ -10,18 +12,14 @@ use crate::library::text::ParNode; pub struct FlowNode(pub StyleVec<FlowChild>); /// A child of a flow node. -#[derive(Hash)] +#[derive(Hash, PartialEq)] pub enum FlowChild { - /// Leading between other children. - Leading, - /// A paragraph / block break. - Parbreak, - /// A column / region break. - Colbreak, /// Vertical spacing between other children. Spacing(Spacing), /// An arbitrary block-level node. Node(LayoutNode), + /// A column / region break. + Colbreak, } impl Layout for FlowNode { @@ -36,25 +34,15 @@ impl Layout for FlowNode { for (child, map) in self.0.iter() { let styles = map.chain(&styles); match child { - FlowChild::Leading => { - let amount = styles.get(ParNode::LEADING); - layouter.layout_spacing(amount.into(), styles); - } - FlowChild::Parbreak => { - let leading = styles.get(ParNode::LEADING); - let spacing = styles.get(ParNode::SPACING); - let amount = leading + spacing; - layouter.layout_spacing(amount.into(), styles); - } - FlowChild::Colbreak => { - layouter.finish_region(); - } FlowChild::Spacing(kind) => { layouter.layout_spacing(*kind, styles); } FlowChild::Node(ref node) => { layouter.layout_node(ctx, node, styles)?; } + FlowChild::Colbreak => { + layouter.finish_region(); + } } } @@ -72,11 +60,18 @@ impl Debug for FlowNode { impl Debug for FlowChild { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { - Self::Leading => f.pad("Leading"), - Self::Parbreak => f.pad("Parbreak"), - Self::Colbreak => f.pad("Colbreak"), Self::Spacing(kind) => write!(f, "{:?}", kind), Self::Node(node) => node.fmt(f), + Self::Colbreak => f.pad("Colbreak"), + } + } +} + +impl PartialOrd for FlowChild { + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { + match (self, other) { + (Self::Spacing(a), Self::Spacing(b)) => a.partial_cmp(b), + _ => None, } } } diff --git a/src/library/layout/pad.rs b/src/library/layout/pad.rs index e688e423..2be21bcb 100644 --- a/src/library/layout/pad.rs +++ b/src/library/layout/pad.rs @@ -13,12 +13,12 @@ pub struct PadNode { impl PadNode { fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { let all = args.find()?; - let hor = args.named("horizontal")?; - let ver = args.named("vertical")?; - let left = args.named("left")?.or(hor).or(all).unwrap_or_default(); - let top = args.named("top")?.or(ver).or(all).unwrap_or_default(); - let right = args.named("right")?.or(hor).or(all).unwrap_or_default(); - let bottom = args.named("bottom")?.or(ver).or(all).unwrap_or_default(); + let x = args.named("x")?; + let y = args.named("y")?; + let left = args.named("left")?.or(x).or(all).unwrap_or_default(); + let top = args.named("top")?.or(y).or(all).unwrap_or_default(); + let right = args.named("right")?.or(x).or(all).unwrap_or_default(); + let bottom = args.named("bottom")?.or(y).or(all).unwrap_or_default(); let body: LayoutNode = args.expect("body")?; let padding = Sides::new(left, top, right, bottom); Ok(Content::block(body.padded(padding))) diff --git a/src/library/layout/page.rs b/src/library/layout/page.rs index 8e5801ea..4307d2f9 100644 --- a/src/library/layout/page.rs +++ b/src/library/layout/page.rs @@ -165,8 +165,8 @@ pub struct PagebreakNode; #[node] impl PagebreakNode { fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { - let soft = args.named("soft")?.unwrap_or(false); - Ok(Content::Pagebreak(soft)) + let weak = args.named("weak")?.unwrap_or(false); + Ok(Content::Pagebreak { weak }) } } diff --git a/src/library/layout/spacing.rs b/src/library/layout/spacing.rs index 3468af5e..8a96e378 100644 --- a/src/library/layout/spacing.rs +++ b/src/library/layout/spacing.rs @@ -1,4 +1,7 @@ +use std::cmp::Ordering; + use crate::library::prelude::*; +use crate::library::text::ParNode; /// Horizontal spacing. pub struct HNode; @@ -6,7 +9,9 @@ pub struct HNode; #[node] impl HNode { fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { - Ok(Content::Horizontal(args.expect("spacing")?)) + let amount = args.expect("spacing")?; + let weak = args.named("weak")?.unwrap_or(false); + Ok(Content::Horizontal { amount, weak }) } } @@ -16,7 +21,9 @@ pub struct VNode; #[node] impl VNode { fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { - Ok(Content::Vertical(args.expect("spacing")?)) + let amount = args.expect("spacing")?; + let weak = args.named("weak")?.unwrap_or(false); + Ok(Content::Vertical { amount, weak, generated: false }) } } @@ -25,7 +32,8 @@ impl VNode { pub enum Spacing { /// Spacing specified in absolute terms and relative to the parent's size. Relative(Relative<RawLength>), - /// Spacing specified as a fraction of the remaining free space in the parent. + /// Spacing specified as a fraction of the remaining free space in the + /// parent. Fractional(Fraction), } @@ -42,6 +50,16 @@ impl From<Length> for Spacing { } } +impl PartialOrd for Spacing { + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { + match (self, other) { + (Self::Relative(a), Self::Relative(b)) => a.partial_cmp(b), + (Self::Fractional(a), Self::Fractional(b)) => a.partial_cmp(b), + _ => None, + } + } +} + castable! { Spacing, Expected: "relative length or fraction", @@ -50,3 +68,24 @@ castable! { Value::Relative(v) => Self::Relative(v), Value::Fraction(v) => Self::Fractional(v), } + +/// Spacing around and between block-level nodes, relative to paragraph spacing. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub struct BlockSpacing(Relative<RawLength>); + +castable!(BlockSpacing: Relative<RawLength>); + +impl Resolve for BlockSpacing { + type Output = Length; + + 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()) + } +} diff --git a/src/library/layout/stack.rs b/src/library/layout/stack.rs index f915c215..bbfeeab0 100644 --- a/src/library/layout/stack.rs +++ b/src/library/layout/stack.rs @@ -1,5 +1,6 @@ use super::{AlignNode, Spacing}; use crate::library::prelude::*; +use crate::library::text::ParNode; /// Arrange nodes and spacing along an axis. #[derive(Debug, Hash)] @@ -180,7 +181,16 @@ impl<'a> StackLayouter<'a> { .downcast::<AlignNode>() .and_then(|node| node.aligns.get(self.axis)) .map(|align| align.resolve(styles)) - .unwrap_or(self.dir.start().into()); + .unwrap_or_else(|| { + if let Some(Content::Styled(styled)) = node.downcast::<Content>() { + let map = &styled.1; + if map.contains(ParNode::ALIGN) { + return StyleChain::with_root(&styled.1).get(ParNode::ALIGN); + } + } + + self.dir.start().into() + }); let frames = node.layout(ctx, &self.regions, styles)?; let len = frames.len(); diff --git a/src/library/math/mod.rs b/src/library/math/mod.rs index 345bb3f6..7b4998ca 100644 --- a/src/library/math/mod.rs +++ b/src/library/math/mod.rs @@ -1,5 +1,6 @@ //! Mathematical formulas. +use crate::library::layout::BlockSpacing; use crate::library::prelude::*; use crate::library::text::FontFamily; @@ -19,6 +20,13 @@ impl MathNode { pub const FAMILY: Smart<FontFamily> = Smart::Custom(FontFamily::new("Latin Modern Math")); + /// 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 construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { Ok(Content::show(Self { formula: args.expect("formula")?, @@ -36,7 +44,11 @@ impl Show for MathNode { } fn realize(&self, _: &mut Context, _: StyleChain) -> TypResult<Content> { - Ok(Content::Text(self.formula.trim().into())) + let mut realized = Content::Text(self.formula.trim().into()); + if self.display { + realized = Content::block(realized); + } + Ok(realized) } fn finalize( @@ -50,12 +62,10 @@ impl Show for MathNode { map.set_family(family.clone(), styles); } - realized = realized.styled_with_map(map); - if self.display { - realized = Content::block(realized); + realized = realized.spaced(styles.get(Self::ABOVE), styles.get(Self::BELOW)); } - Ok(realized) + Ok(realized.styled_with_map(map)) } } diff --git a/src/library/mod.rs b/src/library/mod.rs index c68915c8..e90e5cc4 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -88,7 +88,7 @@ pub fn new() -> Scope { std.def_fn("letter", utility::letter); std.def_fn("roman", utility::roman); std.def_fn("symbol", utility::symbol); - std.def_fn("lipsum", utility::lipsum); + std.def_fn("lorem", utility::lorem); // Predefined colors. std.def_const("black", Color::BLACK); diff --git a/src/library/structure/heading.rs b/src/library/structure/heading.rs index a6c87912..4f6c54f3 100644 --- a/src/library/structure/heading.rs +++ b/src/library/structure/heading.rs @@ -1,3 +1,4 @@ +use crate::library::layout::BlockSpacing; use crate::library::prelude::*; use crate::library::text::{FontFamily, TextNode, TextSize, Toggle}; @@ -6,7 +7,7 @@ use crate::library::text::{FontFamily, TextNode, TextSize, Toggle}; pub struct HeadingNode { /// The logical nesting depth of the section, starting from one. In the /// default style, this controls the text size of the heading. - pub level: usize, + pub level: NonZeroUsize, /// The heading's contents. pub body: Content, } @@ -22,8 +23,12 @@ impl HeadingNode { /// The size of text in the heading. #[property(referenced)] pub const SIZE: Leveled<TextSize> = Leveled::Mapping(|level| { - let upscale = (1.6 - 0.1 * level as f64).max(0.75); - TextSize(Em::new(upscale).into()) + let size = match level.get() { + 1 => 1.4, + 2 => 1.2, + _ => 1.0, + }; + TextSize(Em::new(size).into()) }); /// Whether text in the heading is strengthend. @@ -36,21 +41,24 @@ impl HeadingNode { #[property(referenced)] pub const UNDERLINE: Leveled<bool> = Leveled::Value(false); - /// The extra padding above the heading. - #[property(referenced)] - pub const ABOVE: Leveled<RawLength> = Leveled::Value(Length::zero().into()); - /// The extra padding below the heading. - #[property(referenced)] - pub const BELOW: Leveled<RawLength> = Leveled::Value(Length::zero().into()); - - /// Whether the heading is block-level. - #[property(referenced)] - pub const BLOCK: Leveled<bool> = Leveled::Value(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 Context, args: &mut Args) -> TypResult<Content> { Ok(Content::show(Self { body: args.expect("body")?, - level: args.named("level")?.unwrap_or(1), + level: args.named("level")?.unwrap_or(NonZeroUsize::new(1).unwrap()), })) } } @@ -58,13 +66,13 @@ impl HeadingNode { impl Show for HeadingNode { fn encode(&self) -> Dict { dict! { - "level" => Value::Int(self.level as i64), + "level" => Value::Int(self.level.get() as i64), "body" => Value::Content(self.body.clone()), } } fn realize(&self, _: &mut Context, _: StyleChain) -> TypResult<Content> { - Ok(self.body.clone()) + Ok(Content::block(self.body.clone())) } fn finalize( @@ -103,11 +111,6 @@ impl Show for HeadingNode { } realized = realized.styled_with_map(map); - - if resolve!(Self::BLOCK) { - realized = Content::block(realized); - } - realized = realized.spaced( resolve!(Self::ABOVE).resolve(styles), resolve!(Self::BELOW).resolve(styles), @@ -123,19 +126,19 @@ pub enum Leveled<T> { /// A bare value. Value(T), /// A simple mapping from a heading level to a value. - Mapping(fn(usize) -> T), + 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, ctx: &mut Context, level: usize) -> TypResult<T> { + pub fn resolve(&self, ctx: &mut Context, level: NonZeroUsize) -> TypResult<T> { Ok(match self { Self::Value(value) => value.clone(), Self::Mapping(mapping) => mapping(level), Self::Func(func, span) => { - let args = Args::from_values(*span, [Value::Int(level as i64)]); + let args = Args::from_values(*span, [Value::Int(level.get() as i64)]); func.call(ctx, args)?.cast().at(*span)? } }) diff --git a/src/library/structure/list.rs b/src/library/structure/list.rs index ac705156..4356ffb4 100644 --- a/src/library/structure/list.rs +++ b/src/library/structure/list.rs @@ -2,7 +2,7 @@ use std::fmt::Write; use unscanny::Scanner; -use crate::library::layout::{GridNode, TrackSizing}; +use crate::library::layout::{BlockSpacing, GridNode, TrackSizing}; use crate::library::prelude::*; use crate::library::text::ParNode; use crate::library::utility::Numbering; @@ -12,9 +12,10 @@ use crate::library::utility::Numbering; pub struct ListNode<const L: ListKind = UNORDERED> { /// Where the list starts. pub start: usize, - /// If false, there is paragraph spacing between the items, if true - /// there is list spacing between the items. + /// If true, the items are separated by leading instead of list spacing. 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. pub items: StyleVec<ListItem>, } @@ -38,10 +39,6 @@ impl<const L: ListKind> ListNode<L> { /// How the list is labelled. #[property(referenced)] pub const LABEL: Label = Label::Default; - - /// The spacing between the list items of a non-wide list. - #[property(resolve)] - pub const SPACING: RawLength = RawLength::zero(); /// The indentation of each item's label. #[property(resolve)] pub const INDENT: RawLength = RawLength::zero(); @@ -49,17 +46,21 @@ impl<const L: ListKind> ListNode<L> { #[property(resolve)] pub const BODY_INDENT: RawLength = Em::new(0.5).into(); - /// The extra padding above the list. - #[property(resolve)] - pub const ABOVE: RawLength = RawLength::zero(); - /// The extra padding below the list. + /// 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. #[property(resolve)] - pub const BELOW: RawLength = RawLength::zero(); + pub const SPACING: BlockSpacing = Ratio::one().into(); fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { Ok(Content::show(Self { start: args.named("start")?.unwrap_or(1), tight: args.named("tight")?.unwrap_or(true), + attached: args.named("attached")?.unwrap_or(false), items: args .all()? .into_iter() @@ -78,6 +79,7 @@ impl<const L: ListKind> Show for ListNode<L> { dict! { "start" => Value::Int(self.start as i64), "tight" => Value::Bool(self.tight), + "attached" => Value::Bool(self.attached), "items" => Value::Array( self.items .items() @@ -103,14 +105,12 @@ impl<const L: ListKind> Show for ListNode<L> { number += 1; } - let leading = styles.get(ParNode::LEADING); - let spacing = if self.tight { - styles.get(Self::SPACING) + let gutter = if self.tight { + styles.get(ParNode::LEADING) } else { - styles.get(ParNode::SPACING) + styles.get(Self::SPACING) }; - let gutter = leading + spacing; let indent = styles.get(Self::INDENT); let body_indent = styles.get(Self::BODY_INDENT); @@ -132,7 +132,19 @@ impl<const L: ListKind> Show for ListNode<L> { styles: StyleChain, realized: Content, ) -> TypResult<Content> { - Ok(realized.spaced(styles.get(Self::ABOVE), styles.get(Self::BELOW))) + 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)) } } diff --git a/src/library/structure/table.rs b/src/library/structure/table.rs index 191d3dd3..7b3f2ac5 100644 --- a/src/library/structure/table.rs +++ b/src/library/structure/table.rs @@ -1,4 +1,4 @@ -use crate::library::layout::{GridNode, TrackSizing}; +use crate::library::layout::{BlockSpacing, GridNode, TrackSizing}; use crate::library::prelude::*; /// A table of items. @@ -15,16 +15,24 @@ pub struct TableNode { #[node(showable)] impl TableNode { /// The primary cell fill color. + #[property(shorthand(fill))] pub const PRIMARY: Option<Paint> = None; /// The secondary cell fill color. + #[property(shorthand(fill))] pub const SECONDARY: Option<Paint> = None; /// How to stroke the cells. #[property(resolve, fold)] pub const STROKE: Option<RawStroke> = Some(RawStroke::default()); - /// How much to pad the cells's content. pub const PADDING: Relative<RawLength> = Length::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 Context, args: &mut Args) -> TypResult<Content> { let columns = args.named("columns")?.unwrap_or_default(); let rows = args.named("rows")?.unwrap_or_default(); @@ -40,16 +48,6 @@ impl TableNode { cells: args.all()?, })) } - - fn set(args: &mut Args) -> TypResult<StyleMap> { - let mut styles = StyleMap::new(); - let fill = args.named("fill")?; - styles.set_opt(Self::PRIMARY, args.named("primary")?.or(fill)); - styles.set_opt(Self::SECONDARY, args.named("secondary")?.or(fill)); - styles.set_opt(Self::STROKE, args.named("stroke")?); - styles.set_opt(Self::PADDING, args.named("padding")?); - Ok(styles) - } } impl Show for TableNode { @@ -99,4 +97,13 @@ impl Show for TableNode { cells, })) } + + fn finalize( + &self, + _: &mut Context, + styles: StyleChain, + realized: Content, + ) -> TypResult<Content> { + Ok(realized.spaced(styles.get(Self::ABOVE), styles.get(Self::BELOW))) + } } diff --git a/src/library/text/deco.rs b/src/library/text/deco.rs index 52f8ea80..70040f9c 100644 --- a/src/library/text/deco.rs +++ b/src/library/text/deco.rs @@ -24,7 +24,6 @@ impl<const L: DecoLine> DecoNode<L> { /// tables if `auto`. #[property(shorthand, resolve, fold)] pub const STROKE: Smart<RawStroke> = Smart::Auto; - /// Position of the line relative to the baseline, read from the font tables /// if `auto`. #[property(resolve)] @@ -32,7 +31,6 @@ impl<const L: DecoLine> DecoNode<L> { /// Amount that the line will be longer or shorter than its associated text. #[property(resolve)] pub const EXTENT: RawLength = RawLength::zero(); - /// Whether the line skips sections in which it would collide /// with the glyphs. Does not apply to strikethrough. pub const EVADE: bool = true; diff --git a/src/library/text/mod.rs b/src/library/text/mod.rs index 3cfbc55d..ecc0c546 100644 --- a/src/library/text/mod.rs +++ b/src/library/text/mod.rs @@ -223,11 +223,7 @@ impl Fold for TextSize { } } -castable! { - TextSize, - Expected: "length", - Value::Length(v) => Self(v), -} +castable!(TextSize: RawLength); /// Specifies the bottom or top edge of text. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] @@ -290,11 +286,7 @@ impl Resolve for Smart<HorizontalDir> { #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct Hyphenate(pub bool); -castable! { - Hyphenate, - Expected: "boolean", - Value::Bool(v) => Self(v), -} +castable!(Hyphenate: bool); impl Resolve for Smart<Hyphenate> { type Output = bool; diff --git a/src/library/text/par.rs b/src/library/text/par.rs index 4694993e..669d07ba 100644 --- a/src/library/text/par.rs +++ b/src/library/text/par.rs @@ -1,3 +1,4 @@ +use std::cmp::Ordering; use std::sync::Arc; use unicode_bidi::{BidiInfo, Level}; @@ -15,12 +16,12 @@ use crate::util::{EcoString, MaybeShared}; pub struct ParNode(pub StyleVec<ParChild>); /// A uniformly styled atomic piece of a paragraph. -#[derive(Hash)] +#[derive(Hash, PartialEq)] pub enum ParChild { /// A chunk of text. Text(EcoString), - /// A smart quote, may be single (`false`) or double (`true`). - Quote(bool), + /// A single or double smart quote. + Quote { double: bool }, /// Horizontal spacing between other children. Spacing(Spacing), /// An arbitrary inline-level node. @@ -34,10 +35,12 @@ impl ParNode { pub const LEADING: RawLength = Em::new(0.65).into(); /// The extra spacing between paragraphs. #[property(resolve)] - pub const SPACING: RawLength = Em::new(0.55).into(); + pub const SPACING: RawLength = Em::new(1.2).into(); /// The indent the first line of a consecutive paragraph should have. #[property(resolve)] pub const INDENT: RawLength = RawLength::zero(); + /// Whether to allow paragraph spacing when there is paragraph indent. + pub const SPACING_AND_INDENT: bool = false; /// How to align text and inline objects in their line. #[property(resolve)] @@ -50,10 +53,13 @@ impl ParNode { fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { // The paragraph constructor is special: It doesn't create a paragraph - // since that happens automatically through markup. Instead, it just - // lifts the passed body to the block level so that it won't merge with - // adjacent stuff and it styles the contained paragraphs. - Ok(Content::Block(args.expect("body")?)) + // node. Instead, it just ensures that the passed content lives is in a + // separate paragraph and styles it. + Ok(Content::sequence(vec![ + Content::Parbreak, + args.expect("body")?, + Content::Parbreak, + ])) } } @@ -91,13 +97,22 @@ impl Debug for ParChild { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Self::Text(text) => write!(f, "Text({:?})", text), - Self::Quote(double) => write!(f, "Quote({})", double), + Self::Quote { double } => write!(f, "Quote({double})"), Self::Spacing(kind) => write!(f, "{:?}", kind), Self::Node(node) => node.fmt(f), } } } +impl PartialOrd for ParChild { + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { + match (self, other) { + (Self::Spacing(a), Self::Spacing(b)) => a.partial_cmp(b), + _ => None, + } + } +} + /// A horizontal alignment. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct HorizontalAlign(pub RawAlign); @@ -169,7 +184,7 @@ pub struct LinebreakNode; impl LinebreakNode { fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { let justified = args.named("justified")?.unwrap_or(false); - Ok(Content::Linebreak(justified)) + Ok(Content::Linebreak { justified }) } } @@ -432,7 +447,7 @@ fn collect<'a>( } Segment::Text(full.len() - prev) } - ParChild::Quote(double) => { + ParChild::Quote { double } => { let prev = full.len(); if styles.get(TextNode::SMART_QUOTES) { let lang = styles.get(TextNode::LANG); @@ -440,7 +455,7 @@ fn collect<'a>( let quotes = Quotes::from_lang(lang, region); let peeked = iter.peek().and_then(|(child, _)| match child { ParChild::Text(text) => text.chars().next(), - ParChild::Quote(_) => Some('"'), + ParChild::Quote { .. } => Some('"'), ParChild::Spacing(_) => Some(SPACING_REPLACE), ParChild::Node(_) => Some(NODE_REPLACE), }); diff --git a/src/library/text/raw.rs b/src/library/text/raw.rs index 13daa1b9..ee6c6356 100644 --- a/src/library/text/raw.rs +++ b/src/library/text/raw.rs @@ -4,6 +4,7 @@ use syntect::highlighting::{FontStyle, Highlighter, Style, Theme, ThemeSet}; use syntect::parsing::SyntaxSet; use super::{FontFamily, Hyphenate, TextNode, Toggle}; +use crate::library::layout::BlockSpacing; use crate::library::prelude::*; use crate::source::SourceId; use crate::syntax::{self, RedNode}; @@ -26,13 +27,20 @@ pub struct RawNode { #[node(showable)] impl RawNode { + /// The language to syntax-highlight in. + #[property(referenced)] + pub const LANG: Option<EcoString> = None; + /// The raw text's font family. Just the normal text family if `none`. #[property(referenced)] pub const FAMILY: Smart<FontFamily> = Smart::Custom(FontFamily::new("IBM Plex Mono")); - /// The language to syntax-highlight in. - #[property(referenced)] - 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 Context, args: &mut Args) -> TypResult<Content> { Ok(Content::show(Self { @@ -59,7 +67,7 @@ impl Show for RawNode { .unwrap_or(Color::BLACK) .into(); - if matches!( + let mut realized = if matches!( lang.map(|s| s.to_lowercase()).as_deref(), Some("typ" | "typst") ) { @@ -72,7 +80,7 @@ impl Show for RawNode { seq.push(styled(&self.text[range], foreground, style)); }); - Ok(Content::sequence(seq)) + Content::sequence(seq) } else if let Some(syntax) = lang.and_then(|token| SYNTAXES.find_syntax_by_token(&token)) { @@ -80,7 +88,7 @@ impl Show for RawNode { let mut highlighter = HighlightLines::new(syntax, &THEME); for (i, line) in self.text.lines().enumerate() { if i != 0 { - seq.push(Content::Linebreak(false)); + seq.push(Content::Linebreak { justified: false }); } for (style, piece) in highlighter.highlight(line, &SYNTAXES) { @@ -88,10 +96,16 @@ impl Show for RawNode { } } - Ok(Content::sequence(seq)) + Content::sequence(seq) } else { - Ok(Content::Text(self.text.clone())) + Content::Text(self.text.clone()) + }; + + if self.block { + realized = Content::block(realized); } + + Ok(realized) } fn finalize( @@ -109,13 +123,11 @@ impl Show for RawNode { map.set_family(family.clone(), styles); } - realized = realized.styled_with_map(map); - if self.block { - realized = Content::block(realized); + realized = realized.spaced(styles.get(Self::ABOVE), styles.get(Self::BELOW)); } - Ok(realized) + Ok(realized.styled_with_map(map)) } } diff --git a/src/library/utility/blind.rs b/src/library/utility/blind.rs index a4cfec90..0075ab91 100644 --- a/src/library/utility/blind.rs +++ b/src/library/utility/blind.rs @@ -3,7 +3,7 @@ use lipsum::lipsum_from_seed; use crate::library::prelude::*; /// Create blind text. -pub fn lipsum(_: &mut Context, args: &mut Args) -> TypResult<Value> { +pub fn lorem(_: &mut Context, args: &mut Args) -> TypResult<Value> { let words: usize = args.expect("number of words")?; Ok(Value::Str(lipsum_from_seed(words, 97).into())) } |
