/* load PDF with PDFium
 *
 * 5/4/18
 * 	- from pdfload.c
 * 8/6/18
 * 	- add background param
 * 16/8/18
 * 	- shut down the input file as soon as we can [kleisauke]
 * 8/8/19
 * 	- add locks, since pdfium is not threadsafe in any way
 * 13/10/20
 * 	- have a lock just for pdfium [DarthSim]
 * 	- update for current pdfium
 * 	- add _source input
 * 28/1/22
 * 	- add password
 * 21/5/22
 * 	- improve transparency handling [DarthSim]
 * 21/4/23
 * 	- add support for forms [kleisauke]
 */

/*

	This file is part of VIPS.

	VIPS is free software; you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
	02110-1301  USA

 */

/*

	These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk

 */

/* TODO
 *
 * - what about filename encodings?
 * - need to test on Windows
 */

/* How to build against PDFium:
 *
 * Download the prebuilt binary from:
 *
 * 	https://github.com/bblanchon/pdfium-binaries
 *
 * Untar to the libvips install prefix, for example:
 *
 * 	cd ~/vips
 * 	tar xf ~/pdfium-linux.tgz
 *
 * Create a pdfium.pc like this (update the version number):
 *

VIPSHOME=/home/john/vips
cat > $VIPSHOME/lib/pkgconfig/pdfium.pc << EOF
	 prefix=$VIPSHOME
	 exec_prefix=\${prefix}
	 libdir=\${exec_prefix}/lib
	 includedir=\${prefix}/include
	 Name: pdfium
	 Description: pdfium
	 Version: 4290
	 Requires:
	 Libs: -L\${libdir} -lpdfium
	 Cflags: -I\${includedir}
EOF

 *
 */

/*
#define DEBUG
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <glib/gi18n-lib.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include <vips/vips.h>
#include <vips/buf.h>
#include <vips/internal.h>

#include "pforeign.h"

#ifdef HAVE_PDFIUM

#include <fpdfview.h>
#include <fpdf_doc.h>
#include <fpdf_edit.h>
#include <fpdf_formfill.h>
#include <fpdf_transformpage.h>

#define TILE_SIZE (4000)

typedef struct _VipsForeignLoadPdf {
	VipsForeignLoad parent_object;

	/* Set by subclasses.
	 */
	VipsSource *source;

	/* Load this page.
	 */
	int page_no;

	/* Load this many pages.
	 */
	int n;

	/* Render at this DPI.
	 */
	double dpi;

	/* Scale by this factor.
	 */
	double scale;

	/* The total scale factor we render with.
	 */
	double total_scale;

	/* Background colour.
	 */
	VipsArrayDouble *background;

	/* Decrypt with this.
	 */
	const char *password;

	FPDF_FILEACCESS file_access;
	FPDF_DOCUMENT doc;
	FPDF_PAGE page;
	FPDF_FORMFILLINFO form_callbacks;
	FPDF_FORMHANDLE form;
	int current_page;

	/* Doc has this many pages.
	 */
	int n_pages;

	/* We need to read out the size of each page we will render, and lay
	 * them out in the final image.
	 */
	VipsRect image;
	VipsRect *pages;

	/* The [double] background converted to image format.
	 */
	VipsPel *ink;

	/* Render this page box.
	 */
	VipsForeignPdfPageBox page_box;

} VipsForeignLoadPdf;

typedef VipsForeignLoadClass VipsForeignLoadPdfClass;

G_DEFINE_ABSTRACT_TYPE(VipsForeignLoadPdf, vips_foreign_load_pdf,
	VIPS_TYPE_FOREIGN_LOAD);

static char *vips_pdfium_errors[] = {
	"no error",
	"unknown error",
	"file not found or could not be opened",
	"file not in PDF format or corrupted",
	"password required or incorrect password",
	"unsupported security scheme",
	"page not found or content error"
};

static GMutex vips_pdfium_mutex;

static void
vips_pdfium_error(void)
{
	int err = FPDF_GetLastError();

	if (err >= 0 &&
		err < VIPS_NUMBER(vips_pdfium_errors))
		vips_error("pdfload", "%s", _(vips_pdfium_errors[err]));
	else
		vips_error("pdfload", "%s", _("unknown error"));
}

static void
vips_foreign_load_pdf_close(VipsForeignLoadPdf *pdf)
{
	g_mutex_lock(&vips_pdfium_mutex);

	VIPS_FREEF(FPDF_ClosePage, pdf->page);
	VIPS_FREEF(FPDFDOC_ExitFormFillEnvironment, pdf->form);
	VIPS_FREEF(FPDF_CloseDocument, pdf->doc);
	VIPS_UNREF(pdf->source);

	g_mutex_unlock(&vips_pdfium_mutex);
}

static void
vips_foreign_load_pdf_dispose(GObject *gobject)
{
	VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) gobject;

	vips_foreign_load_pdf_close(pdf);

	G_OBJECT_CLASS(vips_foreign_load_pdf_parent_class)->dispose(gobject);
}

static void *
vips_pdfium_init_cb(void *dummy)
{
	FPDF_LIBRARY_CONFIG config;

	config.version = 2;
	config.m_pUserFontPaths = NULL;
	config.m_pIsolate = NULL;
	config.m_v8EmbedderSlot = 0;

	FPDF_InitLibraryWithConfig(&config);

	return NULL;
}

/* This is the m_GetBlock function for FPDF_FILEACCESS.
 */
static gboolean
vips_pdfium_GetBlock(void *param,
	unsigned long position, unsigned char *pBuf, unsigned long size)
{
	VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) param;

	/* PDFium guarantees these.
	 */
	g_assert(size > 0);
	g_assert(position >= 0);
	g_assert(position + size <= pdf->file_access.m_FileLen);

	if (vips_source_seek(pdf->source, position, SEEK_SET) < 0)
		return FALSE;

	while (size > 0) {
		gint64 bytes_read;

		if ((bytes_read =
					vips_source_read(pdf->source, pBuf, size)) < 0)
			return FALSE;
		pBuf += bytes_read;
		size -= bytes_read;
	}

	return TRUE;
}

static int
vips_foreign_load_pdf_build(VipsObject *object)
{
	static GOnce once = G_ONCE_INIT;

	VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) object;
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(pdf);

	gint64 length;

	VIPS_ONCE(&once, vips_pdfium_init_cb, NULL);

	pdf->total_scale = pdf->scale * pdf->dpi / 72.0;

	pdf->form_callbacks.version = 2;

	/* pdfium must know the file length, unfortunately.
	 */
	if (pdf->source) {
		if ((length = vips_source_length(pdf->source)) <= 0)
			return -1;
		if (length > 1 << 30) {
			vips_error(class->nickname,
				_("%s: too large for pdfium"),
				vips_connection_nick(
					VIPS_CONNECTION(pdf->source)));
			return -1;
		}
		pdf->file_access.m_FileLen = length;
		pdf->file_access.m_GetBlock = vips_pdfium_GetBlock;
		pdf->file_access.m_Param = pdf;

		g_mutex_lock(&vips_pdfium_mutex);

		if (!(pdf->doc = FPDF_LoadCustomDocument(&pdf->file_access,
				  pdf->password))) {
			g_mutex_unlock(&vips_pdfium_mutex);
			vips_pdfium_error();
			vips_error("pdfload",
				_("%s: unable to load"),
				vips_connection_nick(
					VIPS_CONNECTION(pdf->source)));
			return -1;
		}

		if (!(pdf->form = FPDFDOC_InitFormFillEnvironment(pdf->doc,
				  &pdf->form_callbacks))) {
			g_mutex_unlock(&vips_pdfium_mutex);
			vips_pdfium_error();
			vips_error("pdfload",
				_("%s: unable to initialize form fill environment"),
				vips_connection_nick(
					VIPS_CONNECTION(pdf->source)));
			return -1;
		}

		g_mutex_unlock(&vips_pdfium_mutex);
	}

	return VIPS_OBJECT_CLASS(vips_foreign_load_pdf_parent_class)
		->build(object);
}

static VipsForeignFlags
vips_foreign_load_pdf_get_flags_filename(const char *filename)
{
	/* We can render any part of the page on demand.
	 */
	return VIPS_FOREIGN_PARTIAL;
}

static VipsForeignFlags
vips_foreign_load_pdf_get_flags(VipsForeignLoad *load)
{
	return VIPS_FOREIGN_PARTIAL;
}

static int
vips_foreign_load_pdf_get_page(VipsForeignLoadPdf *pdf, int page_no)
{
	if (pdf->current_page != page_no) {
		VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(pdf);

		g_mutex_lock(&vips_pdfium_mutex);

		VIPS_FREEF(FPDF_ClosePage, pdf->page);
		pdf->current_page = -1;

#ifdef DEBUG
		printf("vips_foreign_load_pdf_get_page: %d\n", page_no);
#endif /*DEBUG*/

		if (!(pdf->page = FPDF_LoadPage(pdf->doc, page_no))) {
			g_mutex_unlock(&vips_pdfium_mutex);
			vips_pdfium_error();
			vips_error(class->nickname,
				_("unable to load page %d"), page_no);
			return -1;
		}
		pdf->current_page = page_no;

		g_mutex_unlock(&vips_pdfium_mutex);
	}

	return 0;
}

/* String-based metadata fields we extract.
 */
typedef struct _VipsForeignLoadPdfMetadata {
	char *tag;	 /* as understood by PDFium */
	char *field; /* as understood by libvips */
} VipsForeignLoadPdfMetadata;

static VipsForeignLoadPdfMetadata vips_foreign_load_pdf_metadata[] = {
	{ "Title", "pdf-title" },
	{ "Author", "pdf-author" },
	{ "Subject", "pdf-subject" },
	{ "Keywords", "pdf-keywords" },
	{ "Creator", "pdf-creator" },
	{ "Producer", "pdf-producer" },
	/* poppler has "metadata" as well, but pdfium does not support this */
};
static int n_metadata = VIPS_NUMBER(vips_foreign_load_pdf_metadata);

static int
vips_foreign_load_pdf_set_image(VipsForeignLoadPdf *pdf, VipsImage *out)
{
	int i;
	double res;

#ifdef DEBUG
	printf("vips_foreign_load_pdf_set_image: %p\n", pdf);
#endif /*DEBUG*/

	/* We render to a tilecache, so it has to be SMALLTILE.
	 */
	if (vips_image_pipelinev(out, VIPS_DEMAND_STYLE_SMALLTILE, NULL))
		return -1;

	/* Extract and attach metadata. Set the old name too for compat.
	 */
	vips_image_set_int(out, "pdf-n_pages", pdf->n_pages);
	vips_image_set_int(out, VIPS_META_N_PAGES, pdf->n_pages);

	g_mutex_lock(&vips_pdfium_mutex);

	for (i = 0; i < n_metadata; i++) {
		VipsForeignLoadPdfMetadata *metadata =
			&vips_foreign_load_pdf_metadata[i];

		char text[1024];
		int len;

		len = FPDF_GetMetaText(pdf->doc, metadata->tag, text, 1024);
		if (len > 0) {
			char *str;

			/* Silently ignore coding errors.
			 */
			if ((str = g_utf16_to_utf8((gunichar2 *) text, len,
					 NULL, NULL, NULL))) {
				vips_image_set_string(out,
					metadata->field, str);
				g_free(str);
			}
		}
	}

	g_mutex_unlock(&vips_pdfium_mutex);

	/* We need pixels/mm for vips.
	 */
	res = pdf->dpi / 25.4;

	vips_image_init_fields(out,
		pdf->image.width, pdf->image.height,
		4, VIPS_FORMAT_UCHAR,
		VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, res, res);

	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)
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(load);
	VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) load;

	int top;

#ifdef DEBUG
	printf("vips_foreign_load_pdf_header: %p\n", pdf);
#endif /*DEBUG*/

	g_mutex_lock(&vips_pdfium_mutex);
	pdf->n_pages = FPDF_GetPageCount(pdf->doc);
	g_mutex_unlock(&vips_pdfium_mutex);

	/* @n == -1 means until the end of the doc.
	 */
	if (pdf->n == -1)
		pdf->n = pdf->n_pages - pdf->page_no; // FIXME: Invalidates operation cache

	if (pdf->page_no + pdf->n > pdf->n_pages ||
		pdf->page_no < 0 ||
		pdf->n <= 0) {
		vips_error(class->nickname, "%s", _("pages out of range"));
		return -1;
	}

	/* Lay out the pages in our output image.
	 */
	if (!(pdf->pages = VIPS_ARRAY(pdf, pdf->n, VipsRect)))
		return -1;

	top = 0;
	pdf->image.left = 0;
	pdf->image.top = 0;
	pdf->image.width = 0;
	pdf->image.height = 0;
	for (int i = 0; i < pdf->n; i++) {
		if (vips_foreign_load_pdf_get_page(pdf, pdf->page_no + i))
			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.
		 */
		pdf->pages[i].width = rint(
			FPDF_GetPageWidth(pdf->page) * pdf->total_scale);
		pdf->pages[i].height = rint(
			FPDF_GetPageHeight(pdf->page) * pdf->total_scale);

		/* PDFium allows page width or height to be less than 1 (!!).
		 */
		if (pdf->pages[i].width < 1 ||
			pdf->pages[i].height < 1 ||
			pdf->pages[i].width > VIPS_MAX_COORD ||
			pdf->pages[i].height > VIPS_MAX_COORD) {
			vips_error(class->nickname,
				"%s", _("page size out of range"));
			return -1;
		}

		if (pdf->pages[i].width > pdf->image.width)
			pdf->image.width = pdf->pages[i].width;
		pdf->image.height += pdf->pages[i].height;

		top += pdf->pages[i].height;
	}

	/* If all pages are the same height, we can tag this as a toilet roll
	 * image.
	 */
	for (int i = 1; i < pdf->n; i++)
		if (pdf->pages[i].height != pdf->pages[0].height)
			break;

	/* Only set page-height if we have more than one page, or this could
	 * accidentally turn into an animated image later.
	 */
	if (pdf->n > 1)
		vips_image_set_int(load->out,
			VIPS_META_PAGE_HEIGHT, pdf->pages[0].height);

	vips_foreign_load_pdf_set_image(pdf, load->out);

	/* Convert the background to the image format.
	 */
	if (!(pdf->ink = vips__vector_to_ink(class->nickname,
			  load->out,
			  VIPS_AREA(pdf->background)->data, NULL,
			  VIPS_AREA(pdf->background)->n)))
		return -1;
	vips__bgra2rgba((guint32 *) pdf->ink, 1);

	return 0;
}

static void
vips_foreign_load_pdf_minimise(VipsObject *object, VipsForeignLoadPdf *pdf)
{
	vips_source_minimise(pdf->source);
}

static int
vips_foreign_load_pdf_generate(VipsRegion *out_region,
	void *seq, void *a, void *b, gboolean *stop)
{
	VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) a;
	VipsRect *r = &out_region->valid;

	int top;
	int i;

	/*
	printf("vips_foreign_load_pdf_generate: "
		   "left = %d, top = %d, width = %d, height = %d\n",
		r->left, r->top, r->width, r->height);
	 */

	/* Search through the pages we are drawing for the first containing
	 * this rect. This could be quicker, perhaps a binary search, but who
	 * cares.
	 */
	for (i = 0; i < pdf->n; i++)
		if (VIPS_RECT_BOTTOM(&pdf->pages[i]) > r->top)
			break;

	/* Reset out region. Otherwise there might be parts of previous pages
	 * left.
	 */
	vips_region_black(out_region);

	top = r->top;
	while (top < VIPS_RECT_BOTTOM(r)) {
		FPDF_BITMAP bitmap;

		/* Is the rect within this page? It might not be if the output is more
		 * than one tile wide and this page is narrower.
		 */
		VipsRect rect;
		vips_rect_intersectrect(r, &pdf->pages[i], &rect);
		if (rect.width > 0 &&
			rect.height > 0) {

			if (vips_foreign_load_pdf_get_page(pdf, pdf->page_no + i))
				return -1;

			vips__worker_lock(&vips_pdfium_mutex);

			/* 4 means RGBA.
			 */
			bitmap = FPDFBitmap_CreateEx(rect.width, rect.height, 4,
				VIPS_REGION_ADDR(out_region, rect.left, rect.top),
				VIPS_REGION_LSKIP(out_region));

			/* Only paint the background if there's no transparency.
			 */
			if (!FPDFPage_HasTransparency(pdf->page)) {
				FPDF_DWORD ink = *((guint32 *) pdf->ink);

				FPDFBitmap_FillRect(bitmap,
					0, 0, rect.width, rect.height, ink);
			}

			// pdfium writes bgra by default, we need rgba
			FPDF_RenderPageBitmap(bitmap, pdf->page,
				pdf->pages[i].left - rect.left,
				pdf->pages[i].top - rect.top,
				pdf->pages[i].width, pdf->pages[i].height,
				0, FPDF_ANNOT | FPDF_REVERSE_BYTE_ORDER);

			FPDF_FFLDraw(pdf->form, bitmap, pdf->page,
				pdf->pages[i].left - rect.left,
				pdf->pages[i].top - rect.top,
				pdf->pages[i].width, pdf->pages[i].height,
				0, FPDF_ANNOT | FPDF_REVERSE_BYTE_ORDER);

			FPDFBitmap_Destroy(bitmap);

			g_mutex_unlock(&vips_pdfium_mutex);
		}

		top += rect.height;
		i += 1;
	}

	return 0;
}

static int
vips_foreign_load_pdf_load(VipsForeignLoad *load)
{
	VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) load;
	VipsImage **t = (VipsImage **)
		vips_object_local_array((VipsObject *) load, 2);

#ifdef DEBUG
	printf("vips_foreign_load_pdf_load: %p\n", pdf);
#endif /*DEBUG*/

	/* Read to this image, then cache to out, see below.
	 */
	t[0] = vips_image_new();

	/* Close input immediately at end of read.
	 */
	g_signal_connect(t[0], "minimise",
		G_CALLBACK(vips_foreign_load_pdf_minimise), pdf);

	vips_foreign_load_pdf_set_image(pdf, t[0]);

	if (vips_image_generate(t[0],
			NULL, vips_foreign_load_pdf_generate, NULL, pdf, NULL) ||
		vips_tilecache(t[0], &t[1],
			"tile_width", TILE_SIZE,
			"tile_height", TILE_SIZE,
			"max_tiles", 2 * (1 + t[0]->Xsize / TILE_SIZE),
			NULL) ||
		vips_image_write(t[1], load->real))
		return -1;

	return 0;
}

static void
vips_foreign_load_pdf_class_init(VipsForeignLoadPdfClass *class)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS(class);
	VipsObjectClass *object_class = (VipsObjectClass *) class;
	VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;

	gobject_class->dispose = vips_foreign_load_pdf_dispose;
	gobject_class->set_property = vips_object_set_property;
	gobject_class->get_property = vips_object_get_property;

	object_class->nickname = "pdfload_base";
	object_class->description = _("load PDF with PDFium");
	object_class->build = vips_foreign_load_pdf_build;

	load_class->get_flags_filename =
		vips_foreign_load_pdf_get_flags_filename;
	load_class->get_flags = vips_foreign_load_pdf_get_flags;
	load_class->header = vips_foreign_load_pdf_header;
	load_class->load = vips_foreign_load_pdf_load;

	VIPS_ARG_INT(class, "page", 10,
		_("Page"),
		_("First page to load"),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET(VipsForeignLoadPdf, page_no),
		0, 100000, 0);

	VIPS_ARG_INT(class, "n", 11,
		_("n"),
		_("Number of pages to load, -1 for all"),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET(VipsForeignLoadPdf, n),
		-1, 100000, 1);

	VIPS_ARG_DOUBLE(class, "dpi", 12,
		_("DPI"),
		_("DPI to render at"),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET(VipsForeignLoadPdf, dpi),
		0.001, 100000.0, 72.0);

	VIPS_ARG_DOUBLE(class, "scale", 13,
		_("Scale"),
		_("Factor to scale by"),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET(VipsForeignLoadPdf, scale),
		0.0, 100000.0, 1.0);

	VIPS_ARG_BOXED(class, "background", 14,
		_("Background"),
		_("Background colour"),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET(VipsForeignLoadPdf, background),
		VIPS_TYPE_ARRAY_DOUBLE);

	VIPS_ARG_STRING(class, "password", 25,
		_("Password"),
		_("Password to decrypt with"),
		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
vips_foreign_load_pdf_init(VipsForeignLoadPdf *pdf)
{
	pdf->dpi = 72.0;
	pdf->scale = 1.0;
	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 {
	VipsForeignLoadPdf parent_object;

	/* Filename for load.
	 */
	char *filename;

} VipsForeignLoadPdfFile;

typedef VipsForeignLoadPdfClass VipsForeignLoadPdfFileClass;

G_DEFINE_TYPE(VipsForeignLoadPdfFile, vips_foreign_load_pdf_file,
	vips_foreign_load_pdf_get_type());

static int
vips_foreign_load_pdf_file_header(VipsForeignLoad *load)
{
	VipsForeignLoadPdfFile *file = (VipsForeignLoadPdfFile *) load;

	VIPS_SETSTR(load->out->filename, file->filename);

	return VIPS_FOREIGN_LOAD_CLASS(vips_foreign_load_pdf_file_parent_class)
		->header(load);
}

static int
vips_foreign_load_pdf_file_build(VipsObject *object)
{
	VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) object;
	VipsForeignLoadPdfFile *file = (VipsForeignLoadPdfFile *) pdf;

#ifdef DEBUG
	printf("vips_foreign_load_pdf_file_build: %s\n", file->filename);
#endif /*DEBUG*/

	if (file->filename &&
		!(pdf->source = vips_source_new_from_file(file->filename)))
		return -1;

	return VIPS_OBJECT_CLASS(vips_foreign_load_pdf_file_parent_class)
		->build(object);
}

static void
vips_foreign_load_pdf_file_class_init(
	VipsForeignLoadPdfFileClass *class)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS(class);
	VipsObjectClass *object_class = (VipsObjectClass *) class;
	VipsForeignClass *foreign_class = (VipsForeignClass *) class;
	VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;

	gobject_class->set_property = vips_object_set_property;
	gobject_class->get_property = vips_object_get_property;

	object_class->nickname = "pdfload";
	object_class->description = _("load PDF from file (pdfium)");
	object_class->build = vips_foreign_load_pdf_file_build;

	foreign_class->suffs = vips__pdf_suffs;

	load_class->is_a = vips__pdf_is_a_file;
	load_class->header = vips_foreign_load_pdf_file_header;

	VIPS_ARG_STRING(class, "filename", 1,
		_("Filename"),
		_("Filename to load from"),
		VIPS_ARGUMENT_REQUIRED_INPUT,
		G_STRUCT_OFFSET(VipsForeignLoadPdfFile, filename),
		NULL);
}

static void
vips_foreign_load_pdf_file_init(VipsForeignLoadPdfFile *file)
{
}

typedef struct _VipsForeignLoadPdfBuffer {
	VipsForeignLoadPdf parent_object;

	/* Load from a buffer.
	 */
	VipsArea *buf;

} VipsForeignLoadPdfBuffer;

typedef VipsForeignLoadPdfClass VipsForeignLoadPdfBufferClass;

G_DEFINE_TYPE(VipsForeignLoadPdfBuffer, vips_foreign_load_pdf_buffer,
	vips_foreign_load_pdf_get_type());

static int
vips_foreign_load_pdf_buffer_build(VipsObject *object)
{
	VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) object;
	VipsForeignLoadPdfBuffer *buffer = (VipsForeignLoadPdfBuffer *) pdf;

	if (buffer->buf &&
		!(pdf->source = vips_source_new_from_memory(
			  VIPS_AREA(buffer->buf)->data,
			  VIPS_AREA(buffer->buf)->length)))
		return -1;

	return VIPS_OBJECT_CLASS(vips_foreign_load_pdf_buffer_parent_class)
		->build(object);
}

static void
vips_foreign_load_pdf_buffer_class_init(
	VipsForeignLoadPdfBufferClass *class)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS(class);
	VipsObjectClass *object_class = (VipsObjectClass *) class;
	VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;

	gobject_class->set_property = vips_object_set_property;
	gobject_class->get_property = vips_object_get_property;

	object_class->nickname = "pdfload_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;

	VIPS_ARG_BOXED(class, "buffer", 1,
		_("Buffer"),
		_("Buffer to load from"),
		VIPS_ARGUMENT_REQUIRED_INPUT,
		G_STRUCT_OFFSET(VipsForeignLoadPdfBuffer, buf),
		VIPS_TYPE_BLOB);
}

static void
vips_foreign_load_pdf_buffer_init(VipsForeignLoadPdfBuffer *buffer)
{
}

typedef struct _VipsForeignLoadPdfSource {
	VipsForeignLoadPdf parent_object;

	VipsSource *source;

} VipsForeignLoadPdfSource;

typedef VipsForeignLoadPdfClass VipsForeignLoadPdfSourceClass;

G_DEFINE_TYPE(VipsForeignLoadPdfSource, vips_foreign_load_pdf_source,
	vips_foreign_load_pdf_get_type());

static int
vips_foreign_load_pdf_source_build(VipsObject *object)
{
	VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) object;
	VipsForeignLoadPdfSource *source = (VipsForeignLoadPdfSource *) pdf;

	if (source->source) {
		pdf->source = source->source;
		g_object_ref(pdf->source);
	}

	return VIPS_OBJECT_CLASS(vips_foreign_load_pdf_source_parent_class)
		->build(object);
}

static void
vips_foreign_load_pdf_source_class_init(
	VipsForeignLoadPdfSourceClass *class)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS(class);
	VipsObjectClass *object_class = (VipsObjectClass *) class;
	VipsOperationClass *operation_class = VIPS_OPERATION_CLASS(class);
	VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;

	gobject_class->set_property = vips_object_set_property;
	gobject_class->get_property = vips_object_get_property;

	object_class->nickname = "pdfload_source";
	object_class->description = _("load PDF from source (pdfium)");
	object_class->build = vips_foreign_load_pdf_source_build;

	operation_class->flags |= VIPS_OPERATION_NOCACHE;

	load_class->is_a_source = vips__pdf_is_a_source;

	VIPS_ARG_OBJECT(class, "source", 1,
		_("Source"),
		_("Source to load from"),
		VIPS_ARGUMENT_REQUIRED_INPUT,
		G_STRUCT_OFFSET(VipsForeignLoadPdfSource, source),
		VIPS_TYPE_SOURCE);
}

static void
vips_foreign_load_pdf_source_init(VipsForeignLoadPdfSource *source)
{
}

#endif /*HAVE_PDFIUM*/
