diff options
| author | Gabriel Schneider <[email protected]> | 2026-08-02 23:29:50 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-08-10 09:17:07 -0300 |
| commit | 90eed6a89c66d6243ea43e799113409244308563 (patch) | |
| tree | 70b2842ae1a98ba829aae23c6bee47eb640e6aa1 /src/pdf_bridge.c | |
| parent | f7f18f483483140f1ad0ff0edbe0fb550086b80f (diff) | |
| download | pardes-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.c | 195 |
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, |
