summaryrefslogtreecommitdiff
path: root/src/library/par.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-12-15 11:11:57 +0100
committerLaurenz <laurmaedje@gmail.com>2021-12-15 11:11:57 +0100
commitae38be9097bbb32142ef776e77e627ac12379000 (patch)
treef365a348d4c77d2d607d37fee3bc65a601d00a64 /src/library/par.rs
parentfe21c4d399d291e75165b664762f0aa8bdc4724a (diff)
Set Rules Episode IV: A New Fold
Diffstat (limited to 'src/library/par.rs')
-rw-r--r--src/library/par.rs147
1 files changed, 76 insertions, 71 deletions
diff --git a/src/library/par.rs b/src/library/par.rs
index e7433e3e..6ea960bf 100644
--- a/src/library/par.rs
+++ b/src/library/par.rs
@@ -6,7 +6,7 @@ use unicode_bidi::{BidiInfo, Level};
use xi_unicode::LineBreakIterator;
use super::prelude::*;
-use super::{shape, Decoration, ShapedText, Spacing, TextNode};
+use super::{shape, ShapedText, SpacingKind, SpacingNode, TextNode};
use crate::util::{EcoString, RangeExt, RcExt, SliceExt};
/// `par`: Configure paragraphs.
@@ -41,16 +41,21 @@ pub fn par(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
align = Some(if dir == Dir::LTR { Align::Left } else { Align::Right });
}
- set!(ctx, ParNode::DIR => dir);
- set!(ctx, ParNode::ALIGN => align);
- set!(ctx, ParNode::LEADING => leading);
- set!(ctx, ParNode::SPACING => spacing);
+ set!(ctx.styles, ParNode::DIR => dir);
+ set!(ctx.styles, ParNode::ALIGN => align);
+ set!(ctx.styles, ParNode::LEADING => leading);
+ set!(ctx.styles, ParNode::SPACING => spacing);
Ok(Value::None)
}
+/// `parbreak`: Start a new paragraph.
+pub fn parbreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> {
+ Ok(Value::Node(Node::Parbreak))
+}
+
/// A node that arranges its children into a paragraph.
-#[derive(Debug, Hash)]
+#[derive(Hash)]
pub struct ParNode(pub Vec<ParChild>);
properties! {
@@ -60,11 +65,10 @@ properties! {
DIR: Dir = Dir::LTR,
/// How to align text and inline objects in their line.
ALIGN: Align = Align::Left,
- // TODO(set): Make relative to font size.
/// The spacing between lines (dependent on scaled font size).
- LEADING: Length = Length::pt(6.5),
+ LEADING: Linear = Relative::new(0.65).into(),
/// The spacing between paragraphs (dependent on scaled font size).
- SPACING: Length = Length::pt(12.0),
+ SPACING: Linear = Relative::new(1.2).into(),
}
impl Layout for ParNode {
@@ -118,34 +122,59 @@ impl ParNode {
ParChild::Spacing(_) => " ",
ParChild::Text(ref node) => &node.text,
ParChild::Node(_) => "\u{FFFC}",
- ParChild::Decorate(_) | ParChild::Undecorate => "",
})
}
}
+impl Debug for ParNode {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.write_str("Par ")?;
+ f.debug_list().entries(&self.0).finish()
+ }
+}
+
/// A child of a paragraph node.
#[derive(Hash)]
pub enum ParChild {
/// Spacing between other nodes.
- Spacing(Spacing),
+ Spacing(SpacingNode),
/// A run of text and how to align it in its line.
Text(TextNode),
/// Any child node and how to align it in its line.
Node(PackedNode),
- /// A decoration that applies until a matching `Undecorate`.
- Decorate(Decoration),
- /// The end of a decoration.
- Undecorate,
+}
+
+impl ParChild {
+ /// Create a text child.
+ pub fn text(text: impl Into<EcoString>, styles: Styles) -> Self {
+ Self::Text(TextNode { text: text.into(), styles })
+ }
+
+ /// A reference to the child's styles.
+ pub fn styles(&self) -> &Styles {
+ match self {
+ Self::Spacing(node) => &node.styles,
+ Self::Text(node) => &node.styles,
+ Self::Node(node) => &node.styles,
+ }
+ }
+
+ /// A mutable reference to the child's styles.
+ pub fn styles_mut(&mut self) -> &mut Styles {
+ match self {
+ Self::Spacing(node) => &mut node.styles,
+ Self::Text(node) => &mut node.styles,
+ Self::Node(node) => &mut node.styles,
+ }
+ }
}
impl Debug for ParChild {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
- Self::Spacing(v) => write!(f, "Spacing({:?})", v),
- Self::Text(node) => write!(f, "Text({:?})", node.text),
+ Self::Spacing(node) => node.fmt(f),
+ Self::Text(node) => node.fmt(f),
Self::Node(node) => node.fmt(f),
- Self::Decorate(deco) => write!(f, "Decorate({:?})", deco),
- Self::Undecorate => write!(f, "Undecorate"),
}
}
}
@@ -163,8 +192,6 @@ struct ParLayouter<'a> {
items: Vec<ParItem<'a>>,
/// The ranges of the items in `bidi.text`.
ranges: Vec<Range>,
- /// The decorations and the ranges they span.
- decos: Vec<(Range, &'a Decoration)>,
}
/// Range of a substring of text.
@@ -192,22 +219,22 @@ impl<'a> ParLayouter<'a> {
) -> Self {
let mut items = vec![];
let mut ranges = vec![];
- let mut starts = vec![];
- let mut decos = vec![];
// Layout the children and collect them into items.
for (range, child) in par.ranges().zip(&par.0) {
- match *child {
- ParChild::Spacing(Spacing::Linear(v)) => {
- let resolved = v.resolve(regions.current.x);
- items.push(ParItem::Absolute(resolved));
- ranges.push(range);
- }
- ParChild::Spacing(Spacing::Fractional(v)) => {
- items.push(ParItem::Fractional(v));
- ranges.push(range);
- }
- ParChild::Text(ref node) => {
+ match child {
+ ParChild::Spacing(node) => match node.kind {
+ SpacingKind::Linear(v) => {
+ let resolved = v.resolve(regions.current.x);
+ items.push(ParItem::Absolute(resolved));
+ ranges.push(range);
+ }
+ SpacingKind::Fractional(v) => {
+ items.push(ParItem::Fractional(v));
+ ranges.push(range);
+ }
+ },
+ ParChild::Text(node) => {
// TODO: Also split by language and script.
let mut cursor = range.start;
for (level, group) in bidi.levels[range].group_by_key(|&lvl| lvl) {
@@ -221,36 +248,23 @@ impl<'a> ParLayouter<'a> {
ranges.push(subrange);
}
}
- ParChild::Node(ref node) => {
+ ParChild::Node(node) => {
let size = Size::new(regions.current.x, regions.base.y);
let pod = Regions::one(size, regions.base, Spec::splat(false));
let frame = node.layout(ctx, &pod).remove(0);
items.push(ParItem::Frame(Rc::take(frame.item)));
ranges.push(range);
}
- ParChild::Decorate(ref deco) => {
- starts.push((range.start, deco));
- }
- ParChild::Undecorate => {
- if let Some((start, deco)) = starts.pop() {
- decos.push((start .. range.end, deco));
- }
- }
}
}
- for (start, deco) in starts {
- decos.push((start .. bidi.text.len(), deco));
- }
+ let align = ctx.styles.get(ParNode::ALIGN);
+ let leading = ctx
+ .styles
+ .get(ParNode::LEADING)
+ .resolve(ctx.styles.get(TextNode::SIZE).abs);
- Self {
- align: ctx.styles.get(ParNode::ALIGN),
- leading: ctx.styles.get(ParNode::LEADING),
- bidi,
- items,
- ranges,
- decos,
- }
+ Self { align, leading, bidi, items, ranges }
}
/// Find first-fit line breaks and build the paragraph.
@@ -496,28 +510,19 @@ impl<'a> LineLayout<'a> {
let mut output = Frame::new(size);
output.baseline = Some(self.baseline);
- for (range, item) in self.reordered() {
- let mut position = |mut frame: Frame| {
- // Decorate.
- for (deco_range, deco) in &self.par.decos {
- if deco_range.contains(&range.start) {
- deco.apply(ctx, &mut frame);
- }
- }
-
+ for item in self.reordered() {
+ let mut position = |frame: Frame| {
let x = offset + self.par.align.resolve(remaining);
let y = self.baseline - frame.baseline();
offset += frame.size.x;
-
- // Add to the line's frame.
output.merge_frame(Point::new(x, y), frame);
};
- match *item {
- ParItem::Absolute(v) => offset += v,
+ match item {
+ ParItem::Absolute(v) => offset += *v,
ParItem::Fractional(v) => offset += v.resolve(self.fr, remaining),
- ParItem::Text(ref shaped) => position(shaped.build()),
- ParItem::Frame(ref frame) => position(frame.clone()),
+ ParItem::Text(shaped) => position(shaped.build(&ctx.fonts)),
+ ParItem::Frame(frame) => position(frame.clone()),
}
}
@@ -525,7 +530,7 @@ impl<'a> LineLayout<'a> {
}
/// Iterate through the line's items in visual order.
- fn reordered(&self) -> impl Iterator<Item = (Range, &ParItem<'a>)> {
+ fn reordered(&self) -> impl Iterator<Item = &ParItem<'a>> {
// The bidi crate doesn't like empty lines.
let (levels, runs) = if !self.line.is_empty() {
// Find the paragraph that contains the line.
@@ -557,7 +562,7 @@ impl<'a> LineLayout<'a> {
Either::Right(range.rev())
}
})
- .map(move |idx| (self.ranges[idx].clone(), self.get(idx).unwrap()))
+ .map(move |idx| self.get(idx).unwrap())
}
/// Find the index of the item whose range contains the `text_offset`.