summaryrefslogtreecommitdiff
path: root/src/model/content.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/model/content.rs')
-rw-r--r--src/model/content.rs180
1 files changed, 122 insertions, 58 deletions
diff --git a/src/model/content.rs b/src/model/content.rs
index 6e1e2f1c..31255a29 100644
--- a/src/model/content.rs
+++ b/src/model/content.rs
@@ -40,29 +40,32 @@ use crate::util::EcoString;
pub enum Content {
/// A word space.
Space,
- /// A forced line break. If `true`, the preceding line can still be
- /// justified, if `false` not.
- Linebreak(bool),
+ /// A forced line break.
+ Linebreak { justified: bool },
/// Horizontal spacing.
- Horizontal(Spacing),
+ Horizontal { amount: Spacing, weak: bool },
/// Plain text.
Text(EcoString),
- /// A smart quote, may be single (`false`) or double (`true`).
- Quote(bool),
+ /// A smart quote.
+ Quote { double: bool },
/// An inline-level node.
Inline(LayoutNode),
/// A paragraph break.
Parbreak,
/// A column break.
- Colbreak,
+ Colbreak { weak: bool },
/// Vertical spacing.
- Vertical(Spacing),
+ Vertical {
+ amount: Spacing,
+ weak: bool,
+ generated: bool,
+ },
/// A block-level node.
Block(LayoutNode),
/// A list / enum item.
Item(ListItem),
/// A page break.
- Pagebreak(bool),
+ Pagebreak { weak: bool },
/// A page node.
Page(PageNode),
/// A node that can be realized with styles.
@@ -153,21 +156,28 @@ impl Content {
Self::show(DecoNode::<UNDERLINE>(self))
}
- /// Add vertical spacing above and below the node.
- pub fn spaced(self, above: Length, below: Length) -> Self {
- if above.is_zero() && below.is_zero() {
+ /// Add weak vertical spacing above and below the node.
+ pub fn spaced(self, above: Option<Length>, below: Option<Length>) -> Self {
+ if above.is_none() && below.is_none() {
return self;
}
let mut seq = vec![];
- if !above.is_zero() {
- seq.push(Content::Vertical(above.into()));
+ if let Some(above) = above {
+ seq.push(Content::Vertical {
+ amount: above.into(),
+ weak: true,
+ generated: true,
+ });
}
seq.push(self);
-
- if !below.is_zero() {
- seq.push(Content::Vertical(below.into()));
+ if let Some(below) = below {
+ seq.push(Content::Vertical {
+ amount: below.into(),
+ weak: true,
+ generated: true,
+ });
}
Self::sequence(seq)
@@ -219,17 +229,21 @@ impl Debug for Content {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::Space => f.pad("Space"),
- Self::Linebreak(justified) => write!(f, "Linebreak({justified})"),
- Self::Horizontal(kind) => write!(f, "Horizontal({kind:?})"),
+ Self::Linebreak { justified } => write!(f, "Linebreak({justified})"),
+ Self::Horizontal { amount, weak } => {
+ write!(f, "Horizontal({amount:?}, {weak})")
+ }
Self::Text(text) => write!(f, "Text({text:?})"),
- Self::Quote(double) => write!(f, "Quote({double})"),
+ Self::Quote { double } => write!(f, "Quote({double})"),
Self::Inline(node) => node.fmt(f),
Self::Parbreak => f.pad("Parbreak"),
- Self::Colbreak => f.pad("Colbreak"),
- Self::Vertical(kind) => write!(f, "Vertical({kind:?})"),
+ Self::Colbreak { weak } => write!(f, "Colbreak({weak})"),
+ Self::Vertical { amount, weak, generated } => {
+ write!(f, "Vertical({amount:?}, {weak}, {generated})")
+ }
Self::Block(node) => node.fmt(f),
Self::Item(item) => item.fmt(f),
- Self::Pagebreak(soft) => write!(f, "Pagebreak({soft})"),
+ Self::Pagebreak { weak } => write!(f, "Pagebreak({weak})"),
Self::Page(page) => page.fmt(f),
Self::Show(node) => node.fmt(f),
Self::Styled(styled) => {
@@ -360,7 +374,7 @@ impl<'a, 'ctx> Builder<'a, 'ctx> {
return Ok(());
}
- let keep = matches!(content, Content::Pagebreak(false));
+ let keep = matches!(content, Content::Pagebreak { weak: false });
self.interrupt(Interruption::Page, styles, keep)?;
if let Some(doc) = &mut self.doc {
@@ -419,10 +433,8 @@ impl<'a, 'ctx> Builder<'a, 'ctx> {
if intr >= Interruption::Par {
if !self.par.is_empty() {
- self.flow.0.weak(FlowChild::Leading, 0, styles);
mem::take(&mut self.par).finish(self);
}
- self.flow.0.weak(FlowChild::Leading, 0, styles);
}
if intr >= Interruption::Page {
@@ -456,8 +468,8 @@ struct DocBuilder<'a> {
impl<'a> DocBuilder<'a> {
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) {
match content {
- Content::Pagebreak(soft) => {
- self.keep_next = !soft;
+ Content::Pagebreak { weak } => {
+ self.keep_next = !weak;
}
Content::Page(page) => {
self.pages.push(page.clone(), styles);
@@ -483,16 +495,31 @@ struct FlowBuilder<'a>(CollapsingBuilder<'a, FlowChild>);
impl<'a> FlowBuilder<'a> {
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
+ // Weak flow elements:
+ // Weakness | Element
+ // 0 | weak colbreak
+ // 1 | weak fractional spacing
+ // 2 | weak spacing
+ // 3 | generated weak spacing
+ // 4 | generated weak fractional spacing
+ // 5 | par spacing
+
match content {
- Content::Parbreak => {
- self.0.weak(FlowChild::Parbreak, 1, styles);
- }
- Content::Colbreak => {
- self.0.destructive(FlowChild::Colbreak, styles);
+ Content::Parbreak => {}
+ Content::Colbreak { weak } => {
+ if *weak {
+ self.0.weak(FlowChild::Colbreak, styles, 0);
+ } else {
+ self.0.destructive(FlowChild::Colbreak, styles);
+ }
}
- Content::Vertical(kind) => {
- let child = FlowChild::Spacing(*kind);
- if kind.is_fractional() {
+ &Content::Vertical { amount, weak, generated } => {
+ let child = FlowChild::Spacing(amount);
+ let frac = amount.is_fractional();
+ if weak {
+ let weakness = 1 + u8::from(frac) + 2 * u8::from(generated);
+ self.0.weak(child, styles, weakness);
+ } else if frac {
self.0.destructive(child, styles);
} else {
self.0.ignorant(child, styles);
@@ -512,6 +539,18 @@ impl<'a> FlowBuilder<'a> {
true
}
+ fn par(&mut self, par: ParNode, styles: StyleChain<'a>, indent: bool) {
+ let amount = if indent && !styles.get(ParNode::SPACING_AND_INDENT) {
+ styles.get(ParNode::LEADING).into()
+ } else {
+ styles.get(ParNode::SPACING).into()
+ };
+
+ self.0.weak(FlowChild::Spacing(amount), styles, 5);
+ self.0.supportive(FlowChild::Node(par.pack()), styles);
+ self.0.weak(FlowChild::Spacing(amount), styles, 5);
+ }
+
fn finish(self, doc: &mut DocBuilder<'a>, styles: StyleChain<'a>) {
let (flow, shared) = self.0.finish();
let styles = if flow.is_empty() { styles } else { shared };
@@ -530,24 +569,34 @@ struct ParBuilder<'a>(CollapsingBuilder<'a, ParChild>);
impl<'a> ParBuilder<'a> {
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
+ // Weak par elements:
+ // Weakness | Element
+ // 0 | weak fractional spacing
+ // 1 | weak spacing
+ // 2 | space
+
match content {
Content::Space => {
- self.0.weak(ParChild::Text(' '.into()), 0, styles);
+ self.0.weak(ParChild::Text(' '.into()), styles, 2);
}
- Content::Linebreak(justified) => {
- let c = if *justified { '\u{2028}' } else { '\n' };
+ &Content::Linebreak { justified } => {
+ let c = if justified { '\u{2028}' } else { '\n' };
self.0.destructive(ParChild::Text(c.into()), styles);
}
- Content::Horizontal(kind) => {
- let child = ParChild::Spacing(*kind);
- if kind.is_fractional() {
+ &Content::Horizontal { amount, weak } => {
+ let child = ParChild::Spacing(amount);
+ let frac = amount.is_fractional();
+ if weak {
+ let weakness = u8::from(!frac);
+ self.0.weak(child, styles, weakness);
+ } else if frac {
self.0.destructive(child, styles);
} else {
self.0.ignorant(child, styles);
}
}
- Content::Quote(double) => {
- self.0.supportive(ParChild::Quote(*double), styles);
+ &Content::Quote { double } => {
+ self.0.supportive(ParChild::Quote { double }, styles);
}
Content::Text(text) => {
self.0.supportive(ParChild::Text(text.clone()), styles);
@@ -575,7 +624,7 @@ impl<'a> ParBuilder<'a> {
.items()
.find_map(|child| match child {
ParChild::Spacing(_) => None,
- ParChild::Text(_) | ParChild::Quote(_) => Some(true),
+ ParChild::Text(_) | ParChild::Quote { .. } => Some(true),
ParChild::Node(_) => Some(false),
})
.unwrap_or_default()
@@ -585,10 +634,8 @@ impl<'a> ParBuilder<'a> {
.items()
.rev()
.find_map(|child| match child {
- FlowChild::Leading => None,
- FlowChild::Parbreak => None,
+ FlowChild::Spacing(_) => None,
FlowChild::Node(node) => Some(node.is::<ParNode>()),
- FlowChild::Spacing(_) => Some(false),
FlowChild::Colbreak => Some(false),
})
.unwrap_or_default()
@@ -596,8 +643,7 @@ impl<'a> ParBuilder<'a> {
children.push_front(ParChild::Spacing(indent.into()));
}
- let node = ParNode(children).pack();
- parent.flow.0.supportive(FlowChild::Node(node), shared);
+ parent.flow.par(ParNode(children), shared, !indent.is_zero());
}
fn is_empty(&self) -> bool {
@@ -611,19 +657,24 @@ struct ListBuilder<'a> {
items: StyleVecBuilder<'a, ListItem>,
/// Whether the list contains no paragraph breaks.
tight: bool,
+ /// Whether the list can be attached.
+ attachable: bool,
/// Trailing content for which it is unclear whether it is part of the list.
staged: Vec<(&'a Content, StyleChain<'a>)>,
}
impl<'a> ListBuilder<'a> {
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
- match content {
- Content::Space if !self.items.is_empty() => {
- self.staged.push((content, styles));
- }
- Content::Parbreak if !self.items.is_empty() => {
- self.staged.push((content, styles));
+ if self.items.is_empty() {
+ match content {
+ Content::Space => {}
+ Content::Item(_) => {}
+ Content::Parbreak => self.attachable = false,
+ _ => self.attachable = true,
}
+ }
+
+ match content {
Content::Item(item)
if self
.items
@@ -634,6 +685,9 @@ impl<'a> ListBuilder<'a> {
self.items.push(item.clone(), styles);
self.tight &= self.staged.drain(..).all(|(t, _)| *t != Content::Parbreak);
}
+ Content::Space | Content::Parbreak if !self.items.is_empty() => {
+ self.staged.push((content, styles));
+ }
_ => return false,
}
@@ -647,10 +701,17 @@ impl<'a> ListBuilder<'a> {
None => return Ok(()),
};
+ let start = 1;
let tight = self.tight;
+ let attached = tight && self.attachable;
+
let content = match kind {
- UNORDERED => Content::show(ListNode::<UNORDERED> { start: 1, tight, items }),
- ORDERED | _ => Content::show(ListNode::<ORDERED> { start: 1, tight, items }),
+ UNORDERED => {
+ Content::show(ListNode::<UNORDERED> { start, tight, attached, items })
+ }
+ ORDERED | _ => {
+ Content::show(ListNode::<ORDERED> { start, tight, attached, items })
+ }
};
let stored = parent.scratch.templates.alloc(content);
@@ -660,6 +721,8 @@ impl<'a> ListBuilder<'a> {
parent.accept(content, styles)?;
}
+ parent.list.attachable = true;
+
Ok(())
}
@@ -673,6 +736,7 @@ impl Default for ListBuilder<'_> {
Self {
items: StyleVecBuilder::default(),
tight: true,
+ attachable: true,
staged: vec![],
}
}