mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Placed node
This commit is contained in:
parent
e869c899bc
commit
095fa52be5
@ -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`.
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
39
src/library/placed.rs
Normal 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
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
BIN
tests/ref/layout/placed.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
@ -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]
|
||||
}]
|
||||
|
22
tests/typ/layout/placed.typ
Normal file
22
tests/typ/layout/placed.typ
Normal 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
|
||||
]
|
Loading…
x
Reference in New Issue
Block a user