mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +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::layout::{Layout, PackedNode};
|
||||||
use crate::library::{
|
use crate::library::{
|
||||||
Decoration, DocumentNode, FlowChild, FlowNode, PadNode, PageNode, ParChild, ParNode,
|
Decoration, DocumentNode, FlowChild, FlowNode, PadNode, PageNode, ParChild, ParNode,
|
||||||
Spacing,
|
PlacedNode, Spacing,
|
||||||
};
|
};
|
||||||
use crate::style::Style;
|
use crate::style::Style;
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
@ -331,15 +331,21 @@ impl Builder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Push an inline node into the active paragraph.
|
/// 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()));
|
self.flow.par.push(ParChild::Node(node.into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push a block node into the active flow, finishing the active paragraph.
|
/// Push a block node into the active flow, finishing the active paragraph.
|
||||||
fn block(&mut self, node: impl Into<PackedNode>) {
|
fn block(&mut self, node: PackedNode) {
|
||||||
self.parbreak();
|
|
||||||
self.flow.push(FlowChild::Node(node.into()));
|
|
||||||
self.parbreak();
|
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`.
|
/// 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::frame::Frame;
|
||||||
use crate::geom::{Align, Linear, Spec};
|
use crate::geom::{Align, Linear, Spec};
|
||||||
use crate::image::ImageStore;
|
use crate::image::ImageStore;
|
||||||
use crate::library::{AlignNode, DocumentNode, SizedNode};
|
use crate::library::{AlignNode, DocumentNode, MoveNode, SizedNode};
|
||||||
use crate::Context;
|
use crate::Context;
|
||||||
|
|
||||||
/// Layout a document node into a collection of frames.
|
/// Layout a document node into a collection of frames.
|
||||||
@ -104,21 +104,27 @@ impl PackedNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Force a size for this node.
|
/// Force a size for this node.
|
||||||
pub fn sized(self, width: Option<Linear>, height: Option<Linear>) -> PackedNode {
|
pub fn sized(self, w: Option<Linear>, h: Option<Linear>) -> Self {
|
||||||
if width.is_some() || height.is_some() {
|
if w.is_some() || h.is_some() {
|
||||||
Layout::pack(SizedNode {
|
SizedNode { child: self, sizing: Spec::new(w, h) }.pack()
|
||||||
child: self,
|
|
||||||
sizing: Spec::new(width, height),
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set alignments for this node.
|
/// 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() {
|
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 {
|
} else {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,20 @@ use super::prelude::*;
|
|||||||
|
|
||||||
/// `align`: Configure the alignment along the layouting axes.
|
/// `align`: Configure the alignment along the layouting axes.
|
||||||
pub fn align(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
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 x = args.named("horizontal")?;
|
||||||
let mut y = args.named("vertical")?;
|
let mut y = args.named("vertical")?;
|
||||||
for Spanned { v, span } in args.all::<Spanned<Align>>() {
|
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"),
|
_ => bail!(span, "unexpected argument"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(Spec::new(x, y))
|
||||||
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.
|
/// A node that aligns its child.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
use super::{AlignNode, ParNode, Spacing};
|
use super::{AlignNode, ParNode, PlacedNode, Spacing};
|
||||||
|
|
||||||
/// `flow`: A vertical flow of paragraphs and other layout nodes.
|
/// `flow`: A vertical flow of paragraphs and other layout nodes.
|
||||||
pub fn flow(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub fn flow(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||||
@ -101,7 +101,9 @@ enum FlowItem {
|
|||||||
Absolute(Length),
|
Absolute(Length),
|
||||||
/// Fractional spacing between other items.
|
/// Fractional spacing between other items.
|
||||||
Fractional(Fractional),
|
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>),
|
Frame(Rc<Frame>, Spec<Align>),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,13 +159,27 @@ impl<'a> FlowLayouter<'a> {
|
|||||||
|
|
||||||
/// Layout a node.
|
/// Layout a node.
|
||||||
fn layout_node(&mut self, ctx: &mut LayoutContext, node: &PackedNode) {
|
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(
|
let aligns = Spec::new(
|
||||||
// For non-expanding paragraphs it is crucial that we align the
|
// For non-expanding paragraphs it is crucial that we align the
|
||||||
// whole paragraph according to its internal alignment.
|
// 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.
|
// Vertical align node alignment is respected by the flow node.
|
||||||
node.downcast::<AlignNode>()
|
node.downcast::<AlignNode>()
|
||||||
.and_then(|node| node.aligns.y)
|
.and_then(|aligned| aligned.aligns.y)
|
||||||
.unwrap_or(Align::Top),
|
.unwrap_or(Align::Top),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -207,8 +223,15 @@ impl<'a> FlowLayouter<'a> {
|
|||||||
// Place all frames.
|
// Place all frames.
|
||||||
for item in self.items.drain(..) {
|
for item in self.items.drain(..) {
|
||||||
match item {
|
match item {
|
||||||
FlowItem::Absolute(v) => before += v,
|
FlowItem::Absolute(v) => {
|
||||||
FlowItem::Fractional(v) => before += v.resolve(self.fr, remaining),
|
before += v;
|
||||||
|
}
|
||||||
|
FlowItem::Fractional(v) => {
|
||||||
|
before += v.resolve(self.fr, remaining);
|
||||||
|
}
|
||||||
|
FlowItem::Placed(frame) => {
|
||||||
|
output.push_frame(Point::zero(), frame);
|
||||||
|
}
|
||||||
FlowItem::Frame(frame, aligns) => {
|
FlowItem::Frame(frame, aligns) => {
|
||||||
ruler = ruler.max(aligns.y);
|
ruler = ruler.max(aligns.y);
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ mod image;
|
|||||||
mod pad;
|
mod pad;
|
||||||
mod page;
|
mod page;
|
||||||
mod par;
|
mod par;
|
||||||
|
mod placed;
|
||||||
mod shape;
|
mod shape;
|
||||||
mod sized;
|
mod sized;
|
||||||
mod spacing;
|
mod spacing;
|
||||||
@ -42,6 +43,7 @@ pub use grid::*;
|
|||||||
pub use pad::*;
|
pub use pad::*;
|
||||||
pub use page::*;
|
pub use page::*;
|
||||||
pub use par::*;
|
pub use par::*;
|
||||||
|
pub use placed::*;
|
||||||
pub use shape::*;
|
pub use shape::*;
|
||||||
pub use sized::*;
|
pub use sized::*;
|
||||||
pub use spacing::*;
|
pub use spacing::*;
|
||||||
@ -71,13 +73,14 @@ pub fn new() -> Scope {
|
|||||||
std.def_func("pagebreak", pagebreak);
|
std.def_func("pagebreak", pagebreak);
|
||||||
std.def_func("h", h);
|
std.def_func("h", h);
|
||||||
std.def_func("v", v);
|
std.def_func("v", v);
|
||||||
std.def_func("align", align);
|
|
||||||
std.def_func("box", box_);
|
std.def_func("box", box_);
|
||||||
std.def_func("block", block);
|
std.def_func("block", block);
|
||||||
std.def_func("flow", flow);
|
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("move", move_);
|
||||||
std.def_func("stack", stack);
|
std.def_func("stack", stack);
|
||||||
|
std.def_func("pad", pad);
|
||||||
std.def_func("grid", grid);
|
std.def_func("grid", grid);
|
||||||
|
|
||||||
// Elements.
|
// 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.
|
// Place all frames.
|
||||||
for item in self.items.drain(..) {
|
for item in self.items.drain(..) {
|
||||||
match item {
|
match item {
|
||||||
StackItem::Absolute(v) => before += v,
|
StackItem::Absolute(v) => {
|
||||||
StackItem::Fractional(v) => before += v.resolve(self.fr, remaining),
|
before += v;
|
||||||
|
}
|
||||||
|
StackItem::Fractional(v) => {
|
||||||
|
before += v.resolve(self.fr, remaining);
|
||||||
|
}
|
||||||
StackItem::Frame(frame, align) => {
|
StackItem::Frame(frame, align) => {
|
||||||
ruler = ruler.max(align);
|
ruler = ruler.max(align);
|
||||||
|
|
||||||
@ -240,9 +244,8 @@ impl<'a> StackLayouter<'a> {
|
|||||||
after .. parent - before_with_self
|
after .. parent - before_with_self
|
||||||
});
|
});
|
||||||
|
|
||||||
before += child;
|
|
||||||
|
|
||||||
let pos = Gen::new(Length::zero(), block).to_point(self.axis);
|
let pos = Gen::new(Length::zero(), block).to_point(self.axis);
|
||||||
|
before += child;
|
||||||
output.push_frame(pos, frame);
|
output.push_frame(pos, frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,22 +2,21 @@ use super::prelude::*;
|
|||||||
|
|
||||||
/// `move`: Move content without affecting layout.
|
/// `move`: Move content without affecting layout.
|
||||||
pub fn move_(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub fn move_(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||||
let x = args.named("x")?;
|
let dx = args.named("dx")?;
|
||||||
let y = args.named("y")?;
|
let dy = args.named("dy")?;
|
||||||
let body: Template = args.expect("body")?;
|
let body: Template = args.expect("body")?;
|
||||||
|
|
||||||
Ok(Value::Template(Template::from_inline(move |style| {
|
Ok(Value::Template(Template::from_inline(move |style| {
|
||||||
MoveNode {
|
body.pack(style).moved(dx, dy)
|
||||||
offset: Spec::new(x, y),
|
|
||||||
child: body.pack(style),
|
|
||||||
}
|
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A node that moves its child without affecting layout.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
struct MoveNode {
|
pub struct MoveNode {
|
||||||
offset: Spec<Option<Linear>>,
|
/// The node whose contents should be moved.
|
||||||
child: PackedNode,
|
pub child: PackedNode,
|
||||||
|
/// How much to move the contents.
|
||||||
|
pub offset: Spec<Option<Linear>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for MoveNode {
|
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 = [{
|
#let tex = [{
|
||||||
[T]
|
[T]
|
||||||
h(-0.14 * size)
|
h(-0.14 * size)
|
||||||
move(y: 0.22 * size)[E]
|
move(dy: 0.22 * size)[E]
|
||||||
h(-0.12 * size)
|
h(-0.12 * size)
|
||||||
[X]
|
[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