~jscott/pdfpartition

d3f6e4e7406350fd91299db364cc2201e0ab4783 — John Scott 7 months ago
Initial commit
2 files changed, 252 insertions(+), 0 deletions(-)

A main.c
A meson.build
A  => main.c +204 -0
@@ 1,204 @@
#define _POSIX_C_SOURCE 200809L
#include <assert.h>
#include <cairo.h>
#include <cairo-pdf.h>
#include <errno.h>
#include <fcntl.h>
#include <glib.h>
#include <locale.h>
#include <poppler.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

G_GNUC_ALLOC_SIZE2(2, 3) G_GNUC_WARN_UNUSED_RESULT
static void *reallocarray(void *p, size_t m, size_t n) {
	if(n && m > SIZE_MAX / n) {
		errno = ENOMEM;
		return NULL;
	}
	return realloc(p, m * n);
}

int main(int argc, char *argv[static argc+1]) {
	if(!setlocale(LC_ALL, "")) {
		fputs("Failed to enable default locale\n", stderr);
		exit(EXIT_FAILURE);
	}

	const char *out = "out.pdf";
	int opt;
	while((opt = getopt(argc, argv, "o:")) != -1) {
		switch(opt) {
		case '?':
			exit(EXIT_FAILURE);
		case 'o':
			out = optarg;
		}
	}
	argc -= optind;
	argv += optind;
	if(argc < 2) {
		fputs("Missing argument\n", stderr);
		exit(EXIT_FAILURE);
	}
	if(argc > 2) {
		fprintf(stderr, "%s\n", strerror(E2BIG));
		exit(EXIT_FAILURE);
	}

	const int fileone = open(argv[0], O_RDONLY);
	if(fileone == -1) {
		fprintf(stderr, "Failed to open %s: %s\n", argv[0], strerror(errno));
		exit(EXIT_FAILURE);
	}
	/* Before handing these file descriptors over to Poppler, we
	 * are required to check that they correspond to regular files. */
	struct stat st;
	if(fstat(fileone, &st) == -1) {
		fprintf(stderr, "Failed to get information on %s: %s\n", argv[0], strerror(errno));
		goto endfileone;
	}
	if(!S_ISREG(st.st_mode)) {
		fprintf(stderr, "%s is not a regular file\n", argv[0]);
		goto endfileone;
	}

	const int filetwo = open(argv[1], O_RDONLY);
	if(filetwo == -1) {
		fprintf(stderr, "Failed to open %s: %s\n", argv[1], strerror(errno));
		goto endfileone;
	}
	if(fstat(filetwo, &st) == -1) {
		fprintf(stderr, "Failed to get information on %s: %s\n", argv[1], strerror(errno));
		goto endfiletwo;
	}
	if(!S_ISREG(st.st_mode)) {
		fprintf(stderr, "%s is not a regular file\n", argv[1]);
		goto endfiletwo;
	}

	GError *err = NULL;
	PopplerDocument *const docone = poppler_document_new_from_fd(fileone, NULL, &err);
	if(!docone) {
		fprintf(stderr, "Failed to read %s: %s\n", argv[0], err->message);
		g_error_free(err);
		goto endfiletwo;
	}
	PopplerDocument *const doctwo = poppler_document_new_from_fd(filetwo, NULL, &err);
	if(!doctwo) {
		fprintf(stderr, "Failed to read %s: %s\n", argv[1], err->message);
		g_error_free(err);
		goto enddocone;
	}

	/* Now both documents are open, so let's figure out which one is
	 * our main document and which one is going to be getting slid
	 * in between the pages. */
	PopplerDocument *maindoc, *partitiondoc;
	if(poppler_document_get_n_pages(docone) == 1) {
		partitiondoc = docone;
		maindoc = doctwo;
	} else if(poppler_document_get_n_pages(doctwo) == 1) {
		partitiondoc = doctwo;
		maindoc = docone;
	} else {
		fputs("At least one document must be a single page.\n", stderr);
		goto enddoctwo;
	}
	if(poppler_document_get_n_pages(maindoc) == 1) {
		fputs("At least one document must have multiple pages.\n", stderr);
		goto enddoctwo;
	}

	const int npages = poppler_document_get_n_pages(maindoc);
	assert(npages > 0);

	PopplerPage **const maindocpages = reallocarray(NULL, sizeof(*maindocpages), npages);
	if(!maindocpages) {
		perror("Failed to allocate memory for page list");
		goto enddoctwo;
	}
	for(int i = 0; i < npages; i++) {
		maindocpages[i] = poppler_document_get_page(maindoc, i);
	}
	PopplerPage *const partitionpage = poppler_document_get_page(partitiondoc, 0);

	/* The width and height are stored in points. */
	double width, height;
	poppler_page_get_size(partitionpage, &width, &height);
	for(int i = 0; i < npages; i++) {
		double subwidth, subheight;
		poppler_page_get_size(maindocpages[i], &subwidth, &subheight);
		if(subwidth != width || subheight != height) {
			fputs("Not all pages are the same size\n", stderr);
			goto endpartitionpage;
		}
	}

	cairo_surface_t *outsurface = cairo_pdf_surface_create(out, width, height);
	cairo_status_t s = cairo_surface_status(outsurface);
	if(s != CAIRO_STATUS_SUCCESS) {
		fprintf(stderr, "Failed to create PDF surface: %s\n", cairo_status_to_string(s));
		cairo_surface_destroy(outsurface);
	}
	cairo_t *context = cairo_create(outsurface);
	cairo_surface_destroy(outsurface);
	s = cairo_status(context);
	if(s != CAIRO_STATUS_SUCCESS) {
		fprintf(stderr, "Failed to create Cairo context: %s\n", cairo_status_to_string(s));
		goto endcontext;
	}

	if(npages > INT_MAX/2) {
		fprintf(stderr, "Failed to render pages: %s\n", strerror(EOVERFLOW));
		goto endcontext;
	}
	for(int i = 0; i < 2 * npages; i++) {
		poppler_page_render_for_printing((i % 2) ? partitionpage : maindocpages[i/2], context);
		cairo_show_page(context);
	}

	cairo_destroy(context);
	g_object_unref(partitionpage);
	for(int i = 0; i < npages; i++) {
		g_object_unref(maindocpages[i]);
	}
	free(maindocpages);
	g_object_unref(doctwo);
	g_object_unref(docone);
	if(close(filetwo) == -1) {
		fprintf(stderr, "Failed to close %s: %s\n", argv[1], strerror(errno));
		goto endfileone;
	}
	if(close(fileone) == -1) {
		fprintf(stderr, "Failed to close %s: %s\n", argv[0], strerror(errno));
		exit(EXIT_FAILURE);
	}
	exit(EXIT_SUCCESS);

endcontext:
	cairo_destroy(context);
endpartitionpage:
	g_object_unref(partitionpage);
	for(int i = 0; i < npages; i++) {
		g_object_unref(maindocpages[i]);
	}
	free(maindocpages);
enddoctwo:
	g_object_unref(doctwo);
enddocone:
	g_object_unref(docone);
endfiletwo:
	if(close(filetwo) == -1) {
		fprintf(stderr, "Failed to close %s: %s\n", argv[1], strerror(errno));
	}
endfileone:
	if(close(fileone) == -1) {
		fprintf(stderr, "Failed to close %s: %s\n", argv[0], strerror(errno));
	}
	exit(EXIT_FAILURE);
}

A  => meson.build +48 -0
@@ 1,48 @@
# SPDX-FileCopyrightText: 2022 John Scott <jscott@posteo.net>
# SPDX-License-Identifier: GPL-3.0-or-later
# This is free software subject to the GNU General Public License,
# either version three or, at your option, any later version.
project('pdfpartition', 'c', license: 'GPL-3.0-or-later')

cc = meson.get_compiler('c')
if not cc.compiles('''
#define _POSIX_C_SOURCE 200809L
#include <unistd.h>
#if _POSIX_VERSION < _POSIX_C_SOURCE
#error
#endif''')
	warning('A POSIX-conformant system is required')
endif

posix = ['-D_POSIX_C_SOURCE=200809L']
assert(cc.has_function('fstat', args: posix, prefix: '#include <sys/stat.h>'))
assert(cc.has_type('struct stat', args: posix, prefix: '#include <sys/stat.h>'))
assert(cc.has_function('getopt', args: posix, prefix: '#include <unistd.h>'))
assert(cc.has_function('open', args: posix, prefix: '#include <fcntl.h>'))

cairo = dependency('cairo-pdf')
if not cc.compiles('''
#include <cairo.h>
#include <cairo-pdf.h>
#if !CAIRO_HAS_PDF_SURFACE
#error
#endif''', dependencies: cairo, name: 'check for Cairo PDF support')
	error('Cairo PDF support is required')
endif

glib = dependency('glib-2.0')
poppler = dependency('poppler-glib')

if not cc.compiles('''
#include <poppler.h>
#if !POPPLER_HAS_CAIRO
#error
#endif''', dependencies: poppler, name: 'check that Poppler has Cairo support')
	error('support for Poppler Cairo output is required')
endif

if not cc.has_function('poppler_document_new_from_fd', dependencies: poppler)
	error('poppler_document_new_from_fd() is required; your Poppler version may be too old')
endif

executable('pdfpartition', 'main.c', install: true, dependencies: [cairo, glib, poppler])