typst icon indicating copy to clipboard operation
typst copied to clipboard

`scale`-d grid doesn't get correctly layouted

Open ntjess opened this issue 1 year ago • 7 comments
trafficstars

Description

#set page(height: 2in)
#let body = for ii in range(5) [
  A heading
  #grid(lorem((ii + 1) * 10))
]

#scale(body, x: 50%, y: 50%, origin: top + left)

Produces image

When it should produce no overlapping text

Reproduction URL

No response

Operating system

No response

Typst version

  • [X] I am using the latest version of Typst

ntjess avatar Jan 07 '24 21:01 ntjess

This also applies to lists and tables. The problem seems to be that elements using GridLayouter always break after the total page/region height has been spanned, even if they're inside an unbreakable block (in this case, scale doesn't allow breaking - the same occurs inside figure, for example). In contrast, adding #lorem(400) correctly keeps going inside scale or unbreakable blocks even after the total page height has been spanned. Perhaps there is a way for grid-like elements to be aware if they're inside an unbreakable block, and thus assume an "infinite page height"? Can we pass this information somehow, maybe through Regions? (cc @laurmaedje for possible ideas)

PgBiel avatar Jan 07 '24 23:01 PgBiel

Possibly related: #2073

PgBiel avatar Jan 08 '24 00:01 PgBiel

I think the grid layouter is missing calls to self.regions.in_last(). The flow layouter, for instance, won't finish the region if it knows that it is in the last region.

laurmaedje avatar Jan 08 '24 10:01 laurmaedje

Thanks for the hint! I'll take a look to see if I can fix this soon. (Maybe after I'm done with the Part 2b PR.)

PgBiel avatar Jan 08 '24 15:01 PgBiel

One important part of the problem is regarding the height given to cells in measure_auto_row. Making an auto row unbreakable by checking self.regions.in_last() isn't enough, since that condition is necessary but not sufficient for the grid to be inside an unbreakable box (and would thus cause regressions on non-unbreakable grids). Instead, one must manually check for self.regions.backlog.is_empty() && self.regions.last.is_none() i.e. ensure there's really no way forward.

But just making the row unbreakable is still not enough: the available height (self.regions.size.y) also matters. Setting the available height to infinity made a simple example work (but still not the one at the original post):

image

Code

Hello world!
#block(breakable: false, height: 1em, table(
  [a],
  [b],
  [c],
  [d],
  [e],
))

Wow, cool equations!

Thanks!

Without the change, the table's last rows would be compressed into the same location.

With that said, I wouldn't be too eager to change the behavior demonstrated above, as it could be intended; I used a block just as a way to portray the behavior within a page of 1em height (where it also works).

But even that still doesn't fix scale for some reason. I'll try to investigate more soon.

PgBiel avatar Feb 29 '24 07:02 PgBiel

Thinking further, I think the problem in the OP has (almost) nothing to do with grids (despite the issue I mention in the previous comment, which exists but seems to be unrelated). A heading is clearly not in any grid, and still behaves that way. But it's still related to my observation regarding measuring with infinite height. The following patch makes the OP's example appear to behave correctly:

diff --git a/crates/typst/src/layout/transform.rs b/crates/typst/src/layout/transform.rs
index 13ff66ae..b75c7741 100644
--- a/crates/typst/src/layout/transform.rs
+++ b/crates/typst/src/layout/transform.rs
@@ -387,7 +387,7 @@ fn measure_and_layout(
     }
 
     // Measure the size of the body.
-    let pod = Regions::one(size, Axes::splat(false));
+    let pod = Regions::one(Axes::new(size.x, Abs::inf()), Axes::splat(false));
     let frame = body.measure(engine, styles, pod)?.into_frame();
 
     // Actually perform the layout.

Results in:

image

The patch is, of course, naive, because it appears to break the reflow option. But it demonstrates that perhaps we should use infinite height when measuring unbreakable things which don't affect layout.

PgBiel avatar Mar 06 '24 17:03 PgBiel

I think I might've just ran into a similar issue or at least some related behaviour regarding reflow when trying to scale a figure to page width (like in https://github.com/typst/typst/discussions/1256): I didn't want the figure's caption to be scaled down with the actual image (which is a cetz canvas), so I applied a scale to just the figure contents.

With reflow: true, the figure overlaps with the following heading, paragraph, and subsequent figure (a following table). Even with a manual #pagebreak() before the new section, the scaled figure's caption is rendered on top of (instead of below) the canvas. Using reflow: false, the figure renders on its own page, with the caption placed at the very bottom of the page (presumably because that's where it would be if the content wasn't scaled, the canvas is quite large).

domenicquirl avatar May 14 '24 10:05 domenicquirl