From 717afaf3177a2e0925b18ae3445119efe808b129 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Wed, 16 Sep 2026 14:46:16 -0300 Subject: Add macOS backdrop blur and preserve PDF ink opacity --- src/pdf.zig | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) (limited to 'src/pdf.zig') diff --git a/src/pdf.zig b/src/pdf.zig index 052d34db..712750dc 100644 --- a/src/pdf.zig +++ b/src/pdf.zig @@ -975,6 +975,20 @@ pub const Document = struct { band: Raster.Band, highlights: []const Highlight, rgba: []u8, + ) !void { + return document.renderIntoAtWithPaper(page, request, raster, band, highlights, rgba, false); + } + + /// Preserve the PDF's content coverage when the host paints paper separately. + pub fn renderIntoAtWithPaper( + document: *Document, + page: usize, + request: RenderRequest, + raster: Raster, + band: Raster.Band, + highlights: []const Highlight, + rgba: []u8, + transparent_paper: bool, ) !void { if (rgba.len != band.len) return error.BadPixmap; if (band.height == 0 or band.y + band.height > raster.height) @@ -999,6 +1013,7 @@ pub const Document = struct { @intCast(raster.stride), @intCast(band.y), @intCast(band.height), + @intFromBool(transparent_paper), ) != c.PARDES_PDF_OK) return error.RenderFailed; } @@ -1832,6 +1847,7 @@ test "RGBA allocation and buffer-validation failures leave the document renderab raw_layout.stride, 0, raw_layout.height, + 0, )); for (scratch) |byte| try std.testing.expectEqual(@as(u8, 0xa5), byte); @@ -2074,3 +2090,35 @@ test "offset crop and rotation keep normalized highlights pixel-aligned" { marked.rgba[left + 1] < 10 and marked.rgba[left + 2] > 240); try std.testing.expectEqualSlices(u8, plain.rgba[right .. right + 4], marked.rgba[right .. right + 4]); } + +test "transparent PDF paper preserves ink and band coverage" { + var document = try Document.open("docs/9p.pdf"); + defer document.deinit(); + const request: RenderRequest = .{ .dpi = 96, .max_dimension = 1200 }; + const raster = try document.measureRenderAt(0, request); + const flattened = try std.testing.allocator.alloc(u8, raster.len); + defer std.testing.allocator.free(flattened); + const content = try std.testing.allocator.alloc(u8, raster.len); + defer std.testing.allocator.free(content); + try document.renderIntoAt(0, request, raster, raster.wholePage(), &.{}, flattened); + try document.renderIntoAtWithPaper(0, request, raster, raster.wholePage(), &.{}, content, true); + var paper: usize = 0; + var ink: usize = 0; + var edges: usize = 0; + for (std.mem.bytesAsSlice([4]u8, content), std.mem.bytesAsSlice([4]u8, flattened)) |pixel, reference| { + const a: u32 = pixel[3]; + paper += @intFromBool(a == 0); + ink += @intFromBool(a == 255); + edges += @intFromBool(a > 0 and a < 255); + for (0..3) |channel| { + const over_white = (a * pixel[channel] + (255 - a) * 255 + 127) / 255; + try std.testing.expect(@abs(@as(i32, @intCast(over_white)) - @as(i32, reference[channel])) <= 2); + } + } + try std.testing.expect(paper > 100 and ink > 100 and edges > 100); + const band = raster.band(raster.height / 3, raster.height / 4); + const rows = try std.testing.allocator.alloc(u8, band.len); + defer std.testing.allocator.free(rows); + try document.renderIntoAtWithPaper(0, request, raster, band, &.{}, rows, true); + try std.testing.expectEqualSlices(u8, content[band.y * raster.stride ..][0..band.len], rows); +} -- cgit v1.3