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.
|
/// Push a block node into the active flow, finishing the active paragraph.
|
||||||
fn block(&mut self, node: PackedNode) {
|
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();
|
self.parbreak();
|
||||||
let in_flow = node.downcast::<PlacedNode>().is_none();
|
|
||||||
self.flow.push(FlowChild::Node(node));
|
self.flow.push(FlowChild::Node(node));
|
||||||
if in_flow {
|
self.parbreak();
|
||||||
self.parbreak();
|
|
||||||
} else {
|
// This prevents paragraph spacing between the placed node and
|
||||||
// This prevents duplicate paragraph spacing around placed nodes.
|
// the paragraph below it.
|
||||||
|
if is_placed {
|
||||||
self.flow.last = Last::None;
|
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
|
/// Resize the frame to a new size, distributing new space according to the
|
||||||
/// given alignments.
|
/// given alignments.
|
||||||
pub fn resize(&mut self, new: Size, aligns: Spec<Align>) {
|
pub fn resize(&mut self, target: Size, aligns: Spec<Align>) {
|
||||||
if self.size != new {
|
if self.size != target {
|
||||||
let offset = Point::new(
|
let offset = Point::new(
|
||||||
aligns.x.resolve(new.x - self.size.x),
|
aligns.x.resolve(target.x - self.size.x),
|
||||||
aligns.y.resolve(new.y - self.size.y),
|
aligns.y.resolve(target.y - self.size.y),
|
||||||
);
|
);
|
||||||
self.size = new;
|
self.size = target;
|
||||||
self.baseline += offset.y;
|
self.baseline += offset.y;
|
||||||
self.translate(offset);
|
self.translate(offset);
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,12 @@ pub enum Align {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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.
|
/// The axis this alignment belongs to.
|
||||||
pub const fn axis(self) -> SpecAxis {
|
pub const fn axis(self) -> SpecAxis {
|
||||||
match self {
|
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> {
|
impl BitAnd for Spec<bool> {
|
||||||
type Output = Self;
|
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> {
|
impl BitOrAssign for Spec<bool> {
|
||||||
fn bitor_assign(&mut self, rhs: Self) {
|
fn bitor_assign(&mut self, rhs: Self) {
|
||||||
self.x |= rhs.x;
|
self.x |= rhs.x;
|
||||||
|
@ -136,10 +136,7 @@ impl PackedNode {
|
|||||||
|
|
||||||
/// Transform this node's contents without affecting layout.
|
/// Transform this node's contents without affecting layout.
|
||||||
pub fn moved(self, offset: Point) -> Self {
|
pub fn moved(self, offset: Point) -> Self {
|
||||||
self.transformed(
|
self.transformed(Transform::translation(offset.x, offset.y), Align::LEFT_TOP)
|
||||||
Transform::translation(offset.x, offset.y),
|
|
||||||
Spec::new(Align::Left, Align::Top),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transform this node's contents without affecting layout.
|
/// Transform this node's contents without affecting layout.
|
||||||
|
@ -2,6 +2,18 @@ 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> {
|
||||||
|
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 aligns = args.expect::<Spec<_>>("alignment")?;
|
||||||
let body = args.expect::<Template>("body")?;
|
let body = args.expect::<Template>("body")?;
|
||||||
Ok(Value::Template(Template::from_block(move |style| {
|
Ok(Value::Template(Template::from_block(move |style| {
|
||||||
@ -36,8 +48,8 @@ impl Layout for AlignNode {
|
|||||||
// Layout the child.
|
// Layout the child.
|
||||||
let mut frames = self.child.layout(ctx, &pod);
|
let mut frames = self.child.layout(ctx, &pod);
|
||||||
|
|
||||||
for (Constrained { item: frame, cts }, (current, base)) in
|
for ((current, base), Constrained { item: frame, cts }) in
|
||||||
frames.iter_mut().zip(regions.iter())
|
regions.iter().zip(&mut frames)
|
||||||
{
|
{
|
||||||
// Align in the target size. The target size depends on whether we
|
// Align in the target size. The target size depends on whether we
|
||||||
// should expand.
|
// should expand.
|
||||||
|
@ -101,10 +101,10 @@ enum FlowItem {
|
|||||||
Absolute(Length),
|
Absolute(Length),
|
||||||
/// Fractional spacing between other items.
|
/// Fractional spacing between other items.
|
||||||
Fractional(Fractional),
|
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.
|
/// A frame for a layouted child node and how to align it.
|
||||||
Frame(Rc<Frame>, Spec<Align>),
|
Frame(Rc<Frame>, Spec<Align>),
|
||||||
|
/// An absolutely placed frame.
|
||||||
|
Placed(Rc<Frame>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> FlowLayouter<'a> {
|
impl<'a> FlowLayouter<'a> {
|
||||||
@ -166,17 +166,11 @@ 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(placed) = node.downcast::<PlacedNode>() {
|
||||||
if let Some(aligned) = placed.child.downcast::<AlignNode>() {
|
let frame = node.layout(ctx, &self.regions).remove(0);
|
||||||
if aligned.aligns.y.is_some() {
|
if placed.out_of_flow() {
|
||||||
let base = self.regions.base;
|
self.items.push(FlowItem::Placed(frame.item));
|
||||||
let pod = Regions::one(base, base, Spec::splat(true));
|
return;
|
||||||
let frame = placed.layout(ctx, &pod).remove(0);
|
|
||||||
self.items.push(FlowItem::Placed(frame.item));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,9 +227,6 @@ impl<'a> FlowLayouter<'a> {
|
|||||||
FlowItem::Fractional(v) => {
|
FlowItem::Fractional(v) => {
|
||||||
before += v.resolve(self.fr, remaining);
|
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);
|
||||||
|
|
||||||
@ -253,6 +244,9 @@ impl<'a> FlowLayouter<'a> {
|
|||||||
|
|
||||||
output.push_frame(pos, frame);
|
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;
|
let wide = pixel_ratio > current_ratio;
|
||||||
|
|
||||||
// The space into which the image will be placed according to its fit.
|
// 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
|
current
|
||||||
} else if expand.x || (wide && current.x.is_finite()) {
|
} else if expand.x || (wide && current.x.is_finite()) {
|
||||||
Size::new(current.x, current.y.min(current.x.safe_div(pixel_ratio)))
|
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 {
|
let size = match self.fit {
|
||||||
ImageFit::Contain | ImageFit::Cover => {
|
ImageFit::Contain | ImageFit::Cover => {
|
||||||
if wide == (self.fit == ImageFit::Contain) {
|
if wide == (self.fit == ImageFit::Contain) {
|
||||||
Size::new(canvas.x, canvas.x / pixel_ratio)
|
Size::new(target.x, target.x / pixel_ratio)
|
||||||
} else {
|
} 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
|
// 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.
|
// process.
|
||||||
let mut frame = Frame::new(size);
|
let mut frame = Frame::new(size);
|
||||||
frame.push(Point::zero(), Element::Image(self.id, 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".
|
// Create a clipping group if the fit mode is "cover".
|
||||||
if self.fit == ImageFit::Cover {
|
if self.fit == ImageFit::Cover {
|
||||||
|
@ -153,15 +153,3 @@ castable! {
|
|||||||
Expected: "color",
|
Expected: "color",
|
||||||
Value::Color(color) => Paint::Solid(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 pod = regions.map(|size| shrink(size, self.padding));
|
||||||
let mut frames = self.child.layout(ctx, &pod);
|
let mut frames = self.child.layout(ctx, &pod);
|
||||||
|
|
||||||
for (Constrained { item: frame, cts }, (current, base)) in
|
for ((current, base), Constrained { item: frame, cts }) in
|
||||||
frames.iter_mut().zip(regions.iter())
|
regions.iter().zip(&mut frames)
|
||||||
{
|
{
|
||||||
// Apply the padding inversely such that the grown size padded
|
// Apply the padding inversely such that the grown size padded
|
||||||
// yields the frame's size.
|
// yields the frame's size.
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
|
use super::AlignNode;
|
||||||
|
|
||||||
/// `place`: Place content at an absolute position.
|
/// `place`: Place content at an absolute position.
|
||||||
pub fn place(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
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)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct PlacedNode {
|
pub struct PlacedNode {
|
||||||
/// The node to be placed.
|
/// The node to be placed.
|
||||||
pub child: PackedNode,
|
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 {
|
impl Layout for PlacedNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
let mut frames = self.child.layout(ctx, regions);
|
let out_of_flow = self.out_of_flow();
|
||||||
for frame in frames.iter_mut() {
|
|
||||||
Rc::make_mut(&mut frame.item).size = Size::zero();
|
// 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
|
frames
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,14 +56,14 @@ impl Layout for SizedNode {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut frames = self.child.layout(ctx, &pod);
|
let mut frames = self.child.layout(ctx, &pod);
|
||||||
|
let Constrained { cts, .. } = &mut frames[0];
|
||||||
|
|
||||||
// Set base & exact constraints if the child is automatically sized
|
// 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
|
// since we don't know what the child might have done. Also set base if
|
||||||
// sizing is relative.
|
// our sizing is relative.
|
||||||
let frame = &mut frames[0];
|
*cts = Constraints::new(regions.expand);
|
||||||
frame.cts = Constraints::new(regions.expand);
|
cts.exact = regions.current.filter(is_auto);
|
||||||
frame.cts.exact = regions.current.filter(is_auto);
|
cts.base = regions.base.filter(is_auto | is_rel);
|
||||||
frame.cts.base = regions.base.filter(is_auto | is_rel);
|
|
||||||
|
|
||||||
frames
|
frames
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ fn transform_impl(args: &mut Args, transform: Transform) -> TypResult<Value> {
|
|||||||
let origin = args
|
let origin = args
|
||||||
.named("origin")?
|
.named("origin")?
|
||||||
.unwrap_or(Spec::splat(None))
|
.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| {
|
Ok(Value::Template(Template::from_inline(move |style| {
|
||||||
body.pack(style).transformed(transform, origin)
|
body.pack(style).transformed(transform, origin)
|
||||||
@ -56,11 +56,12 @@ impl Layout for TransformNode {
|
|||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
let mut frames = self.child.layout(ctx, regions);
|
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 Spec { x, y } = self.origin.zip(frame.size).map(|(o, s)| o.resolve(s));
|
||||||
let transform = Transform::translation(x, y)
|
let transform = Transform::translation(x, y)
|
||||||
.pre_concat(self.transform)
|
.pre_concat(self.transform)
|
||||||
.pre_concat(Transform::translation(-x, -y));
|
.pre_concat(Transform::translation(-x, -y));
|
||||||
|
|
||||||
Rc::make_mut(frame).transform(transform);
|
Rc::make_mut(frame).transform(transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user