aboutsummaryrefslogtreecommitdiff
path: root/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'main.c')
-rw-r--r--main.c237
1 files changed, 237 insertions, 0 deletions
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..303d26d
--- /dev/null
+++ b/main.c
@@ -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;
+}