summaryrefslogtreecommitdiff
path: root/src/pdf_bridge.c
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-02 23:29:50 -0300
committerGabriel Schneider <[email protected]>2026-08-10 09:17:07 -0300
commit90eed6a89c66d6243ea43e799113409244308563 (patch)
tree70b2842ae1a98ba829aae23c6bee47eb640e6aa1 /src/pdf_bridge.c
parentf7f18f483483140f1ad0ff0edbe0fb550086b80f (diff)
downloadpardes-90eed6a89c66d6243ea43e799113409244308563.tar.gz
pardes-90eed6a89c66d6243ea43e799113409244308563.zip
render MuPDF pages directly into owned RGBA
Diffstat (limited to 'src/pdf_bridge.c')
-rw-r--r--src/pdf_bridge.c195
1 files changed, 131 insertions, 64 deletions
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,