diff options
Diffstat (limited to 'src/pdf.c')
| -rw-r--r-- | src/pdf.c | 517 |
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; +} |
