Skip to content

Commit e5e5c20

Browse files
authored
pdfload: control region to be rendered via page_box (pdfium only) (#4605)
1 parent cc5e049 commit e5e5c20

File tree

9 files changed

+159
-6
lines changed

9 files changed

+159
-6
lines changed

ChangeLog

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ master
66
- add vips__worker_exit(): enables fast threadpool shutdown
77
- larger mmap windows on 64-bit machines improve random access mode for many
88
file formats
9+
- pdfload: control region to be rendered via `page_box` [lovell]
910

1011
7/7/25 8.17.1
1112

cplusplus/include/vips/VImage8.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4860,6 +4860,7 @@ class VImage : public VObject {
48604860
* - **scale** -- Factor to scale by, double.
48614861
* - **background** -- Background colour, std::vector<double>.
48624862
* - **password** -- Password to decrypt with, const char *.
4863+
* - **page_box** -- The region of the page to render, VipsForeignPdfPageBox.
48634864
* - **memory** -- Force open via memory, bool.
48644865
* - **access** -- Required access pattern for this file, VipsAccess.
48654866
* - **fail_on** -- Error level to fail on, VipsFailOn.
@@ -4881,6 +4882,7 @@ class VImage : public VObject {
48814882
* - **scale** -- Factor to scale by, double.
48824883
* - **background** -- Background colour, std::vector<double>.
48834884
* - **password** -- Password to decrypt with, const char *.
4885+
* - **page_box** -- The region of the page to render, VipsForeignPdfPageBox.
48844886
* - **memory** -- Force open via memory, bool.
48854887
* - **access** -- Required access pattern for this file, VipsAccess.
48864888
* - **fail_on** -- Error level to fail on, VipsFailOn.
@@ -4902,6 +4904,7 @@ class VImage : public VObject {
49024904
* - **scale** -- Factor to scale by, double.
49034905
* - **background** -- Background colour, std::vector<double>.
49044906
* - **password** -- Password to decrypt with, const char *.
4907+
* - **page_box** -- The region of the page to render, VipsForeignPdfPageBox.
49054908
* - **memory** -- Force open via memory, bool.
49064909
* - **access** -- Required access pattern for this file, VipsAccess.
49074910
* - **fail_on** -- Error level to fail on, VipsFailOn.

libvips/foreign/foreign.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2721,6 +2721,9 @@ vips_jxlsave_target(VipsImage *in, VipsTarget *target, ...)
27212721
*
27222722
* Use @password to supply a decryption password.
27232723
*
2724+
* When using pdfium, the region of a page to render can be selected with
2725+
* @page_box, defaulting to the crop box.
2726+
*
27242727
* The operation fills a number of header fields with metadata, for example
27252728
* "pdf-author". They may be useful.
27262729
*
@@ -2733,6 +2736,7 @@ vips_jxlsave_target(VipsImage *in, VipsTarget *target, ...)
27332736
* * @dpi: `gdouble`, render at this DPI
27342737
* * @scale: `gdouble`, scale render by this factor
27352738
* * @background: [struct@ArrayDouble], background colour
2739+
* * @page_box: [enum@ForeignPdfPageBox], use this page box (pdfium only)
27362740
*
27372741
* ::: seealso
27382742
* [ctor@Image.new_from_file], [ctor@Image.magickload].
@@ -2771,6 +2775,7 @@ vips_pdfload(const char *filename, VipsImage **out, ...)
27712775
* * @dpi: `gdouble`, render at this DPI
27722776
* * @scale: `gdouble`, scale render by this factor
27732777
* * @background: [struct@ArrayDouble], background colour
2778+
* * @page_box: [enum@ForeignPdfPageBox], use this page box (pdfium only)
27742779
*
27752780
* ::: seealso
27762781
* [ctor@Image.pdfload].
@@ -2811,6 +2816,7 @@ vips_pdfload_buffer(void *buf, size_t len, VipsImage **out, ...)
28112816
* * @dpi: `gdouble`, render at this DPI
28122817
* * @scale: `gdouble`, scale render by this factor
28132818
* * @background: [struct@ArrayDouble], background colour
2819+
* * @page_box: [enum@ForeignPdfPageBox], use this page box (pdfium only)
28142820
*
28152821
* ::: seealso
28162822
* [ctor@Image.pdfload]

libvips/foreign/pdfiumload.c

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ EOF
110110
#include <fpdf_doc.h>
111111
#include <fpdf_edit.h>
112112
#include <fpdf_formfill.h>
113+
#include <fpdf_transformpage.h>
113114

114115
#define TILE_SIZE (4000)
115116

@@ -165,6 +166,10 @@ typedef struct _VipsForeignLoadPdf {
165166
*/
166167
VipsPel *ink;
167168

169+
/* Render this page box.
170+
*/
171+
VipsForeignPdfPageBox page_box;
172+
168173
} VipsForeignLoadPdf;
169174

170175
typedef VipsForeignLoadClass VipsForeignLoadPdfClass;
@@ -448,6 +453,49 @@ vips_foreign_load_pdf_set_image(VipsForeignLoadPdf *pdf, VipsImage *out)
448453
return 0;
449454
}
450455

456+
static void
457+
vips_foreign_load_pdf_apply_page_box(FPDF_PAGE page, VipsForeignPdfPageBox box)
458+
{
459+
float left, bottom, right, top;
460+
461+
/* Avoid locking when no change in region to render.
462+
*/
463+
if (box == VIPS_FOREIGN_PDF_PAGE_BOX_CROP)
464+
return;
465+
466+
g_mutex_lock(&vips_pdfium_mutex);
467+
switch (box) {
468+
case VIPS_FOREIGN_PDF_PAGE_BOX_MEDIA:
469+
if (FPDFPage_GetMediaBox(page, &left, &bottom, &right, &top))
470+
FPDFPage_SetCropBox(page, left, bottom, right, top);
471+
else
472+
g_warning("missing media box, using default crop box");
473+
break;
474+
case VIPS_FOREIGN_PDF_PAGE_BOX_TRIM:
475+
if (FPDFPage_GetTrimBox(page, &left, &bottom, &right, &top))
476+
FPDFPage_SetCropBox(page, left, bottom, right, top);
477+
else
478+
g_warning("missing trim box, using default crop box");
479+
break;
480+
case VIPS_FOREIGN_PDF_PAGE_BOX_BLEED:
481+
if (FPDFPage_GetBleedBox(page, &left, &bottom, &right, &top))
482+
FPDFPage_SetCropBox(page, left, bottom, right, top);
483+
else
484+
g_warning("missing bleed box, using default crop box");
485+
break;
486+
case VIPS_FOREIGN_PDF_PAGE_BOX_ART:
487+
if (FPDFPage_GetArtBox(page, &left, &bottom, &right, &top))
488+
FPDFPage_SetCropBox(page, left, bottom, right, top);
489+
else
490+
g_warning("missing art box, using default crop box");
491+
break;
492+
case VIPS_FOREIGN_PDF_PAGE_BOX_CROP:
493+
default:
494+
break;
495+
}
496+
g_mutex_unlock(&vips_pdfium_mutex);
497+
}
498+
451499
static int
452500
vips_foreign_load_pdf_header(VipsForeignLoad *load)
453501
{
@@ -492,6 +540,12 @@ vips_foreign_load_pdf_header(VipsForeignLoad *load)
492540
return -1;
493541
pdf->pages[i].left = 0;
494542
pdf->pages[i].top = top;
543+
544+
/* Attempt to apply selected page box using the page coordinate
545+
* system (bottom left) before calculating render dimensions
546+
* using the client coordinate system (top left). */
547+
vips_foreign_load_pdf_apply_page_box(pdf->page, pdf->page_box);
548+
495549
/* We do round to nearest, in the same way that vips_resize()
496550
* does round to nearest. Without this, things like
497551
* shrink-on-load will break.
@@ -736,6 +790,14 @@ vips_foreign_load_pdf_class_init(VipsForeignLoadPdfClass *class)
736790
VIPS_ARGUMENT_OPTIONAL_INPUT,
737791
G_STRUCT_OFFSET(VipsForeignLoadPdf, password),
738792
NULL);
793+
794+
VIPS_ARG_ENUM(class, "page_box", 26,
795+
_("Page box"),
796+
_("The region of the page to render"),
797+
VIPS_ARGUMENT_OPTIONAL_INPUT,
798+
G_STRUCT_OFFSET(VipsForeignLoadPdf, page_box),
799+
VIPS_TYPE_FOREIGN_PDF_PAGE_BOX,
800+
VIPS_FOREIGN_PDF_PAGE_BOX_CROP);
739801
}
740802

741803
static void
@@ -746,6 +808,7 @@ vips_foreign_load_pdf_init(VipsForeignLoadPdf *pdf)
746808
pdf->n = 1;
747809
pdf->current_page = -1;
748810
pdf->background = vips_array_double_newv(1, 255.0);
811+
pdf->page_box = VIPS_FOREIGN_PDF_PAGE_BOX_CROP;
749812
}
750813

751814
typedef struct _VipsForeignLoadPdfFile {
@@ -804,7 +867,7 @@ vips_foreign_load_pdf_file_class_init(
804867
gobject_class->get_property = vips_object_get_property;
805868

806869
object_class->nickname = "pdfload";
807-
object_class->description = _("load PDF from file");
870+
object_class->description = _("load PDF from file (pdfium)");
808871
object_class->build = vips_foreign_load_pdf_file_build;
809872

810873
foreign_class->suffs = vips__pdf_suffs;
@@ -867,7 +930,7 @@ vips_foreign_load_pdf_buffer_class_init(
867930
gobject_class->get_property = vips_object_get_property;
868931

869932
object_class->nickname = "pdfload_buffer";
870-
object_class->description = _("load PDF from buffer");
933+
object_class->description = _("load PDF from buffer (pdfium)");
871934
object_class->build = vips_foreign_load_pdf_buffer_build;
872935

873936
load_class->is_a_buffer = vips__pdf_is_a_buffer;
@@ -925,7 +988,7 @@ vips_foreign_load_pdf_source_class_init(
925988
gobject_class->get_property = vips_object_get_property;
926989

927990
object_class->nickname = "pdfload_source";
928-
object_class->description = _("load PDF from source");
991+
object_class->description = _("load PDF from source (pdfium)");
929992
object_class->build = vips_foreign_load_pdf_source_build;
930993

931994
operation_class->flags |= VIPS_OPERATION_NOCACHE;

libvips/foreign/popplerload.c

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@ typedef struct _VipsForeignLoadPdf {
158158
*/
159159
VipsPel *ink;
160160

161+
/* Render this page box, currently only crop is supported.
162+
*/
163+
VipsForeignPdfPageBox page_box;
164+
161165
} VipsForeignLoadPdf;
162166

163167
typedef struct _VipsForeignLoadPdfClass {
@@ -337,6 +341,9 @@ vips_foreign_load_pdf_header(VipsForeignLoad *load)
337341
if (!(pdf->pages = VIPS_ARRAY(pdf, pdf->n, VipsRect)))
338342
return -1;
339343

344+
if (pdf->page_box != VIPS_FOREIGN_PDF_PAGE_BOX_CROP)
345+
g_warning("only crop page box is supported");
346+
340347
top = 0;
341348
pdf->image.left = 0;
342349
pdf->image.top = 0;
@@ -580,6 +587,14 @@ vips_foreign_load_pdf_class_init(VipsForeignLoadPdfClass *class)
580587
VIPS_ARGUMENT_OPTIONAL_INPUT,
581588
G_STRUCT_OFFSET(VipsForeignLoadPdf, password),
582589
NULL);
590+
591+
VIPS_ARG_ENUM(class, "page_box", 26,
592+
_("Page box"),
593+
_("The region of the page to render, only crop is supported"),
594+
VIPS_ARGUMENT_OPTIONAL_INPUT,
595+
G_STRUCT_OFFSET(VipsForeignLoadPdf, page_box),
596+
VIPS_TYPE_FOREIGN_PDF_PAGE_BOX,
597+
VIPS_FOREIGN_PDF_PAGE_BOX_CROP);
583598
}
584599

585600
static void
@@ -590,6 +605,7 @@ vips_foreign_load_pdf_init(VipsForeignLoadPdf *pdf)
590605
pdf->n = 1;
591606
pdf->current_page = -1;
592607
pdf->background = vips_array_double_newv(1, 255.0);
608+
pdf->page_box = VIPS_FOREIGN_PDF_PAGE_BOX_CROP;
593609
}
594610

595611
typedef struct _VipsForeignLoadPdfFile {
@@ -675,7 +691,7 @@ vips_foreign_load_pdf_file_class_init(
675691
gobject_class->get_property = vips_object_get_property;
676692

677693
object_class->nickname = "pdfload";
678-
object_class->description = _("load PDF from file");
694+
object_class->description = _("load PDF from file (poppler)");
679695
object_class->build = vips_foreign_load_pdf_file_build;
680696

681697
foreign_class->suffs = vips__pdf_suffs;
@@ -738,7 +754,7 @@ vips_foreign_load_pdf_buffer_class_init(
738754
gobject_class->get_property = vips_object_get_property;
739755

740756
object_class->nickname = "pdfload_buffer";
741-
object_class->description = _("load PDF from buffer");
757+
object_class->description = _("load PDF from buffer (poppler)");
742758
object_class->build = vips_foreign_load_pdf_buffer_build;
743759

744760
load_class->is_a_buffer = vips__pdf_is_a_buffer;
@@ -796,7 +812,7 @@ vips_foreign_load_pdf_source_class_init(
796812
gobject_class->get_property = vips_object_get_property;
797813

798814
object_class->nickname = "pdfload_source";
799-
object_class->description = _("load PDF from source");
815+
object_class->description = _("load PDF from source (poppler)");
800816
object_class->build = vips_foreign_load_pdf_source_build;
801817

802818
operation_class->flags |= VIPS_OPERATION_NOCACHE;

libvips/include/vips/foreign.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,29 @@ VIPS_API
835835
int vips_radsave_target(VipsImage *in, VipsTarget *target, ...)
836836
G_GNUC_NULL_TERMINATED;
837837

838+
/**
839+
* VipsForeignPdfPageBox:
840+
* @VIPS_FOREIGN_PDF_PAGE_BOX_MEDIA
841+
* @VIPS_FOREIGN_PDF_PAGE_BOX_CROP
842+
* @VIPS_FOREIGN_PDF_PAGE_BOX_TRIM
843+
* @VIPS_FOREIGN_PDF_PAGE_BOX_BLEED
844+
* @VIPS_FOREIGN_PDF_PAGE_BOX_ART
845+
*
846+
* Each page of a PDF document can contain multiple page boxes,
847+
* also known as boundary boxes or print marks.
848+
*
849+
* Each page box defines a region of the complete page that
850+
* should be rendered. The default region is the crop box.
851+
*/
852+
typedef enum {
853+
VIPS_FOREIGN_PDF_PAGE_BOX_MEDIA,
854+
VIPS_FOREIGN_PDF_PAGE_BOX_CROP,
855+
VIPS_FOREIGN_PDF_PAGE_BOX_TRIM,
856+
VIPS_FOREIGN_PDF_PAGE_BOX_BLEED,
857+
VIPS_FOREIGN_PDF_PAGE_BOX_ART,
858+
VIPS_FOREIGN_PDF_PAGE_BOX_LAST /*< skip >*/
859+
} VipsForeignPdfPageBox;
860+
838861
VIPS_API
839862
int vips_pdfload(const char *filename, VipsImage **out, ...)
840863
G_GNUC_NULL_TERMINATED;

test/test-suite/helpers/helpers.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
FITS_FILE = os.path.join(IMAGES, "WFPC2u5780205r_c0fx.fits")
3434
OPENSLIDE_FILE = os.path.join(IMAGES, "CMU-1-Small-Region.svs")
3535
PDF_FILE = os.path.join(IMAGES, "ISO_12233-reschart.pdf")
36+
PDF_PAGE_BOX_FILE = os.path.join(IMAGES, "page-box.pdf")
3637
CMYK_PDF_FILE = os.path.join(IMAGES, "cmyktest.pdf")
3738
SVG_FILE = os.path.join(IMAGES, "logo.svg")
3839
SVGZ_FILE = os.path.join(IMAGES, "logo.svgz")

test/test-suite/images/page-box.pdf

16.5 KB
Binary file not shown.

test/test-suite/test_foreign.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,6 +1100,46 @@ def pdf_valid(im):
11001100
assert abs(im.width * 2 - x.width) < 2
11011101
assert abs(im.height * 2 - x.height) < 2
11021102

1103+
im = pyvips.Image.new_from_file(PDF_PAGE_BOX_FILE)
1104+
assert im.width == 709
1105+
assert im.height == 955
1106+
assert im.get("pdf-creator") == "Adobe InDesign 20.4 (Windows)"
1107+
assert im.get("pdf-producer") == "Adobe PDF Library 17.0"
1108+
1109+
pdfloadOp = pyvips.Operation.new_from_name("pdfload").get_description()
1110+
1111+
if "poppler" in pdfloadOp:
1112+
# only crop is implemented, ignore requested page box
1113+
im = pyvips.Image.new_from_file(PDF_FILE, page_box="art")
1114+
assert im.width == 1134
1115+
assert im.height == 680
1116+
im = pyvips.Image.new_from_file(PDF_PAGE_BOX_FILE, page_box="art")
1117+
assert im.width == 709
1118+
assert im.height == 955
1119+
1120+
if "pdfium" in pdfloadOp:
1121+
im = pyvips.Image.new_from_file(PDF_FILE, page_box="art")
1122+
assert im.width == 1121
1123+
assert im.height == 680
1124+
im = pyvips.Image.new_from_file(PDF_FILE, page_box="trim") # missing, will fallback to crop
1125+
assert im.width == 1134
1126+
assert im.height == 680
1127+
im = pyvips.Image.new_from_file(PDF_PAGE_BOX_FILE, page_box="media")
1128+
assert im.width == 822
1129+
assert im.height == 1069
1130+
im = pyvips.Image.new_from_file(PDF_PAGE_BOX_FILE, page_box="crop")
1131+
assert im.width == 709
1132+
assert im.height == 955
1133+
im = pyvips.Image.new_from_file(PDF_PAGE_BOX_FILE, page_box="bleed")
1134+
assert im.width == 652
1135+
assert im.height == 899
1136+
im = pyvips.Image.new_from_file(PDF_PAGE_BOX_FILE, page_box="trim")
1137+
assert im.width == 595
1138+
assert im.height == 842
1139+
im = pyvips.Image.new_from_file(PDF_PAGE_BOX_FILE, page_box="art")
1140+
assert im.width == 539
1141+
assert im.height == 785
1142+
11031143
@skip_if_no("gifload")
11041144
def test_gifload(self):
11051145
def gif_valid(im):

0 commit comments

Comments
 (0)
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