aboutsummaryrefslogtreecommitdiffstats
path: root/src/pdf_writer.c
diff options
context:
space:
mode:
authoryzrh <yzrh@tuta.io>2020-08-14 22:04:26 +0000
committeryzrh <yzrh@tuta.io>2020-08-14 22:04:26 +0000
commit12ecdd71592eccf7bdb6214edbc7318246469c1c (patch)
treefda27e41c37a2345702ad3e90480154d975e426f /src/pdf_writer.c
downloadmelon-12ecdd71592eccf7bdb6214edbc7318246469c1c.tar.gz
melon-12ecdd71592eccf7bdb6214edbc7318246469c1c.tar.zst
Initial commit.
Diffstat (limited to 'src/pdf_writer.c')
-rw-r--r--src/pdf_writer.c188
1 files changed, 188 insertions, 0 deletions
diff --git a/src/pdf_writer.c b/src/pdf_writer.c
new file mode 100644
index 0000000..3cf4f7c
--- /dev/null
+++ b/src/pdf_writer.c
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2020, yzrh <yzrh@tuta.io>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <openssl/md5.h>
+
+#include "pdf.h"
+
+int
+pdf_dump_obj(pdf_object_t **pdf, FILE **fp)
+{
+ if (*pdf == NULL || *fp == NULL)
+ return 1;
+
+ long cur;
+
+ pdf_object_t *ptr = (*pdf)->next;
+ while (ptr != NULL) {
+ ptr->address = cur = ftell(*fp);
+
+ fprintf(*fp, "%d 0 obj\n", ptr->id);
+
+ if (ptr->dictionary != NULL)
+ fputs(ptr->dictionary, *fp);
+ else if (ptr->object != NULL)
+ fputs(ptr->object, *fp);
+ else if (ptr->stream == NULL)
+ fputs("null\n", *fp);
+
+ if (ptr->stream != NULL) {
+ fputs("stream\r\n", *fp);
+ fwrite(ptr->stream, ptr->stream_size, 1, *fp);
+ fputs("endstream\n", *fp);
+ }
+
+ fputs("endobj\n", *fp);
+
+ ptr->size = ftell(*fp) - cur;
+
+ ptr = ptr->next;
+ }
+
+ return 0;
+}
+
+int
+pdf_dump_header(pdf_object_t **pdf, FILE **fp)
+{
+ if (*pdf == NULL || *fp == NULL)
+ return 1;
+
+ fputs("%PDF-1.7\n", *fp);
+
+ const unsigned char bin[4] = {
+ 0xf6,
+ 0xe4,
+ 0xfc,
+ 0xdf,
+ };
+
+ fputs("%", *fp);
+ fwrite(bin, 4, 1, *fp);
+ fputs("\n", *fp);
+
+ return 0;
+}
+
+int
+pdf_dump_xref(pdf_object_t **pdf, FILE **fp)
+{
+ if (*pdf == NULL || *fp == NULL)
+ return 1;
+
+ fputs("xref\n", *fp);
+
+ pdf_object_t *ptr = *pdf;
+
+ pdf_object_t *start = ptr;
+ int count = 1;
+
+ while (ptr != NULL) {
+ if (ptr->next == NULL ||
+ (ptr->next != NULL && ptr->next->id != ptr->id + 1)) {
+ fprintf(*fp, "%d %d\n", start->id, count);
+
+ for (; count > 0; count--) {
+ fprintf(*fp, "%010d %05d %s\r\n",
+ start->address,
+ start->address > 0 ? 0 : 65535,
+ start->size > 0 ? "n" : "f");
+ start = start->next;
+ }
+
+ if (ptr->next != NULL)
+ start = ptr->next;
+ }
+
+ ptr = ptr->next;
+ count++;
+ }
+
+ return 0;
+}
+
+int
+pdf_dump_trailer(pdf_object_t **pdf, FILE **fp, int xref)
+{
+ if (*pdf == NULL || *fp == NULL)
+ return 1;
+
+ fputs("trailer\n", *fp);
+
+ fputs("<<\n", *fp);
+
+ /*
+ * File identifiers should be generated using
+ * (a) Current time
+ * (b) File path
+ * (c) Size of file
+ * (d) Values of all entries in the
+ * file's document information dictionary
+ *
+ * It is recommended to be computed according to RFC 1321
+ */
+
+ time_t timestamp = time(NULL);
+ int size = pdf_get_size(pdf);
+
+ int buf_size;
+ char buf[64];
+
+ buf_size = snprintf(buf, 64, "%lx%x", timestamp, size);
+
+ unsigned char str[64];
+ memcpy(str, buf, 64);
+
+ unsigned char fid[MD5_DIGEST_LENGTH];
+ MD5(str, buf_size, fid);
+
+ pdf_object_t *ptr = *pdf;
+ while (ptr->next != NULL)
+ ptr = ptr->next;
+
+ /*
+ * TODO: Document information dictionary
+ * `"/Producer (Melon)"'
+ * `"/CreationDate (D:YYYYMMDDHHmmSS+00'00')"'
+ *
+ * Trailer dictionary
+ * `"/Info %d 0 R"'
+ */
+ fprintf(*fp,
+ "/Size %d\n/Root %d 0 R\n",
+ ptr->id + 1,
+ pdf_get_catalog_id(pdf));
+
+ fputs("/ID [", *fp);
+
+ for (int i = 0; i < 2; i++) {
+ fputs("<", *fp);
+
+ for (int j = 0; j < MD5_DIGEST_LENGTH; j++)
+ fprintf(*fp, "%02x", fid[j]);
+
+ fputs(">", *fp);
+
+ if (i < 1)
+ fputs(" ", *fp);
+ }
+
+ fputs("]\n", *fp);
+
+ fputs(">>\n", *fp);
+
+ fputs("startxref\n", *fp);
+
+ fprintf(*fp, "%d\n", xref);
+
+ fputs("%%EOF\n", *fp);
+
+ return 0;
+}