#define _POSIX_C_SOURCE 200809L
#include <assert.h>
#include <cairo.h>
#include <cairo-pdf.h>
#include <errno.h>
#include <fcntl.h>
#include <gio/gio.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);
}
GFile *fileone, *filetwo;
fileone = g_file_new_for_path(argv[0]);
filetwo = g_file_new_for_path(argv[1]);
GError *err = NULL;
PopplerDocument *const docone = poppler_document_new_from_gfile(fileone, NULL, 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_gfile(filetwo, NULL, 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);
g_object_unref(filetwo);
g_object_unref(fileone);
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:
g_object_unref(filetwo);
g_object_unref(fileone);
exit(EXIT_FAILURE);
}