summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPgBiel <9021226+PgBiel@users.noreply.github.com>2024-12-04 07:00:04 -0300
committerGitHub <noreply@github.com>2024-12-04 10:00:04 +0000
commit884c02872ce9d7432c3b67a920011cf385ce70b2 (patch)
treeb5e2e28510780135cc492575f63727a096d1f98e
parentcda94ab40505a2d812c3a743a8d916bcfe4484c3 (diff)
Fix text fill within `clip: true` blocks in PNG export (#5502)
-rw-r--r--crates/typst-render/src/text.rs36
-rw-r--r--tests/ref/block-clip-text.pngbin1268 -> 908 bytes
-rw-r--r--tests/ref/block-clipping-multiple-pages.pngbin2047 -> 2029 bytes
-rw-r--r--tests/ref/issue-5499-text-fill-in-clip-block.pngbin0 -> 1502 bytes
-rw-r--r--tests/suite/text/font.typ31
5 files changed, 55 insertions, 12 deletions
diff --git a/crates/typst-render/src/text.rs b/crates/typst-render/src/text.rs
index c017b1ce..b47659cb 100644
--- a/crates/typst-render/src/text.rs
+++ b/crates/typst-render/src/text.rs
@@ -164,30 +164,42 @@ fn write_bitmap<S: PaintSampler>(
// If we have a clip mask we first render to a pixmap that we then blend
// with our canvas
if state.mask.is_some() {
+ let cw = canvas.width() as i32;
+ let ch = canvas.height() as i32;
let mw = bitmap.width;
let mh = bitmap.height;
+ let left = bitmap.left;
+ let top = bitmap.top;
+
// Pad the pixmap with 1 pixel in each dimension so that we do
// not get any problem with floating point errors along their border
let mut pixmap = sk::Pixmap::new(mw + 2, mh + 2)?;
+ let pixels = bytemuck::cast_slice_mut::<u8, u32>(pixmap.data_mut());
for x in 0..mw {
for y in 0..mh {
let alpha = bitmap.coverage[(y * mw + x) as usize];
- let color = sampler.sample((x, y));
- pixmap.pixels_mut()[((y + 1) * (mw + 2) + (x + 1)) as usize] =
- sk::ColorU8::from_rgba(
- color.red(),
- color.green(),
- color.blue(),
- alpha,
- )
- .premultiply();
+
+ // To sample at the correct position, we need to convert each
+ // pixel's position in the bitmap (x and y) to its final
+ // expected position in the canvas. Due to padding, this
+ // pixel's position in the pixmap will be (x + 1, y + 1).
+ // Then, when drawing the pixmap to the canvas, we place its
+ // top-left corner at position (left - 1, top - 1). Therefore,
+ // the final position of this pixel in the canvas is given by
+ // (left - 1 + x + 1, top - 1 + y + 1) = (left + x, top + y).
+ let sample_pos = (
+ (left + x as i32).clamp(0, cw) as u32,
+ (top + y as i32).clamp(0, ch) as u32,
+ );
+ let color = sampler.sample(sample_pos);
+ let color = bytemuck::cast(color);
+
+ let applied = alpha_mul(color, alpha as u32);
+ pixels[((y + 1) * (mw + 2) + (x + 1)) as usize] = applied;
}
}
- let left = bitmap.left;
- let top = bitmap.top;
-
canvas.draw_pixmap(
left - 1,
top - 1,
diff --git a/tests/ref/block-clip-text.png b/tests/ref/block-clip-text.png
index 744ce0f2..8c82bc30 100644
--- a/tests/ref/block-clip-text.png
+++ b/tests/ref/block-clip-text.png
Binary files differ
diff --git a/tests/ref/block-clipping-multiple-pages.png b/tests/ref/block-clipping-multiple-pages.png
index ffe2fd08..0b6e7c85 100644
--- a/tests/ref/block-clipping-multiple-pages.png
+++ b/tests/ref/block-clipping-multiple-pages.png
Binary files differ
diff --git a/tests/ref/issue-5499-text-fill-in-clip-block.png b/tests/ref/issue-5499-text-fill-in-clip-block.png
new file mode 100644
index 00000000..5f7962d3
--- /dev/null
+++ b/tests/ref/issue-5499-text-fill-in-clip-block.png
Binary files differ
diff --git a/tests/suite/text/font.typ b/tests/suite/text/font.typ
index 443be6ed..5c972ff3 100644
--- a/tests/suite/text/font.typ
+++ b/tests/suite/text/font.typ
@@ -81,3 +81,34 @@ I
// Warning: 17-34 Typst's default font has changed from Linux Libertine to its successor Libertinus Serif
// Hint: 17-34 please set the font to `"Libertinus Serif"` instead
#set text(font: "Linux Libertine")
+
+--- issue-5499-text-fill-in-clip-block ---
+
+#let pat = pattern(
+ size: (30pt, 30pt),
+ relative: "parent",
+ square(
+ size: 30pt,
+ fill: gradient
+ .conic(..color.map.rainbow),
+ )
+)
+
+#block(clip: false, height: 2em, {
+ text(fill: blue, "Hello")
+ [ ]
+ text(fill: blue.darken(20%).transparentize(50%), "Hello")
+ [ ]
+ text(fill: gradient.linear(..color.map.rainbow), "Hello")
+ [ ]
+ text(fill: pat, "Hello")
+})
+#block(clip: true, height: 2em, {
+ text(fill: blue, "Hello")
+ [ ]
+ text(fill: blue.darken(20%).transparentize(50%), "Hello")
+ [ ]
+ text(fill: gradient.linear(..color.map.rainbow), "Hello")
+ [ ]
+ text(fill: pat, "Hello")
+})