summaryrefslogtreecommitdiff
path: root/src/library
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-02-07 20:00:21 +0100
committerLaurenz <laurmaedje@gmail.com>2022-02-07 20:00:21 +0100
commit68503b9a07b00bce3f4d377bcfe945452de815ea (patch)
tree3719ef491b993c59b619ca215963000f4847e78f /src/library
parent9730e785a885a4ab5fcc52ce705298654f82f9c2 (diff)
Redesigned template layout
Diffstat (limited to 'src/library')
-rw-r--r--src/library/flow.rs178
-rw-r--r--src/library/mod.rs4
-rw-r--r--src/library/par.rs89
-rw-r--r--src/library/spacing.rs7
-rw-r--r--src/library/text.rs8
5 files changed, 152 insertions, 134 deletions
diff --git a/src/library/flow.rs b/src/library/flow.rs
index 227a7a35..bba296a0 100644
--- a/src/library/flow.rs
+++ b/src/library/flow.rs
@@ -1,16 +1,27 @@
//! A flow of paragraphs and other block-level nodes.
-use std::fmt::{self, Debug, Formatter};
-
use super::prelude::*;
use super::{AlignNode, ParNode, PlaceNode, SpacingKind, TextNode};
-/// A vertical flow of content consisting of paragraphs and other layout nodes.
+/// Arrange spacing, paragraphs and other block-level nodes into a flow.
///
/// This node is reponsible for layouting both the top-level content flow and
/// the contents of boxes.
#[derive(Hash)]
-pub struct FlowNode(pub Vec<Styled<FlowChild>>);
+pub struct FlowNode(pub StyleVec<FlowChild>);
+
+/// A child of a flow node.
+#[derive(Hash)]
+pub enum FlowChild {
+ /// A paragraph / block break.
+ Parbreak,
+ /// A column / region break.
+ Colbreak,
+ /// Vertical spacing between other children.
+ Spacing(SpacingKind),
+ /// An arbitrary block-level node.
+ Node(PackedNode),
+}
impl Layout for FlowNode {
fn layout(
@@ -19,45 +30,58 @@ impl Layout for FlowNode {
regions: &Regions,
styles: StyleChain,
) -> Vec<Constrained<Arc<Frame>>> {
- FlowLayouter::new(self, regions.clone()).layout(ctx, styles)
+ let mut layouter = FlowLayouter::new(regions);
+
+ for (child, map) in self.0.iter() {
+ let styles = map.chain(&styles);
+ match child {
+ FlowChild::Parbreak => {
+ let em = styles.get(TextNode::SIZE).abs;
+ let amount = styles.get(ParNode::SPACING).resolve(em);
+ layouter.layout_spacing(SpacingKind::Linear(amount.into()));
+ }
+ FlowChild::Colbreak => {
+ layouter.finish_region();
+ }
+ FlowChild::Spacing(kind) => {
+ layouter.layout_spacing(*kind);
+ }
+ FlowChild::Node(ref node) => {
+ layouter.layout_node(ctx, node, styles);
+ }
+ }
+ }
+
+ layouter.finish()
+ }
+}
+
+impl Merge for FlowChild {
+ fn merge(&mut self, _: &Self) -> bool {
+ false
}
}
impl Debug for FlowNode {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str("Flow ")?;
- f.debug_list().entries(&self.0).finish()
+ self.0.fmt(f)
}
}
-/// A child of a flow node.
-#[derive(Hash)]
-pub enum FlowChild {
- /// A paragraph/block break.
- Break,
- /// Skip the rest of the region and move to the next.
- Skip,
- /// Vertical spacing between other children.
- Spacing(SpacingKind),
- /// An arbitrary node.
- Node(PackedNode),
-}
-
impl Debug for FlowChild {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
- Self::Break => f.pad("Break"),
- Self::Skip => f.pad("Skip"),
- Self::Spacing(kind) => kind.fmt(f),
+ Self::Parbreak => f.pad("Parbreak"),
+ Self::Colbreak => f.pad("Colbreak"),
+ Self::Spacing(kind) => write!(f, "{:?}", kind),
Self::Node(node) => node.fmt(f),
}
}
}
/// Performs flow layout.
-struct FlowLayouter<'a> {
- /// The children of the flow.
- children: &'a [Styled<FlowChild>],
+pub struct FlowLayouter {
/// The regions to layout children into.
regions: Regions,
/// Whether the flow should expand to fill the region.
@@ -69,6 +93,8 @@ struct FlowLayouter<'a> {
used: Size,
/// The sum of fractional ratios in the current region.
fr: Fractional,
+ /// Whether to add leading before the next node.
+ leading: bool,
/// Spacing and layouted nodes.
items: Vec<FlowItem>,
/// Finished frames for previous regions.
@@ -87,98 +113,64 @@ enum FlowItem {
Placed(Arc<Frame>),
}
-impl<'a> FlowLayouter<'a> {
+impl FlowLayouter {
/// Create a new flow layouter.
- fn new(flow: &'a FlowNode, mut regions: Regions) -> Self {
+ pub fn new(regions: &Regions) -> Self {
let expand = regions.expand;
let full = regions.current;
// Disable vertical expansion for children.
+ let mut regions = regions.clone();
regions.expand.y = false;
Self {
- children: &flow.0,
regions,
expand,
full,
used: Size::zero(),
fr: Fractional::zero(),
+ leading: false,
items: vec![],
finished: vec![],
}
}
- /// Layout all children.
- fn layout(
- mut self,
- ctx: &mut LayoutContext,
- styles: StyleChain,
- ) -> Vec<Constrained<Arc<Frame>>> {
- for styled in self.children {
- let styles = styled.map.chain(&styles);
- match styled.item {
- FlowChild::Break => {
- let em = styles.get(TextNode::SIZE).abs;
- let amount = styles.get(ParNode::SPACING).resolve(em);
- self.layout_absolute(amount.into());
- }
- FlowChild::Skip => {
- self.finish_region();
- }
- FlowChild::Spacing(kind) => {
- self.layout_spacing(kind);
- }
- FlowChild::Node(ref node) => {
- if self.regions.is_full() {
- self.finish_region();
- }
-
- self.layout_node(ctx, node, styles);
- }
- }
- }
-
- if self.expand.y {
- while self.regions.backlog.len() > 0 {
- self.finish_region();
- }
- }
-
- self.finish_region();
- self.finished
- }
-
/// Layout spacing.
- fn layout_spacing(&mut self, spacing: SpacingKind) {
+ pub fn layout_spacing(&mut self, spacing: SpacingKind) {
match spacing {
- SpacingKind::Linear(v) => self.layout_absolute(v),
+ SpacingKind::Linear(v) => {
+ // Resolve the linear and limit it to the remaining space.
+ let resolved = v.resolve(self.full.y);
+ let limited = resolved.min(self.regions.current.y);
+ self.regions.current.y -= limited;
+ self.used.y += limited;
+ self.items.push(FlowItem::Absolute(resolved));
+ }
SpacingKind::Fractional(v) => {
self.items.push(FlowItem::Fractional(v));
self.fr += v;
+ self.leading = false;
}
}
}
- /// Layout absolute spacing.
- fn layout_absolute(&mut self, amount: Linear) {
- // Resolve the linear, limiting it to the remaining available space.
- let resolved = amount.resolve(self.full.y);
- let limited = resolved.min(self.regions.current.y);
- self.regions.current.y -= limited;
- self.used.y += limited;
- self.items.push(FlowItem::Absolute(resolved));
- }
-
/// Layout a node.
- fn layout_node(
+ pub fn layout_node(
&mut self,
ctx: &mut LayoutContext,
node: &PackedNode,
styles: StyleChain,
) {
+ // Don't even try layouting into a full region.
+ if self.regions.is_full() {
+ self.finish_region();
+ }
+
// Placed nodes that are out of flow produce placed items which aren't
// aligned later.
+ let mut is_placed = false;
if let Some(placed) = node.downcast::<PlaceNode>() {
+ is_placed = true;
if placed.out_of_flow() {
let frame = node.layout(ctx, &self.regions, styles).remove(0);
self.items.push(FlowItem::Placed(frame.item));
@@ -186,6 +178,13 @@ impl<'a> FlowLayouter<'a> {
}
}
+ // Add leading.
+ if self.leading {
+ let em = styles.get(TextNode::SIZE).abs;
+ let amount = styles.get(ParNode::LEADING).resolve(em);
+ self.layout_spacing(SpacingKind::Linear(amount.into()));
+ }
+
// How to align the node.
let aligns = Spec::new(
// For non-expanding paragraphs it is crucial that we align the
@@ -211,10 +210,12 @@ impl<'a> FlowLayouter<'a> {
self.finish_region();
}
}
+
+ self.leading = !is_placed;
}
/// Finish the frame for one region.
- fn finish_region(&mut self) {
+ pub fn finish_region(&mut self) {
// Determine the size of the flow in this region dependening on whether
// the region expands.
let mut size = self.expand.select(self.full, self.used);
@@ -263,6 +264,19 @@ impl<'a> FlowLayouter<'a> {
self.full = self.regions.current;
self.used = Size::zero();
self.fr = Fractional::zero();
+ self.leading = false;
self.finished.push(output.constrain(cts));
}
+
+ /// Finish layouting and return the resulting frames.
+ pub fn finish(mut self) -> Vec<Constrained<Arc<Frame>>> {
+ if self.expand.y {
+ while self.regions.backlog.len() > 0 {
+ self.finish_region();
+ }
+ }
+
+ self.finish_region();
+ self.finished
+ }
}
diff --git a/src/library/mod.rs b/src/library/mod.rs
index 9e3bf18b..ef0fa016 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -68,8 +68,8 @@ prelude! {
pub use crate::diag::{At, TypResult};
pub use crate::eval::{
- Args, Construct, EvalContext, Template, Property, Scope, Set, Smart, StyleChain,
- StyleMap, Styled, Value,
+ Args, Construct, EvalContext, Merge, Property, Scope, Set, Smart, StyleChain,
+ StyleMap, StyleVec, Template, Value,
};
pub use crate::frame::*;
pub use crate::geom::*;
diff --git a/src/library/par.rs b/src/library/par.rs
index 4568a214..962834f2 100644
--- a/src/library/par.rs
+++ b/src/library/par.rs
@@ -1,6 +1,5 @@
//! Paragraph layout.
-use std::fmt::{self, Debug, Formatter};
use std::sync::Arc;
use itertools::Either;
@@ -11,9 +10,20 @@ use super::prelude::*;
use super::{shape, ShapedText, SpacingKind, TextNode};
use crate::util::{ArcExt, EcoString, RangeExt, SliceExt};
-/// Arrange text, spacing and inline nodes into a paragraph.
+/// Arrange text, spacing and inline-level nodes into a paragraph.
#[derive(Hash)]
-pub struct ParNode(pub Vec<Styled<ParChild>>);
+pub struct ParNode(pub StyleVec<ParChild>);
+
+/// A uniformly styled atomic piece of a paragraph.
+#[derive(Hash)]
+pub enum ParChild {
+ /// A chunk of text.
+ Text(EcoString),
+ /// Horizontal spacing between other children.
+ Spacing(SpacingKind),
+ /// An arbitrary inline-level node.
+ Node(PackedNode),
+}
#[class]
impl ParNode {
@@ -23,8 +33,8 @@ impl ParNode {
pub const ALIGN: Align = Align::Left;
/// The spacing between lines (dependent on scaled font size).
pub const LEADING: Linear = Relative::new(0.65).into();
- /// The spacing between paragraphs (dependent on scaled font size).
- pub const SPACING: Linear = Relative::new(1.2).into();
+ /// The extra spacing between paragraphs (dependent on scaled font size).
+ pub const SPACING: Linear = Relative::new(0.55).into();
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> {
// The paragraph constructor is special: It doesn't create a paragraph
@@ -116,9 +126,9 @@ impl ParNode {
/// The string representation of each child.
fn strings(&self) -> impl Iterator<Item = &str> {
- self.0.iter().map(|styled| match &styled.item {
+ self.0.items().map(|child| match child {
+ ParChild::Text(text) => text,
ParChild::Spacing(_) => " ",
- ParChild::Text(text) => &text.0,
ParChild::Node(_) => "\u{FFFC}",
})
}
@@ -127,38 +137,31 @@ impl ParNode {
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(SpacingKind),
- /// 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),
-}
-
-impl ParChild {
- /// Create a text child.
- pub fn text(text: impl Into<EcoString>) -> Self {
- Self::Text(TextNode(text.into()))
+ self.0.fmt(f)
}
}
impl Debug for ParChild {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
- Self::Spacing(kind) => kind.fmt(f),
- Self::Text(text) => text.fmt(f),
+ Self::Text(text) => write!(f, "Text({:?})", text),
+ Self::Spacing(kind) => write!(f, "{:?}", kind),
Self::Node(node) => node.fmt(f),
}
}
}
+impl Merge for ParChild {
+ fn merge(&mut self, next: &Self) -> bool {
+ if let (Self::Text(left), Self::Text(right)) = (self, next) {
+ left.push_str(right);
+ true
+ } else {
+ false
+ }
+ }
+}
+
/// A paragraph break.
pub struct ParbreakNode;
@@ -222,20 +225,9 @@ impl<'a> ParLayouter<'a> {
let mut ranges = vec![];
// Layout the children and collect them into items.
- for (range, styled) in par.ranges().zip(&par.0) {
- let styles = styled.map.chain(styles);
- match styled.item {
- ParChild::Spacing(kind) => match 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);
- }
- },
+ for (range, (child, map)) in par.ranges().zip(par.0.iter()) {
+ let styles = map.chain(styles);
+ match child {
ParChild::Text(_) => {
// TODO: Also split by language and script.
let mut cursor = range.start;
@@ -249,7 +241,18 @@ impl<'a> ParLayouter<'a> {
ranges.push(subrange);
}
}
- ParChild::Node(ref node) => {
+ ParChild::Spacing(kind) => match *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::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, styles).remove(0);
diff --git a/src/library/spacing.rs b/src/library/spacing.rs
index 0b555510..feadf7f3 100644
--- a/src/library/spacing.rs
+++ b/src/library/spacing.rs
@@ -31,6 +31,13 @@ pub enum SpacingKind {
Fractional(Fractional),
}
+impl SpacingKind {
+ /// Whether this is fractional spacing.
+ pub fn is_fractional(self) -> bool {
+ matches!(self, Self::Fractional(_))
+ }
+}
+
castable! {
SpacingKind,
Expected: "linear or fractional",
diff --git a/src/library/text.rs b/src/library/text.rs
index 4aa1bdec..fb0f7e08 100644
--- a/src/library/text.rs
+++ b/src/library/text.rs
@@ -19,7 +19,7 @@ use crate::util::{EcoString, SliceExt};
/// A single run of text with the same style.
#[derive(Hash)]
-pub struct TextNode(pub EcoString);
+pub struct TextNode;
#[class]
impl TextNode {
@@ -144,12 +144,6 @@ impl TextNode {
}
}
-impl Debug for TextNode {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- write!(f, "Text({:?})", self.0)
- }
-}
-
/// Strong text, rendered in boldface.
pub struct StrongNode;