diff options
Diffstat (limited to 'src/library')
| -rw-r--r-- | src/library/align.rs | 106 | ||||
| -rw-r--r-- | src/library/flow.rs | 69 | ||||
| -rw-r--r-- | src/library/image.rs | 4 | ||||
| -rw-r--r-- | src/library/mod.rs | 6 | ||||
| -rw-r--r-- | src/library/par.rs | 56 | ||||
| -rw-r--r-- | src/library/shape.rs | 14 | ||||
| -rw-r--r-- | src/library/sized.rs | 2 | ||||
| -rw-r--r-- | src/library/stack.rs | 36 |
8 files changed, 176 insertions, 117 deletions
diff --git a/src/library/align.rs b/src/library/align.rs index c6f96a13..591a4085 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -1,51 +1,77 @@ use super::prelude::*; /// `align`: Configure the alignment along the layouting axes. -pub fn align(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { - let first = args.find::<Align>(); - let second = args.find::<Align>(); - let body = args.find::<Template>(); - - let mut horizontal = args.named("horizontal")?; - let mut vertical = args.named("vertical")?; - - 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); - } - _ => {} +pub fn align(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { + let mut x = args.named("horizontal")?; + let mut y = args.named("vertical")?; + for Spanned { v, span } in args.all::<Spanned<Align>>() { + match v.axis() { + None | Some(SpecAxis::Horizontal) if x.is_none() => x = Some(v), + None | Some(SpecAxis::Vertical) if y.is_none() => y = Some(v), + _ => bail!(span, "unexpected argument"), } } - let realign = |template: &mut Template| { - template.modify(move |style| { - if let Some(horizontal) = horizontal { - style.aligns.inline = horizontal; - } + let body = args.expect::<Template>("body")?; + + Ok(Value::Template(Template::from_block(move |style| { + let mut style = style.clone(); + if let Some(x) = x { + style.par_mut().align = x; + } + + body.pack(&style).aligned(x, y) + }))) +} + +/// A node that aligns its child. +#[derive(Debug, Hash)] +pub struct AlignNode { + /// The node to be aligned. + pub child: PackedNode, + /// How to align the node horizontally and vertically. + pub aligns: Spec<Option<Align>>, +} + +impl Layout for AlignNode { + fn layout( + &self, + ctx: &mut LayoutContext, + regions: &Regions, + ) -> Vec<Constrained<Rc<Frame>>> { + // Along axes with specified alignment, the child doesn't need to expand. + let mut pod = regions.clone(); + pod.expand.x &= self.aligns.x.is_none(); + pod.expand.y &= self.aligns.y.is_none(); + + let mut frames = self.child.layout(ctx, &pod); + + for (Constrained { item: frame, cts }, (current, _)) in + frames.iter_mut().zip(regions.iter()) + { + let canvas = Size::new( + if regions.expand.x { current.w } else { frame.size.w }, + if regions.expand.y { current.h } else { frame.size.h }, + ); - if let Some(vertical) = vertical { - style.aligns.block = vertical; + let aligns = self.aligns.unwrap_or(Spec::new(Align::Left, Align::Top)); + let offset = Point::new( + aligns.x.resolve(Length::zero() .. canvas.w - frame.size.w), + aligns.y.resolve(Length::zero() .. canvas.h - frame.size.h), + ); + + let frame = Rc::make_mut(frame); + frame.size = canvas; + frame.baseline += offset.y; + + for (point, _) in &mut frame.elements { + *point += offset; } - }); - if vertical.is_some() { - template.parbreak(); + cts.expand = regions.expand; + cts.exact = current.to_spec().map(Some); } - }; - - Ok(if let Some(body) = body { - let mut template = Template::new(); - template.save(); - realign(&mut template); - template += body; - template.restore(); - Value::Template(template) - } else { - realign(&mut ctx.template); - Value::None - }) + + frames + } } diff --git a/src/library/flow.rs b/src/library/flow.rs index 9fe8b95d..f58df536 100644 --- a/src/library/flow.rs +++ b/src/library/flow.rs @@ -1,7 +1,7 @@ use std::fmt::{self, Debug, Formatter}; use super::prelude::*; -use super::Spacing; +use super::{AlignNode, ParNode, Spacing}; /// `flow`: A vertical flow of paragraphs and other layout nodes. pub fn flow(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { @@ -27,7 +27,7 @@ pub fn flow(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { .iter() .map(|child| match child { Child::Spacing(spacing) => FlowChild::Spacing(*spacing), - Child::Any(node) => FlowChild::Node(node.pack(style), style.aligns.block), + Child::Any(node) => FlowChild::Node(node.pack(style)), }) .collect(); @@ -62,14 +62,14 @@ pub enum FlowChild { /// Vertical spacing between other children. Spacing(Spacing), /// A node and how to align it in the flow. - Node(PackedNode, Align), + Node(PackedNode), } impl Debug for FlowChild { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Self::Spacing(v) => write!(f, "Spacing({:?})", v), - Self::Node(node, _) => node.fmt(f), + Self::Node(node) => node.fmt(f), } } } @@ -101,8 +101,8 @@ enum FlowItem { Absolute(Length), /// Fractional spacing between other items. Fractional(Fractional), - /// A layouted child node. - Frame(Rc<Frame>, Align), + /// A layouted child node and how to align it vertically. + Frame(Rc<Frame>, Spec<Align>), } impl<'a> FlowLayouter<'a> { @@ -135,8 +135,8 @@ impl<'a> FlowLayouter<'a> { self.items.push(FlowItem::Fractional(v)); self.fr += v; } - FlowChild::Node(ref node, align) => { - self.layout_node(ctx, node, align); + FlowChild::Node(ref node) => { + self.layout_node(ctx, node); } } } @@ -156,7 +156,17 @@ impl<'a> FlowLayouter<'a> { } /// Layout a node. - fn layout_node(&mut self, ctx: &mut LayoutContext, node: &PackedNode, align: Align) { + fn layout_node(&mut self, ctx: &mut LayoutContext, node: &PackedNode) { + let aligns = Spec::new( + // For non-expanding paragraphs it is crucial that we align the + // whole paragraph according to its internal alignment. + node.downcast::<ParNode>().map_or(Align::Left, |node| node.align), + // Vertical align node alignment is respected by the flow node. + node.downcast::<AlignNode>() + .and_then(|node| node.aligns.y) + .unwrap_or(Align::Top), + ); + let frames = node.layout(ctx, &self.regions); let len = frames.len(); for (i, frame) in frames.into_iter().enumerate() { @@ -165,7 +175,7 @@ impl<'a> FlowLayouter<'a> { self.used.h += size.h; self.used.w.set_max(size.w); self.regions.current.h -= size.h; - self.items.push(FlowItem::Frame(frame.item, align)); + self.items.push(FlowItem::Frame(frame.item, aligns)); if i + 1 < len { self.finish_region(); @@ -177,43 +187,44 @@ impl<'a> FlowLayouter<'a> { fn finish_region(&mut self) { // Determine the size of the flow in this region dependening on whether // the region expands. - let size = Size::new( + let mut size = Size::new( if self.expand.x { self.full.w } else { self.used.w }, - if self.expand.y || (self.fr.get() > 0.0 && self.full.h.is_finite()) { - self.full.h - } else { - self.used.h - }, + if self.expand.y { self.full.h } else { self.used.h }, ); + // Account for fractional spacing in the size calculation. + let remaining = self.full.h - self.used.h; + if self.fr.get() > 0.0 && self.full.h.is_finite() { + self.used.h = self.full.h; + size.h = self.full.h; + } + let mut output = Frame::new(size, size.h); let mut before = Length::zero(); - let mut ruler = Align::Start; + let mut ruler = Align::Top; let mut first = true; // Place all frames. for item in self.items.drain(..) { match item { FlowItem::Absolute(v) => before += v, - FlowItem::Fractional(v) => { - let remaining = self.full.h - self.used.h; - before += v.resolve(self.fr, remaining); - } - FlowItem::Frame(frame, align) => { - ruler = ruler.max(align); - - // Align vertically. - let range = before .. before + size.h - self.used.h; - let y = ruler.resolve(Dir::TTB, range); + FlowItem::Fractional(v) => before += v.resolve(self.fr, remaining), + FlowItem::Frame(frame, aligns) => { + ruler = ruler.max(aligns.y); + + // Align horizontally and vertically. + let x = aligns.x.resolve(Length::zero() .. size.w - frame.size.w); + let y = ruler.resolve(before .. before + size.h - self.used.h); + let pos = Point::new(x, y); before += frame.size.h; // The baseline of the flow is that of the first frame. if first { - output.baseline = y + frame.baseline; + output.baseline = pos.y + frame.baseline; first = false; } - output.push_frame(Point::new(Length::zero(), y), frame); + output.push_frame(pos, frame); } } } diff --git a/src/library/image.rs b/src/library/image.rs index ea9a050c..ea4f775c 100644 --- a/src/library/image.rs +++ b/src/library/image.rs @@ -54,9 +54,9 @@ impl Layout for ImageNode { let canvas = if expand.x && expand.y { current } else if expand.x || (wide && current.w.is_finite()) { - Size::new(current.w, current.w.safe_div(pixel_ratio)) + Size::new(current.w, current.h.min(current.w.safe_div(pixel_ratio))) } else if current.h.is_finite() { - Size::new(current.h * pixel_ratio, current.h) + Size::new(current.w.min(current.h * pixel_ratio), current.h) } else { Size::new(Length::pt(pixel_w), Length::pt(pixel_h)) }; diff --git a/src/library/mod.rs b/src/library/mod.rs index fdede8bd..4f33b715 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -117,12 +117,10 @@ pub fn new() -> Scope { std.def_const("rtl", Dir::RTL); std.def_const("ttb", Dir::TTB); std.def_const("btt", Dir::BTT); - std.def_const("start", Align::Start); - std.def_const("center", Align::Center); - std.def_const("end", Align::End); std.def_const("left", Align::Left); - std.def_const("right", Align::Right); std.def_const("top", Align::Top); + std.def_const("center", Align::Center); + std.def_const("right", Align::Right); std.def_const("bottom", Align::Bottom); std.def_const("serif", FontFamily::Serif); std.def_const("sans-serif", FontFamily::SansSerif); diff --git a/src/library/par.rs b/src/library/par.rs index 64b68c68..67c2969b 100644 --- a/src/library/par.rs +++ b/src/library/par.rs @@ -36,6 +36,7 @@ pub fn par(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { if let Some(dir) = dir { par.dir = dir; + par.align = if dir == Dir::LTR { Align::Left } else { Align::Right }; } if let Some(leading) = leading { @@ -55,8 +56,10 @@ pub fn par(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { /// A node that arranges its children into a paragraph. #[derive(Debug, Hash)] pub struct ParNode { - /// The inline direction of this paragraph. + /// The text direction (either LTR or RTL). pub dir: Dir, + /// How to align text in its line. + pub align: Align, /// The spacing to insert between each line. pub leading: Length, /// The children to be arranged in a paragraph. @@ -124,9 +127,9 @@ pub enum ParChild { /// Spacing between other nodes. Spacing(Spacing), /// A run of text and how to align it in its line. - Text(EcoString, Align, Rc<TextStyle>), + Text(EcoString, Rc<TextStyle>), /// Any child node and how to align it in its line. - Node(PackedNode, Align), + Node(PackedNode), /// A decoration that applies until a matching `Undecorate`. Decorate(Decoration), /// The end of a decoration. @@ -137,8 +140,8 @@ impl Debug for ParChild { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Self::Spacing(v) => write!(f, "Spacing({:?})", v), - Self::Text(text, ..) => write!(f, "Text({:?})", text), - Self::Node(node, ..) => node.fmt(f), + Self::Text(text, _) => write!(f, "Text({:?})", text), + Self::Node(node) => node.fmt(f), Self::Decorate(deco) => write!(f, "Decorate({:?})", deco), Self::Undecorate => write!(f, "Undecorate"), } @@ -148,9 +151,9 @@ impl Debug for ParChild { /// A paragraph representation in which children are already layouted and text /// is separated into shapable runs. struct ParLayouter<'a> { - /// The top-level direction. - dir: Dir, - /// The line spacing. + /// How to align text in its line. + align: Align, + /// The spacing to insert between each line. leading: Length, /// Bidirectional text embedding levels for the paragraph. bidi: BidiInfo<'a>, @@ -172,9 +175,9 @@ enum ParItem<'a> { /// Fractional spacing between other items. Fractional(Fractional), /// A shaped text run with consistent direction. - Text(ShapedText<'a>, Align), + Text(ShapedText<'a>), /// A layouted child node. - Frame(Frame, Align), + Frame(Frame), } impl<'a> ParLayouter<'a> { @@ -202,7 +205,7 @@ impl<'a> ParLayouter<'a> { items.push(ParItem::Fractional(v)); ranges.push(range); } - ParChild::Text(_, align, ref style) => { + ParChild::Text(_, ref style) => { // TODO: Also split by language and script. let mut cursor = range.start; for (level, group) in bidi.levels[range].group_by_key(|&lvl| lvl) { @@ -211,16 +214,16 @@ impl<'a> ParLayouter<'a> { let subrange = start .. cursor; let text = &bidi.text[subrange.clone()]; let shaped = shape(ctx, text, style, level.dir()); - items.push(ParItem::Text(shaped, align)); + items.push(ParItem::Text(shaped)); ranges.push(subrange); } } - ParChild::Node(ref node, align) => { + ParChild::Node(ref node) => { let size = Size::new(regions.current.w, regions.base.h); let expand = Spec::splat(false); let pod = Regions::one(size, regions.base, expand); let frame = node.layout(ctx, &pod).remove(0); - items.push(ParItem::Frame(Rc::take(frame.item), align)); + items.push(ParItem::Frame(Rc::take(frame.item))); ranges.push(range); } ParChild::Decorate(ref deco) => { @@ -234,7 +237,7 @@ impl<'a> ParLayouter<'a> { } Self { - dir: par.dir, + align: par.align, leading: par.leading, bidi, items, @@ -392,7 +395,7 @@ impl<'a> LineLayout<'a> { // Reshape the last item if it's split in half. let mut last = None; - if let Some((ParItem::Text(shaped, align), rest)) = items.split_last() { + if let Some((ParItem::Text(shaped), rest)) = items.split_last() { // Compute the range we want to shape, trimming whitespace at the // end of the line. let base = par.ranges[last_idx].start; @@ -408,7 +411,7 @@ impl<'a> LineLayout<'a> { if !range.is_empty() || rest.is_empty() { // Reshape that part. let reshaped = shaped.reshape(ctx, range); - last = Some(ParItem::Text(reshaped, *align)); + last = Some(ParItem::Text(reshaped)); } items = rest; @@ -418,7 +421,7 @@ impl<'a> LineLayout<'a> { // Reshape the start item if it's split in half. let mut first = None; - if let Some((ParItem::Text(shaped, align), rest)) = items.split_first() { + if let Some((ParItem::Text(shaped), rest)) = items.split_first() { // Compute the range we want to shape. let Range { start: base, end: first_end } = par.ranges[first_idx]; let start = line.start; @@ -429,7 +432,7 @@ impl<'a> LineLayout<'a> { if range.len() < shaped.text.len() { if !range.is_empty() { let reshaped = shaped.reshape(ctx, range); - first = Some(ParItem::Text(reshaped, *align)); + first = Some(ParItem::Text(reshaped)); } items = rest; @@ -446,8 +449,8 @@ impl<'a> LineLayout<'a> { match *item { ParItem::Absolute(v) => width += v, ParItem::Fractional(v) => fr += v, - ParItem::Text(ShapedText { size, baseline, .. }, _) - | ParItem::Frame(Frame { size, baseline, .. }, _) => { + ParItem::Text(ShapedText { size, baseline, .. }) + | ParItem::Frame(Frame { size, baseline, .. }) => { width += size.w; top.set_max(baseline); bottom.set_max(size.h - baseline); @@ -475,10 +478,9 @@ impl<'a> LineLayout<'a> { let mut output = Frame::new(size, self.baseline); let mut offset = Length::zero(); - let mut ruler = Align::Start; for (range, item) in self.reordered() { - let mut position = |mut frame: Frame, align: Align| { + let mut position = |mut frame: Frame| { // Decorate. for (deco_range, deco) in &self.par.decos { if deco_range.contains(&range.start) { @@ -486,9 +488,7 @@ impl<'a> LineLayout<'a> { } } - // FIXME: Ruler alignment for RTL. - ruler = ruler.max(align); - let x = ruler.resolve(self.par.dir, offset .. remaining + offset); + let x = self.par.align.resolve(offset .. remaining + offset); let y = self.baseline - frame.baseline; offset += frame.size.w; @@ -499,8 +499,8 @@ impl<'a> LineLayout<'a> { match *item { ParItem::Absolute(v) => offset += v, ParItem::Fractional(v) => offset += v.resolve(self.fr, remaining), - ParItem::Text(ref shaped, align) => position(shaped.build(), align), - ParItem::Frame(ref frame, align) => position(frame.clone(), align), + ParItem::Text(ref shaped) => position(shaped.build()), + ParItem::Frame(ref frame) => position(frame.clone()), } } diff --git a/src/library/shape.rs b/src/library/shape.rs index dbd6eea7..5d9b4152 100644 --- a/src/library/shape.rs +++ b/src/library/shape.rs @@ -140,7 +140,7 @@ impl Layout for ShapeNode { // When there's no child, fill the area if expansion is on, // otherwise fall back to a default size. let default = Length::pt(30.0); - let size = Size::new( + let mut size = Size::new( if regions.expand.x { regions.current.w } else { @@ -154,6 +154,11 @@ impl Layout for ShapeNode { if regions.expand.y { regions.current.h } else { default }, ); + if matches!(self.kind, ShapeKind::Square | ShapeKind::Circle) { + size.w = size.w.min(size.h); + size.h = size.w; + } + Frame::new(size, size.h) }; @@ -171,6 +176,13 @@ impl Layout for ShapeNode { frame.prepend(pos, Element::Geometry(geometry, fill)); } + // Ensure frame size matches regions size if expansion is on. + let expand = regions.expand; + frame.size = Size::new( + if expand.x { regions.current.w } else { frame.size.w }, + if expand.y { regions.current.h } else { frame.size.h }, + ); + // Return tight constraints for now. let mut cts = Constraints::new(regions.expand); cts.exact = regions.current.to_spec().map(Some); diff --git a/src/library/sized.rs b/src/library/sized.rs index 6394b0f4..3089f90b 100644 --- a/src/library/sized.rs +++ b/src/library/sized.rs @@ -23,7 +23,7 @@ pub fn block(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { /// A node that sizes its child. #[derive(Debug, Hash)] pub struct SizedNode { - /// The node to-be-sized. + /// The node to be sized. pub child: PackedNode, /// How to size the node horizontally and vertically. pub sizing: Spec<Option<Linear>>, diff --git a/src/library/stack.rs b/src/library/stack.rs index 61406e8e..7ac7e6ee 100644 --- a/src/library/stack.rs +++ b/src/library/stack.rs @@ -1,7 +1,7 @@ use std::fmt::{self, Debug, Formatter}; use super::prelude::*; -use super::Spacing; +use super::{AlignNode, Spacing}; /// `stack`: Stack children along an axis. pub fn stack(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { @@ -118,7 +118,7 @@ enum StackItem { /// Fractional spacing between other items. Fractional(Fractional), /// A layouted child node. - Frame(Rc<Frame>), + Frame(Rc<Frame>, Align), } impl<'a> StackLayouter<'a> { @@ -176,6 +176,12 @@ impl<'a> StackLayouter<'a> { /// Layout a node. fn layout_node(&mut self, ctx: &mut LayoutContext, node: &PackedNode) { + // Align nodes' block-axis alignment is respected by the stack node. + let align = node + .downcast::<AlignNode>() + .and_then(|node| node.aligns.get(self.axis)) + .unwrap_or(self.stack.dir.start().into()); + let frames = node.layout(ctx, &self.regions); let len = frames.len(); for (i, frame) in frames.into_iter().enumerate() { @@ -184,7 +190,7 @@ impl<'a> StackLayouter<'a> { self.used.block += size.block; self.used.inline.set_max(size.inline); *self.regions.current.get_mut(self.axis) -= size.block; - self.items.push(StackItem::Frame(frame.item)); + self.items.push(StackItem::Frame(frame.item, align)); if i + 1 < len { self.finish_region(); @@ -204,29 +210,35 @@ impl<'a> StackLayouter<'a> { // Expand fully if there are fr spacings. let full = self.full.get(self.axis); + let remaining = full - self.used.block; if self.fr.get() > 0.0 && full.is_finite() { + self.used.block = full; size.set(self.axis, full); } let mut output = Frame::new(size, size.h); let mut before = Length::zero(); + let mut ruler: Align = self.stack.dir.start().into(); // Place all frames. for item in self.items.drain(..) { match item { StackItem::Absolute(v) => before += v, - StackItem::Fractional(v) => { - let remaining = self.full.get(self.axis) - self.used.block; - before += v.resolve(self.fr, remaining) - } - StackItem::Frame(frame) => { + StackItem::Fractional(v) => before += v.resolve(self.fr, remaining), + StackItem::Frame(frame, align) => { + ruler = ruler.max(align); + + // Align along the block axis. let parent = size.get(self.axis); let child = frame.size.get(self.axis); - let block = if self.stack.dir.is_positive() { - before + let block = ruler.resolve(if self.stack.dir.is_positive() { + let after = self.used.block - before; + before .. parent - after } else { - parent - (before + child) - }; + let before_with_self = before + child; + let after = self.used.block - before_with_self; + after .. parent - before_with_self + }); before += child; |
