packages feed

jammittools 0.5.5.1 → 0.5.5.2

raw patch · 9 files changed

+2830/−106 lines, 9 filesdep +Win32dep −HPDFdep −temporarydep ~JuicyPixelsdep ~containers

Dependencies added: Win32

Dependencies removed: HPDF, temporary

Dependency ranges changed: JuicyPixels, containers

Files

CHANGELOG.md view
@@ -1,3 +1,8 @@+## v0.5.5.2
+
+  * Supports GHC 8.6
+  * Replaced `HPDF` with C library `pdfgen`
+
 ## v0.5.5.1
 
   * Switched to new `conduit` syntax
+ cbits/pdfgen.c view
@@ -0,0 +1,2242 @@+/**+ * Simple engine for creating PDF files.+ * It supports text, shapes, images etc...+ * Capable of handling millions of objects without too much performance+ * penalty.+ * Public domain license - no warrenty implied; use at your own risk.+ */++/**+ * PDF HINTS & TIPS+ * The following sites have various bits & pieces about PDF document+ * generation+ * http://www.mactech.com/articles/mactech/Vol.15/15.09/PDFIntro/index.html+ * http://gnupdf.org/Introduction_to_PDF+ * http://www.planetpdf.com/mainpage.asp?WebPageID=63+ * http://archive.vector.org.uk/art10008970+ * http://www.adobe.com/devnet/acrobat/pdfs/pdf_reference_1-7.pdf+ * https://blog.idrsolutions.com/2013/01/understanding-the-pdf-file-format-overview/+ *+ * To validate the PDF output, there are several online validators:+ * http://www.validatepdfa.com/online.htm+ * http://www.datalogics.com/products/callas/callaspdfA-onlinedemo.asp+ * http://www.pdf-tools.com/pdf/validate-pdfa-online.aspx+ *+ * In addition the 'pdftk' server can be used to analyse the output:+ * https://www.pdflabs.com/docs/pdftk-cli-examples/+ *+ * PDF page markup operators:+ * b    closepath, fill,and stroke path.+ * B    fill and stroke path.+ * b*   closepath, eofill,and stroke path.+ * B*   eofill and stroke path.+ * BI   begin image.+ * BMC  begin marked content.+ * BT   begin text object.+ * BX   begin section allowing undefined operators.+ * c    curveto.+ * cm   concat. Concatenates the matrix to the current transform.+ * cs   setcolorspace for fill.+ * CS   setcolorspace for stroke.+ * d    setdash.+ * Do   execute the named XObject.+ * DP   mark a place in the content stream, with a dictionary.+ * EI   end image.+ * EMC  end marked content.+ * ET   end text object.+ * EX   end section that allows undefined operators.+ * f    fill path.+ * f*   eofill Even/odd fill path.+ * g    setgray (fill).+ * G    setgray (stroke).+ * gs   set parameters in the extended graphics state.+ * h    closepath.+ * i    setflat.+ * ID   begin image data.+ * j    setlinejoin.+ * J    setlinecap.+ * k    setcmykcolor (fill).+ * K    setcmykcolor (stroke).+ * l    lineto.+ * m    moveto.+ * M    setmiterlimit.+ * n    end path without fill or stroke.+ * q    save graphics state.+ * Q    restore graphics state.+ * re   rectangle.+ * rg   setrgbcolor (fill).+ * RG   setrgbcolor (stroke).+ * s    closepath and stroke path.+ * S    stroke path.+ * sc   setcolor (fill).+ * SC   setcolor (stroke).+ * sh   shfill (shaded fill).+ * Tc   set character spacing.+ * Td   move text current point.+ * TD   move text current point and set leading.+ * Tf   set font name and size.+ * Tj   show text.+ * TJ   show text, allowing individual character positioning.+ * TL   set leading.+ * Tm   set text matrix.+ * Tr   set text rendering mode.+ * Ts   set super/subscripting text rise.+ * Tw   set word spacing.+ * Tz   set horizontal scaling.+ * T*   move to start of next line.+ * v    curveto.+ * w    setlinewidth.+ * W    clip.+ * y    curveto.+ */++// MT: commented this out, breaks things for some reason+// #define _POSIX_SOURCE /* For localtime_r */+#include <ctype.h>+#include <errno.h>+#include <limits.h>+#include <math.h>+#include <stdarg.h>+#include <stdio.h>+#include <stdlib.h>+#include <string.h>+#include <strings.h>+#include <sys/stat.h>+#include <sys/types.h>+#include <time.h>+#include <unistd.h>++#include "pdfgen.h"++#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))++#define PDF_RGB_R(c) ((((c) >> 16) & 0xff) / 255.0)+#define PDF_RGB_G(c) ((((c) >> 8) & 0xff) / 255.0)+#define PDF_RGB_B(c) ((((c) >> 0) & 0xff) / 255.0)+#define PDF_IS_TRANSPARENT(c) (((c) >> 24) == 0xff)++#if defined(_MSC_VER) && (_MSC_VER < 1900)+/*+ * As stated here:+ * http://stackoverflow.com/questions/70013/how-to-detect-if-im-compiling-code-with-visual-studio-2008+ * Visual Studio 2015 has better support for C99+ * We need to use __inline for older version.+ */+#define inline __inline+#define snprintf _snprintf+#define strcasecmp _stricmp+#define strncasecmp _strnicmp+#define SKIP_ATTRIBUTE+#endif // _MSC_VER && _MSC_VER < 1900++#ifndef M_SQRT2+#define M_SQRT2 1.41421356237309504880f+#endif++typedef struct pdf_object pdf_object;++enum {+    OBJ_none, /* skipped */+    OBJ_info,+    OBJ_stream,+    OBJ_font,+    OBJ_page,+    OBJ_bookmark,+    OBJ_outline,+    OBJ_catalog,+    OBJ_pages,+    OBJ_image,++    OBJ_count,+};++struct flexarray {+    void ***bins;+    int item_count;+    int bin_count;+};++/**+ * Simple dynamic string object. Tries to store a reasonable amount on the+ * stack before falling back to malloc once things get large+ */+struct dstr {+    char static_data[128];+    char *data;+    int alloc_len;+    int used_len;+};++struct pdf_object {+    int type;                /* See OBJ_xxxx */+    int index;               /* PDF output index */+    int offset;              /* Byte position within the output file */+    struct pdf_object *prev; /* Previous of this type */+    struct pdf_object *next; /* Next of this type */+    union {+        struct {+            struct pdf_object *page;+            char name[64];+            struct pdf_object *parent;+            struct flexarray children;+        } bookmark;+        struct dstr stream;+        struct {+            int width;+            int height;+            struct flexarray children;+        } page;+        struct pdf_info *info;+        struct {+            char name[64];+            int index;+        } font;+    };+};++struct pdf_doc {+    char errstr[128];+    int errval;+    struct flexarray objects;++    int width;+    int height;++    struct pdf_object *current_font;++    struct pdf_object *last_objects[OBJ_count];+    struct pdf_object *first_objects[OBJ_count];+};++/**+ * Simple flexible resizing array implementation+ * The bins get larger in powers of two+ * bin 0 = 1024 items+ *     1 = 2048 items+ *     2 = 4096 items+ *     etc...+ */+/* What is the first index that will be in the given bin? */+#define MIN_SHIFT 10+#define MIN_OFFSET ((1 << MIN_SHIFT) - 1)+static int bin_offset[] = {+    (1 << (MIN_SHIFT + 0)) - 1 - MIN_OFFSET,+    (1 << (MIN_SHIFT + 1)) - 1 - MIN_OFFSET,+    (1 << (MIN_SHIFT + 2)) - 1 - MIN_OFFSET,+    (1 << (MIN_SHIFT + 3)) - 1 - MIN_OFFSET,+    (1 << (MIN_SHIFT + 4)) - 1 - MIN_OFFSET,+    (1 << (MIN_SHIFT + 5)) - 1 - MIN_OFFSET,+    (1 << (MIN_SHIFT + 6)) - 1 - MIN_OFFSET,+    (1 << (MIN_SHIFT + 7)) - 1 - MIN_OFFSET,+    (1 << (MIN_SHIFT + 8)) - 1 - MIN_OFFSET,+    (1 << (MIN_SHIFT + 9)) - 1 - MIN_OFFSET,+    (1 << (MIN_SHIFT + 10)) - 1 - MIN_OFFSET,+    (1 << (MIN_SHIFT + 11)) - 1 - MIN_OFFSET,+    (1 << (MIN_SHIFT + 12)) - 1 - MIN_OFFSET,+    (1 << (MIN_SHIFT + 13)) - 1 - MIN_OFFSET,+    (1 << (MIN_SHIFT + 14)) - 1 - MIN_OFFSET,+    (1 << (MIN_SHIFT + 15)) - 1 - MIN_OFFSET,+};++static inline int flexarray_get_bin(struct flexarray *flex, int index)+{+    (void)flex;+    for (int i = 0; i < ARRAY_SIZE(bin_offset); i++)+        if (index < bin_offset[i])+            return i - 1;+    return -1;+}++static inline int flexarray_get_bin_size(struct flexarray *flex, int bin)+{+    (void)flex;+    if (bin >= ARRAY_SIZE(bin_offset))+        return -1;+    int next = bin_offset[bin + 1];+    return next - bin_offset[bin];+}++static inline int flexarray_get_bin_offset(struct flexarray *flex, int bin,+                                           int index)+{+    (void)flex;+    return index - bin_offset[bin];+}++static void flexarray_clear(struct flexarray *flex)+{+    for (int i = 0; i < flex->bin_count; i++)+        free(flex->bins[i]);+    free(flex->bins);+    flex->bin_count = 0;+    flex->item_count = 0;+}++static inline int flexarray_size(struct flexarray *flex)+{+    return flex->item_count;+}++static int flexarray_set(struct flexarray *flex, int index, void *data)+{+    int bin = flexarray_get_bin(flex, index);+    if (bin < 0)+        return -EINVAL;+    if (bin >= flex->bin_count) {+        void *bins =+            realloc(flex->bins, (flex->bin_count + 1) * sizeof(flex->bins));+        if (!bins)+            return -ENOMEM;+        flex->bin_count++;+        flex->bins = bins;+        flex->bins[flex->bin_count - 1] =+            calloc(flexarray_get_bin_size(flex, flex->bin_count - 1),+                   sizeof(void *));+        if (!flex->bins[flex->bin_count - 1]) {+            flex->bin_count--;+            return -ENOMEM;+        }+    }+    flex->item_count++;+    flex->bins[bin][flexarray_get_bin_offset(flex, bin, index)] = data;+    return flex->item_count - 1;+}++static inline int flexarray_append(struct flexarray *flex, void *data)+{+    return flexarray_set(flex, flexarray_size(flex), data);+}++static inline void *flexarray_get(struct flexarray *flex, int index)+{+    int bin;++    if (index >= flex->item_count)+        return NULL;+    bin = flexarray_get_bin(flex, index);+    if (bin < 0 || bin >= flex->bin_count)+        return NULL;+    return flex->bins[bin][flexarray_get_bin_offset(flex, bin, index)];+}++/**+ * Simple dynamic string object. Tries to store a reasonable amount on the+ * stack before falling back to malloc once things get large+ */++#define INIT_DSTR                                                            \+    (struct dstr)                                                            \+    {                                                                        \+        .static_data = {0}, .data = NULL, .alloc_len = 0, .used_len = 0      \+    }++static char *dstr_data(struct dstr *str)+{+    return str->data ? str->data : str->static_data;+}++static int dstr_len(struct dstr *str)+{+    return str->used_len;+}++static int dstr_ensure(struct dstr *str, int len)+{+    if (len <= str->alloc_len)+        return 0;+    if (!str->data && len <= sizeof(str->static_data))+        str->alloc_len = len;+    else if (str->alloc_len < len) {+        char *new_data;+        int new_len;++        new_len = len + 4096;+        new_data = realloc(str->data, new_len);+        if (!new_data)+            return -ENOMEM;+        // If we move beyond the on-stack buffer, copy the old data out+        if (!str->data && str->used_len > 0)+            memcpy(new_data, str->static_data, str->used_len + 1);+        str->data = new_data;+        str->alloc_len = new_len;+    }+    return 0;+}++#ifndef SKIP_ATTRIBUTE+static int dstr_printf(struct dstr *str, const char *fmt, ...)+    __attribute__((format(gnu_printf, 2, 3)));+#endif+static int dstr_printf(struct dstr *str, const char *fmt, ...)+{+    va_list ap, aq;+    int len;++    va_start(ap, fmt);+    va_copy(aq, ap);+    len = vsnprintf(NULL, 0, fmt, ap);+    if (dstr_ensure(str, str->used_len + len + 1) < 0) {+        va_end(ap);+        va_end(aq);+        return -ENOMEM;+    }+    vsprintf(dstr_data(str) + str->used_len, fmt, aq);+    str->used_len += len;+    va_end(ap);+    va_end(aq);++    return len;+}++static int dstr_append_data(struct dstr *str, const void *extend, int len)+{+    if (dstr_ensure(str, str->used_len + len) < 0)+        return -ENOMEM;+    memcpy(dstr_data(str) + str->used_len, extend, len);+    str->used_len += len;+    return len;+}++static int dstr_append(struct dstr *str, const char *extend)+{+    int len = strlen(extend);+    if (dstr_ensure(str, str->used_len + len + 1) < 0)+        return -ENOMEM;+    memcpy(dstr_data(str) + str->used_len, extend, len + 1);+    str->used_len += len;+    return len;+}++static void dstr_free(struct dstr *str)+{+    if (str->data)+        free(str->data);+    *str = INIT_DSTR;+}++/**+ * PDF Implementation+ */++#ifndef SKIP_ATTRIBUTE+static int pdf_set_err(struct pdf_doc *doc, int errval, const char *buffer,+                       ...) __attribute__((format(gnu_printf, 3, 4)));+#endif+static int pdf_set_err(struct pdf_doc *doc, int errval, const char *buffer,+                       ...)+{+    va_list ap;+    int len;++    va_start(ap, buffer);+    len = vsnprintf(doc->errstr, sizeof(doc->errstr) - 2, buffer, ap);+    va_end(ap);++    /* Make sure we're properly terminated */+    if (doc->errstr[len] != '\n')+        doc->errstr[len] = '\n';+    doc->errstr[len] = '\0';+    doc->errval = errval;++    return errval;+}++const char *pdf_get_err(struct pdf_doc *pdf, int *errval)+{+    if (!pdf)+        return NULL;+    if (pdf->errstr[0] == '\0')+        return NULL;+    if (errval)+        *errval = pdf->errval;+    return pdf->errstr;+}++void pdf_clear_err(struct pdf_doc *pdf)+{+    if (!pdf)+        return;+    pdf->errstr[0] = '\0';+    pdf->errval = 0;+}++static struct pdf_object *pdf_get_object(struct pdf_doc *pdf, int index)+{+    return flexarray_get(&pdf->objects, index);+}++static int pdf_append_object(struct pdf_doc *pdf, struct pdf_object *obj)+{+    int index = flexarray_append(&pdf->objects, obj);++    if (index < 0)+        return index;+    obj->index = index;++    if (pdf->last_objects[obj->type]) {+        obj->prev = pdf->last_objects[obj->type];+        pdf->last_objects[obj->type]->next = obj;+    }+    pdf->last_objects[obj->type] = obj;++    if (!pdf->first_objects[obj->type])+        pdf->first_objects[obj->type] = obj;++    return 0;+}++static struct pdf_object *pdf_add_object(struct pdf_doc *pdf, int type)+{+    struct pdf_object *obj;++    obj = calloc(1, sizeof(*obj));+    if (!obj) {+        pdf_set_err(pdf, -errno, "Unable to allocate object %d: %s",+                    flexarray_size(&pdf->objects) + 1, strerror(errno));+        return NULL;+    }++    obj->type = type;++    if (pdf_append_object(pdf, obj) < 0) {+        free(obj);+        return NULL;+    }++    return obj;+}++struct pdf_doc *pdf_create(int width, int height, struct pdf_info *info)+{+    struct pdf_doc *pdf;+    struct pdf_object *obj;++    pdf = calloc(1, sizeof(*pdf));+    pdf->width = width;+    pdf->height = height;++    /* We don't want to use ID 0 */+    pdf_add_object(pdf, OBJ_none);++    /* Create the 'info' object */+    obj = pdf_add_object(pdf, OBJ_info);+    if (!obj) {+        pdf_destroy(pdf);+        return NULL;+    }+    obj->info = calloc(sizeof(*obj->info), 1);+    if (!obj->info) {+        pdf_destroy(pdf);+        return NULL;+    }+    if (info) {+        *obj->info = *info;+        obj->info->creator[sizeof(obj->info->creator) - 1] = '\0';+        obj->info->producer[sizeof(obj->info->producer) - 1] = '\0';+        obj->info->title[sizeof(obj->info->title) - 1] = '\0';+        obj->info->author[sizeof(obj->info->author) - 1] = '\0';+        obj->info->subject[sizeof(obj->info->subject) - 1] = '\0';+        obj->info->date[sizeof(obj->info->date) - 1] = '\0';+    }+    /* FIXME: Should be quoting PDF strings? */+    if (!obj->info->date[0]) {+        time_t now = time(NULL);+        struct tm tm;+#ifdef _WIN32+        struct tm *tmp;+        tmp = localtime(&now);+        tm = *tmp;+#else+        localtime_r(&now, &tm);+#endif+        strftime(obj->info->date, sizeof(obj->info->date), "%Y%m%d%H%M%SZ",+                 &tm);+    }+    if (!obj->info->creator[0])+        snprintf(obj->info->creator, sizeof(obj->info->creator), "pdfgen");+    if (!obj->info->producer[0])+        snprintf(obj->info->producer, sizeof(obj->info->producer), "pdfgen");+    if (!obj->info->title[0])+        snprintf(obj->info->title, sizeof(obj->info->title), "pdfgen");+    if (!obj->info->author[0])+        snprintf(obj->info->author, sizeof(obj->info->author), "pdfgen");+    if (!obj->info->subject[0])+        snprintf(obj->info->subject, sizeof(obj->info->subject), "pdfgen");++    pdf_add_object(pdf, OBJ_pages);+    pdf_add_object(pdf, OBJ_catalog);++    pdf_set_font(pdf, "Times-Roman");++    return pdf;+}++int pdf_width(struct pdf_doc *pdf)+{+    return pdf->width;+}++int pdf_height(struct pdf_doc *pdf)+{+    return pdf->height;+}++static void pdf_object_destroy(struct pdf_object *object)+{+    switch (object->type) {+    case OBJ_stream:+    case OBJ_image:+        dstr_free(&object->stream);+        break;+    case OBJ_page:+        flexarray_clear(&object->page.children);+        break;+    case OBJ_info:+        free(object->info);+        break;+    case OBJ_bookmark:+        flexarray_clear(&object->bookmark.children);+        break;+    }+    free(object);+}++void pdf_destroy(struct pdf_doc *pdf)+{+    if (pdf) {+        for (int i = 0; i < flexarray_size(&pdf->objects); i++)+            pdf_object_destroy(pdf_get_object(pdf, i));+        flexarray_clear(&pdf->objects);+        free(pdf);+    }+}++static struct pdf_object *pdf_find_first_object(struct pdf_doc *pdf, int type)+{+    return pdf->first_objects[type];+}++static struct pdf_object *pdf_find_last_object(struct pdf_doc *pdf, int type)+{+    return pdf->last_objects[type];+}++int pdf_set_font(struct pdf_doc *pdf, const char *font)+{+    struct pdf_object *obj;+    int last_index = 0;++    /* See if we've used this font before */+    for (obj = pdf_find_first_object(pdf, OBJ_font); obj; obj = obj->next) {+        if (strcmp(obj->font.name, font) == 0)+            break;+        last_index = obj->font.index;+    }++    /* Create a new font object if we need it */+    if (!obj) {+        obj = pdf_add_object(pdf, OBJ_font);+        if (!obj)+            return pdf->errval;+        strncpy(obj->font.name, font, sizeof(obj->font.name));+        obj->font.name[sizeof(obj->font.name) - 1] = '\0';+        obj->font.index = last_index + 1;+    }++    pdf->current_font = obj;++    return 0;+}++struct pdf_object *pdf_append_page(struct pdf_doc *pdf)+{+    struct pdf_object *page;++    page = pdf_add_object(pdf, OBJ_page);++    if (!page)+        return NULL;++    page->page.width = pdf->width;+    page->page.height = pdf->height;++    return page;+}++int pdf_page_set_size(struct pdf_doc *pdf, struct pdf_object *page, int width,+                      int height)+{+    if (!page)+        page = pdf_find_last_object(pdf, OBJ_page);++    if (!page || page->type != OBJ_page)+        return pdf_set_err(pdf, -EINVAL, "Invalid PDF page");+    page->page.width = width;+    page->page.height = height;+    return 0;+}++static int pdf_save_object(struct pdf_doc *pdf, FILE *fp, int index)+{+    struct pdf_object *object = pdf_get_object(pdf, index);++    if (object->type == OBJ_none)+        return -ENOENT;++    object->offset = ftell(fp);++    fprintf(fp, "%d 0 obj\r\n", index);++    switch (object->type) {+    case OBJ_stream:+    case OBJ_image: {+        fwrite(dstr_data(&object->stream), dstr_len(&object->stream), 1, fp);+        break;+    }+    case OBJ_info: {+        struct pdf_info *info = object->info;++        fprintf(fp,+                "<<\r\n"+                "  /Creator (%s)\r\n"+                "  /Producer (%s)\r\n"+                "  /Title (%s)\r\n"+                "  /Author (%s)\r\n"+                "  /Subject (%s)\r\n"+                "  /CreationDate (D:%s)\r\n"+                ">>\r\n",+                info->creator, info->producer, info->title, info->author,+                info->subject, info->date);+        break;+    }++    case OBJ_page: {+        struct pdf_object *pages = pdf_find_first_object(pdf, OBJ_pages);+        struct pdf_object *image = pdf_find_first_object(pdf, OBJ_image);++        fprintf(fp,+                "<<\r\n"+                "/Type /Page\r\n"+                "/Parent %d 0 R\r\n",+                pages->index);+        fprintf(fp, "/MediaBox [0 0 %d %d]\r\n", object->page.width,+                object->page.height);+        fprintf(fp, "/Resources <<\r\n");+        fprintf(fp, "  /Font <<\r\n");+        for (struct pdf_object *font = pdf_find_first_object(pdf, OBJ_font);+             font; font = font->next)+            fprintf(fp, "    /F%d %d 0 R\r\n", font->font.index, font->index);+        fprintf(fp, "  >>\r\n");++        if (image) {+            fprintf(fp, "  /XObject <<");+            for (; image; image = image->next)+                fprintf(fp, "/Image%d %d 0 R ", image->index, image->index);+            fprintf(fp, ">>\r\n");+        }++        fprintf(fp, ">>\r\n");+        fprintf(fp, "/Contents [\r\n");+        for (int i = 0; i < flexarray_size(&object->page.children); i++) {+            struct pdf_object *child =+                flexarray_get(&object->page.children, i);+            fprintf(fp, "%d 0 R\r\n", child->index);+        }+        fprintf(fp, "]\r\n");+        fprintf(fp, ">>\r\n");+        break;+    }++    case OBJ_bookmark: {+        struct pdf_object *parent, *other;++        parent = object->bookmark.parent;+        if (!parent)+            parent = pdf_find_first_object(pdf, OBJ_outline);+        if (!object->bookmark.page)+            break;+        fprintf(fp,+                "<<\r\n"+                "/A << /Type /Action\r\n"+                "      /S /GoTo\r\n"+                "      /D [%d 0 R /XYZ 0 %d null]\r\n"+                "   >>\r\n"+                "/Parent %d 0 R\r\n"+                "/Title (%s)\r\n",+                object->bookmark.page->index, pdf->height, parent->index,+                object->bookmark.name);+        int nchildren = flexarray_size(&object->bookmark.children);+        if (nchildren > 0) {+            struct pdf_object *f, *l;+            f = flexarray_get(&object->bookmark.children, 0);+            l = flexarray_get(&object->bookmark.children, nchildren - 1);+            fprintf(fp, "/First %d 0 R\r\n", f->index);+            fprintf(fp, "/Last %d 0 R\r\n", l->index);+        }+        // Find the previous bookmark with the same parent+        for (other = object->prev;+             other && other->bookmark.parent != object->bookmark.parent;+             other = other->prev)+            ;+        if (other)+            fprintf(fp, "/Prev %d 0 R\r\n", other->index);+        // Find the next bookmark with the same parent+        for (other = object->next;+             other && other->bookmark.parent != object->bookmark.parent;+             other = other->next)+            ;+        if (other)+            fprintf(fp, "/Next %d 0 R\r\n", other->index);+        fprintf(fp, ">>\r\n");+        break;+    }++    case OBJ_outline: {+        struct pdf_object *first, *last, *cur;+        first = pdf_find_first_object(pdf, OBJ_bookmark);+        last = pdf_find_last_object(pdf, OBJ_bookmark);++        if (first && last) {+            int count = 0;+            cur = first;+            while (cur) {+                if (!cur->bookmark.parent)+                    count++;+                cur = cur->next;+            }++            /* Bookmark outline */+            fprintf(fp,+                    "<<\r\n"+                    "/Count %d\r\n"+                    "/Type /Outlines\r\n"+                    "/First %d 0 R\r\n"+                    "/Last %d 0 R\r\n"+                    ">>\r\n",+                    count, first->index, last->index);+        }+        break;+    }++    case OBJ_font:+        fprintf(fp,+                "<<\r\n"+                "  /Type /Font\r\n"+                "  /Subtype /Type1\r\n"+                "  /BaseFont /%s\r\n"+                "  /Encoding /WinAnsiEncoding\r\n"+                ">>\r\n",+                object->font.name);+        break;++    case OBJ_pages: {+        int npages = 0;++        fprintf(fp, "<<\r\n"+                    "/Type /Pages\r\n"+                    "/Kids [ ");+        for (struct pdf_object *page = pdf_find_first_object(pdf, OBJ_page);+             page; page = page->next) {+            npages++;+            fprintf(fp, "%d 0 R ", page->index);+        }+        fprintf(fp, "]\r\n");+        fprintf(fp, "/Count %d\r\n", npages);+        fprintf(fp, ">>\r\n");+        break;+    }++    case OBJ_catalog: {+        struct pdf_object *outline = pdf_find_first_object(pdf, OBJ_outline);+        struct pdf_object *pages = pdf_find_first_object(pdf, OBJ_pages);++        fprintf(fp, "<<\r\n"+                    "/Type /Catalog\r\n");+        if (outline)+            fprintf(fp,+                    "/Outlines %d 0 R\r\n"+                    "/PageMode /UseOutlines\r\n",+                    outline->index);+        fprintf(fp,+                "/Pages %d 0 R\r\n"+                ">>\r\n",+                pages->index);+        break;+    }++    default:+        return pdf_set_err(pdf, -EINVAL, "Invalid PDF object type %d",+                           object->type);+    }++    fprintf(fp, "endobj\r\n");++    return 0;+}++int pdf_save_file(struct pdf_doc *pdf, FILE *fp)+{+    struct pdf_object *obj;+    int xref_offset;+    int xref_count = 0;++    fprintf(fp, "%%PDF-1.2\r\n");+    /* Hibit bytes */+    fprintf(fp, "%c%c%c%c%c\r\n", 0x25, 0xc7, 0xec, 0x8f, 0xa2);++    /* Dump all the objects & get their file offsets */+    for (int i = 0; i < flexarray_size(&pdf->objects); i++)+        if (pdf_save_object(pdf, fp, i) >= 0)+            xref_count++;++    /* xref */+    xref_offset = ftell(fp);+    fprintf(fp, "xref\r\n");+    fprintf(fp, "0 %d\r\n", xref_count + 1);+    fprintf(fp, "0000000000 65535 f\r\n");+    for (int i = 0; i < flexarray_size(&pdf->objects); i++) {+        obj = pdf_get_object(pdf, i);+        if (obj->type != OBJ_none)+            fprintf(fp, "%10.10d 00000 n\r\n", obj->offset);+    }++    fprintf(fp,+            "trailer\r\n"+            "<<\r\n"+            "/Size %d\r\n",+            xref_count + 1);+    obj = pdf_find_first_object(pdf, OBJ_catalog);+    fprintf(fp, "/Root %d 0 R\r\n", obj->index);+    obj = pdf_find_first_object(pdf, OBJ_info);+    fprintf(fp, "/Info %d 0 R\r\n", obj->index);+    /* FIXME: Not actually generating a unique ID */+    fprintf(fp, "/ID [<%16.16x> <%16.16x>]\r\n", 0x123, 0x123);+    fprintf(fp, ">>\r\n"+                "startxref\r\n");+    fprintf(fp, "%d\r\n", xref_offset);+    fprintf(fp, "%%%%EOF\r\n");++    return 0;+}++int pdf_save(struct pdf_doc *pdf, const char *filename)+{+    FILE *fp;+    int e;++    if (filename == NULL)+        fp = stdout;+    else if ((fp = fopen(filename, "wb")) == NULL)+        return pdf_set_err(pdf, -errno, "Unable to open '%s': %s", filename,+                           strerror(errno));++    e = pdf_save_file(pdf, fp);++    if (fp != stdout)+        if (fclose(fp) != 0 && e >= 0)+            return pdf_set_err(pdf, -errno, "Unable to close '%s': %s",+                               filename, strerror(errno));++    return e;+}++static int pdf_add_stream(struct pdf_doc *pdf, struct pdf_object *page,+                          const char *buffer)+{+    struct pdf_object *obj;+    int len;++    if (!page)+        page = pdf_find_last_object(pdf, OBJ_page);++    if (!page)+        return pdf_set_err(pdf, -EINVAL, "Invalid pdf page");++    len = strlen(buffer);+    /* We don't want any trailing whitespace in the stream */+    while (len >= 1 && (buffer[len - 1] == '\r' || buffer[len - 1] == '\n'))+        len--;++    obj = pdf_add_object(pdf, OBJ_stream);+    if (!obj)+        return pdf->errval;++    dstr_printf(&obj->stream, "<< /Length %d >>stream\r\n", len);+    dstr_append_data(&obj->stream, buffer, len);+    dstr_append(&obj->stream, "\r\nendstream\r\n");++    return flexarray_append(&page->page.children, obj);+}++int pdf_add_bookmark(struct pdf_doc *pdf, struct pdf_object *page, int parent,+                     const char *name)+{+    struct pdf_object *obj;++    if (!page)+        page = pdf_find_last_object(pdf, OBJ_page);++    if (!page)+        return pdf_set_err(pdf, -EINVAL,+                           "Unable to add bookmark, no pages available");++    if (!pdf_find_first_object(pdf, OBJ_outline))+        if (!pdf_add_object(pdf, OBJ_outline))+            return pdf->errval;++    obj = pdf_add_object(pdf, OBJ_bookmark);+    if (!obj)+        return pdf->errval;++    strncpy(obj->bookmark.name, name, sizeof(obj->bookmark.name));+    obj->bookmark.name[sizeof(obj->bookmark.name) - 1] = '\0';+    obj->bookmark.page = page;+    if (parent >= 0) {+        struct pdf_object *parent_obj = pdf_get_object(pdf, parent);+        if (!parent_obj)+            return pdf_set_err(pdf, -EINVAL, "Invalid parent ID %d supplied",+                               parent);+        obj->bookmark.parent = parent_obj;+        flexarray_append(&parent_obj->bookmark.children, obj);+    }++    return obj->index;+}++static int utf8_to_utf32(const char *utf8, int len, uint32_t *utf32)+{+    uint32_t ch = *utf8;+    uint8_t mask;++    if ((ch & 0x80) == 0) {+        len = 1;+        mask = 0x7f;+    } else if ((ch & 0xe0) == 0xc0 && len >= 2) {+        len = 2;+        mask = 0x1f;+    } else if ((ch & 0xf0) == 0xe0 && len >= 3) {+        len = 3;+        mask = 0xf;+    } else if ((ch & 0xf8) == 0xf0 && len >= 4) {+        len = 4;+        mask = 0x7;+    } else+        return -EINVAL;++    ch = 0;+    for (int i = 0; i < len; i++) {+        int shift = (len - i - 1) * 6;+        if (i == 0)+            ch |= ((uint32_t)(*utf8++) & mask) << shift;+        else+            ch |= ((uint32_t)(*utf8++) & 0x3f) << shift;+    }++    *utf32 = ch;++    return len;+}++static int pdf_add_text_spacing(struct pdf_doc *pdf, struct pdf_object *page,+                                const char *text, int size, int xoff,+                                int yoff, uint32_t colour, double spacing)+{+    int ret;+    int len = text ? strlen(text) : 0;+    struct dstr str = INIT_DSTR;++    /* Don't bother adding empty/null strings */+    if (!len)+        return 0;++    dstr_append(&str, "BT ");+    dstr_printf(&str, "%d %d TD ", xoff, yoff);+    dstr_printf(&str, "/F%d %d Tf ", pdf->current_font->font.index, size);+    dstr_printf(&str, "%f %f %f rg ", PDF_RGB_R(colour), PDF_RGB_G(colour),+                PDF_RGB_B(colour));+    dstr_printf(&str, "%f Tc ", spacing);+    dstr_append(&str, "(");++    /* Escape magic characters properly */+    for (int i = 0; i < len;) {+        uint32_t code;+        int code_len;+        code_len = utf8_to_utf32(&text[i], len - i, &code);+        if (code_len < 0) {+            dstr_free(&str);+            return pdf_set_err(pdf, -EINVAL, "Invalid UTF-8 encoding");+        }++        if (code > 255) {+            /* We support *some* minimal UTF-8 characters */+            char buf[5] = {0};+            switch (code) {+            case 0x160:+                buf[0] = (char)0x8a;+                break;+            case 0x161:+                buf[0] = (char)0x9a;+                break;+            case 0x17d:+                buf[0] = (char)0x8e;+                break;+            case 0x17e:+                buf[0] = (char)0x9e;+                break;+            case 0x20ac:+                strcpy(buf, "\\200");+                break;+            default:+                dstr_free(&str);+                return pdf_set_err(pdf, -EINVAL,+                                   "Unsupported UTF-8 character: 0x%x 0o%o",+                                   code, code);+            }+            dstr_append(&str, buf);+        } else if (strchr("()\\", code)) {+            char buf[3];+            /* Escape some characters */+            buf[0] = '\\';+            buf[1] = code;+            buf[2] = '\0';+            dstr_append(&str, buf);+        } else if (strrchr("\n\r\t\b\f", code)) {+            /* Skip over these characters */+            ;+        } else {+            char buf[2];+            buf[0] = code;+            buf[1] = '\0';+            dstr_append(&str, buf);+        }++        i += code_len;+    }+    dstr_append(&str, ") Tj ");+    dstr_append(&str, "ET");++    ret = pdf_add_stream(pdf, page, dstr_data(&str));+    dstr_free(&str);+    return ret;+}++int pdf_add_text(struct pdf_doc *pdf, struct pdf_object *page,+                 const char *text, int size, int xoff, int yoff,+                 uint32_t colour)+{+    return pdf_add_text_spacing(pdf, page, text, size, xoff, yoff, colour, 0);+}++/* How wide is each character, in points, at size 14 */+static const uint16_t helvetica_widths[256] = {+    280, 280, 280, 280,  280, 280, 280, 280,  280,  280, 280,  280, 280,+    280, 280, 280, 280,  280, 280, 280, 280,  280,  280, 280,  280, 280,+    280, 280, 280, 280,  280, 280, 280, 280,  357,  560, 560,  896, 672,+    192, 335, 335, 392,  588, 280, 335, 280,  280,  560, 560,  560, 560,+    560, 560, 560, 560,  560, 560, 280, 280,  588,  588, 588,  560, 1023,+    672, 672, 727, 727,  672, 615, 784, 727,  280,  504, 672,  560, 839,+    727, 784, 672, 784,  727, 672, 615, 727,  672,  951, 672,  672, 615,+    280, 280, 280, 472,  560, 335, 560, 560,  504,  560, 560,  280, 560,+    560, 223, 223, 504,  223, 839, 560, 560,  560,  560, 335,  504, 280,+    560, 504, 727, 504,  504, 504, 336, 262,  336,  588, 352,  560, 352,+    223, 560, 335, 1008, 560, 560, 335, 1008, 672,  335, 1008, 352, 615,+    352, 352, 223, 223,  335, 335, 352, 560,  1008, 335, 1008, 504, 335,+    951, 352, 504, 672,  280, 335, 560, 560,  560,  560, 262,  560, 335,+    742, 372, 560, 588,  335, 742, 335, 403,  588,  335, 335,  335, 560,+    541, 280, 335, 335,  367, 560, 840, 840,  840,  615, 672,  672, 672,+    672, 672, 672, 1008, 727, 672, 672, 672,  672,  280, 280,  280, 280,+    727, 727, 784, 784,  784, 784, 784, 588,  784,  727, 727,  727, 727,+    672, 672, 615, 560,  560, 560, 560, 560,  560,  896, 504,  560, 560,+    560, 560, 280, 280,  280, 280, 560, 560,  560,  560, 560,  560, 560,+    588, 615, 560, 560,  560, 560, 504, 560,  504,+};++static const uint16_t helvetica_bold_widths[256] = {+    280,  280, 280,  280, 280, 280, 280, 280,  280, 280, 280, 280,  280, 280,+    280,  280, 280,  280, 280, 280, 280, 280,  280, 280, 280, 280,  280, 280,+    280,  280, 280,  280, 280, 335, 477, 560,  560, 896, 727, 239,  335, 335,+    392,  588, 280,  335, 280, 280, 560, 560,  560, 560, 560, 560,  560, 560,+    560,  560, 335,  335, 588, 588, 588, 615,  982, 727, 727, 727,  727, 672,+    615,  784, 727,  280, 560, 727, 615, 839,  727, 784, 672, 784,  727, 672,+    615,  727, 672,  951, 672, 672, 615, 335,  280, 335, 588, 560,  335, 560,+    615,  560, 615,  560, 335, 615, 615, 280,  280, 560, 280, 896,  615, 615,+    615,  615, 392,  560, 335, 615, 560, 784,  560, 560, 504, 392,  282, 392,+    588,  352, 560,  352, 280, 560, 504, 1008, 560, 560, 335, 1008, 672, 335,+    1008, 352, 615,  352, 352, 280, 280, 504,  504, 352, 560, 1008, 335, 1008,+    560,  335, 951,  352, 504, 672, 280, 335,  560, 560, 560, 560,  282, 560,+    335,  742, 372,  560, 588, 335, 742, 335,  403, 588, 335, 335,  335, 615,+    560,  280, 335,  335, 367, 560, 840, 840,  840, 615, 727, 727,  727, 727,+    727,  727, 1008, 727, 672, 672, 672, 672,  280, 280, 280, 280,  727, 727,+    784,  784, 784,  784, 784, 588, 784, 727,  727, 727, 727, 672,  672, 615,+    560,  560, 560,  560, 560, 560, 896, 560,  560, 560, 560, 560,  280, 280,+    280,  280, 615,  615, 615, 615, 615, 615,  615, 588, 615, 615,  615, 615,+    615,  560, 615,  560,+};++static const uint16_t helvetica_bold_oblique_widths[256] = {+    280,  280, 280,  280, 280, 280, 280, 280,  280, 280, 280, 280,  280, 280,+    280,  280, 280,  280, 280, 280, 280, 280,  280, 280, 280, 280,  280, 280,+    280,  280, 280,  280, 280, 335, 477, 560,  560, 896, 727, 239,  335, 335,+    392,  588, 280,  335, 280, 280, 560, 560,  560, 560, 560, 560,  560, 560,+    560,  560, 335,  335, 588, 588, 588, 615,  982, 727, 727, 727,  727, 672,+    615,  784, 727,  280, 560, 727, 615, 839,  727, 784, 672, 784,  727, 672,+    615,  727, 672,  951, 672, 672, 615, 335,  280, 335, 588, 560,  335, 560,+    615,  560, 615,  560, 335, 615, 615, 280,  280, 560, 280, 896,  615, 615,+    615,  615, 392,  560, 335, 615, 560, 784,  560, 560, 504, 392,  282, 392,+    588,  352, 560,  352, 280, 560, 504, 1008, 560, 560, 335, 1008, 672, 335,+    1008, 352, 615,  352, 352, 280, 280, 504,  504, 352, 560, 1008, 335, 1008,+    560,  335, 951,  352, 504, 672, 280, 335,  560, 560, 560, 560,  282, 560,+    335,  742, 372,  560, 588, 335, 742, 335,  403, 588, 335, 335,  335, 615,+    560,  280, 335,  335, 367, 560, 840, 840,  840, 615, 727, 727,  727, 727,+    727,  727, 1008, 727, 672, 672, 672, 672,  280, 280, 280, 280,  727, 727,+    784,  784, 784,  784, 784, 588, 784, 727,  727, 727, 727, 672,  672, 615,+    560,  560, 560,  560, 560, 560, 896, 560,  560, 560, 560, 560,  280, 280,+    280,  280, 615,  615, 615, 615, 615, 615,  615, 588, 615, 615,  615, 615,+    615,  560, 615,  560,+};++static const uint16_t helvetica_oblique_widths[256] = {+    280, 280, 280, 280,  280, 280, 280, 280,  280,  280, 280,  280, 280,+    280, 280, 280, 280,  280, 280, 280, 280,  280,  280, 280,  280, 280,+    280, 280, 280, 280,  280, 280, 280, 280,  357,  560, 560,  896, 672,+    192, 335, 335, 392,  588, 280, 335, 280,  280,  560, 560,  560, 560,+    560, 560, 560, 560,  560, 560, 280, 280,  588,  588, 588,  560, 1023,+    672, 672, 727, 727,  672, 615, 784, 727,  280,  504, 672,  560, 839,+    727, 784, 672, 784,  727, 672, 615, 727,  672,  951, 672,  672, 615,+    280, 280, 280, 472,  560, 335, 560, 560,  504,  560, 560,  280, 560,+    560, 223, 223, 504,  223, 839, 560, 560,  560,  560, 335,  504, 280,+    560, 504, 727, 504,  504, 504, 336, 262,  336,  588, 352,  560, 352,+    223, 560, 335, 1008, 560, 560, 335, 1008, 672,  335, 1008, 352, 615,+    352, 352, 223, 223,  335, 335, 352, 560,  1008, 335, 1008, 504, 335,+    951, 352, 504, 672,  280, 335, 560, 560,  560,  560, 262,  560, 335,+    742, 372, 560, 588,  335, 742, 335, 403,  588,  335, 335,  335, 560,+    541, 280, 335, 335,  367, 560, 840, 840,  840,  615, 672,  672, 672,+    672, 672, 672, 1008, 727, 672, 672, 672,  672,  280, 280,  280, 280,+    727, 727, 784, 784,  784, 784, 784, 588,  784,  727, 727,  727, 727,+    672, 672, 615, 560,  560, 560, 560, 560,  560,  896, 504,  560, 560,+    560, 560, 280, 280,  280, 280, 560, 560,  560,  560, 560,  560, 560,+    588, 615, 560, 560,  560, 560, 504, 560,  504,+};++static const uint16_t symbol_widths[256] = {+    252, 252, 252, 252,  252, 252, 252,  252, 252,  252,  252, 252, 252, 252,+    252, 252, 252, 252,  252, 252, 252,  252, 252,  252,  252, 252, 252, 252,+    252, 252, 252, 252,  252, 335, 718,  504, 553,  839,  784, 442, 335, 335,+    504, 553, 252, 553,  252, 280, 504,  504, 504,  504,  504, 504, 504, 504,+    504, 504, 280, 280,  553, 553, 553,  447, 553,  727,  672, 727, 616, 615,+    769, 607, 727, 335,  636, 727, 691,  896, 727,  727,  774, 746, 560, 596,+    615, 695, 442, 774,  650, 801, 615,  335, 869,  335,  663, 504, 504, 636,+    553, 553, 497, 442,  525, 414, 607,  331, 607,  553,  553, 580, 525, 553,+    553, 525, 553, 607,  442, 580, 718,  691, 496,  691,  497, 483, 201, 483,+    553, 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,   0,   756,  624, 248,  553,  168, 718, 504, 759,+    759, 759, 759, 1050, 994, 607, 994,  607, 403,  553,  414, 553, 553, 718,+    497, 463, 553, 553,  553, 553, 1008, 607, 1008, 663,  829, 691, 801, 994,+    774, 774, 829, 774,  774, 718, 718,  718, 718,  718,  718, 718, 774, 718,+    796, 796, 897, 829,  553, 252, 718,  607, 607,  1050, 994, 607, 994, 607,+    497, 331, 796, 796,  792, 718, 387,  387, 387,  387,  387, 387, 497, 497,+    497, 497, 0,   331,  276, 691, 691,  691, 387,  387,  387, 387, 387, 387,+    497, 497, 497, 0,+};++static const uint16_t times_widths[256] = {+    252, 252, 252, 252, 252, 252, 252, 252,  252, 252, 252, 252,  252, 252,+    252, 252, 252, 252, 252, 252, 252, 252,  252, 252, 252, 252,  252, 252,+    252, 252, 252, 252, 252, 335, 411, 504,  504, 839, 784, 181,  335, 335,+    504, 568, 252, 335, 252, 280, 504, 504,  504, 504, 504, 504,  504, 504,+    504, 504, 280, 280, 568, 568, 568, 447,  928, 727, 672, 672,  727, 615,+    560, 727, 727, 335, 392, 727, 615, 896,  727, 727, 560, 727,  672, 560,+    615, 727, 727, 951, 727, 727, 615, 335,  280, 335, 472, 504,  335, 447,+    504, 447, 504, 447, 335, 504, 504, 280,  280, 504, 280, 784,  504, 504,+    504, 504, 335, 392, 280, 504, 504, 727,  504, 504, 447, 483,  201, 483,+    545, 352, 504, 352, 335, 504, 447, 1008, 504, 504, 335, 1008, 560, 335,+    896, 352, 615, 352, 352, 335, 335, 447,  447, 352, 504, 1008, 335, 987,+    392, 335, 727, 352, 447, 727, 252, 335,  504, 504, 504, 504,  201, 504,+    335, 766, 278, 504, 568, 335, 766, 335,  403, 568, 302, 302,  335, 504,+    456, 252, 335, 302, 312, 504, 756, 756,  756, 447, 727, 727,  727, 727,+    727, 727, 896, 672, 615, 615, 615, 615,  335, 335, 335, 335,  727, 727,+    727, 727, 727, 727, 727, 568, 727, 727,  727, 727, 727, 727,  560, 504,+    447, 447, 447, 447, 447, 447, 672, 447,  447, 447, 447, 447,  280, 280,+    280, 280, 504, 504, 504, 504, 504, 504,  504, 568, 504, 504,  504, 504,+    504, 504, 504, 504,+};++static const uint16_t times_bold_widths[256] = {+    252, 252, 252, 252,  252, 252, 252, 252,  252,  252,  252,  252,  252,+    252, 252, 252, 252,  252, 252, 252, 252,  252,  252,  252,  252,  252,+    252, 252, 252, 252,  252, 252, 252, 335,  559,  504,  504,  1008, 839,+    280, 335, 335, 504,  574, 252, 335, 252,  280,  504,  504,  504,  504,+    504, 504, 504, 504,  504, 504, 335, 335,  574,  574,  574,  504,  937,+    727, 672, 727, 727,  672, 615, 784, 784,  392,  504,  784,  672,  951,+    727, 784, 615, 784,  727, 560, 672, 727,  727,  1008, 727,  727,  672,+    335, 280, 335, 585,  504, 335, 504, 560,  447,  560,  447,  335,  504,+    560, 280, 335, 560,  280, 839, 560, 504,  560,  560,  447,  392,  335,+    560, 504, 727, 504,  504, 447, 397, 221,  397,  524,  352,  504,  352,+    335, 504, 504, 1008, 504, 504, 335, 1008, 560,  335,  1008, 352,  672,+    352, 352, 335, 335,  504, 504, 352, 504,  1008, 335,  1008, 392,  335,+    727, 352, 447, 727,  252, 335, 504, 504,  504,  504,  221,  504,  335,+    752, 302, 504, 574,  335, 752, 335, 403,  574,  302,  302,  335,  560,+    544, 252, 335, 302,  332, 504, 756, 756,  756,  504,  727,  727,  727,+    727, 727, 727, 1008, 727, 672, 672, 672,  672,  392,  392,  392,  392,+    727, 727, 784, 784,  784, 784, 784, 574,  784,  727,  727,  727,  727,+    727, 615, 560, 504,  504, 504, 504, 504,  504,  727,  447,  447,  447,+    447, 447, 280, 280,  280, 280, 504, 560,  504,  504,  504,  504,  504,+    574, 504, 560, 560,  560, 560, 504, 560,  504,+};++static const uint16_t times_bold_italic_widths[256] = {+    252, 252, 252, 252, 252, 252, 252, 252,  252, 252, 252, 252,  252, 252,+    252, 252, 252, 252, 252, 252, 252, 252,  252, 252, 252, 252,  252, 252,+    252, 252, 252, 252, 252, 392, 559, 504,  504, 839, 784, 280,  335, 335,+    504, 574, 252, 335, 252, 280, 504, 504,  504, 504, 504, 504,  504, 504,+    504, 504, 335, 335, 574, 574, 574, 504,  838, 672, 672, 672,  727, 672,+    672, 727, 784, 392, 504, 672, 615, 896,  727, 727, 615, 727,  672, 560,+    615, 727, 672, 896, 672, 615, 615, 335,  280, 335, 574, 504,  335, 504,+    504, 447, 504, 447, 335, 504, 560, 280,  280, 504, 280, 784,  560, 504,+    504, 504, 392, 392, 280, 560, 447, 672,  504, 447, 392, 350,  221, 350,+    574, 352, 504, 352, 335, 504, 504, 1008, 504, 504, 335, 1008, 560, 335,+    951, 352, 615, 352, 352, 335, 335, 504,  504, 352, 504, 1008, 335, 1008,+    392, 335, 727, 352, 392, 615, 252, 392,  504, 504, 504, 504,  221, 504,+    335, 752, 268, 504, 610, 335, 752, 335,  403, 574, 302, 302,  335, 580,+    504, 252, 335, 302, 302, 504, 756, 756,  756, 504, 672, 672,  672, 672,+    672, 672, 951, 672, 672, 672, 672, 672,  392, 392, 392, 392,  727, 727,+    727, 727, 727, 727, 727, 574, 727, 727,  727, 727, 727, 615,  615, 504,+    504, 504, 504, 504, 504, 504, 727, 447,  447, 447, 447, 447,  280, 280,+    280, 280, 504, 560, 504, 504, 504, 504,  504, 574, 504, 560,  560, 560,+    560, 447, 504, 447,+};++static const uint16_t times_italic_widths[256] = {+    252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252,  252, 252,+    252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252,  252, 252,+    252, 252, 252, 252, 252, 335, 423, 504, 504, 839, 784, 215,  335, 335,+    504, 680, 252, 335, 252, 280, 504, 504, 504, 504, 504, 504,  504, 504,+    504, 504, 335, 335, 680, 680, 680, 504, 927, 615, 615, 672,  727, 615,+    615, 727, 727, 335, 447, 672, 560, 839, 672, 727, 615, 727,  615, 504,+    560, 727, 615, 839, 615, 560, 560, 392, 280, 392, 425, 504,  335, 504,+    504, 447, 504, 447, 280, 504, 504, 280, 280, 447, 280, 727,  504, 504,+    504, 504, 392, 392, 280, 504, 447, 672, 447, 447, 392, 403,  277, 403,+    545, 352, 504, 352, 335, 504, 560, 896, 504, 504, 335, 1008, 504, 335,+    951, 352, 560, 352, 352, 335, 335, 560, 560, 352, 504, 896,  335, 987,+    392, 335, 672, 352, 392, 560, 252, 392, 504, 504, 504, 504,  277, 504,+    335, 766, 278, 504, 680, 335, 766, 335, 403, 680, 302, 302,  335, 504,+    527, 252, 335, 302, 312, 504, 756, 756, 756, 504, 615, 615,  615, 615,+    615, 615, 896, 672, 615, 615, 615, 615, 335, 335, 335, 335,  727, 672,+    727, 727, 727, 727, 727, 680, 727, 727, 727, 727, 727, 560,  615, 504,+    504, 504, 504, 504, 504, 504, 672, 447, 447, 447, 447, 447,  280, 280,+    280, 280, 504, 504, 504, 504, 504, 504, 504, 680, 504, 504,  504, 504,+    504, 447, 504, 447,+};++static const uint16_t zapfdingbats_widths[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,   280,  981, 968, 981, 987, 724, 795, 796, 797, 695,+    967, 946, 553, 861, 918,  940, 918, 952, 981, 761, 852, 768, 767, 575,+    682, 769, 766, 765, 760,  497, 556, 541, 581, 697, 792, 794, 794, 796,+    799, 800, 822, 829, 795,  847, 829, 839, 822, 837, 930, 749, 728, 754,+    796, 798, 700, 782, 774,  798, 765, 712, 713, 687, 706, 832, 821, 795,+    795, 712, 692, 701, 694,  792, 793, 718, 797, 791, 797, 879, 767, 768,+    768, 765, 765, 899, 899,  794, 790, 441, 139, 279, 418, 395, 395, 673,+    673, 0,   393, 393, 319,  319, 278, 278, 513, 513, 413, 413, 235, 235,+    336, 336, 0,   0,   0,    0,   0,   0,   0,   0,   0,   0,   0,   0,+    0,   0,   0,   0,   0,    0,   0,   737, 548, 548, 917, 672, 766, 766,+    782, 599, 699, 631, 794,  794, 794, 794, 794, 794, 794, 794, 794, 794,+    794, 794, 794, 794, 794,  794, 794, 794, 794, 794, 794, 794, 794, 794,+    794, 794, 794, 794, 794,  794, 794, 794, 794, 794, 794, 794, 794, 794,+    794, 794, 901, 844, 1024, 461, 753, 931, 753, 925, 934, 935, 935, 840,+    879, 834, 931, 931, 924,  937, 938, 466, 890, 842, 842, 873, 873, 701,+    701, 880, 0,   880, 766,  953, 777, 871, 777, 895, 974, 895, 837, 879,+    934, 977, 925, 0,+};++static const uint16_t courier_widths[256] = {+    604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604,+    604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604,+    604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604,+    604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604,+    604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604,+    604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604,+    604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604,+    604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604,+    604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604,+    604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604,+    604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604,+    604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604,+    604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604,+    604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604,+    604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604,+    604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604,+    604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604,+    604,+};++static int pdf_text_pixel_width(struct pdf_doc *pdf, const char *text,+                                int text_len, int size,+                                const uint16_t *widths)+{+    int len = 0;+    if (text_len < 0)+        text_len = strlen(text);++    for (int i = 0; i < text_len;) {+        uint32_t code;+        int code_len;+        code_len = utf8_to_utf32(&text[i], text_len - i, &code);+        if (code_len < 0)+            return pdf_set_err(pdf, code_len,+                               "Invalid unicode string at position %d in %s",+                               i, text);+        if (code >= 255)+            return pdf_set_err(+                pdf, code_len,+                "Unable to determine width of character code %d", code);+        i += code_len;++        if (code != '\n' && code != '\r')+            len += widths[(uint8_t)code];+    }++    /* Our widths arrays are for 14pt fonts */+    return len * size / (14 * 72);+}++static const uint16_t *find_font_widths(const char *font_name)+{+    if (strcasecmp(font_name, "Helvetica") == 0)+        return helvetica_widths;+    if (strcasecmp(font_name, "Helvetica-Bold") == 0)+        return helvetica_bold_widths;+    if (strcasecmp(font_name, "Helvetica-BoldOblique") == 0)+        return helvetica_bold_oblique_widths;+    if (strcasecmp(font_name, "Helvetica-Oblique") == 0)+        return helvetica_oblique_widths;+    if (strcasecmp(font_name, "Courier") == 0 ||+        strcasecmp(font_name, "Courier-Bold") == 0 ||+        strcasecmp(font_name, "Courier-BoldOblique") == 0 ||+        strcasecmp(font_name, "Courier-Oblique") == 0)+        return courier_widths;+    if (strcasecmp(font_name, "Times-Roman") == 0)+        return times_widths;+    if (strcasecmp(font_name, "Times-Bold") == 0)+        return times_bold_widths;+    if (strcasecmp(font_name, "Times-Italic") == 0)+        return times_italic_widths;+    if (strcasecmp(font_name, "Times-BoldItalic") == 0)+        return times_bold_italic_widths;+    if (strcasecmp(font_name, "Symbol") == 0)+        return symbol_widths;+    if (strcasecmp(font_name, "ZapfDingbats") == 0)+        return zapfdingbats_widths;++    return NULL;+}++int pdf_get_font_text_width(struct pdf_doc *pdf, const char *font_name,+                            const char *text, int size)+{+    const uint16_t *widths = find_font_widths(font_name);++    if (!widths)+        return pdf_set_err(pdf, -EINVAL,+                           "Unable to determine width for font '%s'",+                           pdf->current_font->font.name);+    return pdf_text_pixel_width(pdf, text, -1, size, widths);+}++static const char *find_word_break(const char *string)+{+    /* Skip over the actual word */+    while (string && *string && !isspace(*string))+        string++;++    return string;+}++int pdf_add_text_wrap(struct pdf_doc *pdf, struct pdf_object *page,+                      const char *text, int size, int xoff, int yoff,+                      uint32_t colour, int wrap_width, int align)+{+    /* Move through the text string, stopping at word boundaries,+     * trying to find the longest text string we can fit in the given width+     */+    const char *start = text;+    const char *last_best = text;+    const char *end = text;+    char line[512];+    const uint16_t *widths;+    int orig_yoff = yoff;++    widths = find_font_widths(pdf->current_font->font.name);+    if (!widths)+        return pdf_set_err(pdf, -EINVAL,+                           "Unable to determine width for font '%s'",+                           pdf->current_font->font.name);++    while (start && *start) {+        const char *new_end = find_word_break(end + 1);+        int line_width;+        int output = 0;+        int xoff_align = xoff;++        end = new_end;++        line_width =+            pdf_text_pixel_width(pdf, start, end - start, size, widths);+        if (line_width < 0)+            return line_width;++        if (line_width >= wrap_width) {+            if (last_best == start) {+                /* There is a single word that is too long for the line */+                int i;+                /* Find the best character to chop it at */+                for (i = end - start - 1; i > 0; i--) {+                    int e = pdf_text_pixel_width(pdf, start, i, size, widths);+                    if (e < 0)+                        return e;+                    if (e < wrap_width)+                        break;+                }++                end = start + i;+            } else+                end = last_best;+            output = 1;+        }+        if (*end == '\0')+            output = 1;++        if (*end == '\n' || *end == '\r')+            output = 1;++        if (output) {+            int len = end - start;+            double char_spacing = 0;+            if (len >= sizeof(line))+                len = sizeof(line) - 1;+            strncpy(line, start, len);+            line[len] = '\0';++            line_width = pdf_text_pixel_width(pdf, start, len, size, widths);+            if (line_width < 0)+                return line_width;++            switch (align) {+            case PDF_ALIGN_RIGHT:+                xoff_align += wrap_width - line_width;+                break;+            case PDF_ALIGN_CENTER:+                xoff_align += (wrap_width - line_width) / 2;+                break;+            case PDF_ALIGN_JUSTIFY:+                if ((len - 1) > 0 && *end != '\r' && *end != '\n' &&+                    *end != '\0')+                    char_spacing =+                        ((double)(wrap_width - line_width)) / (len - 2);+                break;+            case PDF_ALIGN_JUSTIFY_ALL:+                if ((len - 1) > 0)+                    char_spacing =+                        ((double)(wrap_width - line_width)) / (len - 2);+                break;+            }++            pdf_add_text_spacing(pdf, page, line, size, xoff_align, yoff,+                                 colour, char_spacing);++            if (*end == ' ')+                end++;++            start = last_best = end;+            yoff -= size;+        } else+            last_best = end;+    }++    return orig_yoff - yoff;+}++int pdf_add_line(struct pdf_doc *pdf, struct pdf_object *page, int x1, int y1,+                 int x2, int y2, int width, uint32_t colour)+{+    int ret;+    struct dstr str = INIT_DSTR;++    dstr_append(&str, "BT\r\n");+    dstr_printf(&str, "%d w\r\n", width);+    dstr_printf(&str, "%d %d m\r\n", x1, y1);+    dstr_printf(&str, "/DeviceRGB CS\r\n");+    dstr_printf(&str, "%f %f %f RG\r\n", PDF_RGB_R(colour), PDF_RGB_G(colour),+                PDF_RGB_B(colour));+    dstr_printf(&str, "%d %d l S\r\n", x2, y2);+    dstr_append(&str, "ET");++    ret = pdf_add_stream(pdf, page, dstr_data(&str));+    dstr_free(&str);++    return ret;+}++int pdf_add_ellipse(struct pdf_doc *pdf, struct pdf_object *page, int xr,+                    int yr, int xradius, int yradius, int width,+                    uint32_t colour, uint32_t fill_colour)+{+    int ret;+    struct dstr str = INIT_DSTR;++    float rx = xradius * 1.0f;+    float ry = yradius * 1.0f;+    float lx, ly;+    float x = xr * 1.0f;+    float y = yr * 1.0f;++    lx = (4.0f / 3.0f) * (M_SQRT2 - 1) * rx;+    ly = (4.0f / 3.0f) * (M_SQRT2 - 1) * ry;++    if (!PDF_IS_TRANSPARENT(fill_colour)) {+        dstr_append(&str, "BT ");+        dstr_printf(&str, "/DeviceRGB CS\r\n");+        dstr_printf(&str, "%f %f %f rg\r\n", PDF_RGB_R(fill_colour),+                    PDF_RGB_G(fill_colour), PDF_RGB_B(fill_colour));+        dstr_append(&str, "ET\r\n");+    }++    dstr_append(&str, "BT ");++    /* stroke color */+    dstr_printf(&str, "/DeviceRGB CS\r\n");+    dstr_printf(&str, "%f %f %f RG\r\n", PDF_RGB_R(colour), PDF_RGB_G(colour),+                PDF_RGB_B(colour));++    dstr_printf(&str, "%d w ", width);++    dstr_printf(&str, "%.2f %.2f m ", (x + rx), (y));++    dstr_printf(&str, "%.2f %.2f %.2f %.2f %.2f %.2f c ", (x + rx), (y - ly),+                (x + lx), (y - ry), x, (y - ry));++    dstr_printf(&str, "%.2f %.2f %.2f %.2f %.2f %.2f c ", (x - lx), (y - ry),+                (x - rx), (y - ly), (x - rx), y);++    dstr_printf(&str, "%.2f %.2f %.2f %.2f %.2f %.2f c ", (x - rx), (y + ly),+                (x - lx), (y + ry), x, (y + ry));++    dstr_printf(&str, "%.2f %.2f %.2f %.2f %.2f %.2f c ", (x + lx), (y + ry),+                (x + rx), (y + ly), (x + rx), y);++    if (PDF_IS_TRANSPARENT(fill_colour))+        dstr_printf(&str, "%s", "S ");+    else+        dstr_printf(&str, "%s", "B ");++    dstr_append(&str, "ET");++    ret = pdf_add_stream(pdf, page, dstr_data(&str));+    dstr_free(&str);++    return ret;+}++int pdf_add_circle(struct pdf_doc *pdf, struct pdf_object *page, int xr,+                   int yr, int radius, int width, uint32_t colour,+                   uint32_t fill_colour)+{+    return pdf_add_ellipse(pdf, page, xr, yr, radius, radius, width, colour,+                           fill_colour);+}++int pdf_add_rectangle(struct pdf_doc *pdf, struct pdf_object *page, int x,+                      int y, int width, int height, int border_width,+                      uint32_t colour)+{+    int ret;+    struct dstr str = INIT_DSTR;++    dstr_append(&str, "BT ");+    dstr_printf(&str, "%f %f %f RG ", PDF_RGB_R(colour), PDF_RGB_G(colour),+                PDF_RGB_B(colour));+    dstr_printf(&str, "%d w ", border_width);+    dstr_printf(&str, "%d %d %d %d re S ", x, y, width, height);+    dstr_append(&str, "ET");++    ret = pdf_add_stream(pdf, page, dstr_data(&str));+    dstr_free(&str);++    return ret;+}++int pdf_add_filled_rectangle(struct pdf_doc *pdf, struct pdf_object *page,+                             int x, int y, int width, int height,+                             int border_width, uint32_t colour)+{+    int ret;+    struct dstr str = INIT_DSTR;++    dstr_append(&str, "BT ");+    dstr_printf(&str, "%f %f %f rg ", PDF_RGB_R(colour), PDF_RGB_G(colour),+                PDF_RGB_B(colour));+    dstr_printf(&str, "%d w ", border_width);+    dstr_printf(&str, "%d %d %d %d re f ", x, y, width, height);+    dstr_append(&str, "ET");++    ret = pdf_add_stream(pdf, page, dstr_data(&str));+    dstr_free(&str);++    return ret;+}++int pdf_add_polygon(struct pdf_doc *pdf, struct pdf_object *page, int x[],+                    int y[], int count, int border_width, uint32_t colour)+{+    int ret;+    struct dstr str = INIT_DSTR;++    dstr_append(&str, "BT ");+    dstr_printf(&str, "%f %f %f RG ", PDF_RGB_R(colour), PDF_RGB_G(colour),+                PDF_RGB_B(colour));+    dstr_printf(&str, "%d w ", border_width);+    dstr_printf(&str, "%d %d m ", x[0], y[0]);+    for (int i = 1; i < count; i++) {+        dstr_printf(&str, "%d %d l ", x[i], y[i]);+    }+    dstr_printf(&str, "h S ");+    dstr_append(&str, "ET");++    ret = pdf_add_stream(pdf, page, dstr_data(&str));+    dstr_free(&str);++    return ret;+}++int pdf_add_filled_polygon(struct pdf_doc *pdf, struct pdf_object *page,+                           int x[], int y[], int count, int border_width,+                           uint32_t colour)+{+    int ret;+    struct dstr str = INIT_DSTR;++    dstr_append(&str, "BT ");+    dstr_printf(&str, "%f %f %f RG ", PDF_RGB_R(colour), PDF_RGB_G(colour),+                PDF_RGB_B(colour));+    dstr_printf(&str, "%f %f %f rg ", PDF_RGB_R(colour), PDF_RGB_G(colour),+                PDF_RGB_B(colour));+    dstr_printf(&str, "%d w ", border_width);+    dstr_printf(&str, "%d %d m ", x[0], y[0]);+    for (int i = 1; i < count; i++) {+        dstr_printf(&str, "%d %d l ", x[i], y[i]);+    }+    dstr_printf(&str, "h f ");+    dstr_append(&str, "ET");++    ret = pdf_add_stream(pdf, page, dstr_data(&str));+    dstr_free(&str);++    return ret;+}++static const struct {+    uint32_t code;+    char ch;+} code_128a_encoding[] = {+    {0x212222, ' '},  {0x222122, '!'},  {0x222221, '"'},   {0x121223, '#'},+    {0x121322, '$'},  {0x131222, '%'},  {0x122213, '&'},   {0x122312, '\''},+    {0x132212, '('},  {0x221213, ')'},  {0x221312, '*'},   {0x231212, '+'},+    {0x112232, ','},  {0x122132, '-'},  {0x122231, '.'},   {0x113222, '/'},+    {0x123122, '0'},  {0x123221, '1'},  {0x223211, '2'},   {0x221132, '3'},+    {0x221231, '4'},  {0x213212, '5'},  {0x223112, '6'},   {0x312131, '7'},+    {0x311222, '8'},  {0x321122, '9'},  {0x321221, ':'},   {0x312212, ';'},+    {0x322112, '<'},  {0x322211, '='},  {0x212123, '>'},   {0x212321, '?'},+    {0x232121, '@'},  {0x111323, 'A'},  {0x131123, 'B'},   {0x131321, 'C'},+    {0x112313, 'D'},  {0x132113, 'E'},  {0x132311, 'F'},   {0x211313, 'G'},+    {0x231113, 'H'},  {0x231311, 'I'},  {0x112133, 'J'},   {0x112331, 'K'},+    {0x132131, 'L'},  {0x113123, 'M'},  {0x113321, 'N'},   {0x133121, 'O'},+    {0x313121, 'P'},  {0x211331, 'Q'},  {0x231131, 'R'},   {0x213113, 'S'},+    {0x213311, 'T'},  {0x213131, 'U'},  {0x311123, 'V'},   {0x311321, 'W'},+    {0x331121, 'X'},  {0x312113, 'Y'},  {0x312311, 'Z'},   {0x332111, '['},+    {0x314111, '\\'}, {0x221411, ']'},  {0x431111, '^'},   {0x111224, '_'},+    {0x111422, '`'},  {0x121124, 'a'},  {0x121421, 'b'},   {0x141122, 'c'},+    {0x141221, 'd'},  {0x112214, 'e'},  {0x112412, 'f'},   {0x122114, 'g'},+    {0x122411, 'h'},  {0x142112, 'i'},  {0x142211, 'j'},   {0x241211, 'k'},+    {0x221114, 'l'},  {0x413111, 'm'},  {0x241112, 'n'},   {0x134111, 'o'},+    {0x111242, 'p'},  {0x121142, 'q'},  {0x121241, 'r'},   {0x114212, 's'},+    {0x124112, 't'},  {0x124211, 'u'},  {0x411212, 'v'},   {0x421112, 'w'},+    {0x421211, 'x'},  {0x212141, 'y'},  {0x214121, 'z'},   {0x412121, '{'},+    {0x111143, '|'},  {0x111341, '}'},  {0x131141, '~'},   {0x114113, '\0'},+    {0x114311, '\0'}, {0x411113, '\0'}, {0x411311, '\0'},  {0x113141, '\0'},+    {0x114131, '\0'}, {0x311141, '\0'}, {0x411131, '\0'},  {0x211412, '\0'},+    {0x211214, '\0'}, {0x211232, '\0'}, {0x2331112, '\0'},+};++static int find_128_encoding(char ch)+{+    for (int i = 0; i < ARRAY_SIZE(code_128a_encoding); i++) {+        if (code_128a_encoding[i].ch == ch)+            return i;+    }+    return -1;+}++static int pdf_barcode_128a_ch(struct pdf_doc *pdf, struct pdf_object *page,+                               int x, int y, int width, int height,+                               uint32_t colour, int index, int code_len)+{+    uint32_t code = code_128a_encoding[index].code;+    int line_width = width / 11;++    for (int i = 0; i < code_len; i++) {+        uint8_t shift = (code_len - 1 - i) * 4;+        uint8_t mask = (code >> shift) & 0xf;++        if (!(i % 2))+            pdf_add_filled_rectangle(pdf, page, x, y, line_width * mask,+                                     height, 0, colour);+        x += line_width * mask;+    }+    return x;+}++static int pdf_add_barcode_128a(struct pdf_doc *pdf, struct pdf_object *page,+                                int x, int y, int width, int height,+                                const char *string, uint32_t colour)+{+    const char *s;+    int len = strlen(string) + 3;+    int char_width = width / len;+    int checksum, i;++    if (char_width / 11 <= 0)+        return pdf_set_err(pdf, -EINVAL,+                           "Insufficient width to draw barcode");++    for (s = string; *s; s++)+        if (find_128_encoding(*s) < 0)+            return pdf_set_err(pdf, -EINVAL, "Invalid barcode character 0x%x",+                               *s);++    x = pdf_barcode_128a_ch(pdf, page, x, y, char_width, height, colour, 104,+                            6);+    checksum = 104;++    for (i = 1, s = string; *s; s++, i++) {+        int index = find_128_encoding(*s);+        x = pdf_barcode_128a_ch(pdf, page, x, y, char_width, height, colour,+                                index, 6);+        checksum += index * i;+    }+    x = pdf_barcode_128a_ch(pdf, page, x, y, char_width, height, colour,+                            checksum % 103, 6);+    pdf_barcode_128a_ch(pdf, page, x, y, char_width, height, colour, 106, 7);+    return 0;+}++/* Code 39 character encoding. Each 4-bit value indicates:+ * 0 => wide bar+ * 1 => narrow bar+ * 2 => wide space+ */+static const struct {+    uint32_t code;+    char ch;+} code_39_encoding[] = {+    {0x012110, '1'}, {0x102110, '2'}, {0x002111, '3'},+    {0x112010, '4'}, {0x012011, '5'}, {0x102011, '6'},+    {0x112100, '7'}, {0x012101, '8'}, {0x102101, '9'},+    {0x112001, '0'}, {0x011210, 'A'}, {0x101210, 'B'},+    {0x001211, 'C'}, {0x110210, 'D'}, {0x010211, 'E'},+    {0x100211, 'F'}, {0x111200, 'G'}, {0x011201, 'H'},+    {0x101201, 'I'}, {0x110201, 'J'}, {0x011120, 'K'},+    {0x101120, 'L'}, {0x001121, 'M'}, {0x110120, 'N'},+    {0x010121, 'O'}, {0x100121, 'P'}, {0x111020, 'Q'},+    {0x011021, 'R'}, {0x101021, 'S'}, {0x110021, 'T'},+    {0x021110, 'U'}, {0x120110, 'V'}, {0x020111, 'W'},+    {0x121010, 'X'}, {0x021011, 'Y'}, {0x120011, 'Z'},+    {0x121100, '-'}, {0x021101, '.'}, {0x120101, ' '},+    {0x121001, '*'}, // 'stop' character+};++static int pdf_barcode_39_ch(struct pdf_doc *pdf, struct pdf_object *page,+                             int x, int y, int char_width, int height,+                             uint32_t colour, char ch)+{+    int nw = char_width / 12;+    int ww = char_width / 4;+    int i;+    uint32_t code;++    if (nw <= 1 || ww <= 1)+        return pdf_set_err(pdf, -EINVAL,+                           "Insufficient width for each character");++    for (i = 0; i < ARRAY_SIZE(code_39_encoding); i++) {+        if (code_39_encoding[i].ch == ch) {+            code = code_39_encoding[i].code;+            break;+        }+    }+    if (i == ARRAY_SIZE(code_39_encoding))+        return pdf_set_err(pdf, -EINVAL, "Invalid Code 39 character %c 0x%x",+                           ch, ch);++    for (i = 5; i >= 0; i--) {+        int pattern = (code >> i * 4) & 0xf;+        if (pattern == 0) { // wide+            if (pdf_add_filled_rectangle(pdf, page, x, y, ww - 1, height, 0,+                                         colour) < 0)+                return pdf->errval;+            x += ww;+        }+        if (pattern == 1) { // narrow+            if (pdf_add_filled_rectangle(pdf, page, x, y, nw - 1, height, 0,+                                         colour) < 0)+                return pdf->errval;+            x += nw;+        }+        if (pattern == 2) { // space+            x += nw;+        }+    }+    return x;+}++static int pdf_add_barcode_39(struct pdf_doc *pdf, struct pdf_object *page,+                              int x, int y, int width, int height,+                              const char *string, uint32_t colour)+{+    int len = strlen(string);+    int char_width = width / (len + 2);++    x = pdf_barcode_39_ch(pdf, page, x, y, char_width, height, colour, '*');+    if (x < 0)+        return x;++    while (string && *string) {+        x = pdf_barcode_39_ch(pdf, page, x, y, char_width, height, colour,+                              *string);+        if (x < 0)+            return x;+        string++;+    };++    x = pdf_barcode_39_ch(pdf, page, x, y, char_width, height, colour, '*');+    if (x < 0)+        return x;++    return 0;+}++int pdf_add_barcode(struct pdf_doc *pdf, struct pdf_object *page, int code,+                    int x, int y, int width, int height, const char *string,+                    uint32_t colour)+{+    if (!string || !*string)+        return 0;+    switch (code) {+    case PDF_BARCODE_128A:+        return pdf_add_barcode_128a(pdf, page, x, y, width, height, string,+                                    colour);+    case PDF_BARCODE_39:+        return pdf_add_barcode_39(pdf, page, x, y, width, height, string,+                                  colour);+    default:+        return pdf_set_err(pdf, -EINVAL, "Invalid barcode code %d", code);+    }+}++static pdf_object *pdf_add_raw_rgb24(struct pdf_doc *pdf, uint8_t *data,+                                     int width, int height)+{+    struct pdf_object *obj;+    int len;+    const char *endstream = ">\r\nendstream\r\n";++    obj = pdf_add_object(pdf, OBJ_image);+    if (!obj)+        return NULL;++    dstr_printf(+        &obj->stream,+        "<<\r\n/Type /XObject\r\n/Name /Image%d\r\n/Subtype /Image\r\n"+        "/ColorSpace /DeviceRGB\r\n/Height %d\r\n/Width %d\r\n"+        "/BitsPerComponent 8\r\n/Filter /ASCIIHexDecode\r\n"+        "/Length %d\r\n>>stream\r\n",+        flexarray_size(&pdf->objects), height, width,+        width * height * 3 * 2 + 1);++    len = dstr_len(&obj->stream) + width * height * 3 * 2 ++          strlen(endstream) + 1;+    if (dstr_ensure(&obj->stream, len) < 0) {+        pdf_set_err(pdf, -ENOMEM,+                    "Unable to allocate %d bytes memory for image", len);+        return NULL;+    }+    for (int i = 0; i < width * height * 3; i++) {+        char buf[2] = {"0123456789ABCDEF"[(data[i] >> 4) & 0xf],+                       "0123456789ABCDEF"[data[i] & 0xf]};+        dstr_append_data(&obj->stream, buf, 2);+    }+    dstr_append(&obj->stream, endstream);++    return obj;+}++/* See http://www.64lines.com/jpeg-width-height for details */+static int jpeg_size(unsigned char *data, unsigned int data_size, int *width,+                     int *height)+{+    int i = 0;+    if (i + 3 < data_size && data[i] == 0xFF && data[i + 1] == 0xD8 &&+        data[i + 2] == 0xFF && data[i + 3] == 0xE0) {+        i += 4;+        if (i + 6 < data_size && data[i + 2] == 'J' && data[i + 3] == 'F' &&+            data[i + 4] == 'I' && data[i + 5] == 'F' && data[i + 6] == 0x00) {+            unsigned short block_length = data[i] * 256 + data[i + 1];+            while (i < data_size) {+                i += block_length;+                if ((i + 8) >= data_size)+                    return -1;+                if (data[i] != 0xFF)+                    return -1;+                if (data[i + 1] == 0xC0) {+                    *height = data[i + 5] * 256 + data[i + 6];+                    *width = data[i + 7] * 256 + data[i + 8];+                    return 0;+                }+                i += 2;+                block_length = data[i] * 256 + data[i + 1];+            }+        }+    }++    return -1;+}++// MT: added this function+static pdf_object *pdf_add_raw_jpeg_direct+    ( struct pdf_doc *pdf+    , uint8_t *jpeg_data+    , size_t len+    , int width+    , int height+    )+{+    struct pdf_object *obj;++    obj = pdf_add_object(pdf, OBJ_image);+    if (!obj)+        return NULL;+    dstr_printf(&obj->stream,+                "<<\r\n/Type /XObject\r\n/Name /Image%d\r\n"+                "/Subtype /Image\r\n/ColorSpace /DeviceRGB\r\n"+                "/Width %d\r\n/Height %d\r\n"+                "/BitsPerComponent 8\r\n/Filter /DCTDecode\r\n"+                "/Length %d\r\n>>stream\r\n",+                flexarray_size(&pdf->objects), width, height, (int)len);+    dstr_append_data(&obj->stream, jpeg_data, len);++    dstr_printf(&obj->stream, "\r\nendstream\r\n");++    return obj;+}++static pdf_object *pdf_add_raw_jpeg(struct pdf_doc *pdf,+                                    const char *jpeg_file)+{+    struct stat buf;+    size_t len;+    uint8_t *jpeg_data;+    FILE *fp;+    struct pdf_object *obj;+    int width, height;++    if (stat(jpeg_file, &buf) < 0) {+        pdf_set_err(pdf, -errno, "Unable to access %s: %s", jpeg_file,+                    strerror(errno));+        return NULL;+    }++    len = buf.st_size;++    if ((fp = fopen(jpeg_file, "rb")) == NULL) {+        pdf_set_err(pdf, -errno, "Unable to open %s: %s", jpeg_file,+                    strerror(errno));+        return NULL;+    }++    jpeg_data = malloc(len);+    if (!jpeg_data) {+        pdf_set_err(pdf, -errno, "Unable to allocate: %zd", len);+        fclose(fp);+        return NULL;+    }++    if (fread(jpeg_data, len, 1, fp) != 1) {+        pdf_set_err(pdf, -errno, "Unable to read full jpeg data");+        free(jpeg_data);+        fclose(fp);+        return NULL;+    }+    fclose(fp);++    if (jpeg_size(jpeg_data, len, &width, &height) < 0) {+        free(jpeg_data);+        pdf_set_err(pdf, -EINVAL,+                    "Unable to determine jpeg width/height from %s",+                    jpeg_file);+        return NULL;+    }++    obj = pdf_add_object(pdf, OBJ_image);+    if (!obj)+        return NULL;+    dstr_printf(&obj->stream,+                "<<\r\n/Type /XObject\r\n/Name /Image%d\r\n"+                "/Subtype /Image\r\n/ColorSpace /DeviceRGB\r\n"+                "/Width %d\r\n/Height %d\r\n"+                "/BitsPerComponent 8\r\n/Filter /DCTDecode\r\n"+                "/Length %d\r\n>>stream\r\n",+                flexarray_size(&pdf->objects), width, height, (int)len);+    dstr_append_data(&obj->stream, jpeg_data, len);++    dstr_printf(&obj->stream, "\r\nendstream\r\n");++    free(jpeg_data);++    return obj;+}++static int pdf_add_image(struct pdf_doc *pdf, struct pdf_object *page,+                         struct pdf_object *image, int x, int y, int width,+                         int height)+{+    int ret;+    struct dstr str = INIT_DSTR;++    dstr_append(&str, "q ");+    dstr_printf(&str, "%d 0 0 %d %d %d cm ", width, height, x, y);+    dstr_printf(&str, "/Image%d Do ", image->index);+    dstr_append(&str, "Q");++    ret = pdf_add_stream(pdf, page, dstr_data(&str));+    dstr_free(&str);+    return ret;+}++int pdf_add_ppm(struct pdf_doc *pdf, struct pdf_object *page, int x, int y,+                int display_width, int display_height, const char *ppm_file)+{+    struct pdf_object *obj;+    uint8_t *data;+    FILE *fp;+    char line[1024];+    unsigned width, height, size;++    /* Load the PPM file */+    fp = fopen(ppm_file, "rb");+    if (!fp)+        return pdf_set_err(pdf, -errno, "Unable to open '%s'", ppm_file);+    if (!fgets(line, sizeof(line) - 1, fp)) {+        fclose(fp);+        return pdf_set_err(pdf, -EINVAL, "Invalid PPM file");+    }++    /* We only support binary ppms */+    if (strncmp(line, "P6", 2) != 0) {+        fclose(fp);+        return pdf_set_err(pdf, -EINVAL, "Only binary PPM files supported");+    }++    /* Skip the header comments until we get to the dimensions info */+    do {+        if (!fgets(line, sizeof(line) - 1, fp)) {+            fclose(fp);+            return pdf_set_err(pdf, -EINVAL, "Unable to find PPM size");+        }+        if (line[0] != '#')+            break;+    } while (1);++    if (sscanf(line, "%u %u\n", &width, &height) != 2) {+        fclose(fp);+        return pdf_set_err(pdf, -EINVAL, "Unable to find PPM size");+    }++    /* Skip over the byte-size line */+    if (!fgets(line, sizeof(line) - 1, fp)) {+        fclose(fp);+        return pdf_set_err(pdf, -EINVAL, "No byte-size line in PPM file");+    }++    /* Try and limit the memory usage to sane images */+    if (width > 2 << 14 || height > 2 << 14) {+        fclose(fp);+        return pdf_set_err(pdf, -EINVAL,+                           "Invalid width/height in PPM file: %ux%u", width,+                           height);+    }++    size = width * height * 3;+    data = malloc(size);+    if (!data) {+        fclose(fp);+        return pdf_set_err(pdf, -ENOMEM,+                           "Unable to allocate memory for RGB data");+    }+    if (fread(data, 1, size, fp) != size) {+        free(data);+        fclose(fp);+        return pdf_set_err(pdf, -EINVAL, "Insufficient RGB data available");+    }+    fclose(fp);+    obj = pdf_add_raw_rgb24(pdf, data, width, height);+    free(data);+    if (!obj)+        return pdf->errval;++    return pdf_add_image(pdf, page, obj, x, y, display_width, display_height);+}++int pdf_add_jpeg(struct pdf_doc *pdf, struct pdf_object *page, int x, int y,+                 int display_width, int display_height, const char *jpeg_file)+{+    struct pdf_object *obj;++    obj = pdf_add_raw_jpeg(pdf, jpeg_file);+    if (!obj)+        return pdf->errval;++    return pdf_add_image(pdf, page, obj, x, y, display_width, display_height);+}++// MT: added this function+int pdf_add_jpeg_direct(struct pdf_doc *pdf, struct pdf_object *page, int w, int h,+                 int x, int y, int display_width, int display_height, uint8_t *jpeg_data, size_t len)+{+    struct pdf_object *obj;++    obj = pdf_add_raw_jpeg_direct(pdf, jpeg_data, len, w, h);+    if (!obj)+        return pdf->errval;++    return pdf_add_image(pdf, page, obj, x, y, display_width, display_height);+}++// MT: added this function+struct pdf_doc *pdf_create_nostruct(int width, int height,+    char *creator,+    char *producer,+    char *title,+    char *author,+    char *subject,+    char *date)+{+    struct pdf_info info = {0};+    strncpy(info.creator, creator, 63);+    strncpy(info.producer, producer, 63);+    strncpy(info.title, title, 63);+    strncpy(info.author, author, 63);+    strncpy(info.subject, subject, 63);+    strncpy(info.date, date, 63);+    return pdf_create(width, height, &info);+}
+ cbits/pdfgen.h view
@@ -0,0 +1,456 @@+/**+ * Simple engine for creating PDF files.+ * It supports text, shapes, images etc...+ * Capable of handling millions of objects without too much performance+ * penalty.+ * Public domain license - no warrenty implied; use at your own risk.+ */+#ifndef PDFGEN_H+#define PDFGEN_H++#include <stdbool.h>+#include <stdint.h>++/**+ * @defgroup subsystem Simple PDF Generation+ * Allows for quick generation of simple PDF documents.+ * This is useful for producing easily printed output from C code, where+ * advanced formatting is not required+ *+ * Note: All coordinates/sizes are in points (1/72 of an inch).+ * All coordinates are based on 0,0 being the bottom left of the page.+ * All colours are specified as a packed 32-bit value - see @ref PDF_RGB.+ * Text strings are interpreted as UTF-8 encoded, but only a small subset of+ * characters beyond 7-bit ascii are supported (see @ref pdf_add_text for+ * details).+ *+ * @par PDF library example:+ * @code+#include "pdfgen.h"+ ...+struct pdf_info info = {+         .creator = "My software",+         .producer = "My software",+         .title = "My document",+         .author = "My name",+         .subject = "My subject",+         .date = "Today"+         };+struct pdf_doc *pdf = pdf_create(PDF_A4_WIDTH, PDF_A4_HEIGHT, &info);+pdf_set_font(pdf, "Times-Roman");+pdf_append_page(pdf);+pdf_add_text(pdf, NULL, "This is text", 12, 50, 20);+pdf_add_line(pdf, NULL, 50, 24, 150, 24);+pdf_save(pdf, "output.pdf");+pdf_destroy(pdf);+ * @endcode+ */++struct pdf_doc;+struct pdf_object;++struct pdf_info {+    char creator[64];+    char producer[64];+    char title[64];+    char author[64];+    char subject[64];+    char date[64];+};++/**+ * Convert a value in inches into a number of points.+ * Always returns an integer value+ */+#define PDF_INCH_TO_POINT(inch) ((int)((inch)*72 + 0.5))++/**+ * Convert a value in milli-meters into a number of points.+ * Always returns an integer value+ */+#define PDF_MM_TO_POINT(mm) ((int)((mm)*72 / 25.4 + 0.5))++/**+ * Point width of a standard US-Letter page+ */+#define PDF_LETTER_WIDTH PDF_INCH_TO_POINT(8.5)++/**+ * Point height of a standard US-Letter page+ */+#define PDF_LETTER_HEIGHT PDF_INCH_TO_POINT(11)++/**+ * Point width of a standard A4 page+ */+#define PDF_A4_WIDTH PDF_MM_TO_POINT(210)++/**+ * Point height of a standard A4 page+ */+#define PDF_A4_HEIGHT PDF_MM_TO_POINT(297)++/**+ * Point width of a standard A3 page+ */+#define PDF_A3_WIDTH PDF_MM_TO_POINT(297)++/**+ * Point height of a standard A3 page+ */+#define PDF_A3_HEIGHT PDF_MM_TO_POINT(420)++/**+ * Convert three 8-bit RGB values into a single packed 32-bit+ * colour. These 32-bit colours are used by various functions+ * in PDFGen+ */+#define PDF_RGB(r, g, b)                                                     \+    ((((r)&0xff) << 16) | (((g)&0xff) << 8) | (((b)&0xff)))++/**+ * Utility macro to provide bright red+ */+#define PDF_RED PDF_RGB(0xff, 0, 0)++/**+ * Utility macro to provide bright green+ */+#define PDF_GREEN PDF_RGB(0, 0xff, 0)++/**+ * Utility macro to provide bright blue+ */+#define PDF_BLUE PDF_RGB(0, 0, 0xff)++/**+ * Utility macro to provide black+ */+#define PDF_BLACK PDF_RGB(0, 0, 0)++/**+ * Utility macro to provide white+ */+#define PDF_WHITE PDF_RGB(0xff, 0xff, 0xff)++/**+ * Utility macro to provide a transparent colour+ * This is used in some places for 'fill' colours, where no fill is required+ */+#define PDF_TRANSPARENT (0xff << 24)++enum {+    PDF_ALIGN_LEFT,+    PDF_ALIGN_RIGHT,+    PDF_ALIGN_CENTER,+    PDF_ALIGN_JUSTIFY,+    PDF_ALIGN_JUSTIFY_ALL, // Like PDF_ALIGN_JUSTIFY, except even short lines+                           // will be fully justified+};++/**+ * Create a new PDF object, with the given page+ * width/height+ * @param width Width of the page+ * @param height Height of the page+ * @param info Optional information to be put into the PDF header+ * @return PDF document object, or NULL on failure+ */+struct pdf_doc *pdf_create(int width, int height, struct pdf_info *info);++/**+ * Destroy the pdf object, and all of its associated memory+ */+void pdf_destroy(struct pdf_doc *pdf);++/**+ * Retrieve the error message if any operation fails+ * @param pdf pdf document to retrieve error message from+ * @param errval optional pointer to an integer to be set to the error code+ * @return NULL if no error message, string description of error otherwise+ */+const char *pdf_get_err(struct pdf_doc *pdf, int *errval);++/**+ * Acknowledge an outstanding pdf error+ * @param pdf pdf document to clear the error message from+ */+void pdf_clear_err(struct pdf_doc *pdf);++/**+ * Sets the font to use for text objects. Default value is Times-Roman if+ * this function is not called+ * Note: The font selection should be done before text is output,+ * and will remain until pdf_set_font is called again+ * @param pdf PDF document to update font on+ * @param font New font to use. This must be one of the standard PDF fonts:+ *  Courier, Courier-Bold, Courier-BoldOblique, Courier-Oblique,+ *  Helvetica, Helvetica-Bold, Helvetica-BoldOblique, Helvetica-Oblique,+ *  Times-Roman, Times-Bold, Times-Italic, Times-BoldItalic,+ *  Symbol or ZapfDingbats+ * @return < 0 on failure, 0 on success+ */+int pdf_set_font(struct pdf_doc *pdf, const char *font);++/**+ * Returns the width of a given string in the current font+ * @param pdf PDF document+ * @param font_name Name of the font to get the width of.+ *  This must be one of the standard PDF fonts:+ *  Courier, Courier-Bold, Courier-BoldOblique, Courier-Oblique,+ *  Helvetica, Helvetica-Bold, Helvetica-BoldOblique, Helvetica-Oblique,+ *  Times-Roman, Times-Bold, Times-Italic, Times-BoldItalic,+ *  Symbol or ZapfDingbats+ * @param text Text to determine width of+ * @param size Size of the text, in points+ * @return < 0 on failure, 0 on success+ */+int pdf_get_font_text_width(struct pdf_doc *pdf, const char *font_name,+                            const char *text, int size);++/**+ * Retrieves a PDF document height+ */+int pdf_height(struct pdf_doc *pdf);++/**+ * Retrieves a PDF document width+ */+int pdf_width(struct pdf_doc *pdf);++/**+ * Add a new page to the given pdf+ * @return new page object+ */+struct pdf_object *pdf_append_page(struct pdf_doc *pdf);++/**+ * Adjust the width/height of a specific page+ * @param pdf PDF document that the page belongs to+ * @param page object returned from @ref pdf_append_page+ * @param width Width of the page in points+ * @param height Height of the page in points+ * @return < 0 on failure, 0 on success+ */+int pdf_page_set_size(struct pdf_doc *pdf, struct pdf_object *page, int width,+                      int height);++/**+ * Save the given pdf document to the supplied filename+ * If the filename is NULL, defaults to stdout+ */+int pdf_save(struct pdf_doc *pdf, const char *filename);++/**+ * Save the given pdf document to the given FILE output+ */+int pdf_save_file(struct pdf_doc *pdf, FILE *fp);++/**+ * Add a text string to the document+ * @param pdf PDF document to add to+ * @param page Page to add object to (NULL => most recently added page)+ * @param text String to display+ * @param size Point size of the font+ * @param xoff X location to put it in+ * @param yoff Y location to put it in+ * @param colour Colour to draw the text+ * @return 0 on success, < 0 on failure+ */+int pdf_add_text(struct pdf_doc *pdf, struct pdf_object *page,+                 const char *text, int size, int xoff, int yoff,+                 uint32_t colour);++/**+ * Add a text string to the document, making it wrap if it is too+ * long+ * @param pdf PDF document to add to+ * @param page Page to add object to (NULL => most recently added page)+ * @param text String to display+ * @param size Point size of the font+ * @param xoff X location to put it in+ * @param yoff Y location to put it in+ * @param colour Colour to draw the text+ * @param wrap_width Width at which to wrap the text+ * @param align Text alignment (see PDF_ALIGN_xxx)+ * @return height of drawn text on success, < 0 on failure+ */+int pdf_add_text_wrap(struct pdf_doc *pdf, struct pdf_object *page,+                      const char *text, int size, int xoff, int yoff,+                      uint32_t colour, int wrap_width, int align);++/**+ * Add a line to the document+ * @param pdf PDF document to add to+ * @param page Page to add object to (NULL => most recently added page)+ * @param x1 X offset of start of line+ * @param y1 Y offset of start of line+ * @param x2 X offset of end of line+ * @param y2 Y offset of end of line+ * @param width Width of the line+ * @param colour Colour to draw the line+ * @return 0 on success, < 0 on failure+ */+int pdf_add_line(struct pdf_doc *pdf, struct pdf_object *page, int x1, int y1,+                 int x2, int y2, int width, uint32_t colour);++/**+ * Add an ellipse to the document+ * @param pdf PDF document to add to+ * @param page Page to add object to (NULL => most recently added page)+ * @param x X offset of the center of the ellipse+ * @param y Y offset of the center of the ellipse+ * @param xradius Radius of the ellipse in the X axis+ * @param yradius Radius of the ellipse in the Y axis+ * @param colour Colour to draw the ellipse outline stroke+ * @param width Width of the ellipse outline stroke+ * @param fill_colour Colour to fill the ellipse+ * @return 0 on success, < 0 on failure+ */+int pdf_add_ellipse(struct pdf_doc *pdf, struct pdf_object *page, int x,+                    int y, int xradius, int yradius, int width,+                    uint32_t colour, uint32_t fill_colour);++/**+ * Add a circle to the document+ * @param pdf PDF document to add to+ * @param page Page to add object to (NULL => most recently added page)+ * @param x X offset of the center of the circle+ * @param y Y offset of the center of the circle+ * @param radius Radius of the circle+ * @param width Width of the circle outline stroke+ * @param colour Colour to draw the circle outline stroke+ * @param fill_colour Colour to fill the circle+ * @return 0 on success, < 0 on failure+ */+int pdf_add_circle(struct pdf_doc *pdf, struct pdf_object *page, int x, int y,+                   int radius, int width, uint32_t colour,+                   uint32_t fill_colour);++/**+ * Add an outline rectangle to the document+ * @param pdf PDF document to add to+ * @param page Page to add object to (NULL => most recently added page)+ * @param x X offset to start rectangle at+ * @param y Y offset to start rectangle at+ * @param width Width of rectangle+ * @param height Height of rectangle+ * @param border_width Width of rectangle border+ * @param colour Colour to draw the rectangle+ * @return 0 on succss, < 0 on failure+ */+int pdf_add_rectangle(struct pdf_doc *pdf, struct pdf_object *page, int x,+                      int y, int width, int height, int border_width,+                      uint32_t colour);++/**+ * Add a filled rectangle to the document+ * @param pdf PDF document to add to+ * @param page Page to add object to (NULL => most recently added page)+ * @param x X offset to start rectangle at+ * @param y Y offset to start rectangle at+ * @param width Width of rectangle+ * @param height Height of rectangle+ * @param border_width Width of rectangle border+ * @param colour Colour to draw the rectangle+ * @return 0 on succss, < 0 on failure+ */+int pdf_add_filled_rectangle(struct pdf_doc *pdf, struct pdf_object *page,+                             int x, int y, int width, int height,+                             int border_width, uint32_t colour);++/**+ * Add an outline polygon to the document+ * @param pdf PDF document to add to+ * @param page Page to add object to (NULL => most recently added page)+ * @param x array of X offsets for points comprising the polygon+ * @param y array of Y offsets for points comprising the polygon+ * @param count Number of points comprising the polygon+ * @param border_width Width of polygon border+ * @param colour Colour to draw the polygon+ * @return 0 on succss, < 0 on failure+ */+int pdf_add_polygon(struct pdf_doc *pdf, struct pdf_object *page, int x[],+                    int y[], int count, int border_width, uint32_t colour);++/**+ * Add a filled polygon to the document+ * @param pdf PDF document to add to+ * @param page Page to add object to (NULL => most recently added page)+ * @param x array of X offsets of points comprising the polygon+ * @param y array of Y offsets of points comprising the polygon+ * @param count Number of points comprising the polygon+ * @param border_width Width of polygon border+ * @param colour Colour to draw the polygon+ * @return 0 on succss, < 0 on failure+ */+int pdf_add_filled_polygon(struct pdf_doc *pdf, struct pdf_object *page,+                           int x[], int y[], int count, int border_width,+                           uint32_t colour);++/**+ * Add a bookmark to the document+ *+ * @param pdf PDF document to add bookmark to+ * @param page Page to jump to for bookmark+               (or NULL for the most recently added page)+ * @param parent ID of a previosly created bookmark that is the parent+               of this one. -1 if this should be a top-level bookmark.+ * @param name String to associate with the bookmark+ * @return < 0 on failure, new bookmark id on success+ */+int pdf_add_bookmark(struct pdf_doc *pdf, struct pdf_object *page, int parent,+                     const char *name);++/**+ * List of different barcode encodings that are supported+ */+enum {+    PDF_BARCODE_128A,+    PDF_BARCODE_39,+};++/**+ * Add a barcode to the document+ * @param pdf PDF document to add bookmark to+ * @param page Page to add barcode to (NULL => most recently added page)+ * @param code Type of barcode to add (PDF_BARCODE_xxx)+ * @param x X offset to put barcode at+ * @param y Y offset to put barcode at+ * @param width Width of barcode+ * @param height Height of barcode+ * @param string Barcode contents+ * @param colour Colour to draw barcode+ */+int pdf_add_barcode(struct pdf_doc *pdf, struct pdf_object *page, int code,+                    int x, int y, int width, int height, const char *string,+                    uint32_t colour);++/**+ * Add a PPM file as an image to the document+ * @param pdf PDF document to add bookmark to+ * @param page Page to add PPM to (NULL => most recently added page)+ * @param x X offset to put PPM at+ * @param y Y offset to put PPM at+ * @param display_width Displayed width of image+ * @param display_height Displayed height of image+ * @param ppm_file Filename of P6 (binary) ppm file to display+ */+int pdf_add_ppm(struct pdf_doc *pdf, struct pdf_object *page, int x, int y,+                int display_width, int display_height, const char *ppm_file);++/**+ * Add a JPEG file as an image to the document+ * @param pdf PDF document to add bookmark to+ * @param page Page to add PPM to (NULL => most recently added page)+ * @param x X offset to put JPEG at+ * @param y Y offset to put JPEG at+ * @param display_width Displayed width of image+ * @param display_height Displayed height of image+ * @param jpeg_file Filename of JPEG file to display+ */+int pdf_add_jpeg(struct pdf_doc *pdf, struct pdf_object *page, int x, int y,+                 int display_width, int display_height,+                 const char *jpeg_file);++#endif // PDFGEN_H
jammittools.cabal view
@@ -1,5 +1,5 @@ Name:               jammittools-Version:            0.5.5.1+Version:            0.5.5.2 Synopsis:           Export sheet music and audio from Windows/Mac app Jammit Description: @@ -16,13 +16,14 @@ Author:             Michael Tolly Maintainer:         miketolly@gmail.com Build-Type:         Simple-Cabal-Version:      >= 1.8+Cabal-Version:      >= 1.10 Category:           Sound Homepage:           https://github.com/mtolly/jammittools Bug-Reports:        https://github.com/mtolly/jammittools/issues Extra-Source-Files:   README.md   CHANGELOG.md+  cbits/pdfgen.h  library   exposed-modules:@@ -31,7 +32,6 @@   other-modules:     Sound.Jammit.Internal.Audio     Sound.Jammit.Internal.Image-    Sound.Jammit.Internal.TempIO     Sound.Jammit.Internal.PropertyList   hs-source-dirs:   src   build-depends:@@ -40,19 +40,24 @@     , text          >= 1.2.1.1   && < 1.3     , directory     >= 1.2.0.1   && < 1.4     , filepath      >= 1.3.0.1   && < 1.5-    , containers    >= 0.5.0.0   && < 0.6+    , containers    >= 0.5.0.0   && < 0.7     , process       >= 1.1.0.2   && < 1.7-    , temporary     >= 1.1.2.5   && < 1.4     , transformers  >= 0.3.0.0   && < 0.6-    , JuicyPixels   >= 3.2.7     && < 3.3-    , HPDF          >= 1.4.9     && < 1.5+    , JuicyPixels   >= 3.2.7     && < 3.4     , bytestring    >= 0.10.4.0  && < 0.11     , conduit       >= 1.2.3.1   && < 1.4     , vector        >= 0.10.12.2 && < 0.13     , conduit-audio >= 0.1       && < 0.3     , resourcet     >= 1.1.7.2   && < 1.3+  if os(windows)+    build-depends: Win32+    cpp-options: -DWINDOWS   ghc-options:      -Wall -O2-  c-sources:        cbits/ima4.c+  c-sources:+    cbits/ima4.c+    cbits/pdfgen.c+  cc-options: -DSKIP_ATTRIBUTE+  default-language: Haskell2010  executable jammittools   main-is:          Main.hs@@ -65,6 +70,7 @@     , boxes         >= 0.1.3   && < 0.2     , jammittools   ghc-options:      -Wall -O2+  default-language: Haskell2010  source-repository head   type:             git
src/Sound/Jammit/Export.hs view
@@ -26,7 +26,6 @@ import           Sound.Jammit.Base import           Sound.Jammit.Internal.Audio import           Sound.Jammit.Internal.Image-import           Sound.Jammit.Internal.TempIO import           System.Directory             (getDirectoryContents) import           System.FilePath              (splitFileName, takeFileName,                                                (</>))@@ -124,15 +123,13 @@   -> Int                   -- ^ how many sheet music systems per page   -> FilePath              -- ^ the resulting PDF   -> IO ()-runSheet trks lns fout = runTempIO fout $ do-  trkLns <- liftIO $ forM trks $ \(fp, ht) -> do+runSheet trks lns fout = do+  trkLns <- forM trks $ \(fp, ht) -> do     let (dir, file) = splitFileName fp     ls <- getDirectoryContents dir     return (map (dir </>) $ sort $ filter (file `isPrefixOf`) ls, ht)   jpegs <- partsToPages trkLns lns-  pdf <- newTempFile "pages.pdf"-  liftIO $ jpegsToPDF jpegs pdf-  return pdf+  jpegsToPDF jpegs fout  writeMetronomeTrack :: FilePath -> [Beat] -> IO () writeMetronomeTrack fp beats = runResourceT $ writeWAV fp $ metronomeTrack beats
src/Sound/Jammit/Internal/Audio.hs view
@@ -63,21 +63,24 @@             x <- f end             liftIO $ IO.hSetPosn here             return x+      expect x act = act >>= \y -> if x == y+        then return ()+        else error $ "Expected " <> show x <> " but found " <> show y   frames <- IO.withBinaryFile fp IO.ReadMode $ \h -> do     let chunkBefore = insideChunk h     "FORM" `chunkBefore` Nothing $ \formEnd -> do-      "AIFC" <- liftIO $ B.hGet h 4+      expect "AIFC" $ liftIO $ B.hGet h 4       "COMM" `chunkBefore` Just formEnd $ \_ -> do-        2 <- liftIO (readBE h :: IO Word16) -- channels+        expect 2 $ liftIO (readBE h :: IO Word16) -- channels         frames <- liftIO (readBE h :: IO Word32) -- number of chunk pairs         bits <- liftIO (readBE h :: IO Word16) -- bits per sample, 0 means 16?         unless (bits `elem` [0, 16]) $ error "readIMA: bits per sample not 16 or 0"         -- next 10 bytes are sample rate as long float         -- for now we just compare to the known 44100-        0x400eac44 <- liftIO (readBE h :: IO Word32)-        0 <- liftIO (readBE h :: IO Word32)-        0 <- liftIO (readBE h :: IO Word16)-        "ima4" <- liftIO $ B.hGet h 4+        expect 0x400eac44 $ liftIO (readBE h :: IO Word32)+        expect 0 $ liftIO (readBE h :: IO Word32)+        expect 0 $ liftIO (readBE h :: IO Word16)+        expect "ima4" $ liftIO $ B.hGet h 4         return frames   let src = C.bracketP         (IO.openBinaryFile fp IO.ReadMode)@@ -85,10 +88,10 @@         $ \h -> do           let chunkBefore = insideChunk h           "FORM" `chunkBefore` Nothing $ \formEnd -> do-            "AIFC" <- liftIO $ B.hGet h 4+            expect "AIFC" $ liftIO $ B.hGet h 4             "SSND" `chunkBefore` Just formEnd $ \_ -> do-              0 <- liftIO (readBE h :: IO Word32) -- offset-              0 <- liftIO (readBE h :: IO Word32) -- blocksize+              expect 0 $ liftIO (readBE h :: IO Word32) -- offset+              expect 0 $ liftIO (readBE h :: IO Word32) -- blocksize               let go _     _     0         = return ()                   go predL predR remFrames = do                     chunkL <- liftIO $ B.hGet h 34
src/Sound/Jammit/Internal/Image.hs view
@@ -1,21 +1,69 @@+{-# LANGUAGE CPP #-} module Sound.Jammit.Internal.Image ( partsToPages , jpegsToPDF ) where -import qualified Codec.Picture                as P-import           Codec.Picture.Types          (convertImage)-import           Control.Monad                (forM_, replicateM)-import           Control.Monad.IO.Class       (MonadIO)-import           Control.Monad.Trans.Class    (lift)-import qualified Data.ByteString.Lazy         as BL-import           Data.Conduit                 ((.|))-import qualified Data.Conduit                 as C-import           Data.Maybe                   (catMaybes)-import qualified Data.Vector.Storable         as V-import qualified Graphics.PDF                 as PDF-import           Sound.Jammit.Internal.TempIO+import qualified Codec.Picture          as P+import           Codec.Picture.Jpg      (encodeJpegAtQuality)+import           Codec.Picture.Types    (convertImage)+import           Control.Exception      (bracket)+import           Control.Monad          (forM_, replicateM)+import           Control.Monad.IO.Class (MonadIO (..))+import qualified Data.ByteString        as B+import qualified Data.ByteString.Lazy   as BL+import           Data.Conduit           ((.|))+import qualified Data.Conduit           as C+import           Data.Conduit.List      (consume)+import           Data.Maybe             (catMaybes)+import qualified Data.Vector.Storable   as V+import           Foreign+import           Foreign.C +#ifdef WINDOWS+import           System.Win32.Info      (getShortPathName)+#endif++-- data PDFInfo+data PDFDoc+data PDFObject++-- foreign import ccall unsafe "pdf_create"+--   pdf_create :: CInt -> CInt -> Ptr PDFInfo -> IO (Ptr PDFDoc)++foreign import ccall unsafe "pdf_create_nostruct"+  pdf_create_nostruct :: CInt -> CInt+    -> CString -> CString -> CString -> CString -> CString -> CString+    -> IO (Ptr PDFDoc)++foreign import ccall unsafe "pdf_append_page"+  pdf_append_page :: Ptr PDFDoc -> IO (Ptr PDFObject)++foreign import ccall unsafe "pdf_page_set_size"+  pdf_page_set_size :: Ptr PDFDoc -> Ptr PDFObject -> CInt -> CInt -> IO CInt++-- foreign import ccall unsafe "pdf_add_jpeg"+--   pdf_add_jpeg :: Ptr PDFDoc -> Ptr PDFObject -> CInt -> CInt -> CInt -> CInt -> CString -> IO CInt++foreign import ccall unsafe "pdf_add_jpeg_direct"+  pdf_add_jpeg_direct+    :: Ptr PDFDoc+    -> Ptr PDFObject+    -> CInt -> CInt+    -> CInt -> CInt+    -> CInt -> CInt+    -> Ptr Word8 -> CSize+    -> IO CInt++foreign import ccall unsafe "pdf_save"+  pdf_save :: Ptr PDFDoc -> CString -> IO CInt++foreign import ccall unsafe "pdf_destroy"+  pdf_destroy :: Ptr PDFDoc -> IO ()++foreign import ccall unsafe "pdf_get_err"+  pdf_get_err :: Ptr PDFDoc -> Ptr CInt -> IO CString+ loadPNG :: FilePath -> IO (P.Image P.PixelRGB8) loadPNG fp = do   Right dyn <- P.readImage fp@@ -43,25 +91,14 @@   [] -> return ()   _  -> C.yield (vertConcat $ concat systems) >> chunksToPages n -sinkJPEG :: C.ConduitT (P.Image P.PixelRGB8) C.Void TempIO [FilePath]-sinkJPEG = go [] where-  go jpegs = C.await >>= \x -> case x of-    Nothing -> return jpegs-    Just img -> do-      jpeg <- lift $ newTempFile "page.jpg"-      liftIO $ saveJPEG jpeg img-      go $ jpegs ++ [jpeg]- partsToPages   :: [([FilePath], Integer)] -- ^ [(images, system height)]   -> Int -- ^ systems per page-  -> TempIO [FilePath]+  -> IO [P.Image P.PixelRGB8]+partsToPages []    _ = return [] partsToPages parts n = let   sources = map (\(imgs, h) -> pngChunks (fromIntegral h) imgs) parts-  in C.runConduit $ C.sequenceSources sources .| chunksToPages n .| sinkJPEG--saveJPEG :: FilePath -> P.Image P.PixelRGB8 -> IO ()-saveJPEG fp img = BL.writeFile fp $ P.encodeJpegAtQuality 100 $ convertImage img+  in C.runConduit $ C.sequenceSources sources .| chunksToPages n .| consume  vertConcat :: [P.Image P.PixelRGB8] -> P.Image P.PixelRGB8 vertConcat [] = P.Image 0 0 V.empty@@ -103,16 +140,34 @@       }     in first : vertSplit h rest -imagePage :: PDF.JpegFile -> PDF.PDF ()-imagePage jpeg = do-  let (w, h) = PDF.jpegBounds jpeg-  page <- PDF.addPage $ Just $ PDF.PDFRect 0 0 w h-  ref <- PDF.createPDFJpeg jpeg-  PDF.drawWithPage page $ PDF.drawXObject ref--jpegsToPDF :: [FilePath] -> FilePath -> IO ()+jpegsToPDF :: [P.Image P.PixelRGB8] -> FilePath -> IO () jpegsToPDF [] _ = return () -- Buddy Rich "Love for Sale" Kick channel-jpegsToPDF jpegs pdf = do-  Right js <- fmap sequence $ mapM PDF.readJpegFile jpegs-  PDF.runPdf pdf PDF.standardDocInfo (PDF.PDFRect 0 0 600 400) $-    forM_ js imagePage+jpegsToPDF jpegs pdf = let+  inch i = round $ (i :: Rational) * 72+  pageWidth = inch 8.5+  pageHeight = inch 11+  in withCString "" $ \mt -> do+    bracket (pdf_create_nostruct pageWidth pageHeight mt mt mt mt mt mt) pdf_destroy $ \doc -> do+      let check fn = fn >>= \ret -> case ret of+            0 -> return ()+            e -> do+              str <- pdf_get_err doc nullPtr >>= peekCString+              error $ "PDF generation error (" <> show e <> "): " <> str+      forM_ jpegs $ \jpeg@(P.Image w h _) -> do+        let thisHeight = round $ (toRational h / toRational w) * toRational pageWidth+        page <- pdf_append_page doc+        check $ pdf_page_set_size doc page pageWidth thisHeight+        let bs = encodeJpegAtQuality 100 $ convertImage jpeg+        check $ B.useAsCStringLen (BL.toStrict bs) $ \(p, len) -> pdf_add_jpeg_direct+          doc page+          (fromIntegral w) (fromIntegral h)+          0 0+          pageWidth thisHeight+          (castPtr p) (fromIntegral len)+#ifdef WINDOWS+      B.writeFile pdf B.empty -- file has to exist before you get short name+      pdf' <- getShortPathName pdf+#else+      let pdf' = pdf+#endif+      check $ withCString pdf' $ pdf_save doc
src/Sound/Jammit/Internal/PropertyList.hs view
@@ -58,10 +58,11 @@ value e = case asParent e of   ("array", elts) -> Array <$> mapM value elts   ("dict" , elts) -> Dict . Map.fromList <$> go elts where-    go (x : y : xs) = do-      ("key", k) <- asChild x-      v <- value y-      ((k, v) :) <$> go xs+    go (x : y : xs) = asChild x >>= \case+      ("key", k) -> do+        v <- value y+        ((k, v) :) <$> go xs+      _ -> Left "(value) expected <key> at start of <dict>"     go [] = Right []     go _  = Left "(value) odd number of children when parsing a dict"   ("true" , []) -> Right $ Bool True
− src/Sound/Jammit/Internal/TempIO.hs
@@ -1,41 +0,0 @@--- | A wrapper around IO that allows you to treat temporary files like--- garbage-collected values.-module Sound.Jammit.Internal.TempIO-( TempIO-, runTempIO-, newTempFile-, ask-, liftIO-) where--import           Control.Monad.IO.Class     (liftIO)-import           Control.Monad.Trans.Reader (ReaderT (..), ask)-import           Data.List                  (stripPrefix)-import           System.Directory           (copyFile, renameFile)-import           System.FilePath            (splitPath)-import           System.IO                  (hClose)-import           System.IO.Error            (catchIOError)-import           System.IO.Temp             (openTempFile,-                                             withSystemTempDirectory)---- | A wrapper around IO with a designated directory for temporary files.-type TempIO = ReaderT FilePath IO---- | Creates a new temporary directory to run a computation in. When finished,--- the final file will be moved/copied to the given path, and the temporary--- directory will be deleted.-runTempIO :: FilePath -> TempIO FilePath -> IO ()-runTempIO fout act = withSystemTempDirectory "tempfile" $ \tmp -> do-  res <- runReaderT act tmp-  case stripPrefix (splitPath tmp) (splitPath res) of-    Just f | ".." `notElem` f -> -- try rename if we know the file is in tmp-      catchIOError (renameFile res fout) $ \_ -> copyFile res fout-    _ -> copyFile res fout---- | Creates a new file in the temporary directory, given a template.-newTempFile :: String -> TempIO FilePath-newTempFile pat = do-  tmp <- ask-  (f, h) <- liftIO $ openTempFile tmp pat-  liftIO $ hClose h-  return f