summaryrefslogtreecommitdiff
path: root/src/library
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-04-30 14:12:28 +0200
committerLaurenz <laurmaedje@gmail.com>2022-04-30 14:12:28 +0200
commitf9e115daf54c29358f890b137f50a33a781af680 (patch)
tree496de52246629ea8039db6beea94eb779ed2851d /src/library
parentf7c67cde72e6a67f45180856b332bae9863243bd (diff)
New block spacing model
Diffstat (limited to 'src/library')
-rw-r--r--src/library/graphics/shape.rs1
-rw-r--r--src/library/graphics/transform.rs4
-rw-r--r--src/library/layout/align.rs12
-rw-r--r--src/library/layout/columns.rs5
-rw-r--r--src/library/layout/flow.rs41
-rw-r--r--src/library/layout/pad.rs12
-rw-r--r--src/library/layout/page.rs4
-rw-r--r--src/library/layout/spacing.rs45
-rw-r--r--src/library/layout/stack.rs12
-rw-r--r--src/library/math/mod.rs20
-rw-r--r--src/library/mod.rs2
-rw-r--r--src/library/structure/heading.rs51
-rw-r--r--src/library/structure/list.rs48
-rw-r--r--src/library/structure/table.rs31
-rw-r--r--src/library/text/deco.rs2
-rw-r--r--src/library/text/mod.rs12
-rw-r--r--src/library/text/par.rs39
-rw-r--r--src/library/text/raw.rs36
-rw-r--r--src/library/utility/blind.rs2
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()))
}