Coverage Report

Created: 2026-02-14 06:44

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libvips/libvips/foreign/pdfiumload.c
Line
Count
Source
1
/* load PDF with PDFium
2
 *
3
 * 5/4/18
4
 *  - from pdfload.c
5
 * 8/6/18
6
 *  - add background param
7
 * 16/8/18
8
 *  - shut down the input file as soon as we can [kleisauke]
9
 * 8/8/19
10
 *  - add locks, since pdfium is not threadsafe in any way
11
 * 13/10/20
12
 *  - have a lock just for pdfium [DarthSim]
13
 *  - update for current pdfium
14
 *  - add _source input
15
 * 28/1/22
16
 *  - add password
17
 * 21/5/22
18
 *  - improve transparency handling [DarthSim]
19
 * 21/4/23
20
 *  - add support for forms [kleisauke]
21
 */
22
23
/*
24
25
  This file is part of VIPS.
26
27
  VIPS is free software; you can redistribute it and/or modify
28
  it under the terms of the GNU Lesser General Public License as published by
29
  the Free Software Foundation; either version 2 of the License, or
30
  (at your option) any later version.
31
32
  This program is distributed in the hope that it will be useful,
33
  but WITHOUT ANY WARRANTY; without even the implied warranty of
34
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
35
  GNU Lesser General Public License for more details.
36
37
  You should have received a copy of the GNU Lesser General Public License
38
  along with this program; if not, write to the Free Software
39
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
40
  02110-1301  USA
41
42
 */
43
44
/*
45
46
  These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
47
48
 */
49
50
/* TODO
51
 *
52
 * - what about filename encodings?
53
 * - need to test on Windows
54
 */
55
56
/* How to build against PDFium:
57
 *
58
 * Download the prebuilt binary from:
59
 *
60
 *  https://github.com/bblanchon/pdfium-binaries
61
 *
62
 * Untar to the libvips install prefix, for example:
63
 *
64
 *  cd ~/vips
65
 *  tar xf ~/pdfium-linux.tgz
66
 *
67
 * Create a pdfium.pc like this (update the version number):
68
 *
69
70
VIPSHOME=/home/john/vips
71
cat > $VIPSHOME/lib/pkgconfig/pdfium.pc << EOF
72
   prefix=$VIPSHOME
73
   exec_prefix=\${prefix}
74
   libdir=\${exec_prefix}/lib
75
   includedir=\${prefix}/include
76
   Name: pdfium
77
   Description: pdfium
78
   Version: 4290
79
   Requires:
80
   Libs: -L\${libdir} -lpdfium
81
   Cflags: -I\${includedir}
82
EOF
83
84
 *
85
 */
86
87
/*
88
#define DEBUG
89
 */
90
91
#ifdef HAVE_CONFIG_H
92
#include <config.h>
93
#endif /*HAVE_CONFIG_H*/
94
#include <glib/gi18n-lib.h>
95
96
#include <stdio.h>
97
#include <stdlib.h>
98
#include <string.h>
99
#include <errno.h>
100
101
#include <vips/vips.h>
102
#include <vips/buf.h>
103
#include <vips/internal.h>
104
105
#include "pforeign.h"
106
107
#ifdef HAVE_PDFIUM
108
109
#include <fpdfview.h>
110
#include <fpdf_doc.h>
111
#include <fpdf_edit.h>
112
#include <fpdf_formfill.h>
113
#include <fpdf_transformpage.h>
114
115
129
#define TILE_SIZE (4000)
116
117
typedef struct _VipsForeignLoadPdf {
118
  VipsForeignLoad parent_object;
119
120
  /* Set by subclasses.
121
   */
122
  VipsSource *source;
123
124
  /* Load this page.
125
   */
126
  int page_no;
127
128
  /* Load this many pages.
129
   */
130
  int n;
131
132
  /* Render at this DPI.
133
   */
134
  double dpi;
135
136
  /* Scale by this factor.
137
   */
138
  double scale;
139
140
  /* The total scale factor we render with.
141
   */
142
  double total_scale;
143
144
  /* Background colour.
145
   */
146
  VipsArrayDouble *background;
147
148
  /* Decrypt with this.
149
   */
150
  const char *password;
151
152
  FPDF_FILEACCESS file_access;
153
  FPDF_DOCUMENT doc;
154
  FPDF_PAGE page;
155
  FPDF_FORMFILLINFO form_callbacks;
156
  FPDF_FORMHANDLE form;
157
  int current_page;
158
159
  /* Doc has this many pages.
160
   */
161
  int n_pages;
162
163
  /* We need to read out the size of each page we will render, and lay
164
   * them out in the final image.
165
   */
166
  VipsRect image;
167
  VipsRect *pages;
168
169
  /* The [double] background converted to image format.
170
   */
171
  VipsPel *ink;
172
173
  /* Render this page box.
174
   */
175
  VipsForeignPdfPageBox page_box;
176
177
} VipsForeignLoadPdf;
178
179
typedef VipsForeignLoadClass VipsForeignLoadPdfClass;
180
181
108
G_DEFINE_ABSTRACT_TYPE(VipsForeignLoadPdf, vips_foreign_load_pdf,
182
108
  VIPS_TYPE_FOREIGN_LOAD);
183
108
184
108
static char *vips_pdfium_errors[] = {
185
108
  "no error",
186
108
  "unknown error",
187
108
  "file not found or could not be opened",
188
108
  "file not in PDF format or corrupted",
189
108
  "password required or incorrect password",
190
108
  "unsupported security scheme",
191
108
  "page not found or content error"
192
108
};
193
108
194
108
static GMutex vips_pdfium_mutex;
195
108
196
108
static void
197
108
vips_pdfium_error(void)
198
325
{
199
325
  int err = FPDF_GetLastError();
200
201
325
  if (err >= 0 &&
202
325
    err < VIPS_NUMBER(vips_pdfium_errors))
203
325
    vips_error("pdfload", "%s", _(vips_pdfium_errors[err]));
204
0
  else
205
0
    vips_error("pdfload", "%s", _("unknown error"));
206
325
}
207
208
static void
209
vips_foreign_load_pdf_close(VipsForeignLoadPdf *pdf)
210
702
{
211
702
  g_mutex_lock(&vips_pdfium_mutex);
212
213
702
  VIPS_FREEF(FPDF_ClosePage, pdf->page);
214
702
  VIPS_FREEF(FPDFDOC_ExitFormFillEnvironment, pdf->form);
215
702
  VIPS_FREEF(FPDF_CloseDocument, pdf->doc);
216
702
  VIPS_UNREF(pdf->source);
217
218
702
  g_mutex_unlock(&vips_pdfium_mutex);
219
702
}
220
221
static void
222
vips_foreign_load_pdf_dispose(GObject *gobject)
223
702
{
224
702
  VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) gobject;
225
226
702
  vips_foreign_load_pdf_close(pdf);
227
228
702
  G_OBJECT_CLASS(vips_foreign_load_pdf_parent_class)->dispose(gobject);
229
702
}
230
231
static void *
232
vips_pdfium_init_cb(void *dummy)
233
18
{
234
18
  FPDF_LIBRARY_CONFIG config;
235
236
18
  config.version = 2;
237
18
  config.m_pUserFontPaths = NULL;
238
18
  config.m_pIsolate = NULL;
239
18
  config.m_v8EmbedderSlot = 0;
240
241
18
  FPDF_InitLibraryWithConfig(&config);
242
243
18
  return NULL;
244
18
}
245
246
/* This is the m_GetBlock function for FPDF_FILEACCESS.
247
 */
248
static gboolean
249
vips_pdfium_GetBlock(void *param,
250
  unsigned long position, unsigned char *pBuf, unsigned long size)
251
62.0k
{
252
62.0k
  VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) param;
253
254
  /* PDFium guarantees these.
255
   */
256
62.0k
  g_assert(size > 0);
257
62.0k
  g_assert(position >= 0);
258
62.0k
  g_assert(position + size <= pdf->file_access.m_FileLen);
259
260
62.0k
  if (vips_source_seek(pdf->source, position, SEEK_SET) < 0)
261
0
    return FALSE;
262
263
124k
  while (size > 0) {
264
62.0k
    gint64 bytes_read;
265
266
62.0k
    if ((bytes_read =
267
62.0k
          vips_source_read(pdf->source, pBuf, size)) < 0)
268
0
      return FALSE;
269
62.0k
    pBuf += bytes_read;
270
62.0k
    size -= bytes_read;
271
62.0k
  }
272
273
62.0k
  return TRUE;
274
62.0k
}
275
276
static int
277
vips_foreign_load_pdf_build(VipsObject *object)
278
711
{
279
711
  static GOnce once = G_ONCE_INIT;
280
281
711
  VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) object;
282
711
  VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(pdf);
283
284
711
  gint64 length;
285
286
711
  VIPS_ONCE(&once, vips_pdfium_init_cb, NULL);
287
288
711
  pdf->total_scale = pdf->scale * pdf->dpi / 72.0;
289
290
711
  pdf->form_callbacks.version = 2;
291
292
  /* pdfium must know the file length, unfortunately.
293
   */
294
711
  if (pdf->source) {
295
711
    if ((length = vips_source_length(pdf->source)) <= 0)
296
0
      return -1;
297
711
    if (length > 1 << 30) {
298
0
      vips_error(class->nickname,
299
0
        _("%s: too large for pdfium"),
300
0
        vips_connection_nick(
301
0
          VIPS_CONNECTION(pdf->source)));
302
0
      return -1;
303
0
    }
304
711
    pdf->file_access.m_FileLen = length;
305
711
    pdf->file_access.m_GetBlock = vips_pdfium_GetBlock;
306
711
    pdf->file_access.m_Param = pdf;
307
308
711
    g_mutex_lock(&vips_pdfium_mutex);
309
310
711
    if (!(pdf->doc = FPDF_LoadCustomDocument(&pdf->file_access,
311
711
          pdf->password))) {
312
308
      g_mutex_unlock(&vips_pdfium_mutex);
313
308
      vips_pdfium_error();
314
308
      vips_error("pdfload",
315
308
        _("%s: unable to load"),
316
308
        vips_connection_nick(
317
308
          VIPS_CONNECTION(pdf->source)));
318
308
      return -1;
319
308
    }
320
321
403
    if (!(pdf->form = FPDFDOC_InitFormFillEnvironment(pdf->doc,
322
403
          &pdf->form_callbacks))) {
323
0
      g_mutex_unlock(&vips_pdfium_mutex);
324
0
      vips_pdfium_error();
325
0
      vips_error("pdfload",
326
0
        _("%s: unable to initialize form fill environment"),
327
0
        vips_connection_nick(
328
0
          VIPS_CONNECTION(pdf->source)));
329
0
      return -1;
330
0
    }
331
332
403
    g_mutex_unlock(&vips_pdfium_mutex);
333
403
  }
334
335
403
  return VIPS_OBJECT_CLASS(vips_foreign_load_pdf_parent_class)
336
403
    ->build(object);
337
711
}
338
339
static VipsForeignFlags
340
vips_foreign_load_pdf_get_flags_filename(const char *filename)
341
0
{
342
  /* We can render any part of the page on demand.
343
   */
344
0
  return VIPS_FOREIGN_PARTIAL;
345
0
}
346
347
static VipsForeignFlags
348
vips_foreign_load_pdf_get_flags(VipsForeignLoad *load)
349
403
{
350
403
  return VIPS_FOREIGN_PARTIAL;
351
403
}
352
353
static int
354
vips_foreign_load_pdf_get_page(VipsForeignLoadPdf *pdf, int page_no)
355
434
{
356
434
  if (pdf->current_page != page_no) {
357
386
    VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(pdf);
358
359
386
    g_mutex_lock(&vips_pdfium_mutex);
360
361
386
    VIPS_FREEF(FPDF_ClosePage, pdf->page);
362
386
    pdf->current_page = -1;
363
364
#ifdef DEBUG
365
    printf("vips_foreign_load_pdf_get_page: %d\n", page_no);
366
#endif /*DEBUG*/
367
368
386
    if (!(pdf->page = FPDF_LoadPage(pdf->doc, page_no))) {
369
17
      g_mutex_unlock(&vips_pdfium_mutex);
370
17
      vips_pdfium_error();
371
17
      vips_error(class->nickname,
372
17
        _("unable to load page %d"), page_no);
373
17
      return -1;
374
17
    }
375
369
    pdf->current_page = page_no;
376
377
369
    g_mutex_unlock(&vips_pdfium_mutex);
378
369
  }
379
380
417
  return 0;
381
434
}
382
383
/* String-based metadata fields we extract.
384
 */
385
typedef struct _VipsForeignLoadPdfMetadata {
386
  char *tag;   /* as understood by PDFium */
387
  char *field; /* as understood by libvips */
388
} VipsForeignLoadPdfMetadata;
389
390
static VipsForeignLoadPdfMetadata vips_foreign_load_pdf_metadata[] = {
391
  { "Title", "pdf-title" },
392
  { "Author", "pdf-author" },
393
  { "Subject", "pdf-subject" },
394
  { "Keywords", "pdf-keywords" },
395
  { "Creator", "pdf-creator" },
396
  { "Producer", "pdf-producer" },
397
  /* poppler has "metadata" as well, but pdfium does not support this */
398
};
399
static int n_metadata = VIPS_NUMBER(vips_foreign_load_pdf_metadata);
400
401
static int
402
vips_foreign_load_pdf_set_image(VipsForeignLoadPdf *pdf, VipsImage *out)
403
364
{
404
364
  int i;
405
364
  double res;
406
407
#ifdef DEBUG
408
  printf("vips_foreign_load_pdf_set_image: %p\n", pdf);
409
#endif /*DEBUG*/
410
411
  /* We render to a tilecache, so it has to be SMALLTILE.
412
   */
413
364
  if (vips_image_pipelinev(out, VIPS_DEMAND_STYLE_SMALLTILE, NULL))
414
0
    return -1;
415
416
  /* Extract and attach metadata. Set the old name too for compat.
417
   */
418
364
  vips_image_set_int(out, "pdf-n_pages", pdf->n_pages);
419
364
  vips_image_set_int(out, VIPS_META_N_PAGES, pdf->n_pages);
420
421
364
  g_mutex_lock(&vips_pdfium_mutex);
422
423
2.54k
  for (i = 0; i < n_metadata; i++) {
424
2.18k
    VipsForeignLoadPdfMetadata *metadata =
425
2.18k
      &vips_foreign_load_pdf_metadata[i];
426
427
2.18k
    char text[1024];
428
2.18k
    int len;
429
430
2.18k
    len = FPDF_GetMetaText(pdf->doc, metadata->tag, text, 1024);
431
2.18k
    if (len > 0) {
432
684
      char *str;
433
434
      /* Silently ignore coding errors.
435
       */
436
684
      if ((str = g_utf16_to_utf8((gunichar2 *) text, len,
437
684
           NULL, NULL, NULL))) {
438
667
        vips_image_set_string(out,
439
667
          metadata->field, str);
440
667
        g_free(str);
441
667
      }
442
684
    }
443
2.18k
  }
444
445
364
  g_mutex_unlock(&vips_pdfium_mutex);
446
447
  /* We need pixels/mm for vips.
448
   */
449
364
  res = pdf->dpi / 25.4;
450
451
364
  vips_image_init_fields(out,
452
364
    pdf->image.width, pdf->image.height,
453
364
    4, VIPS_FORMAT_UCHAR,
454
364
    VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, res, res);
455
456
364
  return 0;
457
364
}
458
459
static void
460
vips_foreign_load_pdf_apply_page_box(FPDF_PAGE page, VipsForeignPdfPageBox box)
461
369
{
462
369
  float left, bottom, right, top;
463
464
  /* Avoid locking when no change in region to render.
465
   */
466
369
  if (box == VIPS_FOREIGN_PDF_PAGE_BOX_CROP)
467
369
    return;
468
469
0
  g_mutex_lock(&vips_pdfium_mutex);
470
0
  switch (box) {
471
0
  case VIPS_FOREIGN_PDF_PAGE_BOX_MEDIA:
472
0
    if (FPDFPage_GetMediaBox(page, &left, &bottom, &right, &top))
473
0
      FPDFPage_SetCropBox(page, left, bottom, right, top);
474
0
    else
475
0
      g_warning("missing media box, using default crop box");
476
0
    break;
477
0
  case VIPS_FOREIGN_PDF_PAGE_BOX_TRIM:
478
0
    if (FPDFPage_GetTrimBox(page, &left, &bottom, &right, &top))
479
0
      FPDFPage_SetCropBox(page, left, bottom, right, top);
480
0
    else
481
0
      g_warning("missing trim box, using default crop box");
482
0
    break;
483
0
  case VIPS_FOREIGN_PDF_PAGE_BOX_BLEED:
484
0
    if (FPDFPage_GetBleedBox(page, &left, &bottom, &right, &top))
485
0
      FPDFPage_SetCropBox(page, left, bottom, right, top);
486
0
    else
487
0
      g_warning("missing bleed box, using default crop box");
488
0
    break;
489
0
  case VIPS_FOREIGN_PDF_PAGE_BOX_ART:
490
0
    if (FPDFPage_GetArtBox(page, &left, &bottom, &right, &top))
491
0
      FPDFPage_SetCropBox(page, left, bottom, right, top);
492
0
    else
493
0
      g_warning("missing art box, using default crop box");
494
0
    break;
495
0
  case VIPS_FOREIGN_PDF_PAGE_BOX_CROP:
496
0
  default:
497
0
    break;
498
0
  }
499
0
  g_mutex_unlock(&vips_pdfium_mutex);
500
0
}
501
502
static int
503
vips_foreign_load_pdf_header(VipsForeignLoad *load)
504
403
{
505
403
  VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(load);
506
403
  VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) load;
507
508
403
  int top;
509
510
#ifdef DEBUG
511
  printf("vips_foreign_load_pdf_header: %p\n", pdf);
512
#endif /*DEBUG*/
513
514
403
  g_mutex_lock(&vips_pdfium_mutex);
515
403
  pdf->n_pages = FPDF_GetPageCount(pdf->doc);
516
403
  g_mutex_unlock(&vips_pdfium_mutex);
517
518
  /* @n == -1 means until the end of the doc.
519
   */
520
403
  if (pdf->n == -1)
521
0
    pdf->n = pdf->n_pages - pdf->page_no; // FIXME: Invalidates operation cache
522
523
403
  if (pdf->page_no + pdf->n > pdf->n_pages ||
524
386
    pdf->page_no < 0 ||
525
386
    pdf->n <= 0) {
526
17
    vips_error(class->nickname, "%s", _("pages out of range"));
527
17
    return -1;
528
17
  }
529
530
  /* Lay out the pages in our output image.
531
   */
532
386
  if (!(pdf->pages = VIPS_ARRAY(pdf, pdf->n, VipsRect)))
533
0
    return -1;
534
535
386
  top = 0;
536
386
  pdf->image.left = 0;
537
386
  pdf->image.top = 0;
538
386
  pdf->image.width = 0;
539
386
  pdf->image.height = 0;
540
707
  for (int i = 0; i < pdf->n; i++) {
541
386
    if (vips_foreign_load_pdf_get_page(pdf, pdf->page_no + i))
542
17
      return -1;
543
369
    pdf->pages[i].left = 0;
544
369
    pdf->pages[i].top = top;
545
546
    /* Attempt to apply selected page box using the page coordinate
547
     * system (bottom left) before calculating render dimensions
548
     * using the client coordinate system (top left). */
549
369
    vips_foreign_load_pdf_apply_page_box(pdf->page, pdf->page_box);
550
551
    /* We do round to nearest, in the same way that vips_resize()
552
     * does round to nearest. Without this, things like
553
     * shrink-on-load will break.
554
     */
555
369
    pdf->pages[i].width = rint(
556
369
      FPDF_GetPageWidth(pdf->page) * pdf->total_scale);
557
369
    pdf->pages[i].height = rint(
558
369
      FPDF_GetPageHeight(pdf->page) * pdf->total_scale);
559
560
    /* PDFium allows page width or height to be less than 1 (!!).
561
     */
562
369
    if (pdf->pages[i].width < 1 ||
563
357
      pdf->pages[i].height < 1 ||
564
342
      pdf->pages[i].width > VIPS_MAX_COORD ||
565
334
      pdf->pages[i].height > VIPS_MAX_COORD) {
566
48
      vips_error(class->nickname,
567
48
        "%s", _("page size out of range"));
568
48
      return -1;
569
48
    }
570
571
321
    if (pdf->pages[i].width > pdf->image.width)
572
321
      pdf->image.width = pdf->pages[i].width;
573
321
    pdf->image.height += pdf->pages[i].height;
574
575
321
    top += pdf->pages[i].height;
576
321
  }
577
578
  /* If all pages are the same height, we can tag this as a toilet roll
579
   * image.
580
   */
581
321
  for (int i = 1; i < pdf->n; i++)
582
0
    if (pdf->pages[i].height != pdf->pages[0].height)
583
0
      break;
584
585
  /* Only set page-height if we have more than one page, or this could
586
   * accidentally turn into an animated image later.
587
   */
588
321
  if (pdf->n > 1)
589
0
    vips_image_set_int(load->out,
590
0
      VIPS_META_PAGE_HEIGHT, pdf->pages[0].height);
591
592
321
  vips_foreign_load_pdf_set_image(pdf, load->out);
593
594
  /* Convert the background to the image format.
595
   */
596
321
  if (!(pdf->ink = vips__vector_to_ink(class->nickname,
597
321
        load->out,
598
321
        VIPS_AREA(pdf->background)->data, NULL,
599
321
        VIPS_AREA(pdf->background)->n)))
600
0
    return -1;
601
321
  vips__bgra2rgba((guint32 *) pdf->ink, 1);
602
603
321
  return 0;
604
321
}
605
606
static void
607
vips_foreign_load_pdf_minimise(VipsObject *object, VipsForeignLoadPdf *pdf)
608
93
{
609
93
  vips_source_minimise(pdf->source);
610
93
}
611
612
static int
613
vips_foreign_load_pdf_generate(VipsRegion *out_region,
614
  void *seq, void *a, void *b, gboolean *stop)
615
48
{
616
48
  VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) a;
617
48
  VipsRect *r = &out_region->valid;
618
619
48
  int top;
620
48
  int i;
621
622
  /*
623
  printf("vips_foreign_load_pdf_generate: "
624
       "left = %d, top = %d, width = %d, height = %d\n",
625
    r->left, r->top, r->width, r->height);
626
   */
627
628
  /* Search through the pages we are drawing for the first containing
629
   * this rect. This could be quicker, perhaps a binary search, but who
630
   * cares.
631
   */
632
48
  for (i = 0; i < pdf->n; i++)
633
48
    if (VIPS_RECT_BOTTOM(&pdf->pages[i]) > r->top)
634
48
      break;
635
636
  /* Reset out region. Otherwise there might be parts of previous pages
637
   * left.
638
   */
639
48
  vips_region_black(out_region);
640
641
48
  top = r->top;
642
96
  while (top < VIPS_RECT_BOTTOM(r)) {
643
48
    FPDF_BITMAP bitmap;
644
645
    /* Is the rect within this page? It might not be if the output is more
646
     * than one tile wide and this page is narrower.
647
     */
648
48
    VipsRect rect;
649
48
    vips_rect_intersectrect(r, &pdf->pages[i], &rect);
650
48
    if (rect.width > 0 &&
651
48
      rect.height > 0) {
652
653
48
      if (vips_foreign_load_pdf_get_page(pdf, pdf->page_no + i))
654
0
        return -1;
655
656
48
      vips__worker_lock(&vips_pdfium_mutex);
657
658
      /* 4 means RGBA.
659
       */
660
48
      bitmap = FPDFBitmap_CreateEx(rect.width, rect.height, 4,
661
48
        VIPS_REGION_ADDR(out_region, rect.left, rect.top),
662
48
        VIPS_REGION_LSKIP(out_region));
663
664
      /* Only paint the background if there's no transparency.
665
       */
666
48
      if (!FPDFPage_HasTransparency(pdf->page)) {
667
34
        FPDF_DWORD ink = *((guint32 *) pdf->ink);
668
669
34
        FPDFBitmap_FillRect(bitmap,
670
34
          0, 0, rect.width, rect.height, ink);
671
34
      }
672
673
      // pdfium writes bgra by default, we need rgba
674
48
      FPDF_RenderPageBitmap(bitmap, pdf->page,
675
48
        pdf->pages[i].left - rect.left,
676
48
        pdf->pages[i].top - rect.top,
677
48
        pdf->pages[i].width, pdf->pages[i].height,
678
48
        0, FPDF_ANNOT | FPDF_REVERSE_BYTE_ORDER);
679
680
48
      FPDF_FFLDraw(pdf->form, bitmap, pdf->page,
681
48
        pdf->pages[i].left - rect.left,
682
48
        pdf->pages[i].top - rect.top,
683
48
        pdf->pages[i].width, pdf->pages[i].height,
684
48
        0, FPDF_ANNOT | FPDF_REVERSE_BYTE_ORDER);
685
686
48
      FPDFBitmap_Destroy(bitmap);
687
688
48
      g_mutex_unlock(&vips_pdfium_mutex);
689
48
    }
690
691
48
    top += rect.height;
692
48
    i += 1;
693
48
  }
694
695
48
  return 0;
696
48
}
697
698
static int
699
vips_foreign_load_pdf_load(VipsForeignLoad *load)
700
43
{
701
43
  VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) load;
702
43
  VipsImage **t = (VipsImage **)
703
43
    vips_object_local_array((VipsObject *) load, 2);
704
705
#ifdef DEBUG
706
  printf("vips_foreign_load_pdf_load: %p\n", pdf);
707
#endif /*DEBUG*/
708
709
  /* Read to this image, then cache to out, see below.
710
   */
711
43
  t[0] = vips_image_new();
712
713
  /* Close input immediately at end of read.
714
   */
715
43
  g_signal_connect(t[0], "minimise",
716
43
    G_CALLBACK(vips_foreign_load_pdf_minimise), pdf);
717
718
43
  vips_foreign_load_pdf_set_image(pdf, t[0]);
719
720
43
  if (vips_image_generate(t[0],
721
43
      NULL, vips_foreign_load_pdf_generate, NULL, pdf, NULL) ||
722
43
    vips_tilecache(t[0], &t[1],
723
43
      "tile_width", TILE_SIZE,
724
43
      "tile_height", TILE_SIZE,
725
43
      "max_tiles", 2 * (1 + t[0]->Xsize / TILE_SIZE),
726
43
      NULL) ||
727
43
    vips_image_write(t[1], load->real))
728
0
    return -1;
729
730
43
  return 0;
731
43
}
732
733
static void
734
vips_foreign_load_pdf_class_init(VipsForeignLoadPdfClass *class)
735
18
{
736
18
  GObjectClass *gobject_class = G_OBJECT_CLASS(class);
737
18
  VipsObjectClass *object_class = (VipsObjectClass *) class;
738
18
  VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;
739
740
18
  gobject_class->dispose = vips_foreign_load_pdf_dispose;
741
18
  gobject_class->set_property = vips_object_set_property;
742
18
  gobject_class->get_property = vips_object_get_property;
743
744
18
  object_class->nickname = "pdfload_base";
745
18
  object_class->description = _("load PDF with PDFium");
746
18
  object_class->build = vips_foreign_load_pdf_build;
747
748
18
  load_class->get_flags_filename =
749
18
    vips_foreign_load_pdf_get_flags_filename;
750
18
  load_class->get_flags = vips_foreign_load_pdf_get_flags;
751
18
  load_class->header = vips_foreign_load_pdf_header;
752
18
  load_class->load = vips_foreign_load_pdf_load;
753
754
18
  VIPS_ARG_INT(class, "page", 10,
755
18
    _("Page"),
756
18
    _("First page to load"),
757
18
    VIPS_ARGUMENT_OPTIONAL_INPUT,
758
18
    G_STRUCT_OFFSET(VipsForeignLoadPdf, page_no),
759
18
    0, 100000, 0);
760
761
18
  VIPS_ARG_INT(class, "n", 11,
762
18
    _("n"),
763
18
    _("Number of pages to load, -1 for all"),
764
18
    VIPS_ARGUMENT_OPTIONAL_INPUT,
765
18
    G_STRUCT_OFFSET(VipsForeignLoadPdf, n),
766
18
    -1, 100000, 1);
767
768
18
  VIPS_ARG_DOUBLE(class, "dpi", 12,
769
18
    _("DPI"),
770
18
    _("DPI to render at"),
771
18
    VIPS_ARGUMENT_OPTIONAL_INPUT,
772
18
    G_STRUCT_OFFSET(VipsForeignLoadPdf, dpi),
773
18
    0.001, 100000.0, 72.0);
774
775
18
  VIPS_ARG_DOUBLE(class, "scale", 13,
776
18
    _("Scale"),
777
18
    _("Factor to scale by"),
778
18
    VIPS_ARGUMENT_OPTIONAL_INPUT,
779
18
    G_STRUCT_OFFSET(VipsForeignLoadPdf, scale),
780
18
    0.0, 100000.0, 1.0);
781
782
18
  VIPS_ARG_BOXED(class, "background", 14,
783
18
    _("Background"),
784
18
    _("Background colour"),
785
18
    VIPS_ARGUMENT_OPTIONAL_INPUT,
786
18
    G_STRUCT_OFFSET(VipsForeignLoadPdf, background),
787
18
    VIPS_TYPE_ARRAY_DOUBLE);
788
789
18
  VIPS_ARG_STRING(class, "password", 25,
790
18
    _("Password"),
791
18
    _("Password to decrypt with"),
792
18
    VIPS_ARGUMENT_OPTIONAL_INPUT,
793
18
    G_STRUCT_OFFSET(VipsForeignLoadPdf, password),
794
18
    NULL);
795
796
18
  VIPS_ARG_ENUM(class, "page_box", 26,
797
18
    _("Page box"),
798
18
    _("The region of the page to render"),
799
18
    VIPS_ARGUMENT_OPTIONAL_INPUT,
800
18
    G_STRUCT_OFFSET(VipsForeignLoadPdf, page_box),
801
18
    VIPS_TYPE_FOREIGN_PDF_PAGE_BOX,
802
18
    VIPS_FOREIGN_PDF_PAGE_BOX_CROP);
803
18
}
804
805
static void
806
vips_foreign_load_pdf_init(VipsForeignLoadPdf *pdf)
807
711
{
808
711
  pdf->dpi = 72.0;
809
711
  pdf->scale = 1.0;
810
711
  pdf->n = 1;
811
711
  pdf->current_page = -1;
812
711
  pdf->background = vips_array_double_newv(1, 255.0);
813
711
  pdf->page_box = VIPS_FOREIGN_PDF_PAGE_BOX_CROP;
814
711
}
815
816
typedef struct _VipsForeignLoadPdfFile {
817
  VipsForeignLoadPdf parent_object;
818
819
  /* Filename for load.
820
   */
821
  char *filename;
822
823
} VipsForeignLoadPdfFile;
824
825
typedef VipsForeignLoadPdfClass VipsForeignLoadPdfFileClass;
826
827
36
G_DEFINE_TYPE(VipsForeignLoadPdfFile, vips_foreign_load_pdf_file,
828
36
  vips_foreign_load_pdf_get_type());
829
36
830
36
static int
831
36
vips_foreign_load_pdf_file_header(VipsForeignLoad *load)
832
36
{
833
20
  VipsForeignLoadPdfFile *file = (VipsForeignLoadPdfFile *) load;
834
835
20
  VIPS_SETSTR(load->out->filename, file->filename);
836
837
20
  return VIPS_FOREIGN_LOAD_CLASS(vips_foreign_load_pdf_file_parent_class)
838
20
    ->header(load);
839
20
}
840
841
static int
842
vips_foreign_load_pdf_file_build(VipsObject *object)
843
33
{
844
33
  VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) object;
845
33
  VipsForeignLoadPdfFile *file = (VipsForeignLoadPdfFile *) pdf;
846
847
#ifdef DEBUG
848
  printf("vips_foreign_load_pdf_file_build: %s\n", file->filename);
849
#endif /*DEBUG*/
850
851
33
  if (file->filename &&
852
33
    !(pdf->source = vips_source_new_from_file(file->filename)))
853
0
    return -1;
854
855
33
  return VIPS_OBJECT_CLASS(vips_foreign_load_pdf_file_parent_class)
856
33
    ->build(object);
857
33
}
858
859
static void
860
vips_foreign_load_pdf_file_class_init(
861
  VipsForeignLoadPdfFileClass *class)
862
18
{
863
18
  GObjectClass *gobject_class = G_OBJECT_CLASS(class);
864
18
  VipsObjectClass *object_class = (VipsObjectClass *) class;
865
18
  VipsForeignClass *foreign_class = (VipsForeignClass *) class;
866
18
  VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;
867
868
18
  gobject_class->set_property = vips_object_set_property;
869
18
  gobject_class->get_property = vips_object_get_property;
870
871
18
  object_class->nickname = "pdfload";
872
18
  object_class->description = _("load PDF from file (pdfium)");
873
18
  object_class->build = vips_foreign_load_pdf_file_build;
874
875
18
  foreign_class->suffs = vips__pdf_suffs;
876
877
18
  load_class->is_a = vips__pdf_is_a_file;
878
18
  load_class->header = vips_foreign_load_pdf_file_header;
879
880
18
  VIPS_ARG_STRING(class, "filename", 1,
881
18
    _("Filename"),
882
18
    _("Filename to load from"),
883
18
    VIPS_ARGUMENT_REQUIRED_INPUT,
884
18
    G_STRUCT_OFFSET(VipsForeignLoadPdfFile, filename),
885
18
    NULL);
886
18
}
887
888
static void
889
vips_foreign_load_pdf_file_init(VipsForeignLoadPdfFile *file)
890
33
{
891
33
}
892
893
typedef struct _VipsForeignLoadPdfBuffer {
894
  VipsForeignLoadPdf parent_object;
895
896
  /* Load from a buffer.
897
   */
898
  VipsArea *buf;
899
900
} VipsForeignLoadPdfBuffer;
901
902
typedef VipsForeignLoadPdfClass VipsForeignLoadPdfBufferClass;
903
904
36
G_DEFINE_TYPE(VipsForeignLoadPdfBuffer, vips_foreign_load_pdf_buffer,
905
36
  vips_foreign_load_pdf_get_type());
906
36
907
36
static int
908
36
vips_foreign_load_pdf_buffer_build(VipsObject *object)
909
678
{
910
678
  VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) object;
911
678
  VipsForeignLoadPdfBuffer *buffer = (VipsForeignLoadPdfBuffer *) pdf;
912
913
678
  if (buffer->buf &&
914
678
    !(pdf->source = vips_source_new_from_memory(
915
678
        VIPS_AREA(buffer->buf)->data,
916
678
        VIPS_AREA(buffer->buf)->length)))
917
0
    return -1;
918
919
678
  return VIPS_OBJECT_CLASS(vips_foreign_load_pdf_buffer_parent_class)
920
678
    ->build(object);
921
678
}
922
923
static void
924
vips_foreign_load_pdf_buffer_class_init(
925
  VipsForeignLoadPdfBufferClass *class)
926
18
{
927
18
  GObjectClass *gobject_class = G_OBJECT_CLASS(class);
928
18
  VipsObjectClass *object_class = (VipsObjectClass *) class;
929
18
  VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;
930
931
18
  gobject_class->set_property = vips_object_set_property;
932
18
  gobject_class->get_property = vips_object_get_property;
933
934
18
  object_class->nickname = "pdfload_buffer";
935
18
  object_class->description = _("load PDF from buffer (pdfium)");
936
18
  object_class->build = vips_foreign_load_pdf_buffer_build;
937
938
18
  load_class->is_a_buffer = vips__pdf_is_a_buffer;
939
940
18
  VIPS_ARG_BOXED(class, "buffer", 1,
941
18
    _("Buffer"),
942
18
    _("Buffer to load from"),
943
18
    VIPS_ARGUMENT_REQUIRED_INPUT,
944
18
    G_STRUCT_OFFSET(VipsForeignLoadPdfBuffer, buf),
945
18
    VIPS_TYPE_BLOB);
946
18
}
947
948
static void
949
vips_foreign_load_pdf_buffer_init(VipsForeignLoadPdfBuffer *buffer)
950
678
{
951
678
}
952
953
typedef struct _VipsForeignLoadPdfSource {
954
  VipsForeignLoadPdf parent_object;
955
956
  VipsSource *source;
957
958
} VipsForeignLoadPdfSource;
959
960
typedef VipsForeignLoadPdfClass VipsForeignLoadPdfSourceClass;
961
962
36
G_DEFINE_TYPE(VipsForeignLoadPdfSource, vips_foreign_load_pdf_source,
963
36
  vips_foreign_load_pdf_get_type());
964
36
965
36
static int
966
36
vips_foreign_load_pdf_source_build(VipsObject *object)
967
36
{
968
0
  VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) object;
969
0
  VipsForeignLoadPdfSource *source = (VipsForeignLoadPdfSource *) pdf;
970
971
0
  if (source->source) {
972
0
    pdf->source = source->source;
973
0
    g_object_ref(pdf->source);
974
0
  }
975
976
0
  return VIPS_OBJECT_CLASS(vips_foreign_load_pdf_source_parent_class)
977
0
    ->build(object);
978
0
}
979
980
static void
981
vips_foreign_load_pdf_source_class_init(
982
  VipsForeignLoadPdfSourceClass *class)
983
18
{
984
18
  GObjectClass *gobject_class = G_OBJECT_CLASS(class);
985
18
  VipsObjectClass *object_class = (VipsObjectClass *) class;
986
18
  VipsOperationClass *operation_class = VIPS_OPERATION_CLASS(class);
987
18
  VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;
988
989
18
  gobject_class->set_property = vips_object_set_property;
990
18
  gobject_class->get_property = vips_object_get_property;
991
992
18
  object_class->nickname = "pdfload_source";
993
18
  object_class->description = _("load PDF from source (pdfium)");
994
18
  object_class->build = vips_foreign_load_pdf_source_build;
995
996
18
  operation_class->flags |= VIPS_OPERATION_NOCACHE;
997
998
18
  load_class->is_a_source = vips__pdf_is_a_source;
999
1000
18
  VIPS_ARG_OBJECT(class, "source", 1,
1001
18
    _("Source"),
1002
18
    _("Source to load from"),
1003
18
    VIPS_ARGUMENT_REQUIRED_INPUT,
1004
18
    G_STRUCT_OFFSET(VipsForeignLoadPdfSource, source),
1005
18
    VIPS_TYPE_SOURCE);
1006
18
}
1007
1008
static void
1009
vips_foreign_load_pdf_source_init(VipsForeignLoadPdfSource *source)
1010
0
{
1011
0
}
1012
1013
#endif /*HAVE_PDFIUM*/