summaryrefslogtreecommitdiff
path: root/src/pdf.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/pdf.c')
-rw-r--r--src/pdf.c517
1 files changed, 517 insertions, 0 deletions
diff --git a/src/pdf.c b/src/pdf.c
new file mode 100644
index 0000000..c6ae876
--- /dev/null
+++ b/src/pdf.c
@@ -0,0 +1,517 @@
+#include "pdf.h"
+
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "util.h"
+
+#define PDF_PAGE_H 842.0
+#define PDF_ASCENT 718.0
+
+/* Adobe Core 14 AFM advance widths, indexed by WinAnsi code point. */
+static const unsigned short pdf_width_h[256] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 278, 278, 355, 556, 556, 889, 667, 191, 333, 333, 389, 584, 278, 333, 278, 278,
+ 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 278, 278, 584, 584, 584, 556,
+ 1015, 667, 667, 722, 722, 667, 611, 778, 722, 278, 500, 667, 556, 833, 722, 778,
+ 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 278, 278, 278, 469, 556,
+ 333, 556, 556, 500, 556, 556, 278, 556, 556, 222, 222, 500, 222, 833, 556, 556,
+ 556, 556, 333, 500, 278, 556, 500, 722, 500, 500, 500, 334, 260, 334, 584, 0,
+ 556, 0, 222, 556, 333, 1000, 556, 556, 333, 1000, 667, 333, 1000, 0, 611, 0,
+ 0, 222, 222, 333, 333, 350, 556, 1000, 333, 1000, 500, 333, 944, 0, 500, 667,
+ 278, 333, 556, 556, 556, 556, 260, 556, 333, 737, 370, 556, 584, 333, 737, 333,
+ 400, 584, 333, 333, 333, 556, 537, 278, 333, 333, 365, 556, 834, 834, 834, 611,
+ 667, 667, 667, 667, 667, 667, 1000, 722, 667, 667, 667, 667, 278, 278, 278, 278,
+ 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667, 667, 611,
+ 556, 556, 556, 556, 556, 556, 889, 500, 556, 556, 556, 556, 278, 278, 278, 278,
+ 556, 556, 556, 556, 556, 556, 556, 584, 611, 556, 556, 556, 556, 500, 556, 500,
+};
+
+static const unsigned short pdf_width_hb[256] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 278, 333, 474, 556, 556, 889, 722, 238, 333, 333, 389, 584, 278, 333, 278, 278,
+ 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 333, 333, 584, 584, 584, 611,
+ 975, 722, 722, 722, 722, 667, 611, 778, 722, 278, 556, 722, 611, 833, 722, 778,
+ 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 333, 278, 333, 584, 556,
+ 333, 556, 611, 556, 611, 556, 333, 611, 611, 278, 278, 556, 278, 889, 611, 611,
+ 611, 611, 389, 556, 333, 611, 556, 778, 556, 556, 500, 389, 280, 389, 584, 0,
+ 556, 0, 278, 556, 500, 1000, 556, 556, 333, 1000, 667, 333, 1000, 0, 611, 0,
+ 0, 278, 278, 500, 500, 350, 556, 1000, 333, 1000, 556, 333, 944, 0, 500, 667,
+ 278, 333, 556, 556, 556, 556, 280, 556, 333, 737, 370, 556, 584, 333, 737, 333,
+ 400, 584, 333, 333, 333, 611, 556, 278, 333, 333, 365, 556, 834, 834, 834, 611,
+ 722, 722, 722, 722, 722, 722, 1000, 722, 667, 667, 667, 667, 278, 278, 278, 278,
+ 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667, 667, 611,
+ 556, 556, 556, 556, 556, 556, 889, 556, 556, 556, 556, 556, 278, 278, 278, 278,
+ 611, 611, 611, 611, 611, 611, 611, 584, 611, 611, 611, 611, 611, 556, 611, 556,
+};
+
+struct pdf {
+ struct buf content;
+ int page;
+};
+
+static const unsigned short *widths_for(const char *font)
+{
+ return font && strcmp(font, "HB") == 0 ? pdf_width_hb : pdf_width_h;
+}
+
+static uint32_t utf8_next(const unsigned char **pp)
+{
+ const unsigned char *p = *pp;
+ uint32_t cp;
+ int extra, min;
+ unsigned char c = *p++;
+
+ if (c < 0x80) {
+ cp = c;
+ extra = 0;
+ min = 0;
+ } else if ((c & 0xE0) == 0xC0) {
+ cp = c & 0x1F;
+ extra = 1;
+ min = 0x80;
+ } else if ((c & 0xF0) == 0xE0) {
+ cp = c & 0x0F;
+ extra = 2;
+ min = 0x800;
+ } else if ((c & 0xF8) == 0xF0) {
+ cp = c & 0x07;
+ extra = 3;
+ min = 0x10000;
+ } else {
+ *pp = p;
+ return 0xFFFD;
+ }
+ for (int i = 0; i < extra; i++) {
+ if ((*p & 0xC0) != 0x80) {
+ *pp = p;
+ return 0xFFFD;
+ }
+ cp = (cp << 6) | (*p++ & 0x3F);
+ }
+ *pp = p;
+ if (cp < (uint32_t)min || cp > 0x10FFFF || (cp >= 0xD800 && cp <= 0xDFFF))
+ return 0xFFFD;
+ return cp;
+}
+
+static int winansi_byte(uint32_t cp)
+{
+ if ((cp >= 0x20 && cp < 0x7F) || (cp >= 0xA0 && cp <= 0xFF))
+ return (int)cp;
+ switch (cp) {
+ case 0x20AC: return 0x80;
+ case 0x201A: return 0x82;
+ case 0x0192: return 0x83;
+ case 0x201E: return 0x84;
+ case 0x2026: return 0x85;
+ case 0x2020: return 0x86;
+ case 0x2021: return 0x87;
+ case 0x02C6: return 0x88;
+ case 0x2030: return 0x89;
+ case 0x0160: return 0x8A;
+ case 0x2039: return 0x8B;
+ case 0x0152: return 0x8C;
+ case 0x017D: return 0x8E;
+ case 0x2018: return 0x91;
+ case 0x2019: return 0x92;
+ case 0x201C: return 0x93;
+ case 0x201D: return 0x94;
+ case 0x2022: return 0x95;
+ case 0x2013: return 0x96;
+ case 0x2014: return 0x97;
+ case 0x02DC: return 0x98;
+ case 0x2122: return 0x99;
+ case 0x0161: return 0x9A;
+ case 0x203A: return 0x9B;
+ case 0x0153: return 0x9C;
+ case 0x017E: return 0x9E;
+ case 0x0178: return 0x9F;
+ default: return -1;
+ }
+}
+
+static void bprintf(struct buf *b, const char *fmt, ...)
+ __attribute__((format(printf, 2, 3)));
+
+static void bprintf(struct buf *b, const char *fmt, ...)
+{
+ char tmp[512];
+ va_list ap;
+ int n;
+
+ va_start(ap, fmt);
+ n = vsnprintf(tmp, sizeof tmp, fmt, ap);
+ va_end(ap);
+ if (n < 0)
+ return;
+ if ((size_t)n >= sizeof tmp)
+ n = (int)sizeof tmp - 1;
+ buf_append(b, tmp, (size_t)n);
+}
+
+static void numfmt(char *out, size_t n, double v)
+{
+ long long t;
+ unsigned long long a, ip, fp;
+ const char *sign;
+
+ if (!(v > -1e12 && v < 1e12))
+ v = 0.0;
+ t = (long long)(v * 1000.0 + (v < 0 ? -0.5 : 0.5));
+ a = t < 0 ? (unsigned long long)(-t) : (unsigned long long)t;
+ ip = a / 1000;
+ fp = a % 1000;
+ sign = t < 0 ? "-" : "";
+ if (!fp)
+ snprintf(out, n, "%s%llu", sign, ip);
+ else if (!(fp % 100))
+ snprintf(out, n, "%s%llu.%llu", sign, ip, fp / 100);
+ else if (!(fp % 10))
+ snprintf(out, n, "%s%llu.%02llu", sign, ip, fp / 10);
+ else
+ snprintf(out, n, "%s%llu.%03llu", sign, ip, fp);
+}
+
+static void badd(struct buf *b, const char *s)
+{
+ buf_append(b, s, strlen(s));
+}
+
+static void c_add(struct pdf *p, const char *s)
+{
+ badd(&p->content, s);
+}
+
+static void c_addn(struct pdf *p, double v)
+{
+ char tmp[40];
+ numfmt(tmp, sizeof tmp, v);
+ c_add(p, tmp);
+}
+
+static void parse_rgb(const char *rgb, double out[3])
+{
+ unsigned v[3] = { 0, 0, 0 };
+
+ out[0] = out[1] = out[2] = 0.0;
+ if (!rgb || rgb[0] != '#' || strlen(rgb) < 7)
+ return;
+ for (int i = 0; i < 6; i++) {
+ int c = (unsigned char)rgb[1 + i];
+ int d;
+ if (c >= '0' && c <= '9')
+ d = c - '0';
+ else if (c >= 'a' && c <= 'f')
+ d = c - 'a' + 10;
+ else if (c >= 'A' && c <= 'F')
+ d = c - 'A' + 10;
+ else
+ return;
+ v[i / 2] = v[i / 2] * 16 + (unsigned)d;
+ }
+ out[0] = v[0] / 255.0;
+ out[1] = v[1] / 255.0;
+ out[2] = v[2] / 255.0;
+}
+
+static void c_rgb(struct pdf *p, const char *rgb, int stroke)
+{
+ double c[3];
+ parse_rgb(rgb, c);
+ c_addn(p, c[0]);
+ c_add(p, " ");
+ c_addn(p, c[1]);
+ c_add(p, " ");
+ c_addn(p, c[2]);
+ c_add(p, stroke ? " RG\n" : " rg\n");
+}
+
+static double flip(double y)
+{
+ return PDF_PAGE_H - y;
+}
+
+static unsigned char *utf8_to_winansi(const char *s, size_t *len)
+{
+ size_t cap = strlen(s) + 1;
+ unsigned char *out = xmalloc(cap);
+ size_t n = 0;
+
+ for (const unsigned char *p = (const unsigned char *)s; *p;) {
+ uint32_t cp = utf8_next(&p);
+ int c = winansi_byte(cp);
+ out[n++] = c < 0 ? '?' : (unsigned char)c;
+ }
+ *len = n;
+ return out;
+}
+
+struct pdf *pdf_new(void)
+{
+ struct pdf *p = xcalloc(1, sizeof *p);
+ buf_init(&p->content);
+ return p;
+}
+
+int pdf_page(struct pdf *p)
+{
+ if (!p || p->page)
+ return -1;
+ p->page = 1;
+ return 0;
+}
+
+void pdf_fill_rect(struct pdf *p, double x, double y, double w, double h,
+ const char *rgb)
+{
+ if (!p || w <= 0 || h <= 0)
+ return;
+ c_rgb(p, rgb, 0);
+ c_addn(p, x);
+ c_add(p, " ");
+ c_addn(p, flip(y + h));
+ c_add(p, " ");
+ c_addn(p, w);
+ c_add(p, " ");
+ c_addn(p, h);
+ c_add(p, " re f\n");
+}
+
+void pdf_line(struct pdf *p, double x1, double y1, double x2, double y2,
+ double width, const char *rgb)
+{
+ if (!p || width <= 0)
+ return;
+ c_rgb(p, rgb, 1);
+ c_addn(p, x1);
+ c_add(p, " ");
+ c_addn(p, flip(y1));
+ c_add(p, " m ");
+ c_addn(p, x2);
+ c_add(p, " ");
+ c_addn(p, flip(y2));
+ c_add(p, " l ");
+ c_addn(p, width);
+ c_add(p, " w S\n");
+}
+
+void pdf_text(struct pdf *p, double x, double baseline, const char *font,
+ double size, const char *rgb, const char *utf8)
+{
+ unsigned char *s;
+ size_t n;
+ struct buf esc;
+
+ if (!p || !utf8 || !*utf8 || size <= 0)
+ return;
+ s = utf8_to_winansi(utf8, &n);
+ buf_init(&esc);
+ for (size_t i = 0; i < n; i++) {
+ if (s[i] == '(' || s[i] == ')' || s[i] == '\\')
+ buf_append(&esc, "\\", 1);
+ buf_append(&esc, &s[i], 1);
+ }
+ c_rgb(p, rgb, 0);
+ c_add(p, "BT /");
+ c_add(p, font && strcmp(font, "HB") == 0 ? "F2" : "F1");
+ c_add(p, " ");
+ c_addn(p, size);
+ c_add(p, " Tf ");
+ c_addn(p, x);
+ c_add(p, " ");
+ c_addn(p, flip(baseline));
+ c_add(p, " Td (");
+ buf_append(&p->content, esc.p, esc.len);
+ c_add(p, ") Tj ET\n");
+ buf_free(&esc);
+ free(s);
+}
+
+double pdf_text_width(const char *font, double size, const char *utf8)
+{
+ const unsigned short *w = widths_for(font);
+ double total = 0.0;
+
+ if (!utf8 || size <= 0)
+ return 0.0;
+ for (const unsigned char *p = (const unsigned char *)utf8; *p;) {
+ int c = winansi_byte(utf8_next(&p));
+ total += w[c < 0 ? (int)'?' : c];
+ }
+ return total * size / 1000.0;
+}
+
+double pdf_font_ascent(const char *font, double size)
+{
+ (void)font;
+ return size > 0 ? size * PDF_ASCENT / 1000.0 : 0.0;
+}
+
+static int parse_num(const char **sp, double *out)
+{
+ const char *s = *sp;
+ double v = 0.0, scale = 0.1;
+ int neg = 0, digits = 0, e = 0, esign = 1;
+
+ if (*s == '+' || *s == '-')
+ neg = *s++ == '-';
+ while (*s >= '0' && *s <= '9') {
+ v = v * 10.0 + (*s++ - '0');
+ digits = 1;
+ }
+ if (*s == '.') {
+ s++;
+ while (*s >= '0' && *s <= '9') {
+ v += (*s++ - '0') * scale;
+ scale *= 0.1;
+ digits = 1;
+ }
+ }
+ if (!digits)
+ return 0;
+ if (*s == 'e' || *s == 'E') {
+ const char *q = s + 1;
+ if (*q == '+' || *q == '-')
+ esign = *q++ == '-' ? -1 : 1;
+ if (*q < '0' || *q > '9')
+ return 0;
+ while (*q >= '0' && *q <= '9')
+ e = e * 10 + (*q++ - '0');
+ if (e > 99)
+ return 0;
+ while (e-- > 0)
+ v = esign > 0 ? v * 10.0 : v / 10.0;
+ s = q;
+ }
+ *out = neg ? -v : v;
+ *sp = s;
+ return 1;
+}
+
+static void path_emit(struct pdf *p, const char *op, const double *v,
+ int coords, double x, double y)
+{
+ for (int i = 0; i < coords; i += 2) {
+ if (i)
+ c_add(p, " ");
+ c_addn(p, v[i] + x);
+ c_add(p, " ");
+ c_addn(p, flip(v[i + 1] + y));
+ }
+ c_add(p, " ");
+ c_add(p, op);
+ c_add(p, "\n");
+}
+
+/* SVG-style absolute path: "M 10 10 L 20 10 C ... Z", coordinates after the
+ operator. Extra pairs of M continue as L; repeated C sets continue as C. */
+void pdf_path(struct pdf *p, const char *path, double x, double y,
+ const char *rgb)
+{
+ const char *s = path;
+ double v[6];
+ char op = 0;
+ int n = 0;
+
+ if (!p || !path)
+ return;
+ c_rgb(p, rgb, 0);
+ while (*s) {
+ if ((unsigned char)*s <= ' ' || *s == ',') {
+ s++;
+ continue;
+ }
+ if ((*s >= 'A' && *s <= 'Z') || (*s >= 'a' && *s <= 'z')) {
+ if (*s == 'M' || *s == 'L' || *s == 'C') {
+ op = *s;
+ n = 0;
+ } else if (*s == 'Z') {
+ c_add(p, "h\n");
+ op = 0;
+ n = 0;
+ } else {
+ op = 0;
+ n = 0;
+ }
+ s++;
+ continue;
+ }
+ {
+ double d;
+ if (!parse_num(&s, &d)) {
+ s++;
+ continue;
+ }
+ if (!op)
+ continue;
+ if (n < (int)(sizeof v / sizeof v[0]))
+ v[n++] = d;
+ if ((op == 'M' || op == 'L') && n == 2) {
+ path_emit(p, op == 'M' ? "m" : "l", v, 2, x, y);
+ if (op == 'M')
+ op = 'L';
+ n = 0;
+ } else if (op == 'C' && n == 6) {
+ path_emit(p, "c", v, 6, x, y);
+ n = 0;
+ }
+ }
+ }
+ c_add(p, "f\n");
+}
+
+unsigned char *pdf_finish(struct pdf *p, size_t *len)
+{
+ struct buf out;
+ size_t off[7] = { 0 };
+ size_t xref;
+ unsigned char *data;
+
+ if (len)
+ *len = 0;
+ if (!p)
+ return NULL;
+ buf_init(&out);
+ badd(&out, "%PDF-1.4\n%\xe2\xe3\xcf\xd3\n");
+ off[1] = out.len;
+ badd(&out, "1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
+ off[2] = out.len;
+ badd(&out,
+ "2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n");
+ off[3] = out.len;
+ badd(&out,
+ "3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842]"
+ " /Resources << /Font << /F1 5 0 R /F2 6 0 R >> >>"
+ " /Contents 4 0 R >>\nendobj\n");
+ off[4] = out.len;
+ bprintf(&out, "4 0 obj\n<< /Length %zu >>\nstream\n", p->content.len);
+ buf_append(&out, p->content.p, p->content.len);
+ badd(&out, "\nendstream\nendobj\n");
+ off[5] = out.len;
+ badd(&out,
+ "5 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica"
+ " /Encoding /WinAnsiEncoding >>\nendobj\n");
+ off[6] = out.len;
+ badd(&out,
+ "6 0 obj\n<< /Type /Font /Subtype /Type1"
+ " /BaseFont /Helvetica-Bold"
+ " /Encoding /WinAnsiEncoding >>\nendobj\n");
+ xref = out.len;
+ bprintf(&out, "xref\n0 7\n0000000000 65535 f \n");
+ for (int i = 1; i <= 6; i++)
+ bprintf(&out, "%010zu 00000 n \n", off[i]);
+ bprintf(&out, "trailer\n<< /Size 7 /Root 1 0 R >>\nstartxref\n%zu\n%%%%EOF\n",
+ xref);
+ buf_free(&p->content);
+ data = out.p;
+ if (len)
+ *len = out.len;
+ free(p);
+ return data;
+}