diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-11-17 17:09:19 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2021-11-17 17:09:19 +0100 |
| commit | 89f2e71852e96062ea9b756bf92fbf4e894871b1 (patch) | |
| tree | 836099ebd17adf30a24fc62464dfdd3d9c248480 /src | |
| parent | 9a800daa82833c57eee04e92c701ca9a05a67d3b (diff) | |
Align node
Diffstat (limited to 'src')
| -rw-r--r-- | src/eval/template.rs | 33 | ||||
| -rw-r--r-- | src/eval/walk.rs | 7 | ||||
| -rw-r--r-- | src/geom/align.rs | 64 | ||||
| -rw-r--r-- | src/geom/spec.rs | 13 | ||||
| -rw-r--r-- | src/layout/mod.rs | 47 | ||||
| -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 | ||||
| -rw-r--r-- | src/style/mod.rs | 8 |
14 files changed, 265 insertions, 200 deletions
diff --git a/src/eval/template.rs b/src/eval/template.rs index 0604cc05..2df347aa 100644 --- a/src/eval/template.rs +++ b/src/eval/template.rs @@ -58,7 +58,7 @@ impl Template { pub fn from_inline<F, T>(f: F) -> Self where F: Fn(&Style) -> T + 'static, - T: Layout + Hash + 'static, + T: Layout + Debug + Hash + 'static, { let node = TemplateNode::Inline(Rc::new(move |s| f(s).pack())); Self(Rc::new(vec![node])) @@ -68,7 +68,7 @@ impl Template { pub fn from_block<F, T>(f: F) -> Self where F: Fn(&Style) -> T + 'static, - T: Layout + Hash + 'static, + T: Layout + Debug + Hash + 'static, { let node = TemplateNode::Block(Rc::new(move |s| f(s).pack())); Self(Rc::new(vec![node])) @@ -332,14 +332,13 @@ impl Builder { /// Push an inline node into the active paragraph. fn inline(&mut self, node: impl Into<PackedNode>) { - let align = self.style.aligns.inline; - self.flow.par.push(ParChild::Node(node.into(), align)); + self.flow.par.push(ParChild::Node(node.into())); } /// Push a block node into the active flow, finishing the active paragraph. fn block(&mut self, node: impl Into<PackedNode>) { self.parbreak(); - self.flow.push(FlowChild::Node(node.into(), self.style.aligns.block)); + self.flow.push(FlowChild::Node(node.into())); self.parbreak(); } @@ -372,11 +371,7 @@ impl Builder { /// Construct a text node with the given text and settings from the current /// style. fn make_text_node(&self, text: impl Into<EcoString>) -> ParChild { - ParChild::Text( - text.into(), - self.style.aligns.inline, - Rc::clone(&self.style.text), - ) + ParChild::Text(text.into(), Rc::clone(&self.style.text)) } } @@ -451,8 +446,8 @@ impl FlowBuilder { } struct ParBuilder { - align: Align, dir: Dir, + align: Align, leading: Length, children: Vec<ParChild>, last: Last<ParChild>, @@ -461,8 +456,8 @@ struct ParBuilder { impl ParBuilder { fn new(style: &Style) -> Self { Self { - align: style.aligns.block, dir: style.par.dir, + align: style.par.align, leading: style.leading(), children: vec![], last: Last::None, @@ -486,12 +481,10 @@ impl ParBuilder { } fn push_inner(&mut self, child: ParChild) { - if let ParChild::Text(curr_text, curr_align, curr_props) = &child { - if let Some(ParChild::Text(prev_text, prev_align, prev_props)) = - self.children.last_mut() - { - if prev_align == curr_align && Rc::ptr_eq(prev_props, curr_props) { - prev_text.push_str(curr_text); + if let ParChild::Text(text2, style2) = &child { + if let Some(ParChild::Text(text1, style1)) = self.children.last_mut() { + if Rc::ptr_eq(style1, style2) { + text1.push_str(text2); return; } } @@ -501,9 +494,9 @@ impl ParBuilder { } fn build(self) -> Option<FlowChild> { - let Self { align, dir, leading, children, .. } = self; + let Self { dir, align, leading, children, .. } = self; (!children.is_empty()) - .then(|| FlowChild::Node(ParNode { dir, leading, children }.pack(), align)) + .then(|| FlowChild::Node(ParNode { dir, align, leading, children }.pack())) } } diff --git a/src/eval/walk.rs b/src/eval/walk.rs index fe7f0e98..0898f20b 100644 --- a/src/eval/walk.rs +++ b/src/eval/walk.rs @@ -126,12 +126,9 @@ fn walk_item(ctx: &mut EvalContext, label: EcoString, body: Template) { ctx.template += Template::from_block(move |style| { let label = Layout::pack(ParNode { dir: style.par.dir, + align: style.par.align, leading: style.leading(), - children: vec![ParChild::Text( - label.clone(), - style.aligns.inline, - Rc::clone(&style.text), - )], + children: vec![ParChild::Text(label.clone(), Rc::clone(&style.text))], }); let spacing = style.text.size / 2.0; diff --git a/src/geom/align.rs b/src/geom/align.rs index 59960084..b0cf69db 100644 --- a/src/geom/align.rs +++ b/src/geom/align.rs @@ -3,18 +3,14 @@ use super::*; /// Where to align something along an axis. #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum Align { - /// Align at the start of the axis. - Start, - /// Align in the middle of the axis. - Center, - /// Align at the end of the axis. - End, /// Align at the left side of the axis. Left, - /// Align at the right side of the axis. - Right, /// Align at the top side of the axis. Top, + /// Align in the middle of the axis. + Center, + /// Align at the right side of the axis. + Right, /// Align at the bottom side of the axis. Bottom, } @@ -23,12 +19,10 @@ impl Align { /// The axis this alignment belongs to if it is specific. pub 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::Center => None, + Self::Right => Some(SpecAxis::Horizontal), Self::Bottom => Some(SpecAxis::Vertical), } } @@ -36,60 +30,42 @@ impl Align { /// The inverse alignment. pub fn inv(self) -> Self { match self { - Self::Start => Self::End, - Self::Center => Self::Center, - Self::End => Self::Start, Self::Left => Self::Right, - Self::Right => Self::Left, Self::Top => Self::Bottom, + Self::Center => Self::Center, + Self::Right => Self::Left, Self::Bottom => Self::Top, } } /// Returns the position of this alignment in the given range. - pub fn resolve(self, dir: Dir, range: Range<Length>) -> Length { - #[cfg(debug_assertions)] - if let Some(axis) = self.axis() { - debug_assert_eq!(axis, dir.axis()) - } - + pub fn resolve(self, range: Range<Length>) -> Length { match self { - Self::Start => { - if dir.is_positive() { - range.start - } else { - range.end - } - } - Self::Center => (range.start + range.end) / 2.0, - Self::End => { - if dir.is_positive() { - range.end - } else { - range.start - } - } Self::Left | Self::Top => range.start, Self::Right | Self::Bottom => range.end, + Self::Center => (range.start + range.end) / 2.0, } } } -impl Default for Align { - fn default() -> Self { - Self::Start +impl From<Side> for Align { + fn from(side: Side) -> Self { + match side { + Side::Left => Self::Left, + Side::Top => Self::Top, + Side::Right => Self::Right, + Side::Bottom => Self::Bottom, + } } } impl Debug for Align { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.pad(match self { - Self::Start => "start", - Self::Center => "center", - Self::End => "end", Self::Left => "left", - Self::Right => "right", Self::Top => "top", + Self::Center => "center", + Self::Right => "right", Self::Bottom => "bottom", }) } diff --git a/src/geom/spec.rs b/src/geom/spec.rs index 4d631399..3b49b54a 100644 --- a/src/geom/spec.rs +++ b/src/geom/spec.rs @@ -23,6 +23,11 @@ impl<T> Spec<T> { Self { x: v.clone(), y: v } } + /// Borrows the individual fields. + pub fn as_ref(&self) -> Spec<&T> { + Spec { x: &self.x, y: &self.y } + } + /// Maps the individual fields with `f`. pub fn map<F, U>(self, mut f: F) -> Spec<U> where @@ -40,6 +45,14 @@ impl<T> Spec<T> { } } + /// Whether a condition is true for at least one of fields. + pub fn any<F>(self, mut f: F) -> bool + where + F: FnMut(&T) -> bool, + { + f(&self.x) || f(&self.y) + } + /// Whether a condition is true for both fields. pub fn all<F>(self, mut f: F) -> bool where diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 949fff64..33502fff 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -10,15 +10,16 @@ pub use constraints::*; pub use incremental::*; pub use regions::*; +use std::any::Any; use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::rc::Rc; use crate::font::FontStore; use crate::frame::Frame; -use crate::geom::{Linear, Spec}; +use crate::geom::{Align, Linear, Spec}; use crate::image::ImageStore; -use crate::library::{DocumentNode, SizedNode}; +use crate::library::{AlignNode, DocumentNode, SizedNode}; use crate::Context; /// Layout a document node into a collection of frames. @@ -59,7 +60,7 @@ impl<'a> LayoutContext<'a> { /// /// Layout return one frame per used region alongside constraints that define /// whether the result is reusable in other regions. -pub trait Layout: Debug { +pub trait Layout { /// Layout the node into the given regions, producing constrained frames. fn layout( &self, @@ -70,12 +71,11 @@ pub trait Layout: Debug { /// Convert to a packed node. fn pack(self) -> PackedNode where - Self: Sized + Hash + 'static, + Self: Debug + Hash + Sized + 'static, { PackedNode { #[cfg(feature = "layout-cache")] hash: { - use std::any::Any; let mut state = fxhash::FxHasher64::default(); self.type_id().hash(&mut state); self.hash(&mut state); @@ -89,26 +89,40 @@ pub trait Layout: Debug { /// A packed layouting node with precomputed hash. #[derive(Clone)] pub struct PackedNode { - node: Rc<dyn Layout>, + node: Rc<dyn Bounds>, #[cfg(feature = "layout-cache")] hash: u64, } impl PackedNode { + /// Try to downcast to a specific layout node. + pub fn downcast<T>(&self) -> Option<&T> + where + T: Layout + Debug + Hash + 'static, + { + self.node.as_any().downcast_ref() + } + /// Force a size for this node. - /// - /// If at least one of `width` and `height` is `Some`, this wraps the node - /// in a [`SizedNode`]. Otherwise, it returns the node unchanged. pub fn sized(self, width: Option<Linear>, height: Option<Linear>) -> PackedNode { if width.is_some() || height.is_some() { Layout::pack(SizedNode { - sizing: Spec::new(width, height), child: self, + sizing: Spec::new(width, height), }) } else { self } } + + /// Set alignments for this node. + pub fn aligned(self, x: Option<Align>, y: Option<Align>) -> PackedNode { + if x.is_some() || y.is_some() { + Layout::pack(AlignNode { child: self, aligns: Spec::new(x, y) }) + } else { + self + } + } } impl Layout for PackedNode { @@ -166,3 +180,16 @@ impl Debug for PackedNode { self.node.fmt(f) } } + +trait Bounds: Layout + Debug + 'static { + fn as_any(&self) -> &dyn Any; +} + +impl<T> Bounds for T +where + T: Layout + Debug + 'static, +{ + fn as_any(&self) -> &dyn Any { + self + } +} 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; diff --git a/src/style/mod.rs b/src/style/mod.rs index c2a92568..9cf2de07 100644 --- a/src/style/mod.rs +++ b/src/style/mod.rs @@ -16,8 +16,6 @@ use crate::util::EcoString; /// Defines a set of properties a template can be instantiated with. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct Style { - /// The alignments of layouts in their parents. - pub aligns: Gen<Align>, /// The page settings. pub page: Rc<PageStyle>, /// The paragraph settings. @@ -56,7 +54,6 @@ impl Style { impl Default for Style { fn default() -> Self { Self { - aligns: Gen::splat(Align::Start), page: Rc::new(PageStyle::default()), par: Rc::new(ParStyle::default()), text: Rc::new(TextStyle::default()), @@ -103,8 +100,10 @@ impl Default for PageStyle { /// Defines style properties of paragraphs. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct ParStyle { - /// The direction for text and other inline objects. + /// The direction for text and inline objects. pub dir: Dir, + /// How to align text and inline objects in their line. + pub align: Align, /// The spacing between lines (dependent on scaled font size). pub leading: Linear, /// The spacing between paragraphs (dependent on scaled font size). @@ -115,6 +114,7 @@ impl Default for ParStyle { fn default() -> Self { Self { dir: Dir::LTR, + align: Align::Left, leading: Relative::new(0.65).into(), spacing: Relative::new(1.2).into(), } |
