diff --git a/ChangeLog b/ChangeLog index ae8edb861e..5082780085 100644 --- a/ChangeLog +++ b/ChangeLog @@ -6,6 +6,7 @@ master - add vips__worker_exit(): enables fast threadpool shutdown - larger mmap windows on 64-bit machines improve random access mode for many file formats +- pdfload: control region to be rendered via `page_box` [lovell] 7/7/25 8.17.1 diff --git a/cplusplus/include/vips/VImage8.h b/cplusplus/include/vips/VImage8.h index 58da46420d..745070a433 100644 --- a/cplusplus/include/vips/VImage8.h +++ b/cplusplus/include/vips/VImage8.h @@ -4860,6 +4860,7 @@ class VImage : public VObject { * - **scale** -- Factor to scale by, double. * - **background** -- Background colour, std::vector. * - **password** -- Password to decrypt with, const char *. + * - **page_box** -- The region of the page to render, VipsForeignPdfPageBox. * - **memory** -- Force open via memory, bool. * - **access** -- Required access pattern for this file, VipsAccess. * - **fail_on** -- Error level to fail on, VipsFailOn. @@ -4881,6 +4882,7 @@ class VImage : public VObject { * - **scale** -- Factor to scale by, double. * - **background** -- Background colour, std::vector. * - **password** -- Password to decrypt with, const char *. + * - **page_box** -- The region of the page to render, VipsForeignPdfPageBox. * - **memory** -- Force open via memory, bool. * - **access** -- Required access pattern for this file, VipsAccess. * - **fail_on** -- Error level to fail on, VipsFailOn. @@ -4902,6 +4904,7 @@ class VImage : public VObject { * - **scale** -- Factor to scale by, double. * - **background** -- Background colour, std::vector. * - **password** -- Password to decrypt with, const char *. + * - **page_box** -- The region of the page to render, VipsForeignPdfPageBox. * - **memory** -- Force open via memory, bool. * - **access** -- Required access pattern for this file, VipsAccess. * - **fail_on** -- Error level to fail on, VipsFailOn. diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index 17f147903c..60a049f58c 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -2721,6 +2721,9 @@ vips_jxlsave_target(VipsImage *in, VipsTarget *target, ...) * * Use @password to supply a decryption password. * + * When using pdfium, the region of a page to render can be selected with + * @page_box, defaulting to the crop box. + * * The operation fills a number of header fields with metadata, for example * "pdf-author". They may be useful. * @@ -2733,6 +2736,7 @@ vips_jxlsave_target(VipsImage *in, VipsTarget *target, ...) * * @dpi: `gdouble`, render at this DPI * * @scale: `gdouble`, scale render by this factor * * @background: [struct@ArrayDouble], background colour + * * @page_box: [enum@ForeignPdfPageBox], use this page box (pdfium only) * * ::: seealso * [ctor@Image.new_from_file], [ctor@Image.magickload]. @@ -2771,6 +2775,7 @@ vips_pdfload(const char *filename, VipsImage **out, ...) * * @dpi: `gdouble`, render at this DPI * * @scale: `gdouble`, scale render by this factor * * @background: [struct@ArrayDouble], background colour + * * @page_box: [enum@ForeignPdfPageBox], use this page box (pdfium only) * * ::: seealso * [ctor@Image.pdfload]. @@ -2811,6 +2816,7 @@ vips_pdfload_buffer(void *buf, size_t len, VipsImage **out, ...) * * @dpi: `gdouble`, render at this DPI * * @scale: `gdouble`, scale render by this factor * * @background: [struct@ArrayDouble], background colour + * * @page_box: [enum@ForeignPdfPageBox], use this page box (pdfium only) * * ::: seealso * [ctor@Image.pdfload] diff --git a/libvips/foreign/pdfiumload.c b/libvips/foreign/pdfiumload.c index e3637d147f..49adc4db33 100644 --- a/libvips/foreign/pdfiumload.c +++ b/libvips/foreign/pdfiumload.c @@ -110,6 +110,7 @@ EOF #include #include #include +#include #define TILE_SIZE (4000) @@ -165,6 +166,10 @@ typedef struct _VipsForeignLoadPdf { */ VipsPel *ink; + /* Render this page box. + */ + VipsForeignPdfPageBox page_box; + } VipsForeignLoadPdf; typedef VipsForeignLoadClass VipsForeignLoadPdfClass; @@ -448,6 +453,49 @@ vips_foreign_load_pdf_set_image(VipsForeignLoadPdf *pdf, VipsImage *out) return 0; } +static void +vips_foreign_load_pdf_apply_page_box(FPDF_PAGE page, VipsForeignPdfPageBox box) +{ + float left, bottom, right, top; + + /* Avoid locking when no change in region to render. + */ + if (box == VIPS_FOREIGN_PDF_PAGE_BOX_CROP) + return; + + g_mutex_lock(&vips_pdfium_mutex); + switch (box) { + case VIPS_FOREIGN_PDF_PAGE_BOX_MEDIA: + if (FPDFPage_GetMediaBox(page, &left, &bottom, &right, &top)) + FPDFPage_SetCropBox(page, left, bottom, right, top); + else + g_warning("missing media box, using default crop box"); + break; + case VIPS_FOREIGN_PDF_PAGE_BOX_TRIM: + if (FPDFPage_GetTrimBox(page, &left, &bottom, &right, &top)) + FPDFPage_SetCropBox(page, left, bottom, right, top); + else + g_warning("missing trim box, using default crop box"); + break; + case VIPS_FOREIGN_PDF_PAGE_BOX_BLEED: + if (FPDFPage_GetBleedBox(page, &left, &bottom, &right, &top)) + FPDFPage_SetCropBox(page, left, bottom, right, top); + else + g_warning("missing bleed box, using default crop box"); + break; + case VIPS_FOREIGN_PDF_PAGE_BOX_ART: + if (FPDFPage_GetArtBox(page, &left, &bottom, &right, &top)) + FPDFPage_SetCropBox(page, left, bottom, right, top); + else + g_warning("missing art box, using default crop box"); + break; + case VIPS_FOREIGN_PDF_PAGE_BOX_CROP: + default: + break; + } + g_mutex_unlock(&vips_pdfium_mutex); +} + static int vips_foreign_load_pdf_header(VipsForeignLoad *load) { @@ -492,6 +540,12 @@ vips_foreign_load_pdf_header(VipsForeignLoad *load) return -1; pdf->pages[i].left = 0; pdf->pages[i].top = top; + + /* Attempt to apply selected page box using the page coordinate + * system (bottom left) before calculating render dimensions + * using the client coordinate system (top left). */ + vips_foreign_load_pdf_apply_page_box(pdf->page, pdf->page_box); + /* We do round to nearest, in the same way that vips_resize() * does round to nearest. Without this, things like * shrink-on-load will break. @@ -736,6 +790,14 @@ vips_foreign_load_pdf_class_init(VipsForeignLoadPdfClass *class) VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET(VipsForeignLoadPdf, password), NULL); + + VIPS_ARG_ENUM(class, "page_box", 26, + _("Page box"), + _("The region of the page to render"), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET(VipsForeignLoadPdf, page_box), + VIPS_TYPE_FOREIGN_PDF_PAGE_BOX, + VIPS_FOREIGN_PDF_PAGE_BOX_CROP); } static void @@ -746,6 +808,7 @@ vips_foreign_load_pdf_init(VipsForeignLoadPdf *pdf) pdf->n = 1; pdf->current_page = -1; pdf->background = vips_array_double_newv(1, 255.0); + pdf->page_box = VIPS_FOREIGN_PDF_PAGE_BOX_CROP; } typedef struct _VipsForeignLoadPdfFile { @@ -804,7 +867,7 @@ vips_foreign_load_pdf_file_class_init( gobject_class->get_property = vips_object_get_property; object_class->nickname = "pdfload"; - object_class->description = _("load PDF from file"); + object_class->description = _("load PDF from file (pdfium)"); object_class->build = vips_foreign_load_pdf_file_build; foreign_class->suffs = vips__pdf_suffs; @@ -867,7 +930,7 @@ vips_foreign_load_pdf_buffer_class_init( gobject_class->get_property = vips_object_get_property; object_class->nickname = "pdfload_buffer"; - object_class->description = _("load PDF from buffer"); + object_class->description = _("load PDF from buffer (pdfium)"); object_class->build = vips_foreign_load_pdf_buffer_build; load_class->is_a_buffer = vips__pdf_is_a_buffer; @@ -925,7 +988,7 @@ vips_foreign_load_pdf_source_class_init( gobject_class->get_property = vips_object_get_property; object_class->nickname = "pdfload_source"; - object_class->description = _("load PDF from source"); + object_class->description = _("load PDF from source (pdfium)"); object_class->build = vips_foreign_load_pdf_source_build; operation_class->flags |= VIPS_OPERATION_NOCACHE; diff --git a/libvips/foreign/popplerload.c b/libvips/foreign/popplerload.c index 160b3a2b58..64e0089b91 100644 --- a/libvips/foreign/popplerload.c +++ b/libvips/foreign/popplerload.c @@ -158,6 +158,10 @@ typedef struct _VipsForeignLoadPdf { */ VipsPel *ink; + /* Render this page box, currently only crop is supported. + */ + VipsForeignPdfPageBox page_box; + } VipsForeignLoadPdf; typedef struct _VipsForeignLoadPdfClass { @@ -337,6 +341,9 @@ vips_foreign_load_pdf_header(VipsForeignLoad *load) if (!(pdf->pages = VIPS_ARRAY(pdf, pdf->n, VipsRect))) return -1; + if (pdf->page_box != VIPS_FOREIGN_PDF_PAGE_BOX_CROP) + g_warning("only crop page box is supported"); + top = 0; pdf->image.left = 0; pdf->image.top = 0; @@ -580,6 +587,14 @@ vips_foreign_load_pdf_class_init(VipsForeignLoadPdfClass *class) VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET(VipsForeignLoadPdf, password), NULL); + + VIPS_ARG_ENUM(class, "page_box", 26, + _("Page box"), + _("The region of the page to render, only crop is supported"), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET(VipsForeignLoadPdf, page_box), + VIPS_TYPE_FOREIGN_PDF_PAGE_BOX, + VIPS_FOREIGN_PDF_PAGE_BOX_CROP); } static void @@ -590,6 +605,7 @@ vips_foreign_load_pdf_init(VipsForeignLoadPdf *pdf) pdf->n = 1; pdf->current_page = -1; pdf->background = vips_array_double_newv(1, 255.0); + pdf->page_box = VIPS_FOREIGN_PDF_PAGE_BOX_CROP; } typedef struct _VipsForeignLoadPdfFile { @@ -675,7 +691,7 @@ vips_foreign_load_pdf_file_class_init( gobject_class->get_property = vips_object_get_property; object_class->nickname = "pdfload"; - object_class->description = _("load PDF from file"); + object_class->description = _("load PDF from file (poppler)"); object_class->build = vips_foreign_load_pdf_file_build; foreign_class->suffs = vips__pdf_suffs; @@ -738,7 +754,7 @@ vips_foreign_load_pdf_buffer_class_init( gobject_class->get_property = vips_object_get_property; object_class->nickname = "pdfload_buffer"; - object_class->description = _("load PDF from buffer"); + object_class->description = _("load PDF from buffer (poppler)"); object_class->build = vips_foreign_load_pdf_buffer_build; load_class->is_a_buffer = vips__pdf_is_a_buffer; @@ -796,7 +812,7 @@ vips_foreign_load_pdf_source_class_init( gobject_class->get_property = vips_object_get_property; object_class->nickname = "pdfload_source"; - object_class->description = _("load PDF from source"); + object_class->description = _("load PDF from source (poppler)"); object_class->build = vips_foreign_load_pdf_source_build; operation_class->flags |= VIPS_OPERATION_NOCACHE; diff --git a/libvips/include/vips/foreign.h b/libvips/include/vips/foreign.h index a7635e3ad4..1780e80e8e 100644 --- a/libvips/include/vips/foreign.h +++ b/libvips/include/vips/foreign.h @@ -835,6 +835,29 @@ VIPS_API int vips_radsave_target(VipsImage *in, VipsTarget *target, ...) G_GNUC_NULL_TERMINATED; +/** + * VipsForeignPdfPageBox: + * @VIPS_FOREIGN_PDF_PAGE_BOX_MEDIA + * @VIPS_FOREIGN_PDF_PAGE_BOX_CROP + * @VIPS_FOREIGN_PDF_PAGE_BOX_TRIM + * @VIPS_FOREIGN_PDF_PAGE_BOX_BLEED + * @VIPS_FOREIGN_PDF_PAGE_BOX_ART + * + * Each page of a PDF document can contain multiple page boxes, + * also known as boundary boxes or print marks. + * + * Each page box defines a region of the complete page that + * should be rendered. The default region is the crop box. + */ +typedef enum { + VIPS_FOREIGN_PDF_PAGE_BOX_MEDIA, + VIPS_FOREIGN_PDF_PAGE_BOX_CROP, + VIPS_FOREIGN_PDF_PAGE_BOX_TRIM, + VIPS_FOREIGN_PDF_PAGE_BOX_BLEED, + VIPS_FOREIGN_PDF_PAGE_BOX_ART, + VIPS_FOREIGN_PDF_PAGE_BOX_LAST /*< skip >*/ +} VipsForeignPdfPageBox; + VIPS_API int vips_pdfload(const char *filename, VipsImage **out, ...) G_GNUC_NULL_TERMINATED; diff --git a/test/test-suite/helpers/helpers.py b/test/test-suite/helpers/helpers.py index 82e318d4a2..8f98c12be6 100644 --- a/test/test-suite/helpers/helpers.py +++ b/test/test-suite/helpers/helpers.py @@ -33,6 +33,7 @@ FITS_FILE = os.path.join(IMAGES, "WFPC2u5780205r_c0fx.fits") OPENSLIDE_FILE = os.path.join(IMAGES, "CMU-1-Small-Region.svs") PDF_FILE = os.path.join(IMAGES, "ISO_12233-reschart.pdf") +PDF_PAGE_BOX_FILE = os.path.join(IMAGES, "page-box.pdf") CMYK_PDF_FILE = os.path.join(IMAGES, "cmyktest.pdf") SVG_FILE = os.path.join(IMAGES, "logo.svg") SVGZ_FILE = os.path.join(IMAGES, "logo.svgz") diff --git a/test/test-suite/images/page-box.pdf b/test/test-suite/images/page-box.pdf new file mode 100644 index 0000000000..0f2e93282a Binary files /dev/null and b/test/test-suite/images/page-box.pdf differ diff --git a/test/test-suite/test_foreign.py b/test/test-suite/test_foreign.py index 24450422b8..1cef2b44b5 100644 --- a/test/test-suite/test_foreign.py +++ b/test/test-suite/test_foreign.py @@ -1100,6 +1100,46 @@ def pdf_valid(im): assert abs(im.width * 2 - x.width) < 2 assert abs(im.height * 2 - x.height) < 2 + im = pyvips.Image.new_from_file(PDF_PAGE_BOX_FILE) + assert im.width == 709 + assert im.height == 955 + assert im.get("pdf-creator") == "Adobe InDesign 20.4 (Windows)" + assert im.get("pdf-producer") == "Adobe PDF Library 17.0" + + pdfloadOp = pyvips.Operation.new_from_name("pdfload").get_description() + + if "poppler" in pdfloadOp: + # only crop is implemented, ignore requested page box + im = pyvips.Image.new_from_file(PDF_FILE, page_box="art") + assert im.width == 1134 + assert im.height == 680 + im = pyvips.Image.new_from_file(PDF_PAGE_BOX_FILE, page_box="art") + assert im.width == 709 + assert im.height == 955 + + if "pdfium" in pdfloadOp: + im = pyvips.Image.new_from_file(PDF_FILE, page_box="art") + assert im.width == 1121 + assert im.height == 680 + im = pyvips.Image.new_from_file(PDF_FILE, page_box="trim") # missing, will fallback to crop + assert im.width == 1134 + assert im.height == 680 + im = pyvips.Image.new_from_file(PDF_PAGE_BOX_FILE, page_box="media") + assert im.width == 822 + assert im.height == 1069 + im = pyvips.Image.new_from_file(PDF_PAGE_BOX_FILE, page_box="crop") + assert im.width == 709 + assert im.height == 955 + im = pyvips.Image.new_from_file(PDF_PAGE_BOX_FILE, page_box="bleed") + assert im.width == 652 + assert im.height == 899 + im = pyvips.Image.new_from_file(PDF_PAGE_BOX_FILE, page_box="trim") + assert im.width == 595 + assert im.height == 842 + im = pyvips.Image.new_from_file(PDF_PAGE_BOX_FILE, page_box="art") + assert im.width == 539 + assert im.height == 785 + @skip_if_no("gifload") def test_gifload(self): def gif_valid(im): pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy