summaryrefslogtreecommitdiff
path: root/tests/pdf_check.c
diff options
context:
space:
mode:
authorAnders Betts <anders.betts@gmail.com>2026-09-20 14:50:05 +0200
committerAnders Betts <anders.betts@gmail.com>2026-09-20 14:50:05 +0200
commitea3f9b67e5e4e4b3453df55af9758a94c023d6f7 (patch)
tree3b248af093a62e5b2c9bd21a3111dcf8c22fee87 /tests/pdf_check.c
parent412dfcca675114b582e4dc9d5739a889066e6277 (diff)
downloadbokf-ea3f9b67e5e4e4b3453df55af9758a94c023d6f7.tar.gz
bokf-ea3f9b67e5e4e4b3453df55af9758a94c023d6f7.zip
pdf: minimal PDF writer for generated documents
Diffstat (limited to 'tests/pdf_check.c')
-rw-r--r--tests/pdf_check.c160
1 files changed, 160 insertions, 0 deletions
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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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;
+}