#include "pdf.h" #include #include #include #include #include #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; }