From ea3f9b67e5e4e4b3453df55af9758a94c023d6f7 Mon Sep 17 00:00:00 2001 From: Anders Betts Date: Sun, 20 Sep 2026 14:50:05 +0200 Subject: pdf: minimal PDF writer for generated documents --- tests/pdf_check.c | 160 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 tests/pdf_check.c (limited to 'tests/pdf_check.c') diff --git a/tests/pdf_check.c b/tests/pdf_check.c new file mode 100644 index 0000000..4d56cab --- /dev/null +++ b/tests/pdf_check.c @@ -0,0 +1,160 @@ +#include +#include +#include + +#include "pdf.h" + +static int failures; + +static void check(int cond, const char *what) +{ + if (!cond) { + printf("FAIL %s\n", what); + failures++; + } else { + printf("ok %s\n", what); + } +} + +static void near(const char *what, double got, double want) +{ + double d = got - want; + if (d < 0) + d = -d; + if (d > 0.001) { + printf("FAIL %s: got %.4f want %.4f\n", what, got, want); + failures++; + } else { + printf("ok %s: %.4f\n", what, got); + } +} + +static int contains(const unsigned char *data, size_t len, const char *needle) +{ + size_t n = strlen(needle); + if (n > len) + return 0; + for (size_t i = 0; i + n <= len; i++) + if (memcmp(data + i, needle, n) == 0) + return 1; + return 0; +} + +static void put_text(struct pdf *p, const char *text, double x, double base, + const char *font, double size, const char *rgb) +{ + double w = pdf_text_width(font, size, text); + pdf_text(p, x, base, font, size, rgb, text); + printf("@@%s x=%.3f y=%.3f w=%.3f\n", text, x, base, w); +} + +int main(int argc, char **argv) +{ + struct pdf *p = pdf_new(); + unsigned char *data; + size_t len = 0; + + near("width H 10 Hello", pdf_text_width("H", 10, "Hello"), 22.78); + near("width HB 12 Hello", pdf_text_width("HB", 12, "Hello"), 29.34); + near("width H 12 aao", pdf_text_width("H", 12, "\xc3\xa5\xc3\xa4\xc3\xb6"), + 20.016); + near("width H 12 latin", + pdf_text_width("H", 12, "\xc3\x85\xc3\x84\xc3\x96\xc3\xa5\xc3\xa4" + "\xc3\xb6\xc3\xa9\xc3\xa1\xe2\x80\x93\xe2\x80" + "\x99\xe2\x82\xac"), + 74.712); + near("width H 10 unmappable", pdf_text_width("H", 10, "Unmappable \xe2\x98\x83"), + 65.03); + near("ascent H 10", pdf_font_ascent("H", 10), 7.18); + near("ascent HB 14", pdf_font_ascent("HB", 14), 10.052); + near("width default font", pdf_text_width(NULL, 10, "Hello"), 22.78); + near("width invalid utf8", pdf_text_width("H", 10, "\xff"), 5.56); + near("width 4-byte codepoint", pdf_text_width("H", 10, "\xf0\x9f\x98\x80"), + 5.56); + check(pdf_text_width("H", 0, "Hello") == 0.0, "width size 0"); + check(pdf_text_width("H", 10, NULL) == 0.0, "width NULL text"); + + check(pdf_page(p) == 0, "pdf_page"); + check(pdf_page(p) == -1, "pdf_page twice rejected"); + + pdf_fill_rect(p, 0, 48, 595, 32, "#314c59"); + pdf_fill_rect(p, 300, 300, 60, 40, "#ffffff"); + pdf_line(p, 0, 80, 595, 80, 0.5, "#314c59"); + pdf_line(p, 20, 100, 575, 100, 0.25, "#314c59"); + + put_text(p, "MAKANDRA", 20, 70, "HB", 16, "#ffffff"); + put_text(p, "FAKTURA", 575 - pdf_text_width("H", 16, "FAKTURA"), 70, + "H", 16, "#ffffff"); + put_text(p, "Left", 20, 120, "H", 10, "#314c59"); + put_text(p, "Right", 575 - pdf_text_width("H", 10, "Right"), 120, + "H", 10, "#314c59"); + put_text(p, "V\xc3\xa4nster", 20, 140, "H", 10, "#314c59"); + put_text(p, "H\xc3\xb6ger", 575 - pdf_text_width("HB", 10, "H\xc3\xb6ger"), + 140, "HB", 10, "#314c59"); + put_text(p, "\xc3\x85\xc3\x84\xc3\x96\xc3\xa5\xc3\xa4\xc3\xb6\xc3\xa9" + "\xc3\xa1\xe2\x80\x93\xe2\x80\x99\xe2\x82\xac", + 20, 160, "H", 12, "#314c59"); + put_text(p, "Col", 300, 160, "H", 10, "#314c59"); + put_text(p, "Col2", 300, 180, "H", 10, "#314c59"); + put_text(p, "A(B)C\\D", 20, 180, "H", 10, "#314c59"); + put_text(p, "((((((((((((((((((((((((((((((((", 20, 200, "H", 8, "#314c59"); + near("width 32 parens", pdf_text_width("H", 8, + "(((((((((((((((((((((((((((((((("), 85.248); + + pdf_path(p, "M 10 10 L 90 10 C 100 10 100 60 90 60 L 10 60 Z", + 450, 300, "#314c59"); + pdf_path(p, "M.5.5L20 20Z", 100, 400, "#314c59"); + + data = pdf_finish(p, &len); + check(data != NULL && len > 500, "pdf_finish bytes"); + if (!data) { + printf("FAIL: no data\n"); + return 1; + } + check(memcmp(data, "%PDF-1.4", 8) == 0, "header"); + check(memcmp(data + len - 6, "%%EOF\n", 6) == 0, "trailer eof"); + check(memchr(data, '\0', len) == NULL, "no NUL bytes"); + check(contains(data, len, "/MediaBox [0 0 595 842]"), "MediaBox"); + check(contains(data, len, "/Count 1"), "page count"); + check(contains(data, len, "/BaseFont /Helvetica "), "Helvetica font"); + check(contains(data, len, "/BaseFont /Helvetica-Bold"), "Helvetica-Bold"); + check(contains(data, len, "/Encoding /WinAnsiEncoding"), "WinAnsi"); + check(contains(data, len, "/Length "), "stream length"); + check(contains(data, len, "1 0 obj"), "object 1"); + check(contains(data, len, "6 0 obj"), "object 6"); + check(!contains(data, len, "\r"), "no CR"); + + if (argc > 1) { + FILE *f = fopen(argv[1], "wb"); + if (!f) { + printf("FAIL: cannot write %s\n", argv[1]); + failures++; + } else { + fwrite(data, 1, len, f); + fclose(f); + printf("wrote %s (%zu bytes)\n", argv[1], len); + } + } + free(data); + + { + struct pdf *e = pdf_new(); + unsigned char *d; + size_t n = 1; + + put_text(e, "no explicit pdf_page", 20, 100, "H", 10, "#000000"); + d = pdf_finish(e, &n); + check(d != NULL && n > 0 && memcmp(d, "%PDF-1.4", 8) == 0, + "implicit page document"); + check(d != NULL && memcmp(d + n - 6, "%%EOF\n", 6) == 0, + "implicit page eof"); + free(d); + } + check(pdf_finish(NULL, &len) == NULL && len == 0, "finish NULL"); + + if (failures) + printf("%d failures\n", failures); + else + puts("all ok"); + return failures ? 1 : 0; +} -- cgit v1.3