From f7f18f483483140f1ad0ff0edbe0fb550086b80f Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Sun, 2 Aug 2026 23:18:45 -0300 Subject: cache MuPDF display lists for repeated renders --- src/pdf_bridge.c | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/pdf_bridge.c b/src/pdf_bridge.c index 0fcf74c4..b880cd21 100644 --- a/src/pdf_bridge.c +++ b/src/pdf_bridge.c @@ -14,21 +14,28 @@ struct pardes_pdf_document { fz_document *doc; int page_count; - /* One UI thread owns a document. Page switches invalidate this pair. */ + /* One UI thread owns a document. Page switches invalidate this cache. */ int cached_page_number; fz_page *cached_page; fz_stext_page *cached_text; + fz_display_list *cached_display_list; fz_rect cached_bounds; + + /* Promote only a page which is rendered twice without changing pages. */ + int display_list_candidate_page_number; }; static void pardes_pdf_drop_cached_page(pardes_pdf_document *document) { + fz_drop_display_list(document->ctx, document->cached_display_list); fz_drop_stext_page(document->ctx, document->cached_text); fz_drop_page(document->ctx, document->cached_page); + document->cached_display_list = NULL; document->cached_text = NULL; document->cached_page = NULL; document->cached_page_number = -1; + document->display_list_candidate_page_number = -1; } static int @@ -88,6 +95,45 @@ pardes_pdf_cache_page( document->cached_bounds = bounds; } +/* + * Build only after one successful direct render of the still-cached page. + * This keeps cold renders and sequential document passes on their original + * path instead of recording a list which the next page would immediately + * evict. The temporary list is committed only after its device has closed; + * any partially recorded list is dropped before the exception propagates. + */ +static void +pardes_pdf_cache_display_list(pardes_pdf_document *document) +{ + fz_context *ctx = document->ctx; + fz_display_list *list = NULL; + fz_device *device = NULL; + + if (document->cached_display_list != NULL) + return; + + fz_var(list); + fz_var(device); + fz_try(ctx) + { + list = fz_new_display_list(ctx, document->cached_bounds); + device = fz_new_list_device(ctx, list); + fz_run_page(ctx, document->cached_page, device, fz_identity, NULL); + fz_close_device(ctx, device); + } + fz_always(ctx) + { + fz_drop_device(ctx, device); + } + fz_catch(ctx) + { + fz_drop_display_list(ctx, list); + fz_rethrow(ctx); + } + + document->cached_display_list = list; +} + static float pardes_pdf_clamp_unit(float value) { @@ -187,6 +233,7 @@ pardes_pdf_open(const char *path, int *page_count) if (state == NULL) return NULL; state->cached_page_number = -1; + state->display_list_candidate_page_number = -1; ctx = fz_new_context(NULL, NULL, PARDES_PDF_STORE_LIMIT_BYTES); if (ctx == NULL) { @@ -344,8 +391,16 @@ pardes_pdf_render_with_highlights( * premultiplied transparent samples, while Pardes consumes straight * RGBA after the Zig-side RGB expansion. */ - pixmap = fz_new_pixmap_from_page( - ctx, document->cached_page, ctm, fz_device_rgb(ctx), 0); + 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); + } else { + pixmap = fz_new_pixmap_from_page( + ctx, document->cached_page, ctm, fz_device_rgb(ctx), 0); + } if (fz_pixmap_width(ctx, pixmap) < 1 || fz_pixmap_height(ctx, pixmap) < 1) fz_throw(ctx, FZ_ERROR_FORMAT, "PDF page rendered empty"); @@ -398,6 +453,9 @@ pardes_pdf_render_with_highlights( 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); -- cgit v1.3