diff --git a/crates/typst-render/src/text.rs b/crates/typst-render/src/text.rs index c017b1ce3..b47659cba 100644 --- a/crates/typst-render/src/text.rs +++ b/crates/typst-render/src/text.rs @@ -164,30 +164,42 @@ fn write_bitmap( // 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::(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 744ce0f26..8c82bc309 100644 Binary files a/tests/ref/block-clip-text.png and b/tests/ref/block-clip-text.png differ diff --git a/tests/ref/block-clipping-multiple-pages.png b/tests/ref/block-clipping-multiple-pages.png index ffe2fd08a..0b6e7c85f 100644 Binary files a/tests/ref/block-clipping-multiple-pages.png and b/tests/ref/block-clipping-multiple-pages.png 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 000000000..5f7962d3b Binary files /dev/null and b/tests/ref/issue-5499-text-fill-in-clip-block.png differ diff --git a/tests/suite/text/font.typ b/tests/suite/text/font.typ index 443be6ed1..5c972ff32 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") +})