#include #include #include #include #include #include #define PDF_HEADER_VERSION "%PDF-1.7" /* Inches to pdf user units */ #define UU 72 #define IN2UU(in) (round((in) * UU)) #define A4_WIDTH_IN 8.27 #define A4_HEIGHT_IN 11.69 static int pdf_width = IN2UU(A4_WIDTH_IN); static int pdf_height = IN2UU(A4_HEIGHT_IN); /* ------------------------------------------------- */ typedef struct PdfXref { size_t offset; size_t gen; bool is_free; } PdfXref; PdfXref pdf_get_xref(size_t offset, bool is_first); char* pdf_string_from_xref(PdfXref xe); /* ------------------------------------------------- */ typedef struct PdfXrefs { size_t total; /* Expected total */ size_t pos; /* Next empty position */ PdfXref *xs; } PdfXrefs; PdfXrefs pdf_xrefs_init(size_t n); void pdf_xrefs_append(PdfXrefs *xrefs, PdfXref new); /* ------------------------------------------------- */ #define PDF_INVALID_PARAM -1 #define PDF_SUCCESS 0 #define WRITE_STR(fp, str) fwrite(str, sizeof(char), strlen(str), fp) typedef struct PdfParams { const char* fname; size_t amount_pages; } PdfParams; ssize_t pdf_get_params(PdfParams *p, int argc, char *argv[]); static void pdf_usage(); size_t fmtwrite(FILE *fp, const char *fmt, ...); /* ------------------------------------------------- */ #define ASSERT(expr, msg) (assert(msg && (expr))) ssize_t str2nat(const char *str); /* ------------------------------------------------- */ int main(int argc, char *argv[]) { static PdfParams pdf_params; static unsigned long long pdf_offset = 0; if (pdf_get_params(&pdf_params, argc, argv) == PDF_INVALID_PARAM) { pdf_usage(); return 1; } FILE *fp = fopen(pdf_params.fname, "w"); ASSERT(fp != NULL, "Opening file"); PdfXrefs xrefs = pdf_xrefs_init(pdf_params.amount_pages+3); // Header pdf_offset += WRITE_STR(fp, ""PDF_HEADER_VERSION"\n"); // Body pdf_xrefs_append(&xrefs, pdf_get_xref(pdf_offset, false)); pdf_offset += fmtwrite( fp, "%d 0 obj\n<<\n\t/Type /Catalog\n\t/Pages %d 0 R\n>>\nendobj\n", 1, 2 ); /* [3 0 R 4 0 R ...] */ pdf_xrefs_append(&xrefs, pdf_get_xref(pdf_offset, false)); char *kids = malloc(sizeof(char) * 7 * pdf_params.amount_pages); assert(kids != NULL); for (size_t i = 0; i < pdf_params.amount_pages; ++i) { sprintf( &kids[strlen(kids)], "%s%ld 0 R%s", i == 0 ? "[" : "", i+3, i == pdf_params.amount_pages-1 ? "]" : " " ); } char buf[1024] = { 0 }; sprintf( buf, "%d 0 obj\n<<\n\t/Type /Pages\n\t/Kids %s\n\t/Count %ld\n>>\nendobj\n", 2, kids, pdf_params.amount_pages ); pdf_offset += WRITE_STR(fp, buf); const char *parent = "2 0 R"; for (size_t i = 0; i < pdf_params.amount_pages; ++i) { pdf_xrefs_append(&xrefs, pdf_get_xref(pdf_offset, false)); sprintf( buf, "%ld 0 obj\n<<\n\t/Type /Page\n\t/Parent %s\n\t/Resources <<>>\n\t/MediaBox [0 0 %d %d]\n>>\nendobj\n", i+3, parent, pdf_width, pdf_height ); pdf_offset += WRITE_STR(fp, buf); } // Cross-reference table size_t pos_xref = pdf_offset; pdf_offset += fmtwrite( fp, "xref\n0 %ld\n", xrefs.total ); for (size_t i = 0; i < xrefs.total; ++i) { pdf_offset += fmtwrite(fp, "%s\n", pdf_string_from_xref(xrefs.xs[i])); } // Trailer pdf_offset += fmtwrite( fp, "trailer\n<<\n\t/Size %ld\n\t/Root %ld 0 R\n>>\nstartxref\n%ld\n\%%%%EOF\n", xrefs.total, (size_t) 1, pos_xref ); fclose(fp); return 0; } /* ------------------------------------------------- */ PdfXref pdf_get_xref(size_t offset, bool is_first) { return (PdfXref) { .offset = is_first ? 0 : offset, .gen = is_first ? 65535 : 0, .is_free = is_first }; } char* pdf_string_from_xref(PdfXref xe) { char *buf = malloc(18 * sizeof(char) + 1); assert(buf != NULL); sprintf(buf, "%0*ld %0*ld %c", 10, xe.offset, 5, xe.gen, xe.is_free ? 'f' : 'n'); return buf; } /* ------------------------------------------------- */ PdfXrefs pdf_xrefs_init(size_t n) { PdfXref *xs = malloc(n * sizeof(PdfXref)); assert(xs != NULL); xs[0] = pdf_get_xref(0, true); return (PdfXrefs) { .total = n, .pos = 1, .xs = xs }; } void pdf_xrefs_append(PdfXrefs *xrefs, PdfXref new) { ASSERT( xrefs->pos < xrefs->total, "Tried appending more xrefs to already filled list" ); xrefs->xs[xrefs->pos++] = new; } /* ------------------------------------------------- */ ssize_t pdf_get_params(PdfParams *p, int argc, char *argv[]) { if (argc != 3) { return PDF_INVALID_PARAM; } ssize_t result = str2nat(argv[2]); if (result == -1 || result == 0) return PDF_INVALID_PARAM; p->fname = argv[1]; p->amount_pages = result; return PDF_SUCCESS; } static void pdf_usage() { fprintf(stderr, "Usage: %s [filename].pdf [amount pages]\n", __FILE__); } size_t fmtwrite(FILE *fp, const char *fmt, ...) { char *buf = malloc(strlen(fmt) * sizeof(char)); if (buf == NULL) return 0; va_list args; va_start(args, fmt); vsprintf(buf, fmt, args); va_end(args); size_t status = WRITE_STR(fp, buf); free(buf); return status; } /* ------------------------------------------------- */ ssize_t str2nat(const char *str) { size_t n; int pos; if (str[0] == '-' || sscanf(str, "%ld%n", &n, &pos) == EOF || pos != (int) strlen(str)) { return -1; } return n; }