summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/eval/mod.rs1
-rw-r--r--src/exec/context.rs322
-rw-r--r--src/exec/mod.rs2
-rw-r--r--src/exec/state.rs25
-rw-r--r--src/geom/align.rs3
-rw-r--r--src/geom/dir.rs3
-rw-r--r--src/geom/gen.rs10
-rw-r--r--src/geom/mod.rs5
-rw-r--r--src/geom/point.rs4
-rw-r--r--src/geom/size.rs4
-rw-r--r--src/geom/spec.rs13
-rw-r--r--src/layout/background.rs10
-rw-r--r--src/layout/fixed.rs4
-rw-r--r--src/layout/mod.rs121
-rw-r--r--src/layout/node.rs108
-rw-r--r--src/layout/pad.rs12
-rw-r--r--src/layout/par.rs127
-rw-r--r--src/layout/spacing.rs35
-rw-r--r--src/layout/stack.rs76
-rw-r--r--src/layout/text.rs36
-rw-r--r--src/library/align.rs144
-rw-r--r--src/library/image.rs18
-rw-r--r--src/library/lang.rs45
-rw-r--r--src/library/mod.rs9
-rw-r--r--src/library/pad.rs6
-rw-r--r--src/library/page.rs12
-rw-r--r--src/library/par.rs14
-rw-r--r--src/library/shapes.rs18
-rw-r--r--src/library/spacing.rs21
-rw-r--r--tests/ref/library/lang.pngbin0 -> 1897 bytes
-rw-r--r--tests/ref/library/page.pngbin8180 -> 7438 bytes
-rw-r--r--tests/ref/library/spacing.pngbin3845 -> 3251 bytes
-rw-r--r--tests/typ/library/lang.typ16
-rw-r--r--tests/typ/library/page.typ7
-rw-r--r--tests/typ/library/spacing.typ7
35 files changed, 535 insertions, 703 deletions
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index 802e1347..88110d88 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -39,7 +39,6 @@ pub fn eval(env: &mut Env, tree: &Tree, scope: &Scope) -> Pass<NodeMap> {
pub type NodeMap = HashMap<*const Node, Value>;
/// The context for evaluation.
-#[derive(Debug)]
pub struct EvalContext<'a> {
/// The environment from which resources are gathered.
pub env: &'a mut Env,
diff --git a/src/exec/context.rs b/src/exec/context.rs
index b6a67a2e..333ad3ba 100644
--- a/src/exec/context.rs
+++ b/src/exec/context.rs
@@ -4,15 +4,14 @@ use super::{Exec, FontFamily, State};
use crate::diag::{Diag, DiagSet, Pass};
use crate::env::Env;
use crate::eval::TemplateValue;
-use crate::geom::{Dir, Gen, Linear, Sides, Size};
+use crate::geom::{Align, Dir, Gen, GenAxis, Length, Linear, Sides, Size};
use crate::layout::{
- Node, PadNode, PageRun, ParNode, SpacingNode, StackNode, TextNode, Tree,
+ AnyNode, PadNode, PageRun, ParChild, ParNode, StackChild, StackNode, TextNode, Tree,
};
use crate::parse::{is_newline, Scanner};
-use crate::syntax::{Span, Spanned};
+use crate::syntax::Span;
/// The context for execution.
-#[derive(Debug)]
pub struct ExecContext<'a> {
/// The environment from which resources are gathered.
pub env: &'a mut Env,
@@ -22,13 +21,11 @@ pub struct ExecContext<'a> {
pub diags: DiagSet,
/// The tree of finished page runs.
tree: Tree,
- /// Metrics of the active page.
- page: Option<PageInfo>,
- /// The content of the active stack. This may be the top-level stack for the
- /// page or a lower one created by [`exec`](Self::exec).
- stack: StackNode,
- /// The content of the active paragraph.
- par: ParNode,
+ /// When we are building the top-level stack, this contains metrics of the
+ /// page. While building a group stack through `exec_group`, this is `None`.
+ page: Option<PageBuilder>,
+ /// The currently built stack of paragraphs.
+ stack: StackBuilder,
}
impl<'a> ExecContext<'a> {
@@ -38,9 +35,8 @@ impl<'a> ExecContext<'a> {
env,
diags: DiagSet::new(),
tree: Tree { runs: vec![] },
- page: Some(PageInfo::new(&state, true)),
- stack: StackNode::new(&state),
- par: ParNode::new(&state),
+ page: Some(PageBuilder::new(&state, true)),
+ stack: StackBuilder::new(&state),
state,
}
}
@@ -50,45 +46,23 @@ impl<'a> ExecContext<'a> {
self.diags.insert(diag);
}
- /// Set the directions.
- ///
- /// Produces an error if the axes aligned.
- pub fn set_dirs(&mut self, new: Gen<Option<Spanned<Dir>>>) {
- let dirs = Gen::new(
- new.main.map(|s| s.v).unwrap_or(self.state.dirs.main),
- new.cross.map(|s| s.v).unwrap_or(self.state.dirs.cross),
- );
-
- if dirs.main.axis() != dirs.cross.axis() {
- self.state.dirs = dirs;
- } else {
- for dir in new.main.iter().chain(new.cross.iter()) {
- self.diag(error!(dir.span, "aligned axis"));
- }
- }
- }
-
/// Set the font to monospace.
pub fn set_monospace(&mut self) {
let families = self.state.font.families_mut();
families.list.insert(0, FontFamily::Monospace);
}
- /// Push a layout node into the active paragraph.
- ///
- /// Spacing nodes will be handled according to their
- /// [`softness`](SpacingNode::softness).
- pub fn push(&mut self, node: impl Into<Node>) {
- push(&mut self.par.children, node.into());
- }
+ /// Execute a template and return the result as a stack node.
+ pub fn exec_group(&mut self, template: &TemplateValue) -> StackNode {
+ let snapshot = self.state.clone();
+ let page = self.page.take();
+ let stack = mem::replace(&mut self.stack, StackBuilder::new(&self.state));
- /// Push a word space into the active paragraph.
- pub fn push_space(&mut self) {
- let em = self.state.font.resolve_size();
- self.push(SpacingNode {
- amount: self.state.par.word_spacing.resolve(em),
- softness: 1,
- });
+ template.exec(self);
+
+ self.state = snapshot;
+ self.page = page;
+ mem::replace(&mut self.stack, stack).build()
}
/// Push text into the active paragraph.
@@ -97,96 +71,85 @@ impl<'a> ExecContext<'a> {
pub fn push_text(&mut self, text: &str) {
let mut scanner = Scanner::new(text);
let mut line = String::new();
+ let push = |this: &mut Self, text| {
+ let props = this.state.font.resolve_props();
+ let node = TextNode { text, props };
+ let align = this.state.aligns.cross;
+ this.stack.par.folder.push(ParChild::Text(node, align))
+ };
while let Some(c) = scanner.eat_merging_crlf() {
if is_newline(c) {
- self.push(TextNode::new(mem::take(&mut line), &self.state));
+ push(self, mem::take(&mut line));
self.push_linebreak();
} else {
line.push(c);
}
}
- self.push(TextNode::new(line, &self.state));
+ push(self, line);
+ }
+
+ /// Push a word space.
+ pub fn push_word_space(&mut self) {
+ let em = self.state.font.resolve_size();
+ let amount = self.state.par.word_spacing.resolve(em);
+ self.push_spacing(GenAxis::Cross, amount, 1);
}
/// Apply a forced line break.
pub fn push_linebreak(&mut self) {
let em = self.state.font.resolve_size();
- self.push_into_stack(SpacingNode {
- amount: self.state.par.leading.resolve(em),
- softness: 2,
- });
+ let amount = self.state.par.leading.resolve(em);
+ self.push_spacing(GenAxis::Main, amount, 2);
}
/// Apply a forced paragraph break.
pub fn push_parbreak(&mut self) {
let em = self.state.font.resolve_size();
- self.push_into_stack(SpacingNode {
- amount: self.state.par.spacing.resolve(em),
- softness: 1,
- });
- }
-
- /// Push a node directly into the stack above the paragraph. This finishes
- /// the active paragraph and starts a new one.
- pub fn push_into_stack(&mut self, node: impl Into<Node>) {
- self.finish_par();
- push(&mut self.stack.children, node.into());
+ let amount = self.state.par.spacing.resolve(em);
+ self.push_spacing(GenAxis::Main, amount, 1);
}
- /// Execute a template and return the result as a stack node.
- pub fn exec(&mut self, template: &TemplateValue) -> StackNode {
- let page = self.page.take();
- let stack = mem::replace(&mut self.stack, StackNode::new(&self.state));
- let par = mem::replace(&mut self.par, ParNode::new(&self.state));
-
- template.exec(self);
- let result = self.finish_stack();
-
- self.page = page;
- self.stack = stack;
- self.par = par;
-
- result
- }
-
- /// Finish the active paragraph.
- fn finish_par(&mut self) {
- let mut par = mem::replace(&mut self.par, ParNode::new(&self.state));
- trim(&mut par.children);
-
- if !par.children.is_empty() {
- self.stack.children.push(par.into());
+ /// Push spacing into paragraph or stack depending on `axis`.
+ ///
+ /// The `softness` configures how the spacing interacts with surrounding
+ /// spacing.
+ pub fn push_spacing(&mut self, axis: GenAxis, amount: Length, softness: u8) {
+ match axis {
+ GenAxis::Main => {
+ let spacing = StackChild::Spacing(amount);
+ self.stack.finish_par(&self.state);
+ self.stack.folder.push_soft(spacing, softness);
+ }
+ GenAxis::Cross => {
+ let spacing = ParChild::Spacing(amount);
+ self.stack.par.folder.push_soft(spacing, softness);
+ }
}
}
- /// Finish the active stack.
- fn finish_stack(&mut self) -> StackNode {
- self.finish_par();
-
- let mut stack = mem::replace(&mut self.stack, StackNode::new(&self.state));
- trim(&mut stack.children);
+ /// Push any node into the active paragraph.
+ pub fn push_into_par(&mut self, node: impl Into<AnyNode>) {
+ let align = self.state.aligns.cross;
+ self.stack.par.folder.push(ParChild::Any(node.into(), align));
+ }
- stack
+ /// Push any node directly into the stack of paragraphs.
+ ///
+ /// This finishes the active paragraph and starts a new one.
+ pub fn push_into_stack(&mut self, node: impl Into<AnyNode>) {
+ let aligns = self.state.aligns;
+ self.stack.finish_par(&self.state);
+ self.stack.folder.push(StackChild::Any(node.into(), aligns));
}
/// Finish the active page.
pub fn finish_page(&mut self, keep: bool, hard: bool, source: Span) {
- if let Some(info) = &mut self.page {
- let info = mem::replace(info, PageInfo::new(&self.state, hard));
- let stack = self.finish_stack();
-
- if !stack.children.is_empty() || (keep && info.hard) {
- self.tree.runs.push(PageRun {
- size: info.size,
- child: PadNode {
- padding: info.padding,
- child: stack.into(),
- }
- .into(),
- });
- }
+ if let Some(builder) = &mut self.page {
+ let page = mem::replace(builder, PageBuilder::new(&self.state, hard));
+ let stack = mem::replace(&mut self.stack, StackBuilder::new(&self.state));
+ self.tree.runs.extend(page.build(stack.build(), keep));
} else {
self.diag(error!(source, "cannot modify page from here"));
}
@@ -200,44 +163,13 @@ impl<'a> ExecContext<'a> {
}
}
-/// Push a node into a list, taking care of spacing softness.
-fn push(nodes: &mut Vec<Node>, node: Node) {
- if let Node::Spacing(spacing) = node {
- if nodes.is_empty() && spacing.softness > 0 {
- return;
- }
-
- if let Some(&Node::Spacing(other)) = nodes.last() {
- if spacing.softness > 0 && spacing.softness >= other.softness {
- return;
- }
-
- if spacing.softness < other.softness {
- nodes.pop();
- }
- }
- }
-
- nodes.push(node);
-}
-
-/// Remove trailing soft spacing from a node list.
-fn trim(nodes: &mut Vec<Node>) {
- if let Some(&Node::Spacing(spacing)) = nodes.last() {
- if spacing.softness > 0 {
- nodes.pop();
- }
- }
-}
-
-#[derive(Debug)]
-struct PageInfo {
+struct PageBuilder {
size: Size,
padding: Sides<Linear>,
hard: bool,
}
-impl PageInfo {
+impl PageBuilder {
fn new(state: &State, hard: bool) -> Self {
Self {
size: state.page.size,
@@ -245,37 +177,119 @@ impl PageInfo {
hard,
}
}
+
+ fn build(self, child: StackNode, keep: bool) -> Option<PageRun> {
+ let Self { size, padding, hard } = self;
+ (!child.children.is_empty() || (keep && hard)).then(|| PageRun {
+ size,
+ child: PadNode { padding, child: child.into() }.into(),
+ })
+ }
}
-impl StackNode {
+struct StackBuilder {
+ dirs: Gen<Dir>,
+ folder: SoftFolder<StackChild>,
+ par: ParBuilder,
+}
+
+impl StackBuilder {
fn new(state: &State) -> Self {
Self {
- dirs: state.dirs,
- aligns: state.aligns,
- children: vec![],
+ dirs: Gen::new(Dir::TTB, state.lang.dir),
+ folder: SoftFolder::new(),
+ par: ParBuilder::new(state),
}
}
+
+ fn finish_par(&mut self, state: &State) {
+ let par = mem::replace(&mut self.par, ParBuilder::new(state));
+ self.folder.extend(par.build());
+ }
+
+ fn build(self) -> StackNode {
+ let Self { dirs, mut folder, par } = self;
+ folder.extend(par.build());
+ StackNode { dirs, children: folder.finish() }
+ }
}
-impl ParNode {
+struct ParBuilder {
+ aligns: Gen<Align>,
+ dir: Dir,
+ line_spacing: Length,
+ folder: SoftFolder<ParChild>,
+}
+
+impl ParBuilder {
fn new(state: &State) -> Self {
let em = state.font.resolve_size();
Self {
- dirs: state.dirs,
aligns: state.aligns,
+ dir: state.lang.dir,
line_spacing: state.par.leading.resolve(em),
- children: vec![],
+ folder: SoftFolder::new(),
}
}
+
+ fn build(self) -> Option<StackChild> {
+ let Self { aligns, dir, line_spacing, folder } = self;
+ let children = folder.finish();
+ (!children.is_empty()).then(|| {
+ let node = ParNode { dir, line_spacing, children };
+ StackChild::Any(node.into(), aligns)
+ })
+ }
}
-impl TextNode {
- fn new(text: String, state: &State) -> Self {
- Self {
- text,
- dir: state.dirs.cross,
- aligns: state.aligns,
- props: state.font.resolve_props(),
+/// This is used to remove leading and trailing word/line/paragraph spacing
+/// as well as collapse sequences of spacings into just one.
+struct SoftFolder<N> {
+ nodes: Vec<N>,
+ last: Last<N>,
+}
+
+enum Last<N> {
+ None,
+ Hard,
+ Soft(N, u8),
+}
+
+impl<N> SoftFolder<N> {
+ fn new() -> Self {
+ Self { nodes: vec![], last: Last::Hard }
+ }
+
+ fn push(&mut self, node: N) {
+ let last = mem::replace(&mut self.last, Last::None);
+ if let Last::Soft(soft, _) = last {
+ self.nodes.push(soft);
+ }
+ self.nodes.push(node);
+ }
+
+ fn push_soft(&mut self, node: N, softness: u8) {
+ if softness == 0 {
+ self.last = Last::Hard;
+ self.nodes.push(node);
+ } else {
+ match self.last {
+ Last::Hard => {}
+ Last::Soft(_, other) if softness >= other => {}
+ _ => self.last = Last::Soft(node, softness),
+ }
+ }
+ }
+
+ fn finish(self) -> Vec<N> {
+ self.nodes
+ }
+}
+
+impl<N> Extend<N> for SoftFolder<N> {
+ fn extend<T: IntoIterator<Item = N>>(&mut self, iter: T) {
+ for elem in iter {
+ self.push(elem);
}
}
}
diff --git a/src/exec/mod.rs b/src/exec/mod.rs
index 6f3b9c83..69a41beb 100644
--- a/src/exec/mod.rs
+++ b/src/exec/mod.rs
@@ -65,7 +65,7 @@ impl ExecWithMap for Node {
fn exec_with_map(&self, ctx: &mut ExecContext, map: &NodeMap) {
match self {
Node::Text(text) => ctx.push_text(text),
- Node::Space => ctx.push_space(),
+ Node::Space => ctx.push_word_space(),
_ => map[&(self as *const _)].exec(ctx),
}
}
diff --git a/src/exec/state.rs b/src/exec/state.rs
index 7957f312..c579bc4e 100644
--- a/src/exec/state.rs
+++ b/src/exec/state.rs
@@ -12,30 +12,43 @@ use crate::paper::{Paper, PaperClass, PAPER_A4};
/// The evaluation state.
#[derive(Debug, Clone, PartialEq)]
pub struct State {
- /// The current directions along which layouts are placed in their parents.
- pub dirs: LayoutDirs,
- /// The current alignments of layouts in their parents.
- pub aligns: LayoutAligns,
+ /// The current language-related settings.
+ pub lang: LangState,
/// The current page settings.
pub page: PageState,
/// The current paragraph settings.
pub par: ParState,
/// The current font settings.
pub font: FontState,
+ /// The current alignments of layouts in their parents.
+ pub aligns: Gen<Align>,
}
impl Default for State {
fn default() -> Self {
Self {
- dirs: LayoutDirs::new(Dir::TTB, Dir::LTR),
- aligns: LayoutAligns::new(Align::Start, Align::Start),
+ lang: LangState::default(),
page: PageState::default(),
par: ParState::default(),
font: FontState::default(),
+ aligns: Gen::new(Align::Start, Align::Start),
}
}
}
+/// Defines language properties.
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub struct LangState {
+ /// The direction for text and other inline objects.
+ pub dir: Dir,
+}
+
+impl Default for LangState {
+ fn default() -> Self {
+ Self { dir: Dir::LTR }
+ }
+}
+
/// Defines page properties.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct PageState {
diff --git a/src/geom/align.rs b/src/geom/align.rs
index e13da378..422624d8 100644
--- a/src/geom/align.rs
+++ b/src/geom/align.rs
@@ -1,8 +1,5 @@
use super::*;
-/// The alignments of a layout in its parent.
-pub type LayoutAligns = Gen<Align>;
-
/// Where to align something along a directed axis.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum Align {
diff --git a/src/geom/dir.rs b/src/geom/dir.rs
index 3eddd7d3..cfcb4c09 100644
--- a/src/geom/dir.rs
+++ b/src/geom/dir.rs
@@ -1,8 +1,5 @@
use super::*;
-/// The directions along which layouts are placed in their parent.
-pub type LayoutDirs = Gen<Dir>;
-
/// The four directions into which content can be laid out.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Dir {
diff --git a/src/geom/gen.rs b/src/geom/gen.rs
index c80cc21b..7e021412 100644
--- a/src/geom/gen.rs
+++ b/src/geom/gen.rs
@@ -50,8 +50,8 @@ impl<T> Get<GenAxis> for Gen<T> {
impl<T> Switch for Gen<T> {
type Other = Spec<T>;
- fn switch(self, dirs: LayoutDirs) -> Self::Other {
- match dirs.main.axis() {
+ fn switch(self, main: SpecAxis) -> Self::Other {
+ match main {
SpecAxis::Horizontal => Spec::new(self.main, self.cross),
SpecAxis::Vertical => Spec::new(self.cross, self.main),
}
@@ -86,10 +86,10 @@ impl GenAxis {
impl Switch for GenAxis {
type Other = SpecAxis;
- fn switch(self, dirs: LayoutDirs) -> Self::Other {
+ fn switch(self, main: SpecAxis) -> Self::Other {
match self {
- Self::Main => dirs.main.axis(),
- Self::Cross => dirs.cross.axis(),
+ Self::Main => main,
+ Self::Cross => main.other(),
}
}
}
diff --git a/src/geom/mod.rs b/src/geom/mod.rs
index 5099c6b0..0031c6df 100644
--- a/src/geom/mod.rs
+++ b/src/geom/mod.rs
@@ -53,7 +53,6 @@ pub trait Switch {
/// The type of the other version.
type Other;
- /// The other version of this type based on the current layouting
- /// directions.
- fn switch(self, dirs: LayoutDirs) -> Self::Other;
+ /// The other version of this type based on the current main axis.
+ fn switch(self, main: SpecAxis) -> Self::Other;
}
diff --git a/src/geom/point.rs b/src/geom/point.rs
index cf8bc1a9..29298565 100644
--- a/src/geom/point.rs
+++ b/src/geom/point.rs
@@ -45,8 +45,8 @@ impl Get<SpecAxis> for Point {
impl Switch for Point {
type Other = Gen<Length>;
- fn switch(self, dirs: LayoutDirs) -> Self::Other {
- match dirs.main.axis() {
+ fn switch(self, main: SpecAxis) -> Self::Other {
+ match main {
SpecAxis::Horizontal => Gen::new(self.x, self.y),
SpecAxis::Vertical => Gen::new(self.y, self.x),
}
diff --git a/src/geom/size.rs b/src/geom/size.rs
index 2feaa950..1ba2f04b 100644
--- a/src/geom/size.rs
+++ b/src/geom/size.rs
@@ -74,8 +74,8 @@ impl Get<SpecAxis> for Size {
impl Switch for Size {
type Other = Gen<Length>;
- fn switch(self, dirs: LayoutDirs) -> Self::Other {
- match dirs.main.axis() {
+ fn switch(self, main: SpecAxis) -> Self::Other {
+ match main {
SpecAxis::Horizontal => Gen::new(self.width, self.height),
SpecAxis::Vertical => Gen::new(self.height, self.width),
}
diff --git a/src/geom/spec.rs b/src/geom/spec.rs
index 510bac84..546eac7b 100644
--- a/src/geom/spec.rs
+++ b/src/geom/spec.rs
@@ -66,8 +66,8 @@ impl<T> Get<SpecAxis> for Spec<T> {
impl<T> Switch for Spec<T> {
type Other = Gen<T>;
- fn switch(self, dirs: LayoutDirs) -> Self::Other {
- match dirs.main.axis() {
+ fn switch(self, main: SpecAxis) -> Self::Other {
+ match main {
SpecAxis::Horizontal => Gen::new(self.horizontal, self.vertical),
SpecAxis::Vertical => Gen::new(self.vertical, self.horizontal),
}
@@ -102,13 +102,8 @@ impl SpecAxis {
impl Switch for SpecAxis {
type Other = GenAxis;
- fn switch(self, dirs: LayoutDirs) -> Self::Other {
- if self == dirs.main.axis() {
- GenAxis::Main
- } else {
- debug_assert_eq!(self, dirs.cross.axis());
- GenAxis::Cross
- }
+ fn switch(self, main: SpecAxis) -> Self::Other {
+ if self == main { GenAxis::Main } else { GenAxis::Cross }
}
}
diff --git a/src/layout/background.rs b/src/layout/background.rs
index 17280a86..d3408182 100644
--- a/src/layout/background.rs
+++ b/src/layout/background.rs
@@ -8,7 +8,7 @@ pub struct BackgroundNode {
/// The background fill.
pub fill: Fill,
/// The child node to be filled.
- pub child: Node,
+ pub child: AnyNode,
}
/// The kind of shape to use as a background.
@@ -19,10 +19,10 @@ pub enum BackgroundShape {
}
impl Layout for BackgroundNode {
- fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Fragment {
- let mut fragment = self.child.layout(ctx, areas);
+ fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Vec<Frame> {
+ let mut frames = self.child.layout(ctx, areas);
- for frame in fragment.frames_mut() {
+ for frame in &mut frames {
let (point, shape) = match self.shape {
BackgroundShape::Rect => (Point::ZERO, Shape::Rect(frame.size)),
BackgroundShape::Ellipse => {
@@ -34,7 +34,7 @@ impl Layout for BackgroundNode {
frame.elements.insert(0, (point, element));
}
- fragment
+ frames
}
}
diff --git a/src/layout/fixed.rs b/src/layout/fixed.rs
index 22c45ef1..04ea5a3a 100644
--- a/src/layout/fixed.rs
+++ b/src/layout/fixed.rs
@@ -12,11 +12,11 @@ pub struct FixedNode {
/// The resulting frame will satisfy `width = aspect * height`.
pub aspect: Option<f64>,
/// The child node whose size to fix.
- pub child: Node,
+ pub child: AnyNode,
}
impl Layout for FixedNode {
- fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Fragment {
+ fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Vec<Frame> {
let Areas { current, full, .. } = areas;
let full = Size::new(
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 360c9d84..6f28fcb9 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -3,24 +3,21 @@
mod background;
mod fixed;
mod frame;
-mod node;
mod pad;
mod par;
mod shaping;
-mod spacing;
mod stack;
-mod text;
pub use background::*;
pub use fixed::*;
pub use frame::*;
-pub use node::*;
pub use pad::*;
pub use par::*;
pub use shaping::*;
-pub use spacing::*;
pub use stack::*;
-pub use text::*;
+
+use std::any::Any;
+use std::fmt::{self, Debug, Formatter};
use crate::env::Env;
use crate::geom::*;
@@ -51,25 +48,88 @@ pub struct PageRun {
pub size: Size,
/// The layout node that produces the actual pages (typically a
/// [`StackNode`]).
- pub child: Node,
+ pub child: AnyNode,
}
impl PageRun {
/// Layout the page run.
pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<Frame> {
let areas = Areas::repeat(self.size, Spec::uniform(Expand::Fill));
- self.child.layout(ctx, &areas).into_frames()
+ self.child.layout(ctx, &areas)
+ }
+}
+
+/// A wrapper around a dynamic layouting node.
+pub struct AnyNode(Box<dyn Bounds>);
+
+impl AnyNode {
+ /// Create a new instance from any node that satisifies the required bounds.
+ pub fn new<T>(any: T) -> Self
+ where
+ T: Layout + Debug + Clone + PartialEq + 'static,
+ {
+ Self(Box::new(any))
+ }
+}
+
+impl Layout for AnyNode {
+ fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Vec<Frame> {
+ self.0.layout(ctx, areas)
+ }
+}
+
+impl Clone for AnyNode {
+ fn clone(&self) -> Self {
+ Self(self.0.dyn_clone())
+ }
+}
+
+impl PartialEq for AnyNode {
+ fn eq(&self, other: &Self) -> bool {
+ self.0.dyn_eq(other.0.as_ref())
+ }
+}
+
+impl Debug for AnyNode {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+trait Bounds: Layout + Debug + 'static {
+ fn as_any(&self) -> &dyn Any;
+ fn dyn_eq(&self, other: &dyn Bounds) -> bool;
+ fn dyn_clone(&self) -> Box<dyn Bounds>;
+}
+
+impl<T> Bounds for T
+where
+ T: Layout + Debug + PartialEq + Clone + 'static,
+{
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
+
+ fn dyn_eq(&self, other: &dyn Bounds) -> bool {
+ if let Some(other) = other.as_any().downcast_ref::<Self>() {
+ self == other
+ } else {
+ false
+ }
+ }
+
+ fn dyn_clone(&self) -> Box<dyn Bounds> {
+ Box::new(self.clone())
}
}
/// Layout a node.
pub trait Layout {
/// Layout the node into the given areas.
- fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Fragment;
+ fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Vec<Frame>;
}
/// The context for layouting.
-#[derive(Debug)]
pub struct LayoutContext<'a> {
/// The environment from which fonts are gathered.
pub env: &'a mut Env,
@@ -183,44 +243,3 @@ impl Expand {
}
}
}
-
-/// The result of layouting a node.
-#[derive(Debug, Clone, PartialEq)]
-pub enum Fragment {
- /// Spacing that should be added to the parent.
- Spacing(Length),
- /// A layout that should be added to and aligned in the parent.
- Frame(Frame, LayoutAligns),
- /// Multiple layouts.
- Frames(Vec<Frame>, LayoutAligns),
-}
-
-impl Fragment {
- /// Return a reference to all frames contained in this variant (zero, one or
- /// arbitrarily many).
- pub fn frames(&self) -> &[Frame] {
- match self {
- Self::Spacing(_) => &[],
- Self::Frame(frame, _) => std::slice::from_ref(frame),
- Self::Frames(frames, _) => frames,
- }
- }
-
- /// Return a mutable reference to all frames contained in this variant.
- pub fn frames_mut(&mut self) -> &mut [Frame] {
- match self {
- Self::Spacing(_) => &mut [],
- Self::Frame(frame, _) => std::slice::from_mut(frame),
- Self::Frames(frames, _) => frames,
- }
- }
-
- /// Return all frames contained in this varian.
- pub fn into_frames(self) -> Vec<Frame> {
- match self {
- Self::Spacing(_) => vec![],
- Self::Frame(frame, _) => vec![frame],
- Self::Frames(frames, _) => frames,
- }
- }
-}
diff --git a/src/layout/node.rs b/src/layout/node.rs
deleted file mode 100644
index 443a96ae..00000000
--- a/src/layout/node.rs
+++ /dev/null
@@ -1,108 +0,0 @@
-use std::any::Any;
-use std::fmt::{self, Debug, Formatter};
-
-use super::*;
-
-/// A self-contained layout node.
-#[derive(Clone, PartialEq)]
-pub enum Node {
- /// A text node.
- Text(TextNode),
- /// A spacing node.
- Spacing(SpacingNode),
- /// A dynamic node that can implement custom layouting behaviour.
- Any(AnyNode),
-}
-
-impl Layout for Node {
- fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Fragment {
- match self {
- Self::Spacing(spacing) => spacing.layout(ctx, areas),
- Self::Text(text) => text.layout(ctx, areas),
- Self::Any(any) => any.layout(ctx, areas),
- }
- }
-}
-
-impl Debug for Node {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- match self {
- Self::Spacing(spacing) => spacing.fmt(f),
- Self::Text(text) => text.fmt(f),
- Self::Any(any) => any.fmt(f),
- }
- }
-}
-
-/// A wrapper around a dynamic layouting node.
-pub struct AnyNode(Box<dyn Bounds>);
-
-impl AnyNode {
- /// Create a new instance from any node that satisifies the required bounds.
- pub fn new<T>(any: T) -> Self
- where
- T: Layout + Debug + Clone + PartialEq + 'static,
- {
- Self(Box::new(any))
- }
-}
-
-impl Layout for AnyNode {
- fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Fragment {
- self.0.layout(ctx, areas)
- }
-}
-
-impl Clone for AnyNode {
- fn clone(&self) -> Self {
- Self(self.0.dyn_clone())
- }
-}
-
-impl PartialEq for AnyNode {
- fn eq(&self, other: &Self) -> bool {
- self.0.dyn_eq(other.0.as_ref())
- }
-}
-
-impl Debug for AnyNode {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- self.0.fmt(f)
- }
-}
-
-impl<T> From<T> for Node
-where
- T: Into<AnyNode>,
-{
- fn from(t: T) -> Self {
- Self::Any(t.into())
- }
-}
-
-trait Bounds: Layout + Debug + 'static {
- fn as_any(&self) -> &dyn Any;
- fn dyn_eq(&self, other: &dyn Bounds) -> bool;
- fn dyn_clone(&self) -> Box<dyn Bounds>;
-}
-
-impl<T> Bounds for T
-where
- T: Layout + Debug + PartialEq + Clone + 'static,
-{
- fn as_any(&self) -> &dyn Any {
- self
- }
-
- fn dyn_eq(&self, other: &dyn Bounds) -> bool {
- if let Some(other) = other.as_any().downcast_ref::<Self>() {
- self == other
- } else {
- false
- }
- }
-
- fn dyn_clone(&self) -> Box<dyn Bounds> {
- Box::new(self.clone())
- }
-}
diff --git a/src/layout/pad.rs b/src/layout/pad.rs
index fb038996..2c8712af 100644
--- a/src/layout/pad.rs
+++ b/src/layout/pad.rs
@@ -6,19 +6,17 @@ pub struct PadNode {
/// The amount of padding.
pub padding: Sides<Linear>,
/// The child node whose sides to pad.
- pub child: Node,
+ pub child: AnyNode,
}
impl Layout for PadNode {
- fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Fragment {
+ fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Vec<Frame> {
let areas = shrink(areas, self.padding);
-
- let mut fragment = self.child.layout(ctx, &areas);
- for frame in fragment.frames_mut() {
+ let mut frames = self.child.layout(ctx, &areas);
+ for frame in &mut frames {
pad(frame, self.padding);
}
-
- fragment
+ frames
}
}
diff --git a/src/layout/par.rs b/src/layout/par.rs
index 0364a03a..02e27cbd 100644
--- a/src/layout/par.rs
+++ b/src/layout/par.rs
@@ -1,38 +1,63 @@
+use std::fmt::{self, Debug, Formatter};
+
use super::*;
+use crate::exec::FontProps;
/// A node that arranges its children into a paragraph.
#[derive(Debug, Clone, PartialEq)]
pub struct ParNode {
- /// The `main` and `cross` directions of this paragraph.
- ///
- /// The children are placed in lines along the `cross` direction. The lines
- /// are stacked along the `main` direction.
- pub dirs: LayoutDirs,
- /// How to align this paragraph in its parent.
- pub aligns: LayoutAligns,
- /// The spacing to insert after each line.
+ /// The inline direction of this paragraph.
+ pub dir: Dir,
+ /// The spacing to insert between each line.
pub line_spacing: Length,
/// The nodes to be arranged in a paragraph.
- pub children: Vec<Node>,
+ pub children: Vec<ParChild>,
+}
+
+/// A child of a paragraph node.
+#[derive(Debug, Clone, PartialEq)]
+pub enum ParChild {
+ /// Spacing between other nodes.
+ Spacing(Length),
+ /// A run of text and how to align it in its line.
+ Text(TextNode, Align),
+ /// Any child node and how to align it in its line.
+ Any(AnyNode, Align),
+}
+
+/// A consecutive, styled run of text.
+#[derive(Clone, PartialEq)]
+pub struct TextNode {
+ /// The text.
+ pub text: String,
+ /// Properties used for font selection and layout.
+ pub props: FontProps,
+}
+
+impl Debug for TextNode {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "Text({})", self.text)
+ }
}
impl Layout for ParNode {
- fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Fragment {
- let mut layouter = ParLayouter::new(self.dirs, self.line_spacing, areas.clone());
+ fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Vec<Frame> {
+ let mut layouter = ParLayouter::new(self.dir, self.line_spacing, areas.clone());
for child in &self.children {
- match child.layout(ctx, &layouter.areas) {
- Fragment::Spacing(spacing) => layouter.push_spacing(spacing),
- Fragment::Frame(frame, aligns) => {
- layouter.push_frame(frame, aligns.cross)
+ match *child {
+ ParChild::Spacing(amount) => layouter.push_spacing(amount),
+ ParChild::Text(ref node, align) => {
+ let frame = shape(&node.text, &mut ctx.env.fonts, &node.props);
+ layouter.push_frame(frame, align);
}
- Fragment::Frames(frames, aligns) => {
- for frame in frames {
- layouter.push_frame(frame, aligns.cross);
+ ParChild::Any(ref node, align) => {
+ for frame in node.layout(ctx, &layouter.areas) {
+ layouter.push_frame(frame, align);
}
}
}
}
- Fragment::Frames(layouter.finish(), self.aligns)
+ layouter.finish()
}
}
@@ -43,30 +68,30 @@ impl From<ParNode> for AnyNode {
}
struct ParLayouter {
+ dirs: Gen<Dir>,
main: SpecAxis,
cross: SpecAxis,
- dirs: LayoutDirs,
line_spacing: Length,
areas: Areas,
finished: Vec<Frame>,
- lines: Vec<(Length, Frame, Align)>,
- lines_size: Gen<Length>,
+ stack: Vec<(Length, Frame, Align)>,
+ stack_size: Gen<Length>,
line: Vec<(Length, Frame, Align)>,
line_size: Gen<Length>,
line_ruler: Align,
}
impl ParLayouter {
- fn new(dirs: LayoutDirs, line_spacing: Length, areas: Areas) -> Self {
+ fn new(dir: Dir, line_spacing: Length, areas: Areas) -> Self {
Self {
- main: dirs.main.axis(),
- cross: dirs.cross.axis(),
- dirs,
+ dirs: Gen::new(Dir::TTB, dir),
+ main: SpecAxis::Vertical,
+ cross: SpecAxis::Horizontal,
line_spacing,
areas,
finished: vec![],
- lines: vec![],
- lines_size: Gen::ZERO,
+ stack: vec![],
+ stack_size: Gen::ZERO,
line: vec![],
line_size: Gen::ZERO,
line_ruler: Align::Start,
@@ -122,12 +147,10 @@ impl ParLayouter {
}
}
- let size = frame.size.switch(self.dirs);
-
// A line can contain frames with different alignments. They exact
// positions are calculated later depending on the alignments.
+ let size = frame.size.switch(self.main);
self.line.push((self.line_size.cross, frame, align));
-
self.line_size.cross += size.cross;
self.line_size.main = self.line_size.main.max(size.main);
self.line_ruler = align;
@@ -135,15 +158,15 @@ impl ParLayouter {
fn finish_line(&mut self) {
let full_size = {
- let expand = self.areas.expand.switch(self.dirs);
- let full = self.areas.full.switch(self.dirs);
+ let expand = self.areas.expand.get(self.cross);
+ let full = self.areas.full.get(self.cross);
Gen::new(
self.line_size.main,
- expand.cross.resolve(self.line_size.cross, full.cross),
+ expand.resolve(self.line_size.cross, full),
)
};
- let mut output = Frame::new(full_size.switch(self.dirs).to_size());
+ let mut output = Frame::new(full_size.switch(self.main).to_size());
for (before, frame, align) in std::mem::take(&mut self.line) {
let child_cross_size = frame.size.get(self.cross);
@@ -158,49 +181,47 @@ impl ParLayouter {
full_size.cross - before_with_self .. after
});
- let pos = Gen::new(Length::ZERO, cross).switch(self.dirs).to_point();
+ let pos = Gen::new(Length::ZERO, cross).switch(self.main).to_point();
output.push_frame(pos, frame);
}
// Add line spacing, but only between lines.
- if !self.lines.is_empty() {
- self.lines_size.main += self.line_spacing;
+ if !self.stack.is_empty() {
+ self.stack_size.main += self.line_spacing;
*self.areas.current.get_mut(self.main) -= self.line_spacing;
}
- // Update metrics of the whole paragraph.
- self.lines.push((self.lines_size.main, output, self.line_ruler));
- self.lines_size.main += full_size.main;
- self.lines_size.cross = self.lines_size.cross.max(full_size.cross);
+ // Update metrics of paragraph and reset for line.
+ self.stack.push((self.stack_size.main, output, self.line_ruler));
+ self.stack_size.main += full_size.main;
+ self.stack_size.cross = self.stack_size.cross.max(full_size.cross);
*self.areas.current.get_mut(self.main) -= full_size.main;
-
- // Reset metrics for the single line.
self.line_size = Gen::ZERO;
self.line_ruler = Align::Start;
}
fn finish_area(&mut self) {
- let size = self.lines_size;
- let mut output = Frame::new(size.switch(self.dirs).to_size());
+ let full_size = self.stack_size;
+ let mut output = Frame::new(full_size.switch(self.main).to_size());
- for (before, line, cross_align) in std::mem::take(&mut self.lines) {
- let child_size = line.size.switch(self.dirs);
+ for (before, line, cross_align) in std::mem::take(&mut self.stack) {
+ let child_size = line.size.switch(self.main);
// Position along the main axis.
let main = if self.dirs.main.is_positive() {
before
} else {
- size.main - (before + child_size.main)
+ full_size.main - (before + child_size.main)
};
// Align along the cross axis.
let cross = cross_align.resolve(if self.dirs.cross.is_positive() {
- Length::ZERO .. size.cross - child_size.cross
+ Length::ZERO .. full_size.cross - child_size.cross
} else {
- size.cross - child_size.cross .. Length::ZERO
+ full_size.cross - child_size.cross .. Length::ZERO
});
- let pos = Gen::new(main, cross).switch(self.dirs).to_point();
+ let pos = Gen::new(main, cross).switch(self.main).to_point();
output.push_frame(pos, line);
}
@@ -208,7 +229,7 @@ impl ParLayouter {
self.areas.next();
// Reset metrics for the whole paragraph.
- self.lines_size = Gen::ZERO;
+ self.stack_size = Gen::ZERO;
}
fn finish(mut self) -> Vec<Frame> {
diff --git a/src/layout/spacing.rs b/src/layout/spacing.rs
deleted file mode 100644
index 361b03ee..00000000
--- a/src/layout/spacing.rs
+++ /dev/null
@@ -1,35 +0,0 @@
-use std::fmt::{self, Debug, Formatter};
-
-use super::*;
-
-/// A node that adds spacing to its parent.
-#[derive(Copy, Clone, PartialEq)]
-pub struct SpacingNode {
- /// The amount of spacing to insert.
- pub amount: Length,
- /// Defines how spacing interacts with surrounding spacing.
- ///
- /// Hard spacing (`softness = 0`) assures that a fixed amount of spacing
- /// will always be inserted. Soft spacing (`softness >= 1`) will be consumed
- /// by other spacing with lower softness and can be used to insert
- /// overridable spacing, e.g. between words or paragraphs.
- pub softness: u8,
-}
-
-impl Layout for SpacingNode {
- fn layout(&self, _: &mut LayoutContext, _: &Areas) -> Fragment {
- Fragment::Spacing(self.amount)
- }
-}
-
-impl Debug for SpacingNode {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- write!(f, "Spacing({}, {})", self.amount, self.softness)
- }
-}
-
-impl From<SpacingNode> for Node {
- fn from(spacing: SpacingNode) -> Self {
- Self::Spacing(spacing)
- }
-}
diff --git a/src/layout/stack.rs b/src/layout/stack.rs
index 6a87290e..79fde72d 100644
--- a/src/layout/stack.rs
+++ b/src/layout/stack.rs
@@ -7,28 +7,34 @@ pub struct StackNode {
///
/// The children are stacked along the `main` direction. The `cross`
/// direction is required for aligning the children.
- pub dirs: LayoutDirs,
- /// How to align this stack in its parent.
- pub aligns: LayoutAligns,
+ pub dirs: Gen<Dir>,
/// The nodes to be stacked.
- pub children: Vec<Node>,
+ pub children: Vec<StackChild>,
+}
+
+/// A child of a stack node.
+#[derive(Debug, Clone, PartialEq)]
+pub enum StackChild {
+ /// Spacing between other nodes.
+ Spacing(Length),
+ /// Any child node and how to align it in the stack.
+ Any(AnyNode, Gen<Align>),
}
impl Layout for StackNode {
- fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Fragment {
+ fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Vec<Frame> {
let mut layouter = StackLayouter::new(self.dirs, areas.clone());
for child in &self.children {
- match child.layout(ctx, &layouter.areas) {
- Fragment::Spacing(spacing) => layouter.push_spacing(spacing),
- Fragment::Frame(frame, aligns) => layouter.push_frame(frame, aligns),
- Fragment::Frames(frames, aligns) => {
- for frame in frames {
+ match *child {
+ StackChild::Spacing(amount) => layouter.push_spacing(amount),
+ StackChild::Any(ref node, aligns) => {
+ for frame in node.layout(ctx, &layouter.areas) {
layouter.push_frame(frame, aligns);
}
}
}
}
- Fragment::Frames(layouter.finish(), self.aligns)
+ layouter.finish()
}
}
@@ -39,24 +45,24 @@ impl From<StackNode> for AnyNode {
}
struct StackLayouter {
+ dirs: Gen<Dir>,
main: SpecAxis,
- dirs: LayoutDirs,
areas: Areas,
finished: Vec<Frame>,
- frames: Vec<(Length, Frame, LayoutAligns)>,
- used: Gen<Length>,
+ frames: Vec<(Length, Frame, Gen<Align>)>,
+ size: Gen<Length>,
ruler: Align,
}
impl StackLayouter {
- fn new(dirs: LayoutDirs, areas: Areas) -> Self {
+ fn new(dirs: Gen<Dir>, areas: Areas) -> Self {
Self {
- main: dirs.main.axis(),
dirs,
+ main: dirs.main.axis(),
areas,
finished: vec![],
frames: vec![],
- used: Gen::ZERO,
+ size: Gen::ZERO,
ruler: Align::Start,
}
}
@@ -65,10 +71,10 @@ impl StackLayouter {
let main_rest = self.areas.current.get_mut(self.main);
let capped = amount.min(*main_rest);
*main_rest -= capped;
- self.used.main += capped;
+ self.size.main += capped;
}
- fn push_frame(&mut self, frame: Frame, aligns: LayoutAligns) {
+ fn push_frame(&mut self, frame: Frame, aligns: Gen<Align>) {
if self.ruler > aligns.main {
self.finish_area();
}
@@ -82,21 +88,18 @@ impl StackLayouter {
}
}
- let size = frame.size.switch(self.dirs);
- self.frames.push((self.used.main, frame, aligns));
-
- *self.areas.current.get_mut(self.main) -= size.main;
- self.used.main += size.main;
- self.used.cross = self.used.cross.max(size.cross);
+ let size = frame.size.switch(self.main);
+ self.frames.push((self.size.main, frame, aligns));
self.ruler = aligns.main;
+ self.size.main += size.main;
+ self.size.cross = self.size.cross.max(size.cross);
+ *self.areas.current.get_mut(self.main) -= size.main;
}
fn finish_area(&mut self) {
let full_size = {
- let expand = self.areas.expand;
- let full = self.areas.full;
- let current = self.areas.current;
- let used = self.used.switch(self.dirs).to_size();
+ let Areas { current, full, expand, .. } = self.areas;
+ let used = self.size.switch(self.main).to_size();
let mut size = Size::new(
expand.horizontal.resolve(used.width, full.width),
@@ -113,21 +116,21 @@ impl StackLayouter {
size = Size::new(width, width / aspect);
}
- size.switch(self.dirs)
+ size.switch(self.main)
};
- let mut output = Frame::new(full_size.switch(self.dirs).to_size());
+ let mut output = Frame::new(full_size.switch(self.main).to_size());
for (before, frame, aligns) in std::mem::take(&mut self.frames) {
- let child_size = frame.size.switch(self.dirs);
+ let child_size = frame.size.switch(self.main);
// Align along the main axis.
let main = aligns.main.resolve(if self.dirs.main.is_positive() {
- let after_with_self = self.used.main - before;
+ let after_with_self = self.size.main - before;
before .. full_size.main - after_with_self
} else {
let before_with_self = before + child_size.main;
- let after = self.used.main - (before + child_size.main);
+ let after = self.size.main - (before + child_size.main);
full_size.main - before_with_self .. after
});
@@ -138,15 +141,14 @@ impl StackLayouter {
full_size.cross - child_size.cross .. Length::ZERO
});
- let pos = Gen::new(main, cross).switch(self.dirs).to_point();
+ let pos = Gen::new(main, cross).switch(self.main).to_point();
output.push_frame(pos, frame);
}
self.finished.push(output);
-
self.areas.next();
- self.used = Gen::ZERO;
self.ruler = Align::Start;
+ self.size = Gen::ZERO;
}
fn finish(mut self) -> Vec<Frame> {
diff --git a/src/layout/text.rs b/src/layout/text.rs
deleted file mode 100644
index 39866907..00000000
--- a/src/layout/text.rs
+++ /dev/null
@@ -1,36 +0,0 @@
-use std::fmt::{self, Debug, Formatter};
-
-use super::*;
-use crate::exec::FontProps;
-
-/// A consecutive, styled run of text.
-#[derive(Clone, PartialEq)]
-pub struct TextNode {
- /// The text direction.
- pub dir: Dir,
- /// How to align this text node in its parent.
- pub aligns: LayoutAligns,
- /// The text.
- pub text: String,
- /// Properties used for font selection and layout.
- pub props: FontProps,
-}
-
-impl Layout for TextNode {
- fn layout(&self, ctx: &mut LayoutContext, _: &Areas) -> Fragment {
- let frame = shape(&self.text, &mut ctx.env.fonts, &self.props);
- Fragment::Frame(frame, self.aligns)
- }
-}
-
-impl Debug for TextNode {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- write!(f, "Text({})", self.text)
- }
-}
-
-impl From<TextNode> for Node {
- fn from(text: TextNode) -> Self {
- Self::Text(text)
- }
-}
diff --git a/src/library/align.rs b/src/library/align.rs
index 765ed988..d5811bf4 100644
--- a/src/library/align.rs
+++ b/src/library/align.rs
@@ -6,11 +6,6 @@ use super::*;
/// - Alignments: variadic, of type `alignment`.
/// - Body: optional, of type `template`.
///
-/// Which axis an alignment should apply to (main or cross) is inferred from
-/// either the argument itself (for anything other than `center`) or from the
-/// second argument if present, defaulting to the cross axis for a single
-/// `center` alignment.
-///
/// # Named parameters
/// - Horizontal alignment: `horizontal`, of type `alignment`.
/// - Vertical alignment: `vertical`, of type `alignment`.
@@ -21,32 +16,44 @@ use super::*;
///
/// # Relevant types and constants
/// - Type `alignment`
+/// - `start`
+/// - `center`
+/// - `end`
/// - `left`
/// - `right`
/// - `top`
/// - `bottom`
-/// - `center`
pub fn align(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
- let first = args.find(ctx);
- let second = args.find(ctx);
- let hor = args.get(ctx, "horizontal");
- let ver = args.get(ctx, "vertical");
+ let first = args.find::<AlignValue>(ctx);
+ let second = args.find::<AlignValue>(ctx);
+ let mut horizontal = args.get::<AlignValue>(ctx, "horizontal");
+ let mut vertical = args.get::<AlignValue>(ctx, "vertical");
let body = args.find::<TemplateValue>(ctx);
+ for value in first.into_iter().chain(second) {
+ match value.axis() {
+ Some(SpecAxis::Horizontal) | None if horizontal.is_none() => {
+ horizontal = Some(value);
+ }
+ Some(SpecAxis::Vertical) | None if vertical.is_none() => {
+ vertical = Some(value);
+ }
+ _ => {}
+ }
+ }
+
Value::template("align", move |ctx| {
let snapshot = ctx.state.clone();
- let values = first
- .into_iter()
- .chain(second.into_iter())
- .map(|arg: Spanned<AlignValue>| (arg.v.axis(), arg))
- .chain(hor.into_iter().map(|arg| (Some(SpecAxis::Horizontal), arg)))
- .chain(ver.into_iter().map(|arg| (Some(SpecAxis::Vertical), arg)));
-
- apply(ctx, values);
+ if let Some(horizontal) = horizontal {
+ ctx.state.aligns.cross = horizontal.to_align(ctx.state.lang.dir);
+ }
- if ctx.state.aligns.main != snapshot.aligns.main {
- ctx.push_linebreak();
+ if let Some(vertical) = vertical {
+ ctx.state.aligns.main = vertical.to_align(Dir::TTB);
+ if ctx.state.aligns.main != snapshot.aligns.main {
+ ctx.push_linebreak();
+ }
}
if let Some(body) = &body {
@@ -56,109 +63,48 @@ pub fn align(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
})
}
-/// Deduplicate and apply the alignments.
-fn apply(
- ctx: &mut ExecContext,
- values: impl Iterator<Item = (Option<SpecAxis>, Spanned<AlignValue>)>,
-) {
- let mut had = Gen::uniform(false);
- let mut had_center = false;
-
- for (axis, Spanned { v: arg, span }) in values {
- // Check whether we know which axis this alignment belongs to.
- if let Some(axis) = axis {
- // We know the axis.
- let gen_axis = axis.switch(ctx.state.dirs);
- let gen_align = arg.switch(ctx.state.dirs);
-
- if arg.axis().map_or(false, |a| a != axis) {
- ctx.diag(error!(span, "invalid alignment for {} axis", axis));
- } else if had.get(gen_axis) {
- ctx.diag(error!(span, "duplicate alignment for {} axis", axis));
- } else {
- *ctx.state.aligns.get_mut(gen_axis) = gen_align;
- *had.get_mut(gen_axis) = true;
- }
- } else {
- // We don't know the axis: This has to be a `center` alignment for a
- // positional argument.
- debug_assert_eq!(arg, AlignValue::Center);
-
- if had.main && had.cross {
- ctx.diag(error!(span, "duplicate alignment"));
- } else if had_center {
- // Both this and the previous one are unspecified `center`
- // alignments. Both axes should be centered.
- ctx.state.aligns.main = Align::Center;
- ctx.state.aligns.cross = Align::Center;
- had = Gen::uniform(true);
- } else {
- had_center = true;
- }
- }
-
- // If we we know the other alignment, we can handle the unspecified
- // `center` alignment.
- if had_center && (had.main || had.cross) {
- if had.main {
- ctx.state.aligns.cross = Align::Center;
- } else {
- ctx.state.aligns.main = Align::Center;
- }
- had = Gen::uniform(true);
- had_center = false;
- }
- }
-
- // If `had_center` wasn't flushed by now, it's the only argument and
- // then we default to applying it to the cross axis.
- if had_center {
- ctx.state.aligns.cross = Align::Center;
- }
-}
-
-/// An alignment value.
+/// An alignment specifier passed to `align`.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub(super) enum AlignValue {
- Left,
+ Start,
Center,
+ End,
+ Left,
Right,
Top,
Bottom,
}
impl AlignValue {
- /// The specific axis this alignment refers to.
fn axis(self) -> Option<SpecAxis> {
match self {
+ Self::Start => None,
+ Self::Center => None,
+ Self::End => None,
Self::Left => Some(SpecAxis::Horizontal),
Self::Right => Some(SpecAxis::Horizontal),
Self::Top => Some(SpecAxis::Vertical),
Self::Bottom => Some(SpecAxis::Vertical),
- Self::Center => None,
}
}
-}
-
-impl Switch for AlignValue {
- type Other = Align;
- fn switch(self, dirs: LayoutDirs) -> Self::Other {
- let get = |dir: Dir, at_positive_start| {
- if dir.is_positive() == at_positive_start {
+ fn to_align(self, dir: Dir) -> Align {
+ let side = |is_at_positive_start| {
+ if dir.is_positive() == is_at_positive_start {
Align::Start
} else {
Align::End
}
};
- let dirs = dirs.switch(dirs);
match self {
- Self::Left => get(dirs.horizontal, true),
- Self::Right => get(dirs.horizontal, false),
- Self::Top => get(dirs.vertical, true),
- Self::Bottom => get(dirs.vertical, false),
+ Self::Start => Align::Start,
Self::Center => Align::Center,
+ Self::End => Align::End,
+ Self::Left => side(true),
+ Self::Right => side(false),
+ Self::Top => side(true),
+ Self::Bottom => side(false),
}
}
}
@@ -166,8 +112,10 @@ impl Switch for AlignValue {
impl Display for AlignValue {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(match self {
- Self::Left => "left",
+ Self::Start => "start",
Self::Center => "center",
+ Self::End => "end",
+ Self::Left => "left",
Self::Right => "right",
Self::Top => "top",
Self::Bottom => "bottom",
diff --git a/src/library/image.rs b/src/library/image.rs
index 9f39073b..020f7d50 100644
--- a/src/library/image.rs
+++ b/src/library/image.rs
@@ -2,9 +2,7 @@ use ::image::GenericImageView;
use super::*;
use crate::env::{ImageResource, ResourceId};
-use crate::layout::{
- AnyNode, Areas, Element, Fragment, Frame, Image, Layout, LayoutContext,
-};
+use crate::layout::{AnyNode, Areas, Element, Frame, Image, Layout, LayoutContext};
/// `image`: An image.
///
@@ -25,13 +23,7 @@ pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let loaded = ctx.env.resources.load(&path.v, ImageResource::parse);
if let Some((res, img)) = loaded {
let dimensions = img.buf.dimensions();
- ctx.push(ImageNode {
- res,
- dimensions,
- width,
- height,
- aligns: ctx.state.aligns,
- });
+ ctx.push_into_par(ImageNode { res, dimensions, width, height });
} else {
ctx.diag(error!(path.span, "failed to load image"));
}
@@ -42,8 +34,6 @@ pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
/// An image node.
#[derive(Debug, Clone, PartialEq)]
struct ImageNode {
- /// How to align this image node in its parent.
- aligns: LayoutAligns,
/// The resource id of the image file.
res: ResourceId,
/// The pixel dimensions of the image.
@@ -55,7 +45,7 @@ struct ImageNode {
}
impl Layout for ImageNode {
- fn layout(&self, _: &mut LayoutContext, areas: &Areas) -> Fragment {
+ fn layout(&self, _: &mut LayoutContext, areas: &Areas) -> Vec<Frame> {
let Areas { current, full, .. } = areas;
let pixel_width = self.dimensions.0 as f64;
@@ -86,7 +76,7 @@ impl Layout for ImageNode {
let mut frame = Frame::new(size);
frame.push(Point::ZERO, Element::Image(Image { res: self.res, size }));
- Fragment::Frame(frame, self.aligns)
+ vec![frame]
}
}
diff --git a/src/library/lang.rs b/src/library/lang.rs
new file mode 100644
index 00000000..79015c7d
--- /dev/null
+++ b/src/library/lang.rs
@@ -0,0 +1,45 @@
+use super::*;
+
+/// `lang`: Configure the language.
+///
+/// # Positional parameters
+/// - Language: of type `string`. Has to be a valid ISO 639-1 code.
+///
+/// # Named parameters
+/// - Text direction: `dir`, of type `direction`, must be horizontal.
+///
+/// # Return value
+/// A template that configures language properties.
+///
+/// # Relevant types and constants
+/// - Type `direction`
+/// - `ltr`
+/// - `rtl`
+pub fn lang(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
+ let iso = args.find::<String>(ctx).map(|s| s.to_ascii_lowercase());
+ let dir = args.get::<Spanned<Dir>>(ctx, "dir");
+
+ Value::template("lang", move |ctx| {
+ if let Some(iso) = &iso {
+ ctx.state.lang.dir = lang_dir(iso);
+ }
+
+ if let Some(dir) = dir {
+ if dir.v.axis() == SpecAxis::Horizontal {
+ ctx.state.lang.dir = dir.v;
+ } else {
+ ctx.diag(error!(dir.span, "must be horizontal"));
+ }
+ }
+
+ ctx.push_parbreak();
+ })
+}
+
+/// The default direction for the language identified by `iso`.
+fn lang_dir(iso: &str) -> Dir {
+ match iso {
+ "ar" | "he" | "fa" | "ur" | "ps" | "yi" => Dir::RTL,
+ "en" | "fr" | "de" | _ => Dir::LTR,
+ }
+}
diff --git a/src/library/mod.rs b/src/library/mod.rs
index 1f412cd0..9c2a661a 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -7,6 +7,7 @@ mod align;
mod base;
mod font;
mod image;
+mod lang;
mod markup;
mod pad;
mod page;
@@ -18,6 +19,7 @@ pub use self::image::*;
pub use align::*;
pub use base::*;
pub use font::*;
+pub use lang::*;
pub use markup::*;
pub use pad::*;
pub use page::*;
@@ -31,7 +33,7 @@ use fontdock::{FontStyle, FontWeight};
use crate::eval::{AnyValue, FuncValue, Scope};
use crate::eval::{EvalContext, FuncArgs, TemplateValue, Value};
-use crate::exec::{Exec, ExecContext, FontFamily};
+use crate::exec::{Exec, FontFamily};
use crate::font::VerticalFontMetric;
use crate::geom::*;
use crate::syntax::{Node, Spanned};
@@ -67,6 +69,7 @@ pub fn _new() -> Scope {
func!("font", font);
func!("h", h);
func!("image", image);
+ func!("lang", lang);
func!("pad", pad);
func!("page", page);
func!("pagebreak", pagebreak);
@@ -79,8 +82,10 @@ pub fn _new() -> Scope {
func!("v", v);
// Constants.
- constant!("left", AlignValue::Left);
+ constant!("start", AlignValue::Start);
constant!("center", AlignValue::Center);
+ constant!("end", AlignValue::End);
+ constant!("left", AlignValue::Left);
constant!("right", AlignValue::Right);
constant!("top", AlignValue::Top);
constant!("bottom", AlignValue::Bottom);
diff --git a/src/library/pad.rs b/src/library/pad.rs
index 5a685d2a..d6b69007 100644
--- a/src/library/pad.rs
+++ b/src/library/pad.rs
@@ -31,9 +31,7 @@ pub fn pad(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
);
Value::template("pad", move |ctx| {
- let snapshot = ctx.state.clone();
- let child = ctx.exec(&body).into();
- ctx.push(PadNode { padding, child });
- ctx.state = snapshot;
+ let child = ctx.exec_group(&body).into();
+ ctx.push_into_par(PadNode { padding, child });
})
}
diff --git a/src/library/page.rs b/src/library/page.rs
index 89722ba3..fb3542ed 100644
--- a/src/library/page.rs
+++ b/src/library/page.rs
@@ -17,19 +17,10 @@ use crate::paper::{Paper, PaperClass};
/// - Top margin: `top`, of type `linear` relative to height.
/// - Bottom margin: `bottom`, of type `linear` relative to height.
/// - Flip width and height: `flip`, of type `bool`.
-/// - Main layouting direction: `main-dir`, of type `direction`.
-/// - Cross layouting direction: `cross-dir`, of type `direction`.
///
/// # Return value
/// A template that configures page properties. The effect is scoped to the body
/// if present.
-///
-/// # Relevant types and constants
-/// - Type `direction`
-/// - `ltr` (left to right)
-/// - `rtl` (right to left)
-/// - `ttb` (top to bottom)
-/// - `btt` (bottom to top)
pub fn page(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let paper = args.find::<Spanned<String>>(ctx).and_then(|name| {
Paper::from_name(&name.v).or_else(|| {
@@ -46,8 +37,6 @@ pub fn page(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let right = args.get(ctx, "right");
let bottom = args.get(ctx, "bottom");
let flip = args.get(ctx, "flip");
- let main = args.get(ctx, "main-dir");
- let cross = args.get(ctx, "cross-dir");
let body = args.find::<TemplateValue>(ctx);
let span = args.span;
@@ -94,7 +83,6 @@ pub fn page(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
std::mem::swap(&mut page.size.width, &mut page.size.height);
}
- ctx.set_dirs(Gen::new(main, cross));
ctx.finish_page(false, true, span);
if let Some(body) = &body {
diff --git a/src/library/par.rs b/src/library/par.rs
index 0467af44..cf2549bf 100644
--- a/src/library/par.rs
+++ b/src/library/par.rs
@@ -2,26 +2,19 @@ use super::*;
/// `par`: Configure paragraphs.
///
-/// # Positional parameters
-/// - Body: optional, of type `template`.
-///
/// # Named parameters
/// - Paragraph spacing: `spacing`, of type `linear` relative to current font size.
/// - Line leading: `leading`, of type `linear` relative to current font size.
/// - Word spacing: `word-spacing`, of type `linear` relative to current font size.
///
/// # Return value
-/// A template that configures paragraph properties. The effect is scoped to the
-/// body if present.
+/// A template that configures paragraph properties.
pub fn par(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let spacing = args.get(ctx, "spacing");
let leading = args.get(ctx, "leading");
let word_spacing = args.get(ctx, "word-spacing");
- let body = args.find::<TemplateValue>(ctx);
Value::template("par", move |ctx| {
- let snapshot = ctx.state.clone();
-
if let Some(spacing) = spacing {
ctx.state.par.spacing = spacing;
}
@@ -35,10 +28,5 @@ pub fn par(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
}
ctx.push_parbreak();
-
- if let Some(body) = &body {
- body.exec(ctx);
- ctx.state = snapshot;
- }
})
}
diff --git a/src/library/shapes.rs b/src/library/shapes.rs
index 9f705ef7..6f9e6677 100644
--- a/src/library/shapes.rs
+++ b/src/library/shapes.rs
@@ -59,21 +59,18 @@ fn rect_impl(
body: TemplateValue,
) -> Value {
Value::template(name, move |ctx| {
- let snapshot = ctx.state.clone();
- let child = ctx.exec(&body).into();
+ let child = ctx.exec_group(&body).into();
let node = FixedNode { width, height, aspect, child };
if let Some(color) = fill {
- ctx.push(BackgroundNode {
+ ctx.push_into_par(BackgroundNode {
shape: BackgroundShape::Rect,
fill: Fill::Color(color),
child: node.into(),
});
} else {
- ctx.push(node);
+ ctx.push_into_par(node);
}
-
- ctx.state = snapshot;
})
}
@@ -136,8 +133,7 @@ fn ellipse_impl(
// perfectly into the ellipse.
const PAD: f64 = 0.5 - SQRT_2 / 4.0;
- let snapshot = ctx.state.clone();
- let child = ctx.exec(&body).into();
+ let child = ctx.exec_group(&body).into();
let node = FixedNode {
width,
height,
@@ -150,15 +146,13 @@ fn ellipse_impl(
};
if let Some(color) = fill {
- ctx.push(BackgroundNode {
+ ctx.push_into_par(BackgroundNode {
shape: BackgroundShape::Ellipse,
fill: Fill::Color(color),
child: node.into(),
});
} else {
- ctx.push(node);
+ ctx.push_into_par(node);
}
-
- ctx.state = snapshot;
})
}
diff --git a/src/library/spacing.rs b/src/library/spacing.rs
index d4648566..6a67a653 100644
--- a/src/library/spacing.rs
+++ b/src/library/spacing.rs
@@ -1,5 +1,4 @@
use super::*;
-use crate::layout::SpacingNode;
/// `h`: Horizontal spacing.
///
@@ -9,7 +8,7 @@ use crate::layout::SpacingNode;
/// # Return value
/// A template that inserts horizontal spacing.
pub fn h(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
- spacing_impl(ctx, args, SpecAxis::Horizontal)
+ spacing_impl("h", ctx, args, GenAxis::Cross)
}
/// `v`: Vertical spacing.
@@ -20,20 +19,20 @@ pub fn h(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
/// # Return value
/// A template that inserts vertical spacing.
pub fn v(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
- spacing_impl(ctx, args, SpecAxis::Vertical)
+ spacing_impl("v", ctx, args, GenAxis::Main)
}
-fn spacing_impl(ctx: &mut EvalContext, args: &mut FuncArgs, axis: SpecAxis) -> Value {
+fn spacing_impl(
+ name: &str,
+ ctx: &mut EvalContext,
+ args: &mut FuncArgs,
+ axis: GenAxis,
+) -> Value {
let spacing: Option<Linear> = args.require(ctx, "spacing");
- Value::template("spacing", move |ctx| {
+ Value::template(name, move |ctx| {
if let Some(linear) = spacing {
let amount = linear.resolve(ctx.state.font.resolve_size());
- let spacing = SpacingNode { amount, softness: 0 };
- if axis == ctx.state.dirs.main.axis() {
- ctx.push_into_stack(spacing);
- } else {
- ctx.push(spacing);
- }
+ ctx.push_spacing(axis, amount, 0);
}
})
}
diff --git a/tests/ref/library/lang.png b/tests/ref/library/lang.png
new file mode 100644
index 00000000..98a63b6e
--- /dev/null
+++ b/tests/ref/library/lang.png
Binary files differ
diff --git a/tests/ref/library/page.png b/tests/ref/library/page.png
index 8e5a83ff..9d2a6b95 100644
--- a/tests/ref/library/page.png
+++ b/tests/ref/library/page.png
Binary files differ
diff --git a/tests/ref/library/spacing.png b/tests/ref/library/spacing.png
index c266b9fa..fa403a6d 100644
--- a/tests/ref/library/spacing.png
+++ b/tests/ref/library/spacing.png
Binary files differ
diff --git a/tests/typ/library/lang.typ b/tests/typ/library/lang.typ
new file mode 100644
index 00000000..87d2c154
--- /dev/null
+++ b/tests/typ/library/lang.typ
@@ -0,0 +1,16 @@
+// Test the `lang` function.
+
+---
+Left to right.
+
+#lang("ar")
+Right to left.
+
+#lang(dir: ltr)
+Back again.
+
+---
+// Ref: false
+
+// Error: 12-15 must be horizontal
+#lang(dir: ttb)
diff --git a/tests/typ/library/page.typ b/tests/typ/library/page.typ
index 5123b876..7f9a0d2c 100644
--- a/tests/typ/library/page.typ
+++ b/tests/typ/library/page.typ
@@ -27,9 +27,6 @@
// Error: 7-18 unknown variable
#page(nonexistant)
-// Error: 17-20 aligned axis
-#page(main-dir: ltr)
-
// Flipped predefined paper.
#page("a11", flip: true)[Flipped A11]
@@ -38,10 +35,6 @@
#page(flip: true)
Wide
-// Test changing the layouting directions of pages.
-#page(height: 50pt, main-dir: btt, cross-dir: rtl)
-Right to left!
-
---
// Test a combination of pages with bodies and normal content.
diff --git a/tests/typ/library/spacing.typ b/tests/typ/library/spacing.typ
index 6d50f0dc..bd38631e 100644
--- a/tests/typ/library/spacing.typ
+++ b/tests/typ/library/spacing.typ
@@ -16,10 +16,3 @@ Relative #h(100%) spacing
// Missing spacing.
// Error: 12 missing argument: spacing
Totally #h() ignored
-
-// Swapped axes.
-#page(main-dir: rtl, cross-dir: ttb, height: 80pt)[
- 1 #h(1cm) 2
-
- 3 #v(1cm) 4 #v(-1cm) 5
-]