Placed node

This commit is contained in:
Laurenz 2021-11-17 23:09:23 +01:00
parent e869c899bc
commit 095fa52be5
11 changed files with 153 additions and 48 deletions

View File

@ -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`.

View File

@ -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
}

View File

@ -2,6 +2,20 @@ use super::prelude::*;
/// `align`: Configure the alignment along the layouting axes.
pub fn align(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
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 {
style.par_mut().align = x;
}
body.pack(&style).aligned(x, y)
})))
}
/// 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>>() {
@ -11,17 +25,7 @@ pub fn align(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
_ => bail!(span, "unexpected argument"),
}
}
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)
})))
Ok(Spec::new(x, y))
}
/// A node that aligns its child.

View File

@ -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);

View File

@ -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.

39
src/library/placed.rs Normal file
View File

@ -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
}
}

View File

@ -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);
}
}

View File

@ -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 {

BIN
tests/ref/layout/placed.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -2,7 +2,7 @@
#let tex = [{
[T]
h(-0.14 * size)
move(y: 0.22 * size)[E]
move(dy: 0.22 * size)[E]
h(-0.12 * size)
[X]
}]

View File

@ -0,0 +1,22 @@
#page("a8")
#place(bottom, center)[© Typst]
= Placement
#place(right, image("../../res/tiger.jpg", width: 1.8cm))
Hi there. This is \
a placed node. \
Unfortunately, \
the line breaks still had to be inserted manually.
#stack(
rect(fill: eastern, height: 10pt),
place(right, dy: 1.5pt)[ABC],
rect(fill: conifer, height: 10pt, width: 80%),
rect(fill: forest, height: 10pt),
)
#block[
#place(center, dx: -7pt, dy: -5pt)[Hello]
#place(center, dx: 7pt, dy: 5pt)[Hello]
Hello #h(1fr) Hello
]