λ

pastry

truetype font renderer

settings

commit ff7170bf

authornora aoki <n0ra@cumallover.me>
date2026-10-17 17:24:22 +0530
committernora aoki <n0ra@cumallover.me> (2026-10-17 17:24:22 +0530)
treec89ad41f1a78b851791b164068014eaacc13ac4b
parent

initial

patch diff stats

+2188 -0

.gitattributes added +1 -0

diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000000000000000000000000000000000000..a447c883199e76ef88747e5f960da45b275e15fc
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+firago-extended.ttf filter=lfs diff=lfs merge=lfs -text

.gitignore added +4 -0

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..4daa547e235702f7b30e31e1e2bd2cc3c2d90930
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+pastry.o
+release/
+.idea/
+demo

LICENSE added +22 -0

diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..b5d3a081616f6c2e0bc653bc5ef6e5e101084879
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright © 2025 Nora Aoki
+
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the “Software”), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

Makefile added +39 -0

diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..d503914095ce3db763196687123da1c14ffb893e
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,39 @@
+CC = gcc
+DEMO_CFLAGS     = $(shell pkg-config --cflags x11 xrender harfbuzz)
+DEMO_LIBS       = $(shell pkg-config --libs x11 xrender harfbuzz)
+CFLAGS = -std=c99 -pedantic-errors -Wall -Wextra -Werror -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -Wredundant-decls  \
+             -Wnested-externs -Winline -Wundef -Wshadow -Wpointer-arith -Wcast-qual -Wcast-align -Wwrite-strings -Wconversion -Wsign-conversion \
+             -Wfloat-conversion -Wformat=2 -Wformat-security -Wformat-nonliteral -Wstrict-aliasing=3 -Wstrict-overflow=5 -Wjump-misses-init     \
+             -Wdouble-promotion -Wlogical-op -Wdangling-pointer -Wunreachable-code -Wshift-overflow=2 -Wswitch-enum -Wswitch-default            \
+             -Wshift-negative-value -fno-builtin -fno-common -fstrict-aliasing -fstack-protector-strong -O2 -fno-omit-frame-pointer
+
+SRC = pastry.c
+OBJ = pastry.o
+LIB = libpastry.a
+HDR = pastry.h
+
+RELEASE_DIR = release
+INCLUDE_DIR = $(RELEASE_DIR)/include
+LIB_DIR = $(RELEASE_DIR)/lib
+
+all: $(LIB)
+
+$(LIB): $(OBJ)
+	@mkdir -p $(LIB_DIR)
+	ar rcs $(LIB_DIR)/$(LIB) $<
+	@echo "built $(LIB_DIR)/$(LIB)"
+
+$(OBJ): $(SRC) $(HDR)
+	@mkdir -p $(INCLUDE_DIR)
+	cp $(HDR) $(INCLUDE_DIR)/
+	$(CC) $(CFLAGS) -c $< -o $@
+
+# the demo shapes with harfbuzz at runtime and draws with pastry
+demo: demo.c $(SRC) $(HDR)
+	$(CC) -std=c99 -O2 -Wall -Wextra $(DEMO_CFLAGS) demo.c $(SRC) -o $@ $(DEMO_LIBS)
+
+clean:
+	rm -rf $(RELEASE_DIR)
+	rm -f $(OBJ) demo
+
+.PHONY: all clean

README.md added +32 -0

diff --git a/README.md b/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..72bab6df81a1020ec21d2e923651e7e8c4f23f81
--- /dev/null
+++ b/README.md
@@ -0,0 +1,32 @@
+# pastry
+
+**pastry** is a tiny truetype font renderer written in pure c99. it’s made to be simple, lightweight, and memory-friendly, which is perfect for embedded systems, games, or apps where big engines like freetype feel overkill.
+
+## features
+
+* single-file library
+* solid truetype support
+* memory efficient
+* no external dependencies
+* cross platform
+* handles compound glyphs
+* supports unicode bmp (0x0000–0xffff)
+* hinting support\*
+* ligature support\*
+* affine transforms (scaling, translation, limited rotation)
+
+![screenshot of some sample text](demo.png)
+
+## limitations
+
+* no runtime truetype bytecode hinting. pastry just renders the outlines directly from the font. most modern fonts are pre-hinted anyway, so you're probably fine
+* no text layout or substitution (like auto-turning `f` + `i` into `fi`), but if your system provides the pre-composed ligature glyph, pastry will render it perfectly
+* no opentype features (like contextual alternates, glyph substitution, or positioning)
+* no font collection handling. pastry just loads the full font file &mdash; you can’t pick individual weights or styles from a collection
+* no support for color glyphs (colr/cpal), variable fonts, or characters outside the bmp (so, no emoji)
+* only supports cmap tables 4, 6, 12, and 13, plus simple/compound glyf outlines
+* only has its own fixed grayscale anti-aliasing method
+
+## usage
+
+look at `demo.c` for a usage example &mdash; the api is designed to be pretty simple, tbh.

algorithm.pseudocode added +116 -0

diff --git a/algorithm.pseudocode b/algorithm.pseudocode
new file mode 100644
index 0000000000000000000000000000000000000000..2fc7fc958723030865529bfd42939a96801cf116
--- /dev/null
+++ b/algorithm.pseudocode
@@ -0,0 +1,116 @@
+function render_bezier(beg_point, ctrl_point, end_point, output_buffer, width, height):
+    // set up curve structure
+    curve = Curve(beg_point, end_point, ctrl_point)
+
+    // set up raster buffer
+    buffer = allocate_buffer(width, height)
+    clear_buffer(buffer)
+
+    // tessellate and draw
+    tessellate_and_draw_curve(curve, buffer, width, height)
+
+    // convert to final image
+    post_process(buffer, output_buffer, width, height)
+
+function is_flat(beg, ctrl, end):
+    // calculate twice the area of the triangle formed by the three points
+    // this gives a measure of how "curvy" the curve is
+    vector1 = (ctrl.x - beg.x, ctrl.y - beg.y)
+    vector2 = (end.x - beg.x, end.y - beg.y)
+    area2 = abs(vector1.x * vector2.y - vector1.y * vector2.x)
+
+    // if area is small enough, the curve can be approximated as a straight line
+    return area2 <= 2.0  // threshold value
+
+function tessellate_and_draw_curve(curve, buffer, width, height):
+    stack = empty stack
+    stack.push(curve)
+
+    while stack is not empty:
+        current_curve = stack.pop()
+
+        if is_flat(points[current_curve.beg],
+                  points[current_curve.ctrl],
+                  points[current_curve.end]) or stack is full:
+            // draw as line segment if flat enough
+            draw_line(buffer, width, height,
+                     points[current_curve.beg],
+                     points[current_curve.end])
+        else:
+            // subdivide curve at midpoint using de Casteljau's algorithm
+            left_ctrl = midpoint(points[current_curve.beg], points[current_curve.ctrl])
+            right_ctrl = midpoint(points[current_curve.ctrl], points[current_curve.end])
+            center = midpoint(left_ctrl, right_ctrl)
+
+            // add new points to collection
+            center_idx = add_point(center)
+            left_ctrl_idx = add_point(left_ctrl)
+            right_ctrl_idx = add_point(right_ctrl)
+
+            // push subdivided curves onto stack (right then left for proper order)
+            stack.push(Curve(center_idx, current_curve.end, right_ctrl_idx))
+            stack.push(Curve(current_curve.beg, center_idx, left_ctrl_idx))
+
+function draw_line(buffer, width, height, origin, goal):
+    delta = (goal.x - origin.x, goal.y - origin.y)
+    direction = (sign(delta.x), sign(delta.y))
+
+    // skip horizontal lines (no area contribution)
+    if direction.y == 0:
+        return
+
+    // init grid traversal
+    init_pixel_coordinates(origin, direction, &pixel)
+    init_crossing_distances(origin, delta, direction, pixel, &next_crossing, &crossing_incr)
+
+    prev_distance = 0.0
+    num_steps = calculate_steps(origin, goal, direction)
+    half_delta_x = 0.5 * delta.x
+
+    // walk through each pixel the line passes through
+    for step = 0 to num_steps-1:
+        next_distance = min(next_crossing.x, next_crossing.y)
+
+        // calculate trapezoidal area contribution to current pixel
+        x_average = origin.x + (prev_distance + next_distance) * half_delta_x
+        y_difference = (next_distance - prev_distance) * delta.y
+
+        // update coverage and area for this pixel
+        cell = buffer[pixel.y * width + pixel.x]
+        cell.cover += y_difference
+        cell.area += (1.0 - (x_average - pixel.x)) * y_difference
+        buffer[pixel.y * width + pixel.x] = cell
+
+            // advance to next pixel
+        if next_crossing.x < next_crossing.y:
+            pixel.x += direction.x
+            next_crossing.x += crossing_incr.x
+        else:
+            pixel.y += direction.y
+            next_crossing.y += crossing_incr.y
+
+        prev_distance = next_distance
+
+    // handle final segment to line endpoint
+    next_distance = 1.0
+    x_average = origin.x + (prev_distance + next_distance) * half_delta_x
+    y_difference = (next_distance - prev_distance) * delta.y
+
+    cell = buffer[pixel.y * width + pixel.x]
+    cell.cover += y_difference
+    cell.area += (1.0 - (x_average - pixel.x)) * y_difference
+    buffer[pixel.y * width + pixel.x] = cell
+
+function post_process(buffer, output_image, width, height):
+    accumulated = 0.0
+
+    for each pixel in buffer (row-major order):
+        // combine accumulated coverage with local area
+        value = abs(accumulated + pixel.area)
+        value = clamp(value, 0.0, 1.0)
+
+        // convert to 8-bit grayscale
+        output_image[pixel_index] = round(value * 255.0)
+
+        // update accumulated coverage for next pixel
+        accumulated += pixel.cover

demo.c added +143 -0

diff --git a/demo.c b/demo.c
new file mode 100644
index 0000000000000000000000000000000000000000..ddbdc7e0b5d8611a47939ef1fcc80854f44fad16
--- /dev/null
+++ b/demo.c
@@ -0,0 +1,143 @@
+#include <stdio.h>
+#include <string.h>
+#include <X11/Xlib.h>
+#include <X11/extensions/Xrender.h>
+
+#include <stdint.h>
+#include "pastry.h"
+
+#include <hb.h>
+#include <hb-ot.h>
+
+static int add_glyph(Display *dpy, GlyphSet glyphset, PASTRY *pastry, PASTRY_Glyph glyph)
+{
+#define ABORT(glyph, m) do { fprintf(stderr, "glyph %lu %s\n", (unsigned long)(glyph), m); return -1; } while (0)
+
+    PASTRY_GMetrics mtx;
+    if (pastry_gmetrics(pastry, glyph, &mtx) < 0)
+        ABORT(glyph, "bad glyph metrics");
+
+    PASTRY_Image img = {
+        .width  = (mtx.minWidth + 3) & ~3,
+        .height = mtx.minHeight,
+    };
+    char pixels[img.width * img.height];
+    img.pixels = pixels;
+    if (pastry_render(pastry, glyph, img) < 0)
+        ABORT(glyph, "not rendered");
+
+    XGlyphInfo info = {
+        .x      = (short) -mtx.leftSideBearing,
+        .y      = (short) -mtx.yOffset,
+        .width  = (unsigned short) img.width,
+        .height = (unsigned short) img.height,
+        .xOff   = (short) mtx.advanceWidth,
+        .yOff   = 0
+    };
+    Glyph g = (Glyph) glyph;
+    XRenderAddGlyphs(dpy, glyphset, &g, &info, 1,
+                     img.pixels, (int) (img.width * img.height));
+
+    return 0;
+}
+
+int main(void)
+{
+#define END(m) do { fprintf(stderr, "%s\n", m); return 1; } while (0)
+
+    Display *dpy = XOpenDisplay(NULL);
+    if (dpy == NULL)
+        END("Can't open X display");
+    unsigned int s = 2, screen = (unsigned int)DefaultScreen(dpy);
+
+    Window win = XCreateWindow(dpy, DefaultRootWindow(dpy), 0, 0, 600*s, 440*s, 0,
+                               DefaultDepth(dpy, screen), InputOutput, CopyFromParent, 0, NULL);
+    XSelectInput(dpy, win, ExposureMask);
+    Atom wmDeleteWindow = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
+    XSetWMProtocols(dpy, win, &wmDeleteWindow, 1);
+    XMapRaised(dpy, win);
+
+    XRenderPictFormat *fmt = XRenderFindVisualFormat(dpy, DefaultVisual(dpy, screen));
+    Picture pic = XRenderCreatePicture(dpy, win, fmt, 0, NULL);
+    XRenderColor fg = { 0, 0, 0, 0xFFFF }, bg = { 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF };
+
+    Pixmap fgpix = XCreatePixmap(dpy, win, 1, 1, 24);
+    XRenderPictureAttributes attr = { .repeat = True };
+    fmt = XRenderFindStandardFormat(dpy, PictStandardRGB24);
+    Picture fgpic = XRenderCreatePicture(dpy, fgpix, fmt, CPRepeat, &attr);
+    XRenderFillRectangle(dpy, PictOpSrc, fgpic, &fg, 0, 0, 1, 1);
+
+    fmt = XRenderFindStandardFormat(dpy, PictStandardA8);
+    GlyphSet glyphset = XRenderCreateGlyphSet(dpy, fmt);
+
+    PASTRY pastry = {
+        .xScale = 16*s,
+        .yScale = 16*s,
+        .flags  = PASTRY_DOWNWARD_Y,
+    };
+    pastry.font = pastry_loadfile("firago-extended.ttf");
+    if (pastry.font == NULL)
+        END("failed to load font");
+
+    // harfbuzz does the shaping; pastry just draws the glyph ids it hands us
+    hb_blob_t *blob = hb_blob_create_from_file_or_fail("firago-extended.ttf");
+    if (blob == NULL)
+        END("harfbuzz failed to load font");
+    hb_face_t *face = hb_face_create(blob, 0);
+    hb_font_t *font = hb_font_create(face);
+    hb_ot_font_set_funcs(font);
+    hb_buffer_t *buffer = hb_buffer_create();
+
+    XEvent event;
+    while (!XNextEvent(dpy, &event)) {
+        if (event.type == Expose) {
+            XRenderFillRectangle(dpy, PictOpOver, pic, &bg, 0, 0, (unsigned int)event.xexpose.width, (unsigned int)event.xexpose.height);
+
+            FILE *file = fopen("text.utf8", "r");
+            if (file == NULL)
+                END("cannot open input text");
+
+            PASTRY_LMetrics lmtx;
+            pastry_lmetrics(&pastry, &lmtx);
+            int y = 20 + (int)lmtx.ascender + (int)lmtx.lineGap;
+
+            char text[256];
+            while (fgets(text, sizeof(text), file)) {
+                size_t len = strlen(text);
+                if (len > 0 && text[len - 1] == '\n')
+                    text[--len] = 0;
+
+                hb_buffer_clear_contents(buffer);
+                hb_buffer_add_utf8(buffer, text, (int)len, 0, (int)len);
+                hb_buffer_guess_segment_properties(buffer);
+                hb_shape(font, buffer, NULL, 0);
+
+                unsigned int count = 0;
+                hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, &count);
+                for (unsigned int i = 0; i < count; i++)
+                    add_glyph(dpy, glyphset, &pastry, (PASTRY_Glyph) info[i].codepoint);
+
+                unsigned int glyphs[256];
+                for (unsigned int i = 0; i < count && i < 256; i++)
+                    glyphs[i] = info[i].codepoint;
+                XRenderCompositeString32(dpy, PictOpOver, fgpic, pic, NULL, glyphset, 0, 0, 20, y, glyphs, (int)count);
+
+                y += 2 * ((int)lmtx.ascender + (int)lmtx.descender + (int)lmtx.lineGap);
+            }
+
+            fclose(file);
+        } else if (event.type == ClientMessage) {
+            if ((Atom) event.xclient.data.l[0] == wmDeleteWindow)
+                break;
+        }
+    }
+
+    hb_buffer_destroy(buffer);
+    hb_font_destroy(font);
+    hb_face_destroy(face);
+    hb_blob_destroy(blob);
+
+    pastry_freefont(pastry.font);
+    XCloseDisplay(dpy);
+    return 0;
+}

demo.png added +0 -0

binary file, not shown.

firago-extended.ttf added +3 -0

diff --git a/firago-extended.ttf b/firago-extended.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..509ebeb77a50b4478bfdcfb59b149bee18e8982b
--- /dev/null
+++ b/firago-extended.ttf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7db864491a8bb2fe665329e1ef6d8e0d5c9a93a4c043eff45d89e06baecde9a3
+size 1573820

pastry.c added +1700 -0

diff --git a/pastry.c b/pastry.c
new file mode 100644
index 0000000000000000000000000000000000000000..f94da2bc3e17d91b32c90fac56c3cc7c629497d9
--- /dev/null
+++ b/pastry.c
@@ -0,0 +1,1700 @@
+#define _POSIX_C_SOURCE 200809L
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if defined(_MSC_VER)
+# define restrict __restrict
+#endif
+
+#if defined(_WIN32)
+# define WIN32_LEAN_AND_MEAN 1
+# include <windows.h>
+#else
+# include <fcntl.h>
+# include <sys/mman.h>
+# include <sys/stat.h>
+# include <unistd.h>
+#endif
+
+#include "pastry.h"
+
+#define PASTRY_VERSION "1.0.0"
+
+#define FILE_MAGIC_ONE             0x00010000
+#define FILE_MAGIC_TWO             0x74727565
+
+#define HORIZONTAL_KERNING         0x01
+#define MINIMUM_KERNING            0x02
+#define CROSS_STREAM_KERNING       0x04
+#define OVERRIDE_KERNING           0x08
+
+#define POINT_IS_ON_CURVE          0x01
+#define X_CHANGE_IS_SMALL          0x02
+#define Y_CHANGE_IS_SMALL          0x04
+#define REPEAT_FLAG                0x08
+#define X_CHANGE_IS_ZERO           0x10
+#define X_CHANGE_IS_POSITIVE       0x10
+#define Y_CHANGE_IS_ZERO           0x20
+#define Y_CHANGE_IS_POSITIVE       0x20
+
+#define OFFSETS_ARE_LARGE          0x001
+#define ACTUAL_XY_OFFSETS          0x002
+#define GOT_A_SINGLE_SCALE         0x008
+#define THERE_ARE_MORE_COMPONENTS  0x020
+#define GOT_AN_X_AND_Y_SCALE       0x040
+#define GOT_A_SCALE_MATRIX         0x080
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#define SIGN(x)   (((x) > 0) - ((x) < 0))
+// alloc vals on stack if small enough, else spill to heap
+#define PASTRY_STACK_ALLOC(var, type, thresh, count) \
+    type var##_stack_[thresh]; \
+    var = (count) <= (thresh) ? var##_stack_ : PASTRY_CALLOC(count, sizeof(type));
+#define PASTRY_STACK_FREE(var) \
+    if (var != var##_stack_) PASTRY_FREE(var);
+
+enum
+{ SrcMapping, SrcUser };
+
+typedef struct Point   Point;
+typedef struct Line    Line;
+typedef struct Curve   Curve;
+typedef struct Cell    Cell;
+typedef struct Outline Outline;
+typedef struct Raster  Raster;
+
+struct Point
+{
+    double x, y;
+};
+struct Line
+{
+    uint_least16_t beg, end;
+};
+struct Curve
+{
+    uint_least16_t beg, end, ctrl;
+};
+struct Cell
+{
+    double area, cover;
+};
+
+struct Outline
+{
+    Point *points;
+    Curve *curves;
+    Line  *lines;
+    uint_least16_t numPoints;
+    uint_least16_t capPoints;
+    uint_least16_t numCurves;
+    uint_least16_t capCurves;
+    uint_least16_t numLines;
+    uint_least16_t capLines;
+};
+
+struct Raster
+{
+    Cell *cells;
+    int   width;
+    int   height;
+};
+
+struct PASTRY_Font
+{
+    const uint8_t *memory;
+    uint_fast32_t  size;
+#if defined(_WIN32)
+    HANDLE         mapping;
+#endif
+    int            source;
+
+    uint_least16_t unitsPerEm;
+    int_least16_t  locaFormat;
+    uint_least16_t numLongHmtx;
+};
+
+// generic utils
+static void  *realloc_array(void *optr, size_t nmemb, size_t size);
+static inline int fast_floor(double x);
+static inline int fast_ceil (double x);
+
+// files
+static int  map_file  (PASTRY_Font *font, const char *filename);
+static void unmap_file(PASTRY_Font *font);
+static int  init_font (PASTRY_Font *font);
+
+// math
+static Point midpoint(Point a, Point b);
+static void transform_points(unsigned int numPts, Point *points, double trf[6]);
+static void clip_points(unsigned int numPts, Point *points, int width, int height);
+
+// outline stuff
+static int  init_outline(Outline *outl);
+static void free_outline(Outline *outl);
+static int  grow_points (Outline *outl);
+static int  grow_curves (Outline *outl);
+static int  grow_lines  (Outline *outl);
+
+// parsing utils
+static inline int is_safe_offset(PASTRY_Font *font, uint_fast32_t offset, uint_fast32_t margin);
+static void *csearch(const void *key, const void *base,
+                     size_t nmemb, size_t size, int (*compar)(const void *, const void *));
+static void *binsearch(const void *key, const void *base,
+                       size_t nmemb, size_t size, int (*compar)(const void *, const void *));
+static int  cmpu16(const void *a, const void *b);
+static int  cmpu32(const void *a, const void *b);
+static inline uint_least8_t  getu8 (PASTRY_Font *font, uint_fast32_t offset);
+static inline int_least8_t   geti8 (PASTRY_Font *font, uint_fast32_t offset);
+static inline uint_least16_t getu16(PASTRY_Font *font, uint_fast32_t offset);
+static inline int_least16_t  geti16(PASTRY_Font *font, uint_fast32_t offset);
+static inline uint_least32_t getu32(PASTRY_Font *font, uint_fast32_t offset);
+static int gettable(PASTRY_Font *font, const char tag[4], uint_fast32_t *offset);
+
+// codepoint to glyph id translation
+static int  cmap_fmt4(PASTRY_Font *font, uint_fast32_t table, PASTRY_UChar charCode, uint_fast32_t *glyph);
+static int  cmap_fmt6(PASTRY_Font *font, uint_fast32_t table, PASTRY_UChar charCode, uint_fast32_t *glyph);
+static int  glyph_id(PASTRY_Font *font, PASTRY_UChar charCode, uint_fast32_t *glyph);
+
+// glyph metrics lookup
+static int  hor_metrics(PASTRY_Font *font, uint_fast32_t glyph, int *advanceWidth, int *leftSideBearing);
+static int  glyph_bbox(const PASTRY *pastry, uint_fast32_t outline, int box[4]);
+
+// decoding outlines
+static int  outline_offset(PASTRY_Font *font, uint_fast32_t glyph, uint_fast32_t *offset);
+static int  simple_flags(PASTRY_Font *font, uint_fast32_t *offset, uint_fast16_t numPts, uint8_t *flags);
+static int  simple_points(PASTRY_Font *font, uint_fast32_t offset, uint_fast16_t numPts, uint8_t *flags, Point *points);
+static int  decode_contour(uint8_t *flags, uint_fast16_t basePoint, uint_fast16_t count, Outline *outl);
+static int  simple_outline(PASTRY_Font *font, uint_fast32_t offset, unsigned int numContours, Outline *outl);
+static int  compound_outline(PASTRY_Font *font, uint_fast32_t offset, int recDepth, Outline *outl);
+static int  decode_outline(PASTRY_Font *font, uint_fast32_t offset, int recDepth, Outline *outl);
+
+// tessellation
+static int  is_flat(Outline *outl, Curve curve);
+static int  tessellate_curve(Curve curve, Outline *outl);
+static int  tessellate_curves(Outline *outl);
+
+// silhouette rasterization
+static void draw_line(Raster buf, Point origin, Point goal);
+static void draw_lines(Outline *outl, Raster buf);
+
+// post processing
+static void post_process(Raster buf, uint8_t *image);
+
+// glyph rendering
+static int  render_outline(Outline *outl, double transform[6], PASTRY_Image image);
+
+// ---------- impls start here ----------
+
+const char *
+pastry_version(void)
+{
+    return PASTRY_VERSION;
+}
+
+// loads a font from a given mem range
+PASTRY_Font *
+pastry_loadmem(const void *mem, size_t size)
+{
+    PASTRY_Font *font;
+    if (size > UINT32_MAX) {
+        return NULL;
+    }
+    if (!(font = PASTRY_CALLOC(1, sizeof *font))) {
+        return NULL;
+    }
+    font->memory = mem;
+    font->size   = (uint_fast32_t) size;
+    font->source = SrcUser;
+    if (init_font(font) < 0) {
+        pastry_freefont(font);
+        return NULL;
+    }
+    return font;
+}
+
+// loads a font from the fs
+PASTRY_Font *
+pastry_loadfile(char const *filename)
+{
+    PASTRY_Font *font;
+    if (!(font = PASTRY_CALLOC(1, sizeof *font))) {
+        return NULL;
+    }
+    if (map_file(font, filename) < 0) {
+        PASTRY_FREE(font);
+        return NULL;
+    }
+    if (init_font(font) < 0) {
+        pastry_freefont(font);
+        return NULL;
+    }
+    return font;
+}
+
+void
+pastry_freefont(PASTRY_Font *font)
+{
+    if (!font) return;
+    // only unmap if it was mapped by pastry
+    if (font->source == SrcMapping)
+        unmap_file(font);
+    PASTRY_FREE(font);
+}
+
+int
+pastry_lmetrics(const PASTRY *pastry, PASTRY_LMetrics *metrics)
+{
+    double factor;
+    uint_fast32_t hhea;
+    memset(metrics, 0, sizeof *metrics);
+    if (gettable(pastry->font, "hhea", &hhea) < 0)
+        return -1;
+    if (!is_safe_offset(pastry->font, hhea, 36))
+        return -1;
+    factor = pastry->yScale / pastry->font->unitsPerEm;
+    metrics->ascender  = geti16(pastry->font, hhea + 4) * factor;
+    metrics->descender = geti16(pastry->font, hhea + 6) * factor;
+    metrics->lineGap   = geti16(pastry->font, hhea + 8) * factor;
+    return 0;
+}
+
+int
+pastry_lookup(const PASTRY *pastry, PASTRY_UChar codepoint, PASTRY_Glyph *glyph)
+{
+    return glyph_id(pastry->font, codepoint, glyph);
+}
+
+int
+pastry_gmetrics(const PASTRY *pastry, PASTRY_Glyph glyph, PASTRY_GMetrics *metrics)
+{
+    int adv, lsb;
+    double xScale = pastry->xScale / pastry->font->unitsPerEm;
+    uint_fast32_t outline;
+    int bbox[4];
+
+    memset(metrics, 0, sizeof *metrics);
+
+    if (hor_metrics(pastry->font, glyph, &adv, &lsb) < 0)
+        return -1;
+    metrics->advanceWidth    = adv * xScale;
+    metrics->leftSideBearing = lsb * xScale + pastry->xOffset;
+
+    if (outline_offset(pastry->font, glyph, &outline) < 0)
+        return -1;
+    if (!outline)
+        return 0;
+    if (glyph_bbox(pastry, outline, bbox) < 0)
+        return -1;
+    metrics->minWidth  = bbox[2] - bbox[0] + 1;
+    metrics->minHeight = bbox[3] - bbox[1] + 1;
+    metrics->yOffset   = pastry->flags & PASTRY_DOWNWARD_Y ? -bbox[3] : bbox[1];
+
+    return 0;
+}
+
+int
+pastry_kerning(const PASTRY *pastry, PASTRY_Glyph leftGlyph, PASTRY_Glyph rightGlyph,
+               PASTRY_Kerning *kerning)
+{
+    void *match;
+    uint_fast32_t offset;
+    unsigned int numTables, numPairs, length, format, flags;
+    int value;
+    uint8_t key[4];
+
+    memset(kerning, 0, sizeof *kerning);
+
+    if (gettable(pastry->font, "kern", &offset) < 0)
+        return 0;
+
+    // read kern table header
+    if (!is_safe_offset(pastry->font, offset, 4))
+        return -1;
+    if (getu16(pastry->font, offset) != 0)
+        return 0;
+    numTables = getu16(pastry->font, offset + 2);
+    offset += 4;
+
+    while (numTables > 0) {
+        // read subtable header
+        if (!is_safe_offset(pastry->font, offset, 6))
+            return -1;
+        length = getu16(pastry->font, offset + 2);
+        format = getu8 (pastry->font, offset + 4);
+        flags  = getu8 (pastry->font, offset + 5);
+        offset += 6;
+
+        if (format == 0 && (flags & HORIZONTAL_KERNING) && !(flags & MINIMUM_KERNING)) {
+            // read format 0 header
+            if (!is_safe_offset(pastry->font, offset, 8))
+                return -1;
+            numPairs = getu16(pastry->font, offset);
+            offset += 8;
+            // look up char code pair
+            key[0] = (leftGlyph  >> 8) & 0xFF;
+            key[1] =  leftGlyph  & 0xFF;
+            key[2] = (rightGlyph >> 8) & 0xFF;
+            key[3] =  rightGlyph & 0xFF;
+            if ((match = binsearch(key, pastry->font->memory + offset,
+                                   numPairs, 6, cmpu32)) != NULL) {
+
+                value = geti16(pastry->font, (uint_fast32_t) ((uint8_t *) match - pastry->font->memory + 4));
+                if (flags & CROSS_STREAM_KERNING) {
+                    kerning->yShift += value;
+                } else {
+                    kerning->xShift += value;
+                }
+            }
+
+        }
+
+        offset += length;
+        --numTables;
+    }
+
+    kerning->xShift = kerning->xShift / pastry->font->unitsPerEm * pastry->xScale;
+    kerning->yShift = kerning->yShift / pastry->font->unitsPerEm * pastry->yScale;
+
+    return 0;
+}
+
+int
+pastry_render(const PASTRY *pastry, PASTRY_Glyph glyph, PASTRY_Image image)
+{
+    uint_fast32_t outline;
+    double transform[6];
+    int bbox[4];
+    Outline outl;
+
+    if (outline_offset(pastry->font, glyph, &outline) < 0)
+        return -1;
+    if (!outline)
+        return 0;
+    if (glyph_bbox(pastry, outline, bbox) < 0)
+        return -1;
+    /* set up transformation matrix such that
+     * transformed bounding boxes min corner
+     * lines up with the (0, 0) point */
+    transform[0] = pastry->xScale / pastry->font->unitsPerEm;
+    transform[1] = 0.0;
+    transform[2] = 0.0;
+    transform[4] = pastry->xOffset - bbox[0];
+    if (pastry->flags & PASTRY_DOWNWARD_Y) {
+        transform[3] = -pastry->yScale / pastry->font->unitsPerEm;
+        transform[5] = bbox[3] - pastry->yOffset;
+    } else {
+        transform[3] = +pastry->yScale / pastry->font->unitsPerEm;
+        transform[5] = pastry->yOffset - bbox[1];
+    }
+
+    memset(&outl, 0, sizeof outl);
+    if (init_outline(&outl) < 0)
+        goto failure;
+
+    if (decode_outline(pastry->font, outline, 0, &outl) < 0)
+        goto failure;
+    if (render_outline(&outl, transform, image) < 0)
+        goto failure;
+
+    free_outline(&outl);
+    return 0;
+
+failure:
+    free_outline(&outl);
+    return -1;
+}
+
+/* sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX
+ * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW */
+#define MUL_NO_OVERFLOW    ((size_t)1 << (sizeof(size_t) * 4))
+
+/* copy of openbsd's reallocarray function from the stdlib
+ * a wrapper for realloc() that takes two size args like calloc()
+ * useful cuz it eliminates some integer overflow bugs that pmo */
+static void *
+realloc_array(void *optr, size_t nmemb, size_t size)
+{
+    if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
+            nmemb > 0 && SIZE_MAX / nmemb < size) {
+        errno = ENOMEM;
+        return NULL;
+    }
+    return PASTRY_REALLOC(optr, (size * nmemb));
+}
+
+static inline int
+fast_floor(double x)
+{
+    int i = (int) x;
+    return i - (i > x);
+}
+
+static inline double
+fast_abs(double x)
+{
+    return x < 0.0 ? -x : x;
+}
+
+static inline int
+fast_ceil(double x)
+{
+    int i = (int) x;
+    return i + (i < x);
+}
+
+// largest double strictly less than x; x must be finite and positive.
+// stands in for nextafter(x, 0.0) without pulling in libm.
+static inline double
+fast_prev(double x)
+{
+    union { double d; uint64_t u; } bits;
+    bits.d = x;
+    bits.u -= 1;
+    return bits.d;
+}
+
+#if defined(_WIN32)
+
+static int
+map_file(PASTRY_Font *font, const char *filename)
+{
+    HANDLE file;
+    DWORD high, low;
+
+    font->mapping = NULL;
+    font->memory  = NULL;
+
+    file = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
+    if (file == INVALID_HANDLE_VALUE) {
+        return -1;
+    }
+
+    low = GetFileSize(file, &high);
+    if (low == INVALID_FILE_SIZE) {
+        CloseHandle(file);
+        return -1;
+    }
+
+    font->size = (size_t)high << (8 * sizeof(DWORD)) | low;
+
+    font->mapping = CreateFileMapping(file, NULL, PAGE_READONLY, high, low, NULL);
+    if (!font->mapping) {
+        CloseHandle(file);
+        return -1;
+    }
+
+    CloseHandle(file);
+
+    font->memory = MapViewOfFile(font->mapping, FILE_MAP_READ, 0, 0, 0);
+    if (!font->memory) {
+        CloseHandle(font->mapping);
+        font->mapping = NULL;
+        return -1;
+    }
+
+    return 0;
+}
+
+static void
+unmap_file(PASTRY_Font *font)
+{
+    if (font->memory) {
+        UnmapViewOfFile(font->memory);
+        font->memory = NULL;
+    }
+    if (font->mapping) {
+        CloseHandle(font->mapping);
+        font->mapping = NULL;
+    }
+}
+
+#elif defined(PASTRY_NO_MMAP)
+
+static int
+map_file(PASTRY_Font *font, const char *filename)
+{
+    FILE *f = fopen(filename, "rb");
+    if (!f) return -1;
+
+    if (fseek(f, 0, SEEK_END) != 0) {
+        fclose(f);
+        return -1;
+    }
+
+    long sz = ftell(f);
+    if (sz < 0) {
+        fclose(f);
+        return -1;
+    }
+    rewind(f);
+
+    // allocate and read
+    void *mem = PASTRY_MALLOC((size_t)sz);
+    if (!mem) {
+        fclose(f);
+        return -1;
+    }
+
+    size_t read_bytes = fread(mem, 1, (size_t)sz, f);
+    fclose(f);
+    if (read_bytes != (size_t)sz) {
+        PASTRY_FREE(mem);
+        return -1;
+    }
+
+    font->memory = mem;
+    font->size   = (uint_fast32_t) sz;
+    font->source = SrcCopy;
+
+    return 0;
+}
+
+static void
+unmap_file(PASTRY_Font *font)
+{
+    if (font->memory) {
+        PASTRY_FREE(font->memory);
+        font->memory = NULL;
+        font->size   = 0;
+    }
+}
+
+#else
+
+static int
+map_file(PASTRY_Font *font, const char *filename)
+{
+    struct stat info;
+    int fd;
+    font->memory = MAP_FAILED;
+    font->size   = 0;
+    font->source = SrcMapping;
+    if ((fd = open(filename, O_RDONLY)) < 0) {
+        return -1;
+    }
+    if (fstat(fd, &info) < 0) {
+        close(fd);
+        return -1;
+    }
+    // gottem
+    assert(info.st_size > 0);
+    font->memory = mmap(NULL, (size_t) info.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+    font->size   = (uint_fast32_t) info.st_size;
+    close(fd);
+    return font->memory == MAP_FAILED ? -1 : 0;
+}
+
+static void
+unmap_file(PASTRY_Font *font)
+{
+    assert(font->memory != MAP_FAILED);
+    munmap((void *) (uintptr_t) font->memory, font->size);
+}
+
+#endif
+
+static int
+init_font(PASTRY_Font *font)
+{
+    uint_fast32_t scalerType, head, hhea;
+
+    if (!is_safe_offset(font, 0, 12))
+        return -1;
+    // check for a compatible scalerType (magic number). */
+    scalerType = getu32(font, 0);
+    if (scalerType != FILE_MAGIC_ONE && scalerType != FILE_MAGIC_TWO)
+        return -1;
+
+    if (gettable(font, "head", &head) < 0)
+        return -1;
+    if (!is_safe_offset(font, head, 54))
+        return -1;
+    font->unitsPerEm = getu16(font, head + 18);
+    font->locaFormat = geti16(font, head + 50);
+
+    if (gettable(font, "hhea", &hhea) < 0)
+        return -1;
+    if (!is_safe_offset(font, hhea, 36))
+        return -1;
+    font->numLongHmtx = getu16(font, hhea + 34);
+
+    return 0;
+}
+
+static Point
+midpoint(Point a, Point b)
+{
+    return (Point) {
+        0.5 * (a.x + b.x),
+            0.5 * (a.y + b.y)
+    };
+}
+
+// apply an affine linear transformation matrix to a set of points
+static void
+transform_points(unsigned int numPts, Point *points, double trf[6])
+{
+    assert(numPts == 0 || points != NULL);
+    assert(trf != NULL);
+    Point pt;
+    unsigned int i;
+    for (i = 0; i < numPts; ++i) {
+        pt = points[i];
+        points[i] = (Point) {
+            pt.x * trf[0] + pt.y * trf[2] + trf[4],
+                 pt.x * trf[1] + pt.y * trf[3] + trf[5]
+        };
+    }
+}
+
+static void
+clip_points(unsigned int numPts, Point *points, int width, int height)
+{
+    assert(numPts == 0 || points != NULL);
+    assert(width > 0 && height > 0);
+    Point pt;
+    unsigned int i;
+
+    for (i = 0; i < numPts; ++i) {
+        pt = points[i];
+
+        if (pt.x < 0.0) {
+            points[i].x = 0.0;
+        }
+        if (pt.x >= width) {
+            points[i].x = fast_prev((double) width);
+        }
+        if (pt.y < 0.0) {
+            points[i].y = 0.0;
+        }
+        if (pt.y >= height) {
+            points[i].y = fast_prev((double) height);
+        }
+    }
+}
+
+static int
+init_outline(Outline *outl)
+{
+    assert(outl != NULL);
+    // add smaller initial allocations later (someday)
+    outl->numPoints = 0;
+    outl->capPoints = 64;
+    if (!(outl->points = PASTRY_MALLOC(outl->capPoints * sizeof *outl->points)))
+        return -1;
+    outl->numCurves = 0;
+    outl->capCurves = 64;
+    if (!(outl->curves = PASTRY_MALLOC(outl->capCurves * sizeof *outl->curves)))
+        return -1;
+    outl->numLines = 0;
+    outl->capLines = 64;
+    if (!(outl->lines = PASTRY_MALLOC(outl->capLines * sizeof *outl->lines)))
+        return -1;
+    return 0;
+}
+
+static void
+free_outline(Outline *outl)
+{
+    PASTRY_FREE(outl->points);
+    PASTRY_FREE(outl->curves);
+    PASTRY_FREE(outl->lines);
+}
+
+static int
+grow_points(Outline *outl)
+{
+    void *mem;
+    uint_fast16_t cap;
+    assert(outl->capPoints);
+    // since uint_fast16_t is used for capacities, be extra careful not to trigger int overflow
+    if (outl->capPoints > UINT16_MAX / 2)
+        return -1;
+    cap = (uint_fast16_t) (2U * outl->capPoints);
+    if (!(mem = realloc_array(outl->points, cap, sizeof *outl->points)))
+        return -1;
+    outl->capPoints = (uint_least16_t) cap;
+    outl->points    = mem;
+    return 0;
+}
+
+static int
+grow_curves(Outline *outl)
+{
+    void *mem;
+    uint_fast16_t cap;
+    assert(outl->capCurves);
+    if (outl->capCurves > UINT16_MAX / 2)
+        return -1;
+    cap = (uint_fast16_t) (2U * outl->capCurves);
+    if (!(mem = realloc_array(outl->curves, cap, sizeof *outl->curves)))
+        return -1;
+    outl->capCurves = (uint_least16_t) cap;
+    outl->curves    = mem;
+    return 0;
+}
+
+static int
+grow_lines(Outline *outl)
+{
+    void *mem;
+    uint_fast16_t cap;
+    assert(outl->capLines);
+    if (outl->capLines > UINT16_MAX / 2)
+        return -1;
+    cap = (uint_fast16_t) (2U * outl->capLines);
+    if (!(mem = realloc_array(outl->lines, cap, sizeof *outl->lines)))
+        return -1;
+    outl->capLines = (uint_least16_t) cap;
+    outl->lines    = mem;
+    return 0;
+}
+
+static inline int
+is_safe_offset(PASTRY_Font *font, uint_fast32_t offset, uint_fast32_t margin)
+{
+    if (offset > font->size) return 0;
+    if (font->size - offset < margin) return 0;
+    return 1;
+}
+
+// like bsearch(), but returns next highest element if key could not be found`
+static void *
+csearch(const void *key, const void *base,
+        size_t nmemb, size_t size,
+        int (*compar)(const void *, const void *))
+{
+    const uint8_t *bytes = base, *sample;
+    size_t low = 0, high = nmemb - 1, mid;
+    if (!nmemb) return NULL;
+    while (low != high) {
+        mid = low + (high - low) / 2;
+        sample = bytes + mid * size;
+        if (compar(key, sample) > 0) {
+            low = mid + 1;
+        } else {
+            high = mid;
+        }
+    }
+    return (void *) ((uintptr_t)bytes + low * size);
+}
+
+// bsearch(), minus the libc. returns NULL when the key is absent.
+static void *
+binsearch(const void *key, const void *base,
+          size_t nmemb, size_t size,
+          int (*compar)(const void *, const void *))
+{
+    const uint8_t *bytes = base;
+    size_t low = 0, high = nmemb;
+    while (low < high) {
+        size_t mid = low + (high - low) / 2;
+        const uint8_t *sample = bytes + mid * size;
+        int cmp = compar(key, sample);
+        if (cmp == 0)
+            return (void *) (uintptr_t) sample;
+        if (cmp < 0)
+            high = mid;
+        else
+            low = mid + 1;
+    }
+    return NULL;
+}
+
+// used as comparison function for [bc]search()
+static int
+cmpu16(const void *a, const void *b)
+{
+    return memcmp(a, b, 2);
+}
+
+// used as comparison function for [bc]search()
+static int
+cmpu32(const void *a, const void *b)
+{
+    return memcmp(a, b, 4);
+}
+
+static inline uint_least8_t
+getu8(PASTRY_Font *font, uint_fast32_t offset)
+{
+    assert(offset + 1 <= font->size);
+    return *(font->memory + offset);
+}
+
+static inline int_least8_t
+geti8(PASTRY_Font *font, uint_fast32_t offset)
+{
+    return (int_least8_t) getu8(font, offset);
+}
+
+static inline uint_least16_t
+getu16(PASTRY_Font *font, uint_fast32_t offset)
+{
+    assert(offset + 2 <= font->size);
+    const uint8_t *base = font->memory + offset;
+    uint_least16_t b1 = base[0], b0 = base[1];
+    return (uint_least16_t) (b1 << 8 | b0);
+}
+
+static inline int16_t
+geti16(PASTRY_Font *font, uint_fast32_t offset)
+{
+    return (int_least16_t) getu16(font, offset);
+}
+
+static inline uint32_t
+getu32(PASTRY_Font *font, uint_fast32_t offset)
+{
+    assert(offset + 4 <= font->size);
+    const uint8_t *base = font->memory + offset;
+    uint_least32_t b3 = base[0], b2 = base[1], b1 = base[2], b0 = base[3];
+    return (uint_least32_t) (b3 << 24 | b2 << 16 | b1 << 8 | b0);
+}
+
+static int
+gettable(PASTRY_Font *font, const char tag[4], uint_fast32_t *offset)
+{
+    void *match;
+    unsigned int numTables;
+    // no need to bounds-check access to the first 12 bytes cuz it's already checked by init_font()
+    numTables = getu16(font, 4);
+    if (!is_safe_offset(font, 12, (uint_fast32_t) numTables * 16))
+        return -1;
+    if (!(match = binsearch(tag, font->memory + 12, numTables, 16, cmpu32)))
+        return -1;
+    *offset = getu32(font, (uint_fast32_t) ((uint8_t *) match - font->memory + 8));
+    return 0;
+}
+
+static int
+cmap_fmt4(PASTRY_Font *font, uint_fast32_t table, PASTRY_UChar charCode, PASTRY_Glyph *glyph)
+{
+    const uint8_t *segPtr;
+    uint_fast32_t segIdxX2;
+    uint_fast32_t endCodes, startCodes, idDeltas, idRangeOffsets, idOffset;
+    uint_fast16_t segCountX2, idRangeOffset, startCode, shortCode, idDelta, id;
+    uint8_t key[2] = { (uint8_t) (charCode >> 8), (uint8_t) charCode };
+    // cmap format 4 only supports the unicode bmp
+    if (charCode > 0xFFFF) {
+        *glyph = 0;
+        return 0;
+    }
+    shortCode = (uint_fast16_t) charCode;
+    if (!is_safe_offset(font, table, 8))
+        return -1;
+    segCountX2 = getu16(font, table);
+    if ((segCountX2 & 1) || !segCountX2)
+        return -1;
+    // find starting positions of the relevant arrays
+    endCodes       = table + 8;
+    startCodes     = endCodes + segCountX2 + 2;
+    idDeltas       = startCodes + segCountX2;
+    idRangeOffsets = idDeltas + segCountX2;
+    if (!is_safe_offset(font, idRangeOffsets, segCountX2))
+        return -1;
+    /* find the segment that contains shortCode by binary searching over
+     * the highest codes in the segments. */
+    segPtr = csearch(key, font->memory + endCodes, segCountX2 / 2, 2, cmpu16);
+    segIdxX2 = (uint_fast32_t) (segPtr - (font->memory + endCodes));
+    // look up segment info from the arrays & short circuit if the spec requires
+    if ((startCode = getu16(font, startCodes + segIdxX2)) > shortCode)
+        return 0;
+    idDelta = getu16(font, idDeltas + segIdxX2);
+    if (!(idRangeOffset = getu16(font, idRangeOffsets + segIdxX2))) {
+        // intentional integer under/overflow
+        *glyph = (shortCode + idDelta) & 0xFFFF;
+        return 0;
+    }
+    // calculate offset into glyph array and determine ultimate value.
+    idOffset = idRangeOffsets + segIdxX2 + idRangeOffset + 2U * (unsigned int) (shortCode - startCode);
+    if (!is_safe_offset(font, idOffset, 2))
+        return -1;
+    id = getu16(font, idOffset);
+    // intentional integer under/overflow
+    *glyph = id ? (id + idDelta) & 0xFFFF : 0;
+    return 0;
+}
+
+static int
+cmap_fmt6(PASTRY_Font *font, uint_fast32_t table, PASTRY_UChar charCode, PASTRY_Glyph *glyph)
+{
+    unsigned int firstCode, entryCount;
+    // cmap format 6 only supports the unicode bmp
+    if (charCode > 0xFFFF) {
+        *glyph = 0;
+        return 0;
+    }
+    if (!is_safe_offset(font, table, 4))
+        return -1;
+    firstCode  = getu16(font, table);
+    entryCount = getu16(font, table + 2);
+    if (!is_safe_offset(font, table, 4 + 2 * entryCount))
+        return -1;
+    if (charCode < firstCode)
+        return -1;
+    charCode -= firstCode;
+    if (!(charCode < entryCount))
+        return -1;
+    *glyph = getu16(font, table + 4 + 2 * charCode);
+    return 0;
+}
+
+static int
+cmap_fmt12_13(PASTRY_Font *font, uint_fast32_t table, PASTRY_UChar charCode, PASTRY_Glyph *glyph, int which)
+{
+    uint32_t len, numEntries;
+    uint_fast32_t i;
+
+    *glyph = 0;
+
+    // ensure entire header
+    if (!is_safe_offset(font, table, 16))
+        return -1;
+
+    len = getu32(font, table + 4);
+
+    // smol header is 16 bytes
+    if (len < 16)
+        return -1;
+
+    if (!is_safe_offset(font, table, len))
+        return -1;
+
+    numEntries = getu32(font, table + 12);
+
+    for (i = 0; i < numEntries; ++i) {
+        uint32_t firstCode, lastCode, glyphOffset;
+        firstCode = getu32(font, table + (i * 12) + 16);
+        lastCode = getu32(font, table + (i * 12) + 16 + 4);
+        if (charCode < firstCode || charCode > lastCode)
+            continue;
+        glyphOffset = getu32(font, table + (i * 12) + 16 + 8);
+        if (which == 12)
+            *glyph = (charCode-firstCode) + glyphOffset;
+        else
+            *glyph = glyphOffset;
+        return 0;
+    }
+
+    return 0;
+}
+
+// maps unicode code points to glyph indices
+static int
+glyph_id(PASTRY_Font *font, PASTRY_UChar charCode, PASTRY_Glyph *glyph)
+{
+    uint_fast32_t cmap, entry, table;
+    unsigned int idx, numEntries;
+    int type, format;
+
+    *glyph = 0;
+
+    if (gettable(font, "cmap", &cmap) < 0)
+        return -1;
+
+    if (!is_safe_offset(font, cmap, 4))
+        return -1;
+    numEntries = getu16(font, cmap + 2);
+
+    if (!is_safe_offset(font, cmap, 4 + numEntries * 8))
+        return -1;
+
+    // first look for a full repertoire map
+    for (idx = 0; idx < numEntries; ++idx) {
+        entry = cmap + 4 + idx * 8;
+        type = getu16(font, entry) * 0100 + getu16(font, entry + 2);
+        // complete unicode map
+        if (type == 0004 || type == 0312) {
+            table = cmap + getu32(font, entry + 4);
+            if (!is_safe_offset(font, table, 8))
+                return -1;
+            /* Dispatch based on cmap format. */
+            format = getu16(font, table);
+            switch (format) {
+            case 12:
+                return cmap_fmt12_13(font, table, charCode, glyph, 12);
+            default:
+                return -1;
+            }
+        }
+    }
+
+    // if no full repertoire cmap was found, try looking for a bmp map
+    for (idx = 0; idx < numEntries; ++idx) {
+        entry = cmap + 4 + idx * 8;
+        type = getu16(font, entry) * 0100 + getu16(font, entry + 2);
+        // unicode bmp
+        if (type == 0003 || type == 0301) {
+            table = cmap + getu32(font, entry + 4);
+            if (!is_safe_offset(font, table, 6))
+                return -1;
+            // dispatch based on cmap format
+            switch (getu16(font, table)) {
+            case 4:
+                return cmap_fmt4(font, table + 6, charCode, glyph);
+            case 6:
+                return cmap_fmt6(font, table + 6, charCode, glyph);
+            default:
+                return -1;
+            }
+        }
+    }
+
+    return -1;
+}
+
+static int
+hor_metrics(PASTRY_Font *font, PASTRY_Glyph glyph, int *advanceWidth, int *leftSideBearing)
+{
+    uint_fast32_t hmtx, offset, boundary;
+    if (gettable(font, "hmtx", &hmtx) < 0)
+        return -1;
+    if (glyph < font->numLongHmtx) {
+        // glyph is inside long metrics segment
+        offset = hmtx + 4 * glyph;
+        if (!is_safe_offset(font, offset, 4))
+            return -1;
+        *advanceWidth = getu16(font, offset);
+        *leftSideBearing = geti16(font, offset + 2);
+        return 0;
+    } else {
+        // glyph is inside short metrics segment
+        boundary = hmtx + 4U * (uint_fast32_t) font->numLongHmtx;
+        if (boundary < 4)
+            return -1;
+
+        offset = boundary - 4;
+        if (!is_safe_offset(font, offset, 4))
+            return -1;
+        *advanceWidth = getu16(font, offset);
+
+        offset = boundary + 2 * (glyph - font->numLongHmtx);
+        if (!is_safe_offset(font, offset, 2))
+            return -1;
+        *leftSideBearing = geti16(font, offset);
+        return 0;
+    }
+}
+
+static int
+glyph_bbox(const PASTRY *pastry, uint_fast32_t outline, int box[4])
+{
+    double xScale, yScale;
+    // raad bounding box from the font file verbatim
+    if (!is_safe_offset(pastry->font, outline, 10))
+        return -1;
+    box[0] = geti16(pastry->font, outline + 2);
+    box[1] = geti16(pastry->font, outline + 4);
+    box[2] = geti16(pastry->font, outline + 6);
+    box[3] = geti16(pastry->font, outline + 8);
+    if (box[2] <= box[0] || box[3] <= box[1])
+        return -1;
+    // transform bounding box into PASTRY coordinate space
+    xScale = pastry->xScale / pastry->font->unitsPerEm;
+    yScale = pastry->yScale / pastry->font->unitsPerEm;
+    box[0] = fast_floor(box[0] * xScale + pastry->xOffset);
+    box[1] = fast_floor(box[1] * yScale + pastry->yOffset);
+    box[2] = fast_ceil (box[2] * xScale + pastry->xOffset);
+    box[3] = fast_ceil (box[3] * yScale + pastry->yOffset);
+    return 0;
+}
+
+// returns the offset into the font that the glyph's outline is stored at
+static int
+outline_offset(PASTRY_Font *font, PASTRY_Glyph glyph, uint_fast32_t *offset)
+{
+    uint_fast32_t loca, glyf;
+    uint_fast32_t base, this, next;
+
+    if (gettable(font, "loca", &loca) < 0)
+        return -1;
+    if (gettable(font, "glyf", &glyf) < 0)
+        return -1;
+
+    if (font->locaFormat == 0) {
+        base = loca + 2 * glyph;
+
+        if (!is_safe_offset(font, base, 4))
+            return -1;
+
+        this = 2U * (uint_fast32_t) getu16(font, base);
+        next = 2U * (uint_fast32_t) getu16(font, base + 2);
+    } else {
+        base = loca + 4 * glyph;
+
+        if (!is_safe_offset(font, base, 8))
+            return -1;
+
+        this = getu32(font, base);
+        next = getu32(font, base + 4);
+    }
+
+    *offset = this == next ? 0 : glyf + this;
+    return 0;
+}
+
+// for a simple outline, determines each point of the outline with a set of flags
+static int
+simple_flags(PASTRY_Font *font, uint_fast32_t *offset, uint_fast16_t numPts, uint8_t *flags)
+{
+    assert(flags != NULL);
+    uint_fast32_t off = *offset;
+    uint_fast16_t i;
+    uint8_t value = 0, repeat = 0;
+    for (i = 0; i < numPts; ++i) {
+        if (repeat) {
+            --repeat;
+        } else {
+            if (!is_safe_offset(font, off, 1))
+                return -1;
+            value = getu8(font, off++);
+            if (value & REPEAT_FLAG) {
+                if (!is_safe_offset(font, off, 1))
+                    return -1;
+                repeat = getu8(font, off++);
+            }
+        }
+        flags[i] = value;
+    }
+    *offset = off;
+    return 0;
+}
+
+// for a simple outline, decodes both X and Y coordinates for each point of the outline
+static int
+simple_points(PASTRY_Font *font, uint_fast32_t offset, uint_fast16_t numPts, uint8_t *flags, Point *points)
+{
+    assert(flags != NULL);
+    assert(points != NULL);
+    long accum, value, bit;
+    uint_fast16_t i;
+
+    accum = 0L;
+    for (i = 0; i < numPts; ++i) {
+        if (flags[i] & X_CHANGE_IS_SMALL) {
+            if (!is_safe_offset(font, offset, 1))
+                return -1;
+            value = (long) getu8(font, offset++);
+            bit = !!(flags[i] & X_CHANGE_IS_POSITIVE);
+            accum -= (value ^ -bit) + bit;
+        } else if (!(flags[i] & X_CHANGE_IS_ZERO)) {
+            if (!is_safe_offset(font, offset, 2))
+                return -1;
+            accum += geti16(font, offset);
+            offset += 2;
+        }
+        points[i].x = (double) accum;
+    }
+
+    accum = 0L;
+    for (i = 0; i < numPts; ++i) {
+        if (flags[i] & Y_CHANGE_IS_SMALL) {
+            if (!is_safe_offset(font, offset, 1))
+                return -1;
+            value = (long) getu8(font, offset++);
+            bit = !!(flags[i] & Y_CHANGE_IS_POSITIVE);
+            accum -= (value ^ -bit) + bit;
+        } else if (!(flags[i] & Y_CHANGE_IS_ZERO)) {
+            if (!is_safe_offset(font, offset, 2))
+                return -1;
+            accum += geti16(font, offset);
+            offset += 2;
+        }
+        points[i].y = (double) accum;
+    }
+
+    return 0;
+}
+
+static int
+decode_contour(uint8_t *flags, uint_fast16_t basePoint, uint_fast16_t count, Outline *outl)
+{
+    assert(basePoint + count <= outl->numPoints);
+    uint_fast16_t i;
+    uint_least16_t looseEnd, beg, ctrl, center, cur;
+    unsigned int gotCtrl;
+
+    /* skip contours with less than two points, since my algo can't handle them and
+     * they should appear invisible either way (because they don't have any area lmao) */
+    if (count < 2) return 0;
+
+    assert(basePoint <= UINT16_MAX - count);
+
+    if (flags[0] & POINT_IS_ON_CURVE) {
+        looseEnd = (uint_least16_t) basePoint++;
+        ++flags;
+        --count;
+    } else if (flags[count - 1] & POINT_IS_ON_CURVE) {
+        looseEnd = (uint_least16_t) (basePoint + --count);
+    } else {
+        if (outl->numPoints >= outl->capPoints && grow_points(outl) < 0)
+            return -1;
+
+        looseEnd = outl->numPoints;
+        outl->points[outl->numPoints++] = midpoint(
+                                              outl->points[basePoint],
+                                              outl->points[basePoint + count - 1]);
+    }
+    beg = looseEnd;
+    gotCtrl = 0;
+    for (i = 0; i < count; ++i) {
+        // cur can't overflow because we ensure that basePoint + count < 0xFFFF before calling decode_contour()
+        cur = (uint_least16_t) (basePoint + i);
+        if (flags[i] & POINT_IS_ON_CURVE) {
+            if (gotCtrl) {
+                if (outl->numCurves >= outl->capCurves && grow_curves(outl) < 0)
+                    return -1;
+                outl->curves[outl->numCurves++] = (Curve) {
+                    beg, cur, ctrl
+                };
+            } else {
+                if (outl->numLines >= outl->capLines && grow_lines(outl) < 0)
+                    return -1;
+                outl->lines[outl->numLines++] = (Line) {
+                    beg, cur
+                };
+            }
+            beg = cur;
+            gotCtrl = 0;
+        } else {
+            if (gotCtrl) {
+                center = outl->numPoints;
+                if (outl->numPoints >= outl->capPoints && grow_points(outl) < 0)
+                    return -1;
+                outl->points[center] = midpoint(outl->points[ctrl], outl->points[cur]);
+                ++outl->numPoints;
+
+                if (outl->numCurves >= outl->capCurves && grow_curves(outl) < 0)
+                    return -1;
+                outl->curves[outl->numCurves++] = (Curve) {
+                    beg, center, ctrl
+                };
+
+                beg = center;
+            }
+            ctrl = cur;
+            gotCtrl = 1;
+        }
+    }
+    if (gotCtrl) {
+        if (outl->numCurves >= outl->capCurves && grow_curves(outl) < 0)
+            return -1;
+        outl->curves[outl->numCurves++] = (Curve) {
+            beg, looseEnd, ctrl
+        };
+    } else {
+        if (outl->numLines >= outl->capLines && grow_lines(outl) < 0)
+            return -1;
+        outl->lines[outl->numLines++] = (Line) {
+            beg, looseEnd
+        };
+    }
+
+    return 0;
+}
+
+static int
+simple_outline(PASTRY_Font *font, uint_fast32_t offset, unsigned int numContours, Outline *outl)
+{
+    uint_fast16_t *endPts = NULL;
+    uint8_t *flags = NULL;
+    uint_fast16_t numPts;
+    unsigned int i;
+    uint_fast16_t beg;
+
+    assert(numContours > 0);
+
+    uint_fast16_t basePoint = outl->numPoints;
+
+    if (!is_safe_offset(font, offset, numContours * 2 + 2))
+        goto failure;
+    numPts = getu16(font, offset + (numContours - 1) * 2);
+    if (numPts >= UINT16_MAX)
+        goto failure;
+    numPts++;
+    if (outl->numPoints > UINT16_MAX - numPts)
+        goto failure;
+
+    while (outl->capPoints < basePoint + numPts) {
+        if (grow_points(outl) < 0)
+            goto failure;
+    }
+
+    PASTRY_STACK_ALLOC(endPts, uint_fast16_t, 16, numContours);
+    if (endPts == NULL)
+        goto failure;
+    PASTRY_STACK_ALLOC(flags, uint8_t, 128, numPts);
+    if (flags == NULL)
+        goto failure;
+
+    for (i = 0; i < numContours; ++i) {
+        endPts[i] = getu16(font, offset);
+        offset += 2;
+    }
+    /* ensure that endPts are never falling
+     * falling endPts have no sensible interpretation and idk probably only exist in bad inputs
+     *
+     * so... just bail? */
+    for (i = 0; i < numContours - 1; ++i) {
+        if (endPts[i + 1] < endPts[i] + 1)
+            goto failure;
+    }
+    offset += 2U + getu16(font, offset);
+
+    if (simple_flags(font, &offset, numPts, flags) < 0)
+        goto failure;
+    if (simple_points(font, offset, numPts, flags, outl->points + basePoint) < 0)
+        goto failure;
+    outl->numPoints = (uint_least16_t) (outl->numPoints + numPts);
+
+    beg = 0;
+    for (i = 0; i < numContours; ++i) {
+        uint_fast16_t count = endPts[i] - beg + 1;
+        if (decode_contour(flags + beg, basePoint + beg, count, outl) < 0)
+            goto failure;
+        beg = endPts[i] + 1;
+    }
+
+    PASTRY_STACK_FREE(endPts);
+    PASTRY_STACK_FREE(flags);
+    return 0;
+failure:
+    PASTRY_STACK_FREE(endPts);
+    PASTRY_STACK_FREE(flags);
+    return -1;
+}
+
+static int
+compound_outline(PASTRY_Font *font, uint_fast32_t offset, int recDepth, Outline *outl)
+{
+    double local[6];
+    uint_fast32_t outline;
+    unsigned int flags, glyph, basePoint;
+    // guard against compound glyphs that have themselves as component (wtf)
+    if (recDepth >= 4)
+        return -1;
+    do {
+        memset(local, 0, sizeof local);
+        if (!is_safe_offset(font, offset, 4))
+            return -1;
+        flags = getu16(font, offset);
+        glyph = getu16(font, offset + 2);
+        offset += 4;
+        // i refuse to implement point matching, and neither does stb_truetype for that matter, so fuck you
+        if (!(flags & ACTUAL_XY_OFFSETS))
+            return -1;
+        // read additional X and Y offsets (in FUnits) of this component
+        if (flags & OFFSETS_ARE_LARGE) {
+            if (!is_safe_offset(font, offset, 4))
+                return -1;
+            local[4] = geti16(font, offset);
+            local[5] = geti16(font, offset + 2);
+            offset += 4;
+        } else {
+            if (!is_safe_offset(font, offset, 2))
+                return -1;
+            local[4] = geti8(font, offset);
+            local[5] = geti8(font, offset + 1);
+            offset += 2;
+        }
+        if (flags & GOT_A_SINGLE_SCALE) {
+            if (!is_safe_offset(font, offset, 2))
+                return -1;
+            local[0] = geti16(font, offset) / 16384.0;
+            local[3] = local[0];
+            offset += 2;
+        } else if (flags & GOT_AN_X_AND_Y_SCALE) {
+            if (!is_safe_offset(font, offset, 4))
+                return -1;
+            local[0] = geti16(font, offset + 0) / 16384.0;
+            local[3] = geti16(font, offset + 2) / 16384.0;
+            offset += 4;
+        } else if (flags & GOT_A_SCALE_MATRIX) {
+            if (!is_safe_offset(font, offset, 8))
+                return -1;
+            local[0] = geti16(font, offset + 0) / 16384.0;
+            local[1] = geti16(font, offset + 2) / 16384.0;
+            local[2] = geti16(font, offset + 4) / 16384.0;
+            local[3] = geti16(font, offset + 6) / 16384.0;
+            offset += 8;
+        } else {
+            local[0] = 1.0;
+            local[3] = 1.0;
+        }
+        /* at this point, apple more or less tells you to scale the matrix by its own L1
+         * norm but stb_truetype scales by the L2 norm, and freetype doesn't scale at all
+         * microsoft's spec doesn't even mention anything like this, it just tells you to
+         * go fuck yourself
+         *
+         * hm idk it feels like nobody ever uses this feature */
+        if (outline_offset(font, glyph, &outline) < 0)
+            return -1;
+        if (outline) {
+            basePoint = outl->numPoints;
+            if (decode_outline(font, outline, recDepth + 1, outl) < 0)
+                return -1;
+            transform_points(outl->numPoints - basePoint, outl->points + basePoint, local);
+        }
+    } while (flags & THERE_ARE_MORE_COMPONENTS);
+
+    return 0;
+}
+
+static int
+decode_outline(PASTRY_Font *font, uint_fast32_t offset, int recDepth, Outline *outl)
+{
+    int numContours;
+    if (!is_safe_offset(font, offset, 10))
+        return -1;
+    numContours = geti16(font, offset);
+    if (numContours > 0) {
+        // glyph has a simple outline consisting of a number of contours
+        return simple_outline(font, offset + 10, (unsigned int) numContours, outl);
+    } else if (numContours < 0) {
+        // glyph has a compound outline combined from mutiple other outlines
+        return compound_outline(font, offset + 10, recDepth, outl);
+    } else {
+        return 0;
+    }
+}
+
+// weird thing to tell whether a given curve can be approximated closely enough by a line
+static int
+is_flat(Outline *outl, Curve curve)
+{
+    const double maxArea2 = 2.0;
+    Point a = outl->points[curve.beg];
+    Point b = outl->points[curve.ctrl];
+    Point c = outl->points[curve.end];
+    Point g = { b.x-a.x, b.y-a.y };
+    Point h = { c.x-a.x, c.y-a.y };
+    double area2 = fast_abs(g.x*h.y-h.x*g.y);
+    return area2 <= maxArea2;
+}
+
+static int
+tessellate_curve(Curve curve, Outline *outl)
+{
+    /* i can conclude that this stack barely reaches a top height of 4 elements
+     * even for the largest font sizes that i wanna support, and as space reqs
+     * only grow logarithmically, 6 feels like it's more than enough */
+#define STACK_SIZE 6
+    Curve stack[STACK_SIZE];
+    unsigned int top = 0;
+    for (;;) {
+        if (is_flat(outl, curve) || top >= STACK_SIZE) {
+            if (outl->numLines >= outl->capLines && grow_lines(outl) < 0)
+                return -1;
+            outl->lines[outl->numLines++] = (Line) {
+                curve.beg, curve.end
+            };
+            if (top == 0) break;
+            curve = stack[--top];
+        } else {
+            uint_least16_t ctrl0 = outl->numPoints;
+            if (outl->numPoints >= outl->capPoints && grow_points(outl) < 0)
+                return -1;
+            outl->points[ctrl0] = midpoint(outl->points[curve.beg], outl->points[curve.ctrl]);
+            ++outl->numPoints;
+
+            uint_least16_t ctrl1 = outl->numPoints;
+            if (outl->numPoints >= outl->capPoints && grow_points(outl) < 0)
+                return -1;
+            outl->points[ctrl1] = midpoint(outl->points[curve.ctrl], outl->points[curve.end]);
+            ++outl->numPoints;
+
+            uint_least16_t pivot = outl->numPoints;
+            if (outl->numPoints >= outl->capPoints && grow_points(outl) < 0)
+                return -1;
+            outl->points[pivot] = midpoint(outl->points[ctrl0], outl->points[ctrl1]);
+            ++outl->numPoints;
+
+            stack[top++] = (Curve) {
+                curve.beg, pivot, ctrl0
+            };
+            curve = (Curve) {
+                pivot, curve.end, ctrl1
+            };
+        }
+    }
+    return 0;
+#undef STACK_SIZE
+}
+
+static int
+tessellate_curves(Outline *outl)
+{
+    unsigned int i;
+    for (i = 0; i < outl->numCurves; ++i) {
+        if (tessellate_curve(outl->curves[i], outl) < 0)
+            return -1;
+    }
+    return 0;
+}
+
+// draws a line into the buffer, custom 2d raycasting algo is used
+static void
+draw_line(Raster buf, Point origin, Point goal)
+{
+    assert(buf.cells != NULL);
+    assert(buf.width > 0 && buf.height > 0);
+    Point delta;
+    Point nextCrossing;
+    Point crossingIncr;
+    double halfDeltaX;
+    double prevDistance = 0.0, nextDistance;
+    double xAverage, yDifference;
+    struct
+    {
+        int x, y;
+    } pixel;
+    struct
+    {
+        int x, y;
+    } dir;
+    int step, numSteps = 0;
+    Cell *restrict cptr, cell;
+
+    delta.x = goal.x - origin.x;
+    delta.y = goal.y - origin.y;
+    dir.x = SIGN(delta.x);
+    dir.y = SIGN(delta.y);
+
+    if (!dir.y) {
+        return;
+    }
+
+    crossingIncr.x = dir.x ? fast_abs(1.0 / delta.x) : 1.0;
+    crossingIncr.y = fast_abs(1.0 / delta.y);
+
+    if (!dir.x) {
+        pixel.x = fast_floor(origin.x);
+        nextCrossing.x = 100.0;
+    } else {
+        if (dir.x > 0) {
+            pixel.x = fast_floor(origin.x);
+            nextCrossing.x = (origin.x - pixel.x) * crossingIncr.x;
+            nextCrossing.x = crossingIncr.x - nextCrossing.x;
+            numSteps += fast_ceil(goal.x) - fast_floor(origin.x) - 1;
+        } else {
+            pixel.x = fast_ceil(origin.x) - 1;
+            nextCrossing.x = (origin.x - pixel.x) * crossingIncr.x;
+            numSteps += fast_ceil(origin.x) - fast_floor(goal.x) - 1;
+        }
+    }
+
+    if (dir.y > 0) {
+        pixel.y = fast_floor(origin.y);
+        nextCrossing.y = (origin.y - pixel.y) * crossingIncr.y;
+        nextCrossing.y = crossingIncr.y - nextCrossing.y;
+        numSteps += fast_ceil(goal.y) - fast_floor(origin.y) - 1;
+    } else {
+        pixel.y = fast_ceil(origin.y) - 1;
+        nextCrossing.y = (origin.y - pixel.y) * crossingIncr.y;
+        numSteps += fast_ceil(origin.y) - fast_floor(goal.y) - 1;
+    }
+
+    nextDistance = MIN(nextCrossing.x, nextCrossing.y);
+    halfDeltaX = 0.5 * delta.x;
+
+    for (step = 0; step < numSteps; ++step) {
+        xAverage = origin.x + (prevDistance + nextDistance) * halfDeltaX;
+        yDifference = (nextDistance - prevDistance) * delta.y;
+        cptr = &buf.cells[pixel.y * buf.width + pixel.x];
+        cell = *cptr;
+        cell.cover += yDifference;
+        xAverage -= (double) pixel.x;
+        cell.area += (1.0 - xAverage) * yDifference;
+        *cptr = cell;
+        prevDistance = nextDistance;
+        int alongX = nextCrossing.x < nextCrossing.y;
+        pixel.x += alongX ? dir.x : 0;
+        pixel.y += alongX ? 0 : dir.y;
+        nextCrossing.x += alongX ? crossingIncr.x : 0.0;
+        nextCrossing.y += alongX ? 0.0 : crossingIncr.y;
+        nextDistance = MIN(nextCrossing.x, nextCrossing.y);
+    }
+
+    xAverage = origin.x + (prevDistance + 1.0) * halfDeltaX;
+    yDifference = (1.0 - prevDistance) * delta.y;
+    cptr = &buf.cells[pixel.y * buf.width + pixel.x];
+    cell = *cptr;
+    cell.cover += yDifference;
+    xAverage -= (double) pixel.x;
+    cell.area += (1.0 - xAverage) * yDifference;
+    *cptr = cell;
+}
+
+static void
+draw_lines(Outline *outl, Raster buf)
+{
+    unsigned int i;
+    for (i = 0; i < outl->numLines; ++i) {
+        Line  line   = outl->lines[i];
+        Point origin = outl->points[line.beg];
+        Point goal   = outl->points[line.end];
+        draw_line(buf, origin, goal);
+    }
+}
+
+// integrate the values in the buffer to arrive at the final grayscale image
+static void
+post_process(Raster buf, uint8_t *image)
+{
+    Cell cell;
+    double accum = 0.0, value;
+    unsigned int i, num;
+    num = (unsigned int) buf.width * (unsigned int) buf.height;
+    for (i = 0; i < num; ++i) {
+        cell     = buf.cells[i];
+        value    = fast_abs(accum + cell.area);
+        value    = MIN(value, 1.0);
+        value    = value * 255.0 + 0.5;
+        image[i] = (uint8_t) value;
+        accum   += cell.cover;
+    }
+}
+
+static int
+render_outline(Outline *outl, double transform[6], PASTRY_Image image)
+{
+    assert(outl != NULL);
+    assert(transform != NULL);
+    assert(image.pixels != NULL);
+    assert(image.width > 0 && image.height > 0);
+    Cell *cells = NULL;
+    Raster buf;
+    unsigned int numPixels;
+
+    numPixels = (unsigned int) image.width * (unsigned int) image.height;
+
+    PASTRY_STACK_ALLOC(cells, Cell, 128 * 128, numPixels);
+    if (!cells) {
+        return -1;
+    }
+    memset(cells, 0, numPixels * sizeof *cells);
+    buf.cells  = cells;
+    buf.width  = image.width;
+    buf.height = image.height;
+
+    transform_points(outl->numPoints, outl->points, transform);
+
+    clip_points(outl->numPoints, outl->points, image.width, image.height);
+
+    if (tessellate_curves(outl) < 0) {
+        PASTRY_STACK_FREE(cells);
+        return -1;
+    }
+
+    draw_lines(outl, buf);
+
+    post_process(buf, image.pixels);
+
+    PASTRY_STACK_FREE(cells);
+    return 0;
+}

pastry.h added +113 -0

diff --git a/pastry.h b/pastry.h
new file mode 100644
index 0000000000000000000000000000000000000000..cea47b7fe6e66810d083d632a5563af93ce2d1a2
--- /dev/null
+++ b/pastry.h
@@ -0,0 +1,113 @@
+#ifndef PASTRY_H
+#define PASTRY_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef PASTRY_MALLOC
+# define PASTRY_MALLOC(size) malloc(size)
+#endif
+
+#ifndef PASTRY_FREE
+# define PASTRY_FREE(ptr) free(ptr)
+#endif
+
+#ifndef PASTRY_REALLOC
+# define PASTRY_REALLOC(ptr, size) realloc(ptr, size)
+#endif
+
+#ifndef PASTRY_CALLOC
+static inline void* pastry_calloc_impl(size_t n, size_t size) {
+    if (n == 0 || size == 0) return NULL;
+    if (n > SIZE_MAX / size) return NULL;
+
+    size_t total = n * size;
+    void* ptr = PASTRY_MALLOC(total);
+    // most environments do have memset tbh, even really limited ones
+    // this should be fine
+    if (ptr) memset(ptr, 0, total);
+    return ptr;
+}
+# define PASTRY_CALLOC(n, size) pastry_calloc_impl(n, size)
+#endif
+
+// if mmap is not present in your environment
+// #define PASTRY_NO_MMAP
+
+#ifndef PASTRY_DOWNWARD_Y
+# define PASTRY_DOWNWARD_Y 0x01
+#endif
+
+typedef struct PASTRY          PASTRY;
+typedef struct PASTRY_Font     PASTRY_Font;
+typedef uint_least32_t         PASTRY_UChar;
+typedef uint_fast32_t          PASTRY_Glyph;
+typedef struct PASTRY_LMetrics PASTRY_LMetrics;
+typedef struct PASTRY_GMetrics PASTRY_GMetrics;
+typedef struct PASTRY_Kerning  PASTRY_Kerning;
+typedef struct PASTRY_Image    PASTRY_Image;
+
+struct PASTRY
+{
+    PASTRY_Font *font;
+    double    xScale;
+    double    yScale;
+    double    xOffset;
+    double    yOffset;
+    int       flags;
+};
+
+struct PASTRY_LMetrics
+{
+    double ascender;
+    double descender;
+    double lineGap;
+};
+
+struct PASTRY_GMetrics
+{
+    double advanceWidth;
+    double leftSideBearing;
+    int    yOffset;
+    int    minWidth;
+    int    minHeight;
+};
+
+struct PASTRY_Kerning
+{
+    double xShift;
+    double yShift;
+};
+
+struct PASTRY_Image
+{
+    void *pixels;
+    int   width;
+    int   height;
+};
+
+const char *pastry_version(void);
+
+PASTRY_Font *pastry_loadmem (const void *mem, size_t size);
+PASTRY_Font *pastry_loadfile(const char *filename);
+void         pastry_freefont(PASTRY_Font *font);
+
+int pastry_lmetrics(const PASTRY *pastry, PASTRY_LMetrics *metrics);
+int pastry_lookup  (const PASTRY *pastry, PASTRY_UChar codepoint, PASTRY_Glyph *glyph);
+int pastry_gmetrics(const PASTRY *pastry, PASTRY_Glyph glyph, PASTRY_GMetrics *metrics);
+int pastry_kerning (const PASTRY *pastry, PASTRY_Glyph leftGlyph, PASTRY_Glyph rightGlyph,
+                    PASTRY_Kerning *kerning);
+int pastry_render  (const PASTRY *pastry, PASTRY_Glyph glyph, PASTRY_Image image);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

text.utf8 added +15 -0

diff --git a/text.utf8 b/text.utf8
new file mode 100644
index 0000000000000000000000000000000000000000..9787d099b5214d564cf67b02daba5205ceaf53ac
--- /dev/null
+++ b/text.utf8
@@ -0,0 +1,15 @@
+ 1. Euro Symbol: €
+ 2. Greek: Μπορώ να φάω σπασμένα γυαλιά χωρίς να πάθω τίποτα.
+ 3. Íslenska / Icelandic: Ég get etið gler án þess að meiða mig
+ 4. Polish: Mogę jeść szkło, i mi nie szkodzi
+ 5. Romanian: Pot să mănânc sticlă și ea nu mă rănește
+ 6. Ukrainian: Я можу їсти шкло, й воно мені не пошкодить
+ 7. Georgian: მინას ვჭამ და არა მტკივა
+ 8. Hindi: मैं काँच खा सकता हूँ, मुझे उस से कोई पीडा नहीं होती
+ 9. Hebrew: אני יכול לאכול זכוכית וזה לא מזיק לי
+10. Yiddish: איך קען עסן גלאָז און עס טוט מיר נישט װײ
+11. Arabic: أنا قادر على أكل الزجاج و هذا لا يؤلمني.
+12. Thai: ฉันกินกระจกได้ แต่มันไม่ทำให้ฉันเจ็บ
+13. Random Egyptian hieroglyphs: 𓀌 𓀹 𓁚 𓁭
+
+text examples from Frank da Cruz's sampler: http://kermitproject.org/utf8.html