summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build.zig4
-rw-r--r--src/pdf.zig216
-rw-r--r--src/pdf_bridge.c195
-rw-r--r--src/pdf_bridge.h28
-rw-r--r--test/pdf_bench.zig8
5 files changed, 288 insertions, 163 deletions
diff --git a/build.zig b/build.zig
index 95592c80..ff2dddfc 100644
--- a/build.zig
+++ b/build.zig
@@ -369,8 +369,8 @@ pub fn build(b: *std.Build) void {
root_mod.addImport("mupdf", mupdf_mod);
hx_core_mod.addImport("mupdf", mupdf_mod);
// Compile the @cImport + exception bridge and exercise the Zig
- // wrapper's stride-aware RGB-to-RGBA boundary alongside the C
- // end-to-end document probe below.
+ // wrapper's caller-owned RGBA boundary alongside the C end-to-end
+ // document probe below.
const mupdf_test = b.addTest(.{ .root_module = mupdf_mod });
mupdf_check.dependOn(&b.addRunArtifact(mupdf_test).step);
mupdf_check.dependOn(mupdf_build.addProbe(b, mupdf, .{
diff --git a/src/pdf.zig b/src/pdf.zig
index a1c05681..28f976e4 100644
--- a/src/pdf.zig
+++ b/src/pdf.zig
@@ -166,8 +166,7 @@ pub const Document = struct {
return .{ .width = size.width, .height = size.height };
}
- /// Render one zero-based page, bounded before allocation. MuPDF supplies
- /// row-strided RGB; Pardes' attachment boundary is packed straight RGBA.
+ /// Render one zero-based page directly into allocator-owned packed RGBA.
pub fn render(document: *Document, gpa: std.mem.Allocator, page: usize) !Render {
return document.renderAt(gpa, page, default_render_request);
}
@@ -227,62 +226,42 @@ pub const Document = struct {
request.minimum_height,
@as(u32, @intCast(std.math.maxInt(c_int))),
));
- var pixmap: c.pardes_pdf_pixmap = std.mem.zeroes(c.pardes_pdf_pixmap);
- const status = if (highlights) |items|
- if (items.len == 0)
- c.pardes_pdf_render(
- document.handle,
- @intCast(page),
- request.dpi,
- minimum_width,
- minimum_height,
- request.max_dimension,
- &pixmap,
- )
- else
- c.pardes_pdf_render_with_highlights(
- document.handle,
- @intCast(page),
- request.dpi,
- minimum_width,
- minimum_height,
- request.max_dimension,
- @ptrCast(items.ptr),
- items.len,
- &pixmap,
- )
- else
- c.pardes_pdf_render(
- document.handle,
- @intCast(page),
- request.dpi,
- minimum_width,
- minimum_height,
- request.max_dimension,
- &pixmap,
- );
- if (status != c.PARDES_PDF_OK) return error.RenderFailed;
- defer c.pardes_pdf_drop_pixmap(document.handle, pixmap.handle);
-
- if (pixmap.samples == null or pixmap.width < 1 or pixmap.height < 1 or
- pixmap.stride < 1 or pixmap.components != 3)
- return error.BadPixmap;
- const width: usize = @intCast(pixmap.width);
- const height: usize = @intCast(pixmap.height);
- const stride: usize = @intCast(pixmap.stride);
- if (width > std.math.maxInt(usize) / 4 or
- height > std.math.maxInt(usize) / (width * 4) or
- height > std.math.maxInt(usize) / stride or
- stride < width * 3)
- return error.BadPixmap;
+ const items: []const Highlight = highlights orelse &.{};
+ if (items.len > c.PARDES_PDF_MAX_RESULT_QUADS)
+ return error.RenderFailed;
+ var raw_layout: c.pardes_pdf_raster_layout = std.mem.zeroes(c.pardes_pdf_raster_layout);
+ if (c.pardes_pdf_measure_render(
+ document.handle,
+ @intCast(page),
+ request.dpi,
+ minimum_width,
+ minimum_height,
+ request.max_dimension,
+ &raw_layout,
+ ) != c.PARDES_PDF_OK) return error.RenderFailed;
+ const layout = try checkedRasterLayout(raw_layout);
+ if (layout.len > max_owned_raster_bytes) return error.PixmapTooLarge;
- const rgba_len = width * height * 4;
- if (rgba_len > max_owned_raster_bytes) return error.PixmapTooLarge;
- const rgba = try gpa.alloc(u8, rgba_len);
+ const rgba = try gpa.alloc(u8, layout.len);
errdefer gpa.free(rgba);
- const samples: [*]const u8 = @ptrCast(pixmap.samples);
- try expandRgb(rgba, samples[0 .. stride * height], width, height, stride);
- return .{ .rgba = rgba, .width = width, .height = height };
+ const highlight_ptr: ?[*]const c.pardes_pdf_highlight =
+ if (items.len == 0) null else @ptrCast(items.ptr);
+ if (c.pardes_pdf_render_into(
+ document.handle,
+ @intCast(page),
+ request.dpi,
+ minimum_width,
+ minimum_height,
+ request.max_dimension,
+ highlight_ptr,
+ items.len,
+ rgba.ptr,
+ rgba.len,
+ @intCast(layout.width),
+ @intCast(layout.height),
+ @intCast(layout.stride),
+ ) != c.PARDES_PDF_OK) return error.RenderFailed;
+ return .{ .rgba = rgba, .width = layout.width, .height = layout.height };
}
/// Plain UTF-8-ish text projection for one zero-based page. MuPDF owns the
@@ -428,26 +407,33 @@ fn validQuad(quad: Quad) bool {
validPoint(quad.ll) and validPoint(quad.lr);
}
-fn expandRgb(
- rgba: []u8,
- rgb: []const u8,
+const RasterLayout = struct {
width: usize,
height: usize,
stride: usize,
-) !void {
- if (rgba.len != width * height * 4 or stride < width * 3 or
- rgb.len < stride * height)
+ len: usize,
+};
+
+fn checkedRasterLayout(raw: c.pardes_pdf_raster_layout) !RasterLayout {
+ if (raw.width < 1 or raw.height < 1 or raw.stride < 1)
return error.BadPixmap;
- for (0..height) |y| {
- const src = rgb[y * stride ..][0 .. width * 3];
- const dst = rgba[y * width * 4 ..][0 .. width * 4];
- for (0..width) |x| {
- dst[x * 4 + 0] = src[x * 3 + 0];
- dst[x * 4 + 1] = src[x * 3 + 1];
- dst[x * 4 + 2] = src[x * 3 + 2];
- dst[x * 4 + 3] = 0xff;
- }
- }
+ const width: usize = @intCast(raw.width);
+ const height: usize = @intCast(raw.height);
+ const stride: usize = @intCast(raw.stride);
+ const expected_stride = std.math.mul(usize, width, 4) catch
+ return error.BadPixmap;
+ if (stride != expected_stride) return error.BadPixmap;
+ const len = std.math.mul(usize, stride, height) catch
+ return error.BadPixmap;
+ if (raw.samples_len != len) return error.BadPixmap;
+ return .{ .width = width, .height = height, .stride = stride, .len = len };
+}
+
+fn expectOpaque(rgba: []const u8) !void {
+ if (rgba.len % 4 != 0) return error.BadPixmap;
+ var alpha: usize = 3;
+ while (alpha < rgba.len) : (alpha += 4)
+ try std.testing.expectEqual(@as(u8, 0xff), rgba[alpha]);
}
fn makeOffsetRotatedPdf(gpa: std.mem.Allocator) ![]u8 {
@@ -476,17 +462,78 @@ fn makeOffsetRotatedPdf(gpa: std.mem.Allocator) ![]u8 {
return bytes.toOwnedSlice(gpa);
}
-test "RGB pixmap expansion respects row stride and writes opaque RGBA" {
- const rgb = [_]u8{
- 1, 2, 3, 4, 5, 6, 99, 99,
- 7, 8, 9, 10, 11, 12, 88, 88,
- };
- var rgba: [16]u8 = undefined;
- try expandRgb(&rgba, &rgb, 2, 2, 8);
- try std.testing.expectEqualSlices(u8, &.{
- 1, 2, 3, 255, 4, 5, 6, 255,
- 7, 8, 9, 255, 10, 11, 12, 255,
- }, &rgba);
+test "caller-owned RGBA layout is packed and overflow checked" {
+ const layout = try checkedRasterLayout(.{
+ .width = 2,
+ .height = 3,
+ .stride = 8,
+ .samples_len = 24,
+ });
+ try std.testing.expectEqual(@as(usize, 2), layout.width);
+ try std.testing.expectEqual(@as(usize, 3), layout.height);
+ try std.testing.expectEqual(@as(usize, 8), layout.stride);
+ try std.testing.expectEqual(@as(usize, 24), layout.len);
+ try std.testing.expectError(error.BadPixmap, checkedRasterLayout(.{
+ .width = 2,
+ .height = 3,
+ .stride = 7,
+ .samples_len = 21,
+ }));
+}
+
+test "RGBA allocation and buffer-validation failures leave the document renderable" {
+ var document = try Document.open("docs/design.pdf");
+ defer document.deinit();
+
+ var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{
+ .fail_index = 0,
+ });
+ try std.testing.expectError(
+ error.OutOfMemory,
+ document.render(failing.allocator(), 0),
+ );
+
+ var raw_layout: c.pardes_pdf_raster_layout = std.mem.zeroes(c.pardes_pdf_raster_layout);
+ try std.testing.expectEqual(c.PARDES_PDF_OK, c.pardes_pdf_measure_render(
+ document.handle,
+ 0,
+ default_render_request.dpi,
+ default_render_request.minimum_width,
+ default_render_request.minimum_height,
+ default_render_request.max_dimension,
+ &raw_layout,
+ ));
+ const layout = try checkedRasterLayout(raw_layout);
+ {
+ const scratch = try std.testing.allocator.alloc(u8, layout.len);
+ defer std.testing.allocator.free(scratch);
+ @memset(scratch, 0xa5);
+ try std.testing.expectEqual(c.PARDES_PDF_ERROR, c.pardes_pdf_render_into(
+ document.handle,
+ 0,
+ default_render_request.dpi,
+ default_render_request.minimum_width,
+ default_render_request.minimum_height,
+ default_render_request.max_dimension,
+ null,
+ 0,
+ scratch.ptr,
+ scratch.len - 1,
+ raw_layout.width,
+ raw_layout.height,
+ raw_layout.stride,
+ ));
+ for (scratch) |byte|
+ try std.testing.expectEqual(@as(u8, 0xa5), byte);
+ }
+
+ const after = try document.render(std.testing.allocator, 0);
+ defer std.testing.allocator.free(after.rgba);
+ const repeated = try document.render(std.testing.allocator, 0);
+ defer std.testing.allocator.free(repeated.rgba);
+ try std.testing.expectEqualSlices(u8, after.rgba, repeated.rgba);
+ try expectOpaque(after.rgba);
+ try expectOpaque(repeated.rgba);
}
test "MuPDF search returns normalized oriented quads and word selection text" {
@@ -544,6 +591,9 @@ test "highlighted render changes pixels while plain render stays stable" {
const plain_after = try document.render(std.testing.allocator, 0);
defer std.testing.allocator.free(plain_after.rgba);
+ try expectOpaque(plain_before.rgba);
+ try expectOpaque(marked.rgba);
+ try expectOpaque(plain_after.rgba);
try std.testing.expectEqual(plain_before.width, marked.width);
try std.testing.expectEqual(plain_before.height, marked.height);
try std.testing.expect(!std.mem.eql(u8, plain_before.rgba, marked.rgba));
diff --git a/src/pdf_bridge.c b/src/pdf_bridge.c
index b880cd21..202ef4f7 100644
--- a/src/pdf_bridge.c
+++ b/src/pdf_bridge.c
@@ -3,6 +3,7 @@
#include <mupdf/fitz.h>
#include <math.h>
+#include <limits.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
@@ -310,23 +311,107 @@ pardes_pdf_get_page_size(
return PARDES_PDF_OK;
}
+static void
+pardes_pdf_render_geometry(
+ pardes_pdf_document *document,
+ int page_number,
+ int dpi,
+ int minimum_width,
+ int minimum_height,
+ int max_dimension,
+ fz_matrix *ctm_out,
+ fz_irect *bbox_out)
+{
+ fz_rect transformed;
+ fz_rect bounds;
+ float page_width;
+ float page_height;
+ float longest;
+ float scale;
+
+ pardes_pdf_cache_page(document, page_number, 0);
+ bounds = document->cached_bounds;
+ page_width = bounds.x1 - bounds.x0;
+ page_height = bounds.y1 - bounds.y0;
+ longest = fmaxf(page_width, page_height);
+ if (!(longest > 0.0f))
+ fz_throw(document->ctx, FZ_ERROR_FORMAT, "PDF page has empty bounds");
+ scale = (float)dpi / 72.0f;
+ if (minimum_width > 0)
+ scale = fmaxf(scale, (float)minimum_width / page_width);
+ if (minimum_height > 0)
+ scale = fmaxf(scale, (float)minimum_height / page_height);
+ if (longest * scale > (float)max_dimension)
+ scale = (float)max_dimension / longest;
+
+ /*
+ * Keep this identical to fz_new_pixmap_from_page/display_list: transform
+ * non-zero crop boxes to the origin, transform the bounds, then round the
+ * complete rectangle once. The same CTM also aligns highlight geometry.
+ */
+ *ctm_out = fz_transform_page(bounds, scale * 72.0f, 0.0f);
+ transformed = fz_transform_rect(bounds, *ctm_out);
+ *bbox_out = fz_round_rect(transformed);
+ if (fz_irect_width(*bbox_out) < 1 || fz_irect_height(*bbox_out) < 1)
+ fz_throw(document->ctx, FZ_ERROR_FORMAT, "PDF page rendered empty");
+}
+
int
-pardes_pdf_render(
+pardes_pdf_measure_render(
pardes_pdf_document *document,
int page_number,
int dpi,
int minimum_width,
int minimum_height,
int max_dimension,
- pardes_pdf_pixmap *out)
+ pardes_pdf_raster_layout *out)
{
- return pardes_pdf_render_with_highlights(
- document, page_number, dpi, minimum_width, minimum_height,
- max_dimension, NULL, 0, out);
+ fz_context *ctx;
+ fz_matrix ctm;
+ fz_irect bbox;
+ int width = 0;
+ int height = 0;
+ int stride = 0;
+ size_t samples_len = 0;
+
+ if (out == NULL)
+ return PARDES_PDF_ERROR;
+ memset(out, 0, sizeof(*out));
+ if (document == NULL || page_number < 0 ||
+ page_number >= document->page_count || dpi < 1 ||
+ minimum_width < 0 || minimum_height < 0 || max_dimension < 1)
+ return PARDES_PDF_ERROR;
+
+ ctx = document->ctx;
+ fz_try(ctx)
+ {
+ pardes_pdf_render_geometry(document, page_number, dpi,
+ minimum_width, minimum_height, max_dimension, &ctm, &bbox);
+ width = fz_irect_width(bbox);
+ height = fz_irect_height(bbox);
+ if (width > INT_MAX / 4)
+ fz_throw(ctx, FZ_ERROR_LIMIT, "PDF raster row is too large");
+ stride = width * 4;
+ if ((size_t)height > SIZE_MAX / (size_t)stride)
+ fz_throw(ctx, FZ_ERROR_LIMIT, "PDF raster is too large");
+ samples_len = (size_t)stride * (size_t)height;
+ }
+ fz_catch(ctx)
+ {
+ fz_report_error(ctx);
+ memset(out, 0, sizeof(*out));
+ return PARDES_PDF_ERROR;
+ }
+
+ out->width = width;
+ out->height = height;
+ out->stride = stride;
+ out->samples_len = samples_len;
+ return PARDES_PDF_OK;
}
int
-pardes_pdf_render_with_highlights(
+pardes_pdf_render_into(
pardes_pdf_document *document,
int page_number,
int dpi,
@@ -335,75 +420,69 @@ pardes_pdf_render_with_highlights(
int max_dimension,
const pardes_pdf_highlight *highlights,
size_t highlight_count,
- pardes_pdf_pixmap *out)
+ unsigned char *samples,
+ size_t samples_len,
+ int width,
+ int height,
+ int stride)
{
fz_context *ctx;
fz_pixmap *pixmap = NULL;
fz_device *device = NULL;
fz_path *path = NULL;
fz_matrix ctm;
- fz_rect bounds;
- float page_width;
- float page_height;
- float longest;
- float scale;
+ fz_irect bbox;
size_t i;
- if (document == NULL || out == NULL || page_number < 0 ||
+ if (document == NULL || page_number < 0 ||
page_number >= document->page_count || dpi < 1 ||
minimum_width < 0 || minimum_height < 0 || max_dimension < 1 ||
(highlight_count != 0 && highlights == NULL) ||
- highlight_count > PARDES_PDF_MAX_RESULT_QUADS)
+ highlight_count > PARDES_PDF_MAX_RESULT_QUADS || samples == NULL ||
+ width < 1 || height < 1 || stride < 1 || width > INT_MAX / 4 ||
+ stride != width * 4 ||
+ (size_t)height > SIZE_MAX / (size_t)stride ||
+ samples_len != (size_t)stride * (size_t)height)
return PARDES_PDF_ERROR;
- memset(out, 0, sizeof(*out));
ctx = document->ctx;
fz_var(pixmap);
fz_var(device);
fz_var(path);
fz_try(ctx)
{
- pardes_pdf_cache_page(document, page_number, 0);
- bounds = document->cached_bounds;
- page_width = bounds.x1 - bounds.x0;
- page_height = bounds.y1 - bounds.y0;
- longest = fmaxf(page_width, page_height);
- if (!(longest > 0.0f))
- fz_throw(ctx, FZ_ERROR_FORMAT, "PDF page has empty bounds");
- scale = (float)dpi / 72.0f;
- if (minimum_width > 0)
- scale = fmaxf(scale, (float)minimum_width / page_width);
- if (minimum_height > 0)
- scale = fmaxf(scale, (float)minimum_height / page_height);
- if (longest * scale > (float)max_dimension)
- scale = (float)max_dimension / longest;
- /*
- * Unlike a bare scale, fz_transform_page translates non-zero crop
- * boxes to the pixmap origin and rounds both dimensions coherently.
- * The overlay draw device receives this exact CTM, so normalized page
- * geometry and page pixels stay aligned for offset and rotated pages.
- */
- ctm = fz_transform_page(bounds, scale * 72.0f, 0.0f);
+ pardes_pdf_render_geometry(document, page_number, dpi,
+ minimum_width, minimum_height, max_dimension, &ctm, &bbox);
+ if (fz_irect_width(bbox) != width ||
+ fz_irect_height(bbox) != height)
+ fz_throw(ctx, FZ_ERROR_ARGUMENT,
+ "PDF raster layout changed between measure and render");
+
+ /* External samples are never marked FZ_PIXMAP_FLAG_FREE_SAMPLES. */
+ pixmap = fz_new_pixmap_with_bbox_and_data(
+ ctx, fz_device_rgb(ctx), bbox, NULL, 1, samples);
+ if (fz_pixmap_samples(ctx, pixmap) != samples ||
+ fz_pixmap_width(ctx, pixmap) != width ||
+ fz_pixmap_height(ctx, pixmap) != height ||
+ fz_pixmap_stride(ctx, pixmap) != stride ||
+ fz_pixmap_components(ctx, pixmap) != 4)
+ fz_throw(ctx, FZ_ERROR_FORMAT,
+ "MuPDF wrapped an unexpected RGBA layout");
+ fz_clear_pixmap_with_value(ctx, pixmap, 0xFF);
- /*
- * alpha=0 is intentional. MuPDF clears this RGB pixmap to opaque
- * white before drawing. Asking for alpha=1 instead produces
- * premultiplied transparent samples, while Pardes consumes straight
- * RGBA after the Zig-side RGB expansion.
- */
if (document->cached_display_list != NULL ||
document->display_list_candidate_page_number == page_number) {
pardes_pdf_cache_display_list(document);
- pixmap = fz_new_pixmap_from_display_list(
- ctx, document->cached_display_list, ctm,
- fz_device_rgb(ctx), 0);
+ device = fz_new_draw_device(ctx, ctm, pixmap);
+ fz_run_display_list(ctx, document->cached_display_list, device,
+ fz_identity, fz_infinite_rect, NULL);
} else {
- pixmap = fz_new_pixmap_from_page(
- ctx, document->cached_page, ctm, fz_device_rgb(ctx), 0);
+ device = fz_new_draw_device(ctx, ctm, pixmap);
+ fz_run_page(ctx, document->cached_page, device, fz_identity, NULL);
}
- if (fz_pixmap_width(ctx, pixmap) < 1 ||
- fz_pixmap_height(ctx, pixmap) < 1)
- fz_throw(ctx, FZ_ERROR_FORMAT, "PDF page rendered empty");
+ fz_close_device(ctx, device);
+ fz_drop_device(ctx, device);
+ device = NULL;
if (highlight_count != 0) {
device = fz_new_draw_device(ctx, ctm, pixmap);
@@ -445,33 +524,21 @@ pardes_pdf_render_with_highlights(
{
fz_drop_path(ctx, path);
fz_drop_device(ctx, device);
+ /* Drops only the wrapper: caller-owned samples remain untouched. */
+ fz_drop_pixmap(ctx, pixmap);
}
fz_catch(ctx)
{
fz_report_error(ctx);
- fz_drop_pixmap(ctx, pixmap);
return PARDES_PDF_ERROR;
}
if (document->cached_display_list == NULL)
document->display_list_candidate_page_number = page_number;
- out->handle = pixmap;
- out->samples = fz_pixmap_samples(ctx, pixmap);
- out->width = fz_pixmap_width(ctx, pixmap);
- out->height = fz_pixmap_height(ctx, pixmap);
- out->stride = fz_pixmap_stride(ctx, pixmap);
- out->components = fz_pixmap_components(ctx, pixmap);
return PARDES_PDF_OK;
}
-void
-pardes_pdf_drop_pixmap(pardes_pdf_document *document, void *pixmap)
-{
- if (document != NULL && pixmap != NULL)
- fz_drop_pixmap(document->ctx, (fz_pixmap *)pixmap);
-}
-
int
pardes_pdf_page_text(
pardes_pdf_document *document,
diff --git a/src/pdf_bridge.h b/src/pdf_bridge.h
index cc706c33..17323a29 100644
--- a/src/pdf_bridge.h
+++ b/src/pdf_bridge.h
@@ -14,14 +14,12 @@ extern "C" {
*/
typedef struct pardes_pdf_document pardes_pdf_document;
-typedef struct pardes_pdf_pixmap {
- void *handle;
- const unsigned char *samples;
+typedef struct pardes_pdf_raster_layout {
int width;
int height;
int stride;
- int components;
-} pardes_pdf_pixmap;
+ size_t samples_len;
+} pardes_pdf_raster_layout;
typedef struct pardes_pdf_page_size {
float width;
@@ -120,18 +118,25 @@ int pardes_pdf_get_page_size(
/*
* Start at dpi, raise the uniform scale until both optional minimum pixel
* dimensions are met, then clamp the page's longest side to max_dimension.
+ * Measuring may populate the page cache, but never advances adaptive display
+ * list promotion; only a successful render_into does that.
*/
-int pardes_pdf_render(
+int pardes_pdf_measure_render(
pardes_pdf_document *document,
int page,
int dpi,
int minimum_width,
int minimum_height,
int max_dimension,
- pardes_pdf_pixmap *out
+ pardes_pdf_raster_layout *out
);
-int pardes_pdf_render_with_highlights(
+/*
+ * Rasterize directly into an exactly-sized caller-owned packed RGBA buffer.
+ * MuPDF wraps but never owns or frees samples. Every successful pixel is
+ * opaque, including the caller-supplied highlights composited onto the page.
+ */
+int pardes_pdf_render_into(
pardes_pdf_document *document,
int page,
int dpi,
@@ -140,9 +145,12 @@ int pardes_pdf_render_with_highlights(
int max_dimension,
const pardes_pdf_highlight *highlights,
size_t highlight_count,
- pardes_pdf_pixmap *out
+ unsigned char *samples,
+ size_t samples_len,
+ int width,
+ int height,
+ int stride
);
-void pardes_pdf_drop_pixmap(pardes_pdf_document *document, void *pixmap);
int pardes_pdf_page_text(
pardes_pdf_document *document,
diff --git a/test/pdf_bench.zig b/test/pdf_bench.zig
index a1713d31..1325def5 100644
--- a/test/pdf_bench.zig
+++ b/test/pdf_bench.zig
@@ -6,10 +6,10 @@
//! zig build pdf-bench -- --path other.pdf --page 2 --pages 6
//!
//! The benchmark links the real src/pdf.zig wrapper and C exception bridge in
-//! ReleaseFast. Timed regions include MuPDF rasterization, RGBA allocation and
-//! RGB-to-RGBA expansion; checksum/free/reporting stay outside. "cold" means a
-//! fresh Document and MuPDF page cache (the OS filesystem cache is deliberately
-//! uncontrolled), while warm scenarios retain one Document across rounds.
+//! ReleaseFast. Timed regions include MuPDF rasterization directly into the
+//! allocator-owned RGBA output; checksum/free/reporting stay outside. "cold"
+//! means a fresh Document and MuPDF page cache (the OS filesystem cache is
+//! deliberately uncontrolled), while warm scenarios retain one Document.
const std = @import("std");
const pdf = @import("mupdf");