summaryrefslogtreecommitdiff
path: root/src/pdf.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/pdf.zig')
-rw-r--r--src/pdf.zig48
1 files changed, 48 insertions, 0 deletions
diff --git a/src/pdf.zig b/src/pdf.zig
index 052d34db..712750dc 100644
--- a/src/pdf.zig
+++ b/src/pdf.zig
@@ -976,6 +976,20 @@ pub const Document = struct {
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)
return error.BadPixmap;
@@ -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);
+}