summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-11-17 23:09:23 +0100
committerLaurenz <laurmaedje@gmail.com>2021-11-17 23:09:23 +0100
commit095fa52be5d7ed135f39553359e0253cfea6b71b (patch)
tree71e8a71a8b7755b32221a30c32f62cc146acdd33 /src
parente869c899bcaefb19c3c47955577396b85494b823 (diff)
Placed node
Diffstat (limited to 'src')
-rw-r--r--src/eval/template.rs16
-rw-r--r--src/layout/mod.rs24
-rw-r--r--src/library/align.rs26
-rw-r--r--src/library/flow.rs35
-rw-r--r--src/library/mod.rs7
-rw-r--r--src/library/placed.rs39
-rw-r--r--src/library/stack.rs11
-rw-r--r--src/library/transform.rs19
8 files changed, 130 insertions, 47 deletions
diff --git a/src/eval/template.rs b/src/eval/template.rs
index 2df347aa..6c1223cb 100644
--- a/src/eval/template.rs
+++ b/src/eval/template.rs
@@ -10,7 +10,7 @@ use crate::geom::{Align, Dir, GenAxis, Length, Linear, Sides, Size};
use crate::layout::{Layout, PackedNode};
use crate::library::{
Decoration, DocumentNode, FlowChild, FlowNode, PadNode, PageNode, ParChild, ParNode,
- Spacing,
+ PlacedNode, Spacing,
};
use crate::style::Style;
use crate::util::EcoString;
@@ -331,15 +331,21 @@ impl Builder {
}
/// Push an inline node into the active paragraph.
- fn inline(&mut self, node: impl Into<PackedNode>) {
+ fn inline(&mut self, node: PackedNode) {
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()));
+ fn block(&mut self, node: PackedNode) {
self.parbreak();
+ let in_flow = node.downcast::<PlacedNode>().is_none();
+ self.flow.push(FlowChild::Node(node));
+ if in_flow {
+ self.parbreak();
+ } else {
+ // This prevents duplicate paragraph spacing around placed nodes.
+ self.flow.last = Last::None;
+ }
}
/// Push spacing into the active paragraph or flow depending on the `axis`.
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 33502fff..3ac32722 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -19,7 +19,7 @@ use crate::font::FontStore;
use crate::frame::Frame;
use crate::geom::{Align, Linear, Spec};
use crate::image::ImageStore;
-use crate::library::{AlignNode, DocumentNode, SizedNode};
+use crate::library::{AlignNode, DocumentNode, MoveNode, SizedNode};
use crate::Context;
/// Layout a document node into a collection of frames.
@@ -104,21 +104,27 @@ impl PackedNode {
}
/// Force a size for this node.
- pub fn sized(self, width: Option<Linear>, height: Option<Linear>) -> PackedNode {
- if width.is_some() || height.is_some() {
- Layout::pack(SizedNode {
- child: self,
- sizing: Spec::new(width, height),
- })
+ pub fn sized(self, w: Option<Linear>, h: Option<Linear>) -> Self {
+ if w.is_some() || h.is_some() {
+ SizedNode { child: self, sizing: Spec::new(w, h) }.pack()
} else {
self
}
}
/// Set alignments for this node.
- pub fn aligned(self, x: Option<Align>, y: Option<Align>) -> PackedNode {
+ pub fn aligned(self, x: Option<Align>, y: Option<Align>) -> Self {
if x.is_some() || y.is_some() {
- Layout::pack(AlignNode { child: self, aligns: Spec::new(x, y) })
+ AlignNode { child: self, aligns: Spec::new(x, y) }.pack()
+ } else {
+ self
+ }
+ }
+
+ /// Move this node's contents without affecting layout.
+ pub fn moved(self, dx: Option<Linear>, dy: Option<Linear>) -> Self {
+ if dx.is_some() || dy.is_some() {
+ MoveNode { child: self, offset: Spec::new(dx, dy) }.pack()
} else {
self
}
diff --git a/src/library/align.rs b/src/library/align.rs
index 591a4085..19c52f98 100644
--- a/src/library/align.rs
+++ b/src/library/align.rs
@@ -2,18 +2,8 @@ use super::prelude::*;
/// `align`: Configure the alignment along the layouting axes.
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 Spec { x, y } = parse_aligns(args)?;
let body = args.expect::<Template>("body")?;
-
Ok(Value::Template(Template::from_block(move |style| {
let mut style = style.clone();
if let Some(x) = x {
@@ -24,6 +14,20 @@ pub fn align(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
})))
}
+/// Parse alignment arguments with shorthand.
+pub(super) fn parse_aligns(args: &mut Args) -> TypResult<Spec<Option<Align>>> {
+ 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"),
+ }
+ }
+ Ok(Spec::new(x, y))
+}
+
/// A node that aligns its child.
#[derive(Debug, Hash)]
pub struct AlignNode {
diff --git a/src/library/flow.rs b/src/library/flow.rs
index f58df536..185e60bb 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::{AlignNode, ParNode, Spacing};
+use super::{AlignNode, ParNode, PlacedNode, Spacing};
/// `flow`: A vertical flow of paragraphs and other layout nodes.
pub fn flow(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
@@ -101,7 +101,9 @@ enum FlowItem {
Absolute(Length),
/// Fractional spacing between other items.
Fractional(Fractional),
- /// A layouted child node and how to align it vertically.
+ /// A frame to be placed directly at the origin.
+ Placed(Rc<Frame>),
+ /// A frame for a layouted child node and how to align it.
Frame(Rc<Frame>, Spec<Align>),
}
@@ -157,13 +159,27 @@ impl<'a> FlowLayouter<'a> {
/// Layout a node.
fn layout_node(&mut self, ctx: &mut LayoutContext, node: &PackedNode) {
+ // Placed nodes with vertical alignment are handled separately
+ // because their position shouldn't depend on other flow elements.
+ if let Some(placed) = node.downcast::<PlacedNode>() {
+ if let Some(aligned) = placed.child.downcast::<AlignNode>() {
+ if aligned.aligns.y.is_some() {
+ let base = self.regions.base;
+ let pod = Regions::one(base, base, Spec::splat(true));
+ let frame = placed.layout(ctx, &pod).remove(0);
+ self.items.push(FlowItem::Placed(frame.item));
+ return;
+ }
+ }
+ }
+
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),
+ node.downcast::<ParNode>().map_or(Align::Left, |par| par.align),
// Vertical align node alignment is respected by the flow node.
node.downcast::<AlignNode>()
- .and_then(|node| node.aligns.y)
+ .and_then(|aligned| aligned.aligns.y)
.unwrap_or(Align::Top),
);
@@ -207,8 +223,15 @@ impl<'a> FlowLayouter<'a> {
// Place all frames.
for item in self.items.drain(..) {
match item {
- FlowItem::Absolute(v) => before += v,
- FlowItem::Fractional(v) => before += v.resolve(self.fr, remaining),
+ FlowItem::Absolute(v) => {
+ before += v;
+ }
+ FlowItem::Fractional(v) => {
+ before += v.resolve(self.fr, remaining);
+ }
+ FlowItem::Placed(frame) => {
+ output.push_frame(Point::zero(), frame);
+ }
FlowItem::Frame(frame, aligns) => {
ruler = ruler.max(aligns.y);
diff --git a/src/library/mod.rs b/src/library/mod.rs
index 4f33b715..7b8acf9e 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -12,6 +12,7 @@ mod image;
mod pad;
mod page;
mod par;
+mod placed;
mod shape;
mod sized;
mod spacing;
@@ -42,6 +43,7 @@ pub use grid::*;
pub use pad::*;
pub use page::*;
pub use par::*;
+pub use placed::*;
pub use shape::*;
pub use sized::*;
pub use spacing::*;
@@ -71,13 +73,14 @@ pub fn new() -> Scope {
std.def_func("pagebreak", pagebreak);
std.def_func("h", h);
std.def_func("v", v);
- std.def_func("align", align);
std.def_func("box", box_);
std.def_func("block", block);
std.def_func("flow", flow);
- std.def_func("pad", pad);
+ std.def_func("align", align);
+ std.def_func("place", place);
std.def_func("move", move_);
std.def_func("stack", stack);
+ std.def_func("pad", pad);
std.def_func("grid", grid);
// Elements.
diff --git a/src/library/placed.rs b/src/library/placed.rs
new file mode 100644
index 00000000..0d92fc35
--- /dev/null
+++ b/src/library/placed.rs
@@ -0,0 +1,39 @@
+use super::parse_aligns;
+use super::prelude::*;
+
+/// `place`: Place content at an absolute position.
+pub fn place(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
+ let Spec { x, y } = parse_aligns(args)?;
+ let dx = args.named("dx")?;
+ let dy = args.named("dy")?;
+ let body: Template = args.expect("body")?;
+ Ok(Value::Template(Template::from_block(move |style| {
+ PlacedNode {
+ child: body
+ .pack(style)
+ .moved(dx, dy)
+ .aligned(Some(x.unwrap_or(Align::Left)), y),
+ }
+ })))
+}
+
+/// A node that places its child out-of-flow.
+#[derive(Debug, Hash)]
+pub struct PlacedNode {
+ /// The node to be placed.
+ pub child: PackedNode,
+}
+
+impl Layout for PlacedNode {
+ fn layout(
+ &self,
+ ctx: &mut LayoutContext,
+ regions: &Regions,
+ ) -> Vec<Constrained<Rc<Frame>>> {
+ let mut frames = self.child.layout(ctx, regions);
+ for frame in frames.iter_mut() {
+ Rc::make_mut(&mut frame.item).size = Size::zero();
+ }
+ frames
+ }
+}
diff --git a/src/library/stack.rs b/src/library/stack.rs
index 7ac7e6ee..cc02592f 100644
--- a/src/library/stack.rs
+++ b/src/library/stack.rs
@@ -223,8 +223,12 @@ impl<'a> StackLayouter<'a> {
// Place all frames.
for item in self.items.drain(..) {
match item {
- StackItem::Absolute(v) => before += v,
- StackItem::Fractional(v) => before += v.resolve(self.fr, remaining),
+ StackItem::Absolute(v) => {
+ before += v;
+ }
+ StackItem::Fractional(v) => {
+ before += v.resolve(self.fr, remaining);
+ }
StackItem::Frame(frame, align) => {
ruler = ruler.max(align);
@@ -240,9 +244,8 @@ impl<'a> StackLayouter<'a> {
after .. parent - before_with_self
});
- before += child;
-
let pos = Gen::new(Length::zero(), block).to_point(self.axis);
+ before += child;
output.push_frame(pos, frame);
}
}
diff --git a/src/library/transform.rs b/src/library/transform.rs
index d0a27622..ef9caf2e 100644
--- a/src/library/transform.rs
+++ b/src/library/transform.rs
@@ -2,22 +2,21 @@ use super::prelude::*;
/// `move`: Move content without affecting layout.
pub fn move_(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
- let x = args.named("x")?;
- let y = args.named("y")?;
+ let dx = args.named("dx")?;
+ let dy = args.named("dy")?;
let body: Template = args.expect("body")?;
-
Ok(Value::Template(Template::from_inline(move |style| {
- MoveNode {
- offset: Spec::new(x, y),
- child: body.pack(style),
- }
+ body.pack(style).moved(dx, dy)
})))
}
+/// A node that moves its child without affecting layout.
#[derive(Debug, Hash)]
-struct MoveNode {
- offset: Spec<Option<Linear>>,
- child: PackedNode,
+pub struct MoveNode {
+ /// The node whose contents should be moved.
+ pub child: PackedNode,
+ /// How much to move the contents.
+ pub offset: Spec<Option<Linear>>,
}
impl Layout for MoveNode {