diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | README | 4 | ||||
| -rwxr-xr-x | build.sh | 3 | ||||
| -rw-r--r-- | main.c | 237 |
4 files changed, 245 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89f9ac0 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +out/ @@ -0,0 +1,4 @@ +fdp - pdf +========= + +PDF playground diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..20907d7 --- /dev/null +++ b/build.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +gcc -g -Wall -Wextra main.c -o out/main @@ -0,0 +1,237 @@ +#include <stdio.h> +#include <assert.h> +#include <string.h> +#include <stdlib.h> +#include <math.h> +#include <stdarg.h> + +#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; +} |
