mirror of
https://github.com/typst/typst
synced 2025-05-30 00:36:24 +08:00
More independent placed node
This commit is contained in:
parent
3a15922d2f
commit
50bd863471
@ -337,13 +337,24 @@ impl Builder {
|
||||
|
||||
/// Push a block node into the active flow, finishing the active paragraph.
|
||||
fn block(&mut self, node: PackedNode) {
|
||||
let mut is_placed = false;
|
||||
if let Some(placed) = node.downcast::<PlacedNode>() {
|
||||
is_placed = true;
|
||||
|
||||
// This prevents paragraph spacing after the placed node if it
|
||||
// is completely out-of-flow.
|
||||
if placed.out_of_flow() {
|
||||
self.flow.last = Last::None;
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
// This prevents paragraph spacing between the placed node and
|
||||
// the paragraph below it.
|
||||
if is_placed {
|
||||
self.flow.last = Last::None;
|
||||
}
|
||||
}
|
||||
|
10
src/frame.rs
10
src/frame.rs
@ -57,13 +57,13 @@ impl Frame {
|
||||
|
||||
/// Resize the frame to a new size, distributing new space according to the
|
||||
/// given alignments.
|
||||
pub fn resize(&mut self, new: Size, aligns: Spec<Align>) {
|
||||
if self.size != new {
|
||||
pub fn resize(&mut self, target: Size, aligns: Spec<Align>) {
|
||||
if self.size != target {
|
||||
let offset = Point::new(
|
||||
aligns.x.resolve(new.x - self.size.x),
|
||||
aligns.y.resolve(new.y - self.size.y),
|
||||
aligns.x.resolve(target.x - self.size.x),
|
||||
aligns.y.resolve(target.y - self.size.y),
|
||||
);
|
||||
self.size = new;
|
||||
self.size = target;
|
||||
self.baseline += offset.y;
|
||||
self.translate(offset);
|
||||
}
|
||||
|
@ -18,6 +18,12 @@ pub enum Align {
|
||||
}
|
||||
|
||||
impl Align {
|
||||
/// Top-left alignment.
|
||||
pub const LEFT_TOP: Spec<Self> = Spec { x: Align::Left, y: Align::Top };
|
||||
|
||||
/// Center-horizon alignment.
|
||||
pub const CENTER_HORIZON: Spec<Self> = Spec { x: Align::Center, y: Align::Horizon };
|
||||
|
||||
/// The axis this alignment belongs to.
|
||||
pub const fn axis(self) -> SpecAxis {
|
||||
match self {
|
||||
|
@ -281,6 +281,14 @@ impl BitOr for Spec<bool> {
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr<bool> for Spec<bool> {
|
||||
type Output = Self;
|
||||
|
||||
fn bitor(self, rhs: bool) -> Self::Output {
|
||||
Self { x: self.x | rhs, y: self.y | rhs }
|
||||
}
|
||||
}
|
||||
|
||||
impl BitAnd for Spec<bool> {
|
||||
type Output = Self;
|
||||
|
||||
@ -289,6 +297,14 @@ impl BitAnd for Spec<bool> {
|
||||
}
|
||||
}
|
||||
|
||||
impl BitAnd<bool> for Spec<bool> {
|
||||
type Output = Self;
|
||||
|
||||
fn bitand(self, rhs: bool) -> Self::Output {
|
||||
Self { x: self.x & rhs, y: self.y & rhs }
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOrAssign for Spec<bool> {
|
||||
fn bitor_assign(&mut self, rhs: Self) {
|
||||
self.x |= rhs.x;
|
||||
|
@ -136,10 +136,7 @@ impl PackedNode {
|
||||
|
||||
/// Transform this node's contents without affecting layout.
|
||||
pub fn moved(self, offset: Point) -> Self {
|
||||
self.transformed(
|
||||
Transform::translation(offset.x, offset.y),
|
||||
Spec::new(Align::Left, Align::Top),
|
||||
)
|
||||
self.transformed(Transform::translation(offset.x, offset.y), Align::LEFT_TOP)
|
||||
}
|
||||
|
||||
/// Transform this node's contents without affecting layout.
|
||||
|
@ -2,6 +2,18 @@ use super::prelude::*;
|
||||
|
||||
/// `align`: Configure the alignment along the layouting axes.
|
||||
pub fn align(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
castable! {
|
||||
Spec<Option<Align>>,
|
||||
Expected: "1d or 2d alignment",
|
||||
@align: Align => {
|
||||
let mut aligns = Spec::default();
|
||||
aligns.set(align.axis(), Some(*align));
|
||||
aligns
|
||||
},
|
||||
@aligns: Spec<Align> => aligns.map(Some),
|
||||
|
||||
}
|
||||
|
||||
let aligns = args.expect::<Spec<_>>("alignment")?;
|
||||
let body = args.expect::<Template>("body")?;
|
||||
Ok(Value::Template(Template::from_block(move |style| {
|
||||
@ -36,8 +48,8 @@ impl Layout for AlignNode {
|
||||
// Layout the child.
|
||||
let mut frames = self.child.layout(ctx, &pod);
|
||||
|
||||
for (Constrained { item: frame, cts }, (current, base)) in
|
||||
frames.iter_mut().zip(regions.iter())
|
||||
for ((current, base), Constrained { item: frame, cts }) in
|
||||
regions.iter().zip(&mut frames)
|
||||
{
|
||||
// Align in the target size. The target size depends on whether we
|
||||
// should expand.
|
||||
|
@ -101,10 +101,10 @@ enum FlowItem {
|
||||
Absolute(Length),
|
||||
/// Fractional spacing between other items.
|
||||
Fractional(Fractional),
|
||||
/// 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>),
|
||||
/// An absolutely placed frame.
|
||||
Placed(Rc<Frame>),
|
||||
}
|
||||
|
||||
impl<'a> FlowLayouter<'a> {
|
||||
@ -166,19 +166,13 @@ 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);
|
||||
let frame = node.layout(ctx, &self.regions).remove(0);
|
||||
if placed.out_of_flow() {
|
||||
self.items.push(FlowItem::Placed(frame.item));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let aligns = Spec::new(
|
||||
// For non-expanding paragraphs it is crucial that we align the
|
||||
@ -233,9 +227,6 @@ impl<'a> FlowLayouter<'a> {
|
||||
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);
|
||||
|
||||
@ -253,6 +244,9 @@ impl<'a> FlowLayouter<'a> {
|
||||
|
||||
output.push_frame(pos, frame);
|
||||
}
|
||||
FlowItem::Placed(frame) => {
|
||||
output.push_frame(Point::with_y(before), frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,7 +51,7 @@ impl Layout for ImageNode {
|
||||
let wide = pixel_ratio > current_ratio;
|
||||
|
||||
// The space into which the image will be placed according to its fit.
|
||||
let canvas = if expand.x && expand.y {
|
||||
let target = if expand.x && expand.y {
|
||||
current
|
||||
} else if expand.x || (wide && current.x.is_finite()) {
|
||||
Size::new(current.x, current.y.min(current.x.safe_div(pixel_ratio)))
|
||||
@ -65,20 +65,20 @@ impl Layout for ImageNode {
|
||||
let size = match self.fit {
|
||||
ImageFit::Contain | ImageFit::Cover => {
|
||||
if wide == (self.fit == ImageFit::Contain) {
|
||||
Size::new(canvas.x, canvas.x / pixel_ratio)
|
||||
Size::new(target.x, target.x / pixel_ratio)
|
||||
} else {
|
||||
Size::new(canvas.y * pixel_ratio, canvas.y)
|
||||
Size::new(target.y * pixel_ratio, target.y)
|
||||
}
|
||||
}
|
||||
ImageFit::Stretch => canvas,
|
||||
ImageFit::Stretch => target,
|
||||
};
|
||||
|
||||
// First, place the image in a frame of exactly its size and then resize
|
||||
// the frame to the canvas size, center aligning the image in the
|
||||
// the frame to the target size, center aligning the image in the
|
||||
// process.
|
||||
let mut frame = Frame::new(size);
|
||||
frame.push(Point::zero(), Element::Image(self.id, size));
|
||||
frame.resize(canvas, Spec::new(Align::Center, Align::Horizon));
|
||||
frame.resize(target, Align::CENTER_HORIZON);
|
||||
|
||||
// Create a clipping group if the fit mode is "cover".
|
||||
if self.fit == ImageFit::Cover {
|
||||
|
@ -153,15 +153,3 @@ castable! {
|
||||
Expected: "color",
|
||||
Value::Color(color) => Paint::Solid(color),
|
||||
}
|
||||
|
||||
castable! {
|
||||
Spec<Option<Align>>,
|
||||
Expected: "1d or 2d alignment",
|
||||
@align: Align => {
|
||||
let mut aligns = Spec::default();
|
||||
aligns.set(align.axis(), Some(*align));
|
||||
aligns
|
||||
},
|
||||
@aligns: Spec<Align> => aligns.map(Some),
|
||||
|
||||
}
|
||||
|
@ -39,8 +39,8 @@ impl Layout for PadNode {
|
||||
let pod = regions.map(|size| shrink(size, self.padding));
|
||||
let mut frames = self.child.layout(ctx, &pod);
|
||||
|
||||
for (Constrained { item: frame, cts }, (current, base)) in
|
||||
frames.iter_mut().zip(regions.iter())
|
||||
for ((current, base), Constrained { item: frame, cts }) in
|
||||
regions.iter().zip(&mut frames)
|
||||
{
|
||||
// Apply the padding inversely such that the grown size padded
|
||||
// yields the frame's size.
|
||||
|
@ -1,4 +1,5 @@
|
||||
use super::prelude::*;
|
||||
use super::AlignNode;
|
||||
|
||||
/// `place`: Place content at an absolute position.
|
||||
pub fn place(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
@ -13,23 +14,60 @@ pub fn place(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
})))
|
||||
}
|
||||
|
||||
/// A node that places its child out-of-flow.
|
||||
/// A node that places its child absolutely.
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct PlacedNode {
|
||||
/// The node to be placed.
|
||||
pub child: PackedNode,
|
||||
}
|
||||
|
||||
impl PlacedNode {
|
||||
/// Whether this node wants to be placed relative to its its parent's base
|
||||
/// origin. instead of relative to the parent's current flow/cursor
|
||||
/// position.
|
||||
pub fn out_of_flow(&self) -> bool {
|
||||
self.child
|
||||
.downcast::<AlignNode>()
|
||||
.map_or(false, |node| node.aligns.y.is_some())
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
let out_of_flow = self.out_of_flow();
|
||||
|
||||
// The pod is the base area of the region because for absolute
|
||||
// placement we don't really care about the already used area (current).
|
||||
let pod = {
|
||||
let expand = if out_of_flow { Spec::splat(true) } else { regions.expand };
|
||||
Regions::one(regions.base, regions.base, expand)
|
||||
};
|
||||
|
||||
let mut frames = self.child.layout(ctx, &pod);
|
||||
let Constrained { item: frame, cts } = &mut frames[0];
|
||||
|
||||
// If expansion is off, zero all sizes so that we don't take up any
|
||||
// space in our parent. Otherwise, respect the expand settings.
|
||||
let target = regions.expand.select(regions.current, Size::zero());
|
||||
Rc::make_mut(frame).resize(target, Align::LEFT_TOP);
|
||||
|
||||
// Place relative to parent's base origin by offsetting our elements by
|
||||
// the negative cursor position.
|
||||
if out_of_flow {
|
||||
let offset = (regions.current - regions.base).to_point();
|
||||
Rc::make_mut(frame).translate(offset);
|
||||
}
|
||||
|
||||
// Set base constraint because our pod size is base and exact
|
||||
// constraints if we needed to expand or offset.
|
||||
*cts = Constraints::new(regions.expand);
|
||||
cts.base = regions.base.map(Some);
|
||||
cts.exact = regions.current.filter(regions.expand | out_of_flow);
|
||||
|
||||
frames
|
||||
}
|
||||
}
|
||||
|
@ -56,14 +56,14 @@ impl Layout for SizedNode {
|
||||
};
|
||||
|
||||
let mut frames = self.child.layout(ctx, &pod);
|
||||
let Constrained { cts, .. } = &mut frames[0];
|
||||
|
||||
// Set base & exact constraints if the child is automatically sized
|
||||
// since we don't know what the child might do. Also set base if our
|
||||
// sizing is relative.
|
||||
let frame = &mut frames[0];
|
||||
frame.cts = Constraints::new(regions.expand);
|
||||
frame.cts.exact = regions.current.filter(is_auto);
|
||||
frame.cts.base = regions.base.filter(is_auto | is_rel);
|
||||
// since we don't know what the child might have done. Also set base if
|
||||
// our sizing is relative.
|
||||
*cts = Constraints::new(regions.expand);
|
||||
cts.exact = regions.current.filter(is_auto);
|
||||
cts.base = regions.base.filter(is_auto | is_rel);
|
||||
|
||||
frames
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ fn transform_impl(args: &mut Args, transform: Transform) -> TypResult<Value> {
|
||||
let origin = args
|
||||
.named("origin")?
|
||||
.unwrap_or(Spec::splat(None))
|
||||
.unwrap_or(Spec::new(Align::Center, Align::Horizon));
|
||||
.unwrap_or(Align::CENTER_HORIZON);
|
||||
|
||||
Ok(Value::Template(Template::from_inline(move |style| {
|
||||
body.pack(style).transformed(transform, origin)
|
||||
@ -56,11 +56,12 @@ impl Layout for TransformNode {
|
||||
) -> Vec<Constrained<Rc<Frame>>> {
|
||||
let mut frames = self.child.layout(ctx, regions);
|
||||
|
||||
for Constrained { item: frame, .. } in frames.iter_mut() {
|
||||
for Constrained { item: frame, .. } in &mut frames {
|
||||
let Spec { x, y } = self.origin.zip(frame.size).map(|(o, s)| o.resolve(s));
|
||||
let transform = Transform::translation(x, y)
|
||||
.pre_concat(self.transform)
|
||||
.pre_concat(Transform::translation(-x, -y));
|
||||
|
||||
Rc::make_mut(frame).transform(transform);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user