mirror of
https://github.com/typst/typst
synced 2025-05-22 13:05:29 +08:00
Positions placed elements relative to real container size (#1745)
This positions placed elements relative to the real container size instead of relative to the base size of the region. This makes its usage more versatile. Fixes #82 Fixes #685 Fixes #1705
This commit is contained in:
parent
b37c1e2731
commit
3dcd8e6e6b
@ -66,6 +66,8 @@ impl Layout for FlowElem {
|
|||||||
sticky: true,
|
sticky: true,
|
||||||
movable: false,
|
movable: false,
|
||||||
});
|
});
|
||||||
|
} else if let Some(placed) = child.to::<PlaceElem>() {
|
||||||
|
layouter.layout_placed(vt, placed, styles)?;
|
||||||
} else if child.can::<dyn Layout>() {
|
} else if child.can::<dyn Layout>() {
|
||||||
layouter.layout_multiple(vt, child, styles)?;
|
layouter.layout_multiple(vt, child, styles)?;
|
||||||
} else if child.is::<ColbreakElem>() {
|
} else if child.is::<ColbreakElem>() {
|
||||||
@ -128,7 +130,13 @@ enum FlowItem {
|
|||||||
/// (to keep it together with its footnotes).
|
/// (to keep it together with its footnotes).
|
||||||
Frame { frame: Frame, aligns: Axes<Align>, sticky: bool, movable: bool },
|
Frame { frame: Frame, aligns: Axes<Align>, sticky: bool, movable: bool },
|
||||||
/// An absolutely placed frame.
|
/// An absolutely placed frame.
|
||||||
Placed { frame: Frame, y_align: Smart<Option<Align>>, float: bool, clearance: Abs },
|
Placed {
|
||||||
|
frame: Frame,
|
||||||
|
x_align: Align,
|
||||||
|
y_align: Smart<Option<Align>>,
|
||||||
|
float: bool,
|
||||||
|
clearance: Abs,
|
||||||
|
},
|
||||||
/// A footnote frame (can also be the separator).
|
/// A footnote frame (can also be the separator).
|
||||||
Footnote(Frame),
|
Footnote(Frame),
|
||||||
}
|
}
|
||||||
@ -258,6 +266,25 @@ impl<'a> FlowLayouter<'a> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Layout a placed element.
|
||||||
|
fn layout_placed(
|
||||||
|
&mut self,
|
||||||
|
vt: &mut Vt,
|
||||||
|
placed: &PlaceElem,
|
||||||
|
styles: StyleChain,
|
||||||
|
) -> SourceResult<()> {
|
||||||
|
let float = placed.float(styles);
|
||||||
|
let clearance = placed.clearance(styles);
|
||||||
|
let alignment = placed.alignment(styles);
|
||||||
|
let x_align = alignment.map_or(Align::Center, |aligns| {
|
||||||
|
aligns.x.unwrap_or(GenAlign::Start).resolve(styles)
|
||||||
|
});
|
||||||
|
let y_align = alignment.map(|align| align.y.resolve(styles));
|
||||||
|
let frame = placed.layout(vt, styles, self.regions)?.into_frame();
|
||||||
|
let item = FlowItem::Placed { frame, x_align, y_align, float, clearance };
|
||||||
|
self.layout_item(vt, item)
|
||||||
|
}
|
||||||
|
|
||||||
/// Layout into multiple regions.
|
/// Layout into multiple regions.
|
||||||
fn layout_multiple(
|
fn layout_multiple(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -265,16 +292,6 @@ impl<'a> FlowLayouter<'a> {
|
|||||||
block: &Content,
|
block: &Content,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
// Handle placed elements.
|
|
||||||
if let Some(placed) = block.to::<PlaceElem>() {
|
|
||||||
let float = placed.float(styles);
|
|
||||||
let clearance = placed.clearance(styles);
|
|
||||||
let y_align = placed.alignment(styles).map(|align| align.y.resolve(styles));
|
|
||||||
let frame = placed.layout_inner(vt, styles, self.regions)?.into_frame();
|
|
||||||
let item = FlowItem::Placed { frame, y_align, float, clearance };
|
|
||||||
return self.layout_item(vt, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Temporarily delegerate rootness to the columns.
|
// Temporarily delegerate rootness to the columns.
|
||||||
let is_root = self.root;
|
let is_root = self.root;
|
||||||
if is_root && block.is::<ColumnsElem>() {
|
if is_root && block.is::<ColumnsElem>() {
|
||||||
@ -491,7 +508,8 @@ impl<'a> FlowLayouter<'a> {
|
|||||||
offset += frame.height();
|
offset += frame.height();
|
||||||
output.push_frame(pos, frame);
|
output.push_frame(pos, frame);
|
||||||
}
|
}
|
||||||
FlowItem::Placed { frame, y_align, float, .. } => {
|
FlowItem::Placed { frame, x_align, y_align, float, .. } => {
|
||||||
|
let x = x_align.position(size.x - frame.width());
|
||||||
let y = if float {
|
let y = if float {
|
||||||
match y_align {
|
match y_align {
|
||||||
Smart::Custom(Some(Align::Top)) => {
|
Smart::Custom(Some(Align::Top)) => {
|
||||||
@ -505,7 +523,7 @@ impl<'a> FlowLayouter<'a> {
|
|||||||
float_bottom_offset += frame.height();
|
float_bottom_offset += frame.height();
|
||||||
y
|
y
|
||||||
}
|
}
|
||||||
_ => offset + ruler.position(size.y - used.y),
|
_ => unreachable!("float must be y aligned"),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match y_align {
|
match y_align {
|
||||||
@ -516,7 +534,7 @@ impl<'a> FlowLayouter<'a> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
output.push_frame(Point::with_y(y), frame);
|
output.push_frame(Point::new(x, y), frame);
|
||||||
}
|
}
|
||||||
FlowItem::Footnote(frame) => {
|
FlowItem::Footnote(frame) => {
|
||||||
let y = size.y - footnote_height + footnote_offset;
|
let y = size.y - footnote_height + footnote_offset;
|
||||||
|
@ -266,6 +266,8 @@ fn realize_block<'a>(
|
|||||||
content: &'a Content,
|
content: &'a Content,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
) -> SourceResult<(Content, StyleChain<'a>)> {
|
) -> SourceResult<(Content, StyleChain<'a>)> {
|
||||||
|
// These elements implement `Layout` but still require a flow for
|
||||||
|
// proper layout.
|
||||||
if content.can::<dyn Layout>()
|
if content.can::<dyn Layout>()
|
||||||
&& !content.is::<LineElem>()
|
&& !content.is::<LineElem>()
|
||||||
&& !content.is::<RectElem>()
|
&& !content.is::<RectElem>()
|
||||||
@ -275,6 +277,7 @@ fn realize_block<'a>(
|
|||||||
&& !content.is::<ImageElem>()
|
&& !content.is::<ImageElem>()
|
||||||
&& !content.is::<PolygonElem>()
|
&& !content.is::<PolygonElem>()
|
||||||
&& !content.is::<PathElem>()
|
&& !content.is::<PathElem>()
|
||||||
|
&& !content.is::<PlaceElem>()
|
||||||
&& !applicable(content, styles)
|
&& !applicable(content, styles)
|
||||||
{
|
{
|
||||||
return Ok((content.clone(), styles));
|
return Ok((content.clone(), styles));
|
||||||
|
@ -91,36 +91,13 @@ impl Layout for PlaceElem {
|
|||||||
vt: &mut Vt,
|
vt: &mut Vt,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
) -> SourceResult<Fragment> {
|
|
||||||
let mut frame = self.layout_inner(vt, styles, regions)?.into_frame();
|
|
||||||
|
|
||||||
// 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.size, Size::zero());
|
|
||||||
frame.resize(target, Align::LEFT_TOP);
|
|
||||||
|
|
||||||
Ok(Fragment::frame(frame))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PlaceElem {
|
|
||||||
/// Layout without zeroing the frame size.
|
|
||||||
pub fn layout_inner(
|
|
||||||
&self,
|
|
||||||
vt: &mut Vt,
|
|
||||||
styles: StyleChain,
|
|
||||||
regions: Regions,
|
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
// The pod is the base area of the region because for absolute
|
// The pod is the base area of the region because for absolute
|
||||||
// placement we don't really care about the already used area.
|
// placement we don't really care about the already used area.
|
||||||
let base = regions.base();
|
let base = regions.base();
|
||||||
let expand =
|
|
||||||
Axes::new(base.x.is_finite(), base.y.is_finite() && !self.float(styles));
|
|
||||||
|
|
||||||
let pod = Regions::one(base, expand);
|
|
||||||
|
|
||||||
let float = self.float(styles);
|
let float = self.float(styles);
|
||||||
let alignment = self.alignment(styles);
|
let alignment = self.alignment(styles);
|
||||||
|
|
||||||
if float
|
if float
|
||||||
&& !matches!(
|
&& !matches!(
|
||||||
alignment,
|
alignment,
|
||||||
@ -145,7 +122,9 @@ impl PlaceElem {
|
|||||||
alignment.unwrap_or_else(|| Axes::with_x(Some(Align::Center.into()))),
|
alignment.unwrap_or_else(|| Axes::with_x(Some(Align::Center.into()))),
|
||||||
);
|
);
|
||||||
|
|
||||||
child.layout(vt, styles, pod)
|
let pod = Regions::one(base, Axes::splat(false));
|
||||||
|
let frame = child.layout(vt, styles, pod)?.into_frame();
|
||||||
|
Ok(Fragment::frame(frame))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BIN
tests/ref/bugs/place-nested.png
Normal file
BIN
tests/ref/bugs/place-nested.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
tests/ref/layout/place-nested.png
Normal file
BIN
tests/ref/layout/place-nested.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.1 KiB |
33
tests/typ/layout/place-nested.typ
Normal file
33
tests/typ/layout/place-nested.typ
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// Test vertical alignment with nested placement.
|
||||||
|
|
||||||
|
---
|
||||||
|
#box(
|
||||||
|
fill: aqua,
|
||||||
|
width: 30pt,
|
||||||
|
height: 30pt,
|
||||||
|
place(bottom,
|
||||||
|
place(line(start: (0pt, 0pt), end: (20pt, 0pt), stroke: red + 3pt))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
---
|
||||||
|
#box(
|
||||||
|
fill: aqua,
|
||||||
|
width: 30pt,
|
||||||
|
height: 30pt,
|
||||||
|
{
|
||||||
|
box(fill: yellow, {
|
||||||
|
[Hello]
|
||||||
|
place(horizon, line(start: (0pt, 0pt), end: (20pt, 0pt), stroke: red + 2pt))
|
||||||
|
})
|
||||||
|
place(horizon, line(start: (0pt, 0pt), end: (20pt, 0pt), stroke: green + 3pt))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
---
|
||||||
|
#box(fill: aqua)[
|
||||||
|
#place(bottom + right)[Hi]
|
||||||
|
Hello World \
|
||||||
|
How are \
|
||||||
|
you?
|
||||||
|
]
|
Loading…
x
Reference in New Issue
Block a user