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:
Laurenz 2023-07-19 12:53:36 +02:00 committed by GitHub
parent b37c1e2731
commit 3dcd8e6e6b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 72 additions and 39 deletions

View file

@ -66,6 +66,8 @@ impl Layout for FlowElem {
sticky: true,
movable: false,
});
} else if let Some(placed) = child.to::<PlaceElem>() {
layouter.layout_placed(vt, placed, styles)?;
} else if child.can::<dyn Layout>() {
layouter.layout_multiple(vt, child, styles)?;
} else if child.is::<ColbreakElem>() {
@ -128,7 +130,13 @@ enum FlowItem {
/// (to keep it together with its footnotes).
Frame { frame: Frame, aligns: Axes<Align>, sticky: bool, movable: bool },
/// 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).
Footnote(Frame),
}
@ -258,6 +266,25 @@ impl<'a> FlowLayouter<'a> {
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.
fn layout_multiple(
&mut self,
@ -265,16 +292,6 @@ impl<'a> FlowLayouter<'a> {
block: &Content,
styles: StyleChain,
) -> 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.
let is_root = self.root;
if is_root && block.is::<ColumnsElem>() {
@ -491,7 +508,8 @@ impl<'a> FlowLayouter<'a> {
offset += frame.height();
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 {
match y_align {
Smart::Custom(Some(Align::Top)) => {
@ -505,7 +523,7 @@ impl<'a> FlowLayouter<'a> {
float_bottom_offset += frame.height();
y
}
_ => offset + ruler.position(size.y - used.y),
_ => unreachable!("float must be y aligned"),
}
} else {
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) => {
let y = size.y - footnote_height + footnote_offset;

View file

@ -266,6 +266,8 @@ fn realize_block<'a>(
content: &'a Content,
styles: StyleChain<'a>,
) -> SourceResult<(Content, StyleChain<'a>)> {
// These elements implement `Layout` but still require a flow for
// proper layout.
if content.can::<dyn Layout>()
&& !content.is::<LineElem>()
&& !content.is::<RectElem>()
@ -275,6 +277,7 @@ fn realize_block<'a>(
&& !content.is::<ImageElem>()
&& !content.is::<PolygonElem>()
&& !content.is::<PathElem>()
&& !content.is::<PlaceElem>()
&& !applicable(content, styles)
{
return Ok((content.clone(), styles));

View file

@ -91,36 +91,13 @@ impl Layout for PlaceElem {
vt: &mut Vt,
styles: StyleChain,
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> {
// The pod is the base area of the region because for absolute
// placement we don't really care about the already used area.
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 alignment = self.alignment(styles);
if float
&& !matches!(
alignment,
@ -145,7 +122,9 @@ impl PlaceElem {
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))
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

View 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?
]