pastry
truetype font renderer
pastry.c
50017 bytes raw
1#define _POSIX_C_SOURCE 200809L
2#include <assert.h>
3#include <errno.h>
4#include <stdio.h>
5#include <stdlib.h>
6#include <string.h>
7
8#if defined(_MSC_VER)
9# define restrict __restrict
10#endif
11
12#if defined(_WIN32)
13# define WIN32_LEAN_AND_MEAN 1
14# include <windows.h>
15#else
16# include <fcntl.h>
17# include <sys/mman.h>
18# include <sys/stat.h>
19# include <unistd.h>
20#endif
21
22#include "pastry.h"
23
24#define PASTRY_VERSION "1.0.0"
25
26#define FILE_MAGIC_ONE 0x00010000
27#define FILE_MAGIC_TWO 0x74727565
28
29#define HORIZONTAL_KERNING 0x01
30#define MINIMUM_KERNING 0x02
31#define CROSS_STREAM_KERNING 0x04
32#define OVERRIDE_KERNING 0x08
33
34#define POINT_IS_ON_CURVE 0x01
35#define X_CHANGE_IS_SMALL 0x02
36#define Y_CHANGE_IS_SMALL 0x04
37#define REPEAT_FLAG 0x08
38#define X_CHANGE_IS_ZERO 0x10
39#define X_CHANGE_IS_POSITIVE 0x10
40#define Y_CHANGE_IS_ZERO 0x20
41#define Y_CHANGE_IS_POSITIVE 0x20
42
43#define OFFSETS_ARE_LARGE 0x001
44#define ACTUAL_XY_OFFSETS 0x002
45#define GOT_A_SINGLE_SCALE 0x008
46#define THERE_ARE_MORE_COMPONENTS 0x020
47#define GOT_AN_X_AND_Y_SCALE 0x040
48#define GOT_A_SCALE_MATRIX 0x080
49
50#define MIN(a, b) ((a) < (b) ? (a) : (b))
51#define SIGN(x) (((x) > 0) - ((x) < 0))
52// alloc vals on stack if small enough, else spill to heap
53#define PASTRY_STACK_ALLOC(var, type, thresh, count) \
54 type var##_stack_[thresh]; \
55 var = (count) <= (thresh) ? var##_stack_ : PASTRY_CALLOC(count, sizeof(type));
56#define PASTRY_STACK_FREE(var) \
57 if (var != var##_stack_) PASTRY_FREE(var);
58
59enum
60{ SrcMapping, SrcUser };
61
62typedef struct Point Point;
63typedef struct Line Line;
64typedef struct Curve Curve;
65typedef struct Cell Cell;
66typedef struct Outline Outline;
67typedef struct Raster Raster;
68
69struct Point
70{
71 double x, y;
72};
73struct Line
74{
75 uint_least16_t beg, end;
76};
77struct Curve
78{
79 uint_least16_t beg, end, ctrl;
80};
81struct Cell
82{
83 double area, cover;
84};
85
86struct Outline
87{
88 Point *points;
89 Curve *curves;
90 Line *lines;
91 uint_least16_t numPoints;
92 uint_least16_t capPoints;
93 uint_least16_t numCurves;
94 uint_least16_t capCurves;
95 uint_least16_t numLines;
96 uint_least16_t capLines;
97};
98
99struct Raster
100{
101 Cell *cells;
102 int width;
103 int height;
104};
105
106struct PASTRY_Font
107{
108 const uint8_t *memory;
109 uint_fast32_t size;
110#if defined(_WIN32)
111 HANDLE mapping;
112#endif
113 int source;
114
115 uint_least16_t unitsPerEm;
116 int_least16_t locaFormat;
117 uint_least16_t numLongHmtx;
118};
119
120// generic utils
121static void *realloc_array(void *optr, size_t nmemb, size_t size);
122static inline int fast_floor(double x);
123static inline int fast_ceil (double x);
124
125// files
126static int map_file (PASTRY_Font *font, const char *filename);
127static void unmap_file(PASTRY_Font *font);
128static int init_font (PASTRY_Font *font);
129
130// math
131static Point midpoint(Point a, Point b);
132static void transform_points(unsigned int numPts, Point *points, double trf[6]);
133static void clip_points(unsigned int numPts, Point *points, int width, int height);
134
135// outline stuff
136static int init_outline(Outline *outl);
137static void free_outline(Outline *outl);
138static int grow_points (Outline *outl);
139static int grow_curves (Outline *outl);
140static int grow_lines (Outline *outl);
141
142// parsing utils
143static inline int is_safe_offset(PASTRY_Font *font, uint_fast32_t offset, uint_fast32_t margin);
144static void *csearch(const void *key, const void *base,
145 size_t nmemb, size_t size, int (*compar)(const void *, const void *));
146static void *binsearch(const void *key, const void *base,
147 size_t nmemb, size_t size, int (*compar)(const void *, const void *));
148static int cmpu16(const void *a, const void *b);
149static int cmpu32(const void *a, const void *b);
150static inline uint_least8_t getu8 (PASTRY_Font *font, uint_fast32_t offset);
151static inline int_least8_t geti8 (PASTRY_Font *font, uint_fast32_t offset);
152static inline uint_least16_t getu16(PASTRY_Font *font, uint_fast32_t offset);
153static inline int_least16_t geti16(PASTRY_Font *font, uint_fast32_t offset);
154static inline uint_least32_t getu32(PASTRY_Font *font, uint_fast32_t offset);
155static int gettable(PASTRY_Font *font, const char tag[4], uint_fast32_t *offset);
156
157// codepoint to glyph id translation
158static int cmap_fmt4(PASTRY_Font *font, uint_fast32_t table, PASTRY_UChar charCode, uint_fast32_t *glyph);
159static int cmap_fmt6(PASTRY_Font *font, uint_fast32_t table, PASTRY_UChar charCode, uint_fast32_t *glyph);
160static int glyph_id(PASTRY_Font *font, PASTRY_UChar charCode, uint_fast32_t *glyph);
161
162// glyph metrics lookup
163static int hor_metrics(PASTRY_Font *font, uint_fast32_t glyph, int *advanceWidth, int *leftSideBearing);
164static int glyph_bbox(const PASTRY *pastry, uint_fast32_t outline, int box[4]);
165
166// decoding outlines
167static int outline_offset(PASTRY_Font *font, uint_fast32_t glyph, uint_fast32_t *offset);
168static int simple_flags(PASTRY_Font *font, uint_fast32_t *offset, uint_fast16_t numPts, uint8_t *flags);
169static int simple_points(PASTRY_Font *font, uint_fast32_t offset, uint_fast16_t numPts, uint8_t *flags, Point *points);
170static int decode_contour(uint8_t *flags, uint_fast16_t basePoint, uint_fast16_t count, Outline *outl);
171static int simple_outline(PASTRY_Font *font, uint_fast32_t offset, unsigned int numContours, Outline *outl);
172static int compound_outline(PASTRY_Font *font, uint_fast32_t offset, int recDepth, Outline *outl);
173static int decode_outline(PASTRY_Font *font, uint_fast32_t offset, int recDepth, Outline *outl);
174
175// tessellation
176static int is_flat(Outline *outl, Curve curve);
177static int tessellate_curve(Curve curve, Outline *outl);
178static int tessellate_curves(Outline *outl);
179
180// silhouette rasterization
181static void draw_line(Raster buf, Point origin, Point goal);
182static void draw_lines(Outline *outl, Raster buf);
183
184// post processing
185static void post_process(Raster buf, uint8_t *image);
186
187// glyph rendering
188static int render_outline(Outline *outl, double transform[6], PASTRY_Image image);
189
190// ---------- impls start here ----------
191
192const char *
193pastry_version(void)
194{
195 return PASTRY_VERSION;
196}
197
198// loads a font from a given mem range
199PASTRY_Font *
200pastry_loadmem(const void *mem, size_t size)
201{
202 PASTRY_Font *font;
203 if (size > UINT32_MAX) {
204 return NULL;
205 }
206 if (!(font = PASTRY_CALLOC(1, sizeof *font))) {
207 return NULL;
208 }
209 font->memory = mem;
210 font->size = (uint_fast32_t) size;
211 font->source = SrcUser;
212 if (init_font(font) < 0) {
213 pastry_freefont(font);
214 return NULL;
215 }
216 return font;
217}
218
219// loads a font from the fs
220PASTRY_Font *
221pastry_loadfile(char const *filename)
222{
223 PASTRY_Font *font;
224 if (!(font = PASTRY_CALLOC(1, sizeof *font))) {
225 return NULL;
226 }
227 if (map_file(font, filename) < 0) {
228 PASTRY_FREE(font);
229 return NULL;
230 }
231 if (init_font(font) < 0) {
232 pastry_freefont(font);
233 return NULL;
234 }
235 return font;
236}
237
238void
239pastry_freefont(PASTRY_Font *font)
240{
241 if (!font) return;
242 // only unmap if it was mapped by pastry
243 if (font->source == SrcMapping)
244 unmap_file(font);
245 PASTRY_FREE(font);
246}
247
248int
249pastry_lmetrics(const PASTRY *pastry, PASTRY_LMetrics *metrics)
250{
251 double factor;
252 uint_fast32_t hhea;
253 memset(metrics, 0, sizeof *metrics);
254 if (gettable(pastry->font, "hhea", &hhea) < 0)
255 return -1;
256 if (!is_safe_offset(pastry->font, hhea, 36))
257 return -1;
258 factor = pastry->yScale / pastry->font->unitsPerEm;
259 metrics->ascender = geti16(pastry->font, hhea + 4) * factor;
260 metrics->descender = geti16(pastry->font, hhea + 6) * factor;
261 metrics->lineGap = geti16(pastry->font, hhea + 8) * factor;
262 return 0;
263}
264
265int
266pastry_lookup(const PASTRY *pastry, PASTRY_UChar codepoint, PASTRY_Glyph *glyph)
267{
268 return glyph_id(pastry->font, codepoint, glyph);
269}
270
271int
272pastry_gmetrics(const PASTRY *pastry, PASTRY_Glyph glyph, PASTRY_GMetrics *metrics)
273{
274 int adv, lsb;
275 double xScale = pastry->xScale / pastry->font->unitsPerEm;
276 uint_fast32_t outline;
277 int bbox[4];
278
279 memset(metrics, 0, sizeof *metrics);
280
281 if (hor_metrics(pastry->font, glyph, &adv, &lsb) < 0)
282 return -1;
283 metrics->advanceWidth = adv * xScale;
284 metrics->leftSideBearing = lsb * xScale + pastry->xOffset;
285
286 if (outline_offset(pastry->font, glyph, &outline) < 0)
287 return -1;
288 if (!outline)
289 return 0;
290 if (glyph_bbox(pastry, outline, bbox) < 0)
291 return -1;
292 metrics->minWidth = bbox[2] - bbox[0] + 1;
293 metrics->minHeight = bbox[3] - bbox[1] + 1;
294 metrics->yOffset = pastry->flags & PASTRY_DOWNWARD_Y ? -bbox[3] : bbox[1];
295
296 return 0;
297}
298
299int
300pastry_kerning(const PASTRY *pastry, PASTRY_Glyph leftGlyph, PASTRY_Glyph rightGlyph,
301 PASTRY_Kerning *kerning)
302{
303 void *match;
304 uint_fast32_t offset;
305 unsigned int numTables, numPairs, length, format, flags;
306 int value;
307 uint8_t key[4];
308
309 memset(kerning, 0, sizeof *kerning);
310
311 if (gettable(pastry->font, "kern", &offset) < 0)
312 return 0;
313
314 // read kern table header
315 if (!is_safe_offset(pastry->font, offset, 4))
316 return -1;
317 if (getu16(pastry->font, offset) != 0)
318 return 0;
319 numTables = getu16(pastry->font, offset + 2);
320 offset += 4;
321
322 while (numTables > 0) {
323 // read subtable header
324 if (!is_safe_offset(pastry->font, offset, 6))
325 return -1;
326 length = getu16(pastry->font, offset + 2);
327 format = getu8 (pastry->font, offset + 4);
328 flags = getu8 (pastry->font, offset + 5);
329 offset += 6;
330
331 if (format == 0 && (flags & HORIZONTAL_KERNING) && !(flags & MINIMUM_KERNING)) {
332 // read format 0 header
333 if (!is_safe_offset(pastry->font, offset, 8))
334 return -1;
335 numPairs = getu16(pastry->font, offset);
336 offset += 8;
337 // look up char code pair
338 key[0] = (leftGlyph >> 8) & 0xFF;
339 key[1] = leftGlyph & 0xFF;
340 key[2] = (rightGlyph >> 8) & 0xFF;
341 key[3] = rightGlyph & 0xFF;
342 if ((match = binsearch(key, pastry->font->memory + offset,
343 numPairs, 6, cmpu32)) != NULL) {
344
345 value = geti16(pastry->font, (uint_fast32_t) ((uint8_t *) match - pastry->font->memory + 4));
346 if (flags & CROSS_STREAM_KERNING) {
347 kerning->yShift += value;
348 } else {
349 kerning->xShift += value;
350 }
351 }
352
353 }
354
355 offset += length;
356 --numTables;
357 }
358
359 kerning->xShift = kerning->xShift / pastry->font->unitsPerEm * pastry->xScale;
360 kerning->yShift = kerning->yShift / pastry->font->unitsPerEm * pastry->yScale;
361
362 return 0;
363}
364
365int
366pastry_render(const PASTRY *pastry, PASTRY_Glyph glyph, PASTRY_Image image)
367{
368 uint_fast32_t outline;
369 double transform[6];
370 int bbox[4];
371 Outline outl;
372
373 if (outline_offset(pastry->font, glyph, &outline) < 0)
374 return -1;
375 if (!outline)
376 return 0;
377 if (glyph_bbox(pastry, outline, bbox) < 0)
378 return -1;
379 /* set up transformation matrix such that
380 * transformed bounding boxes min corner
381 * lines up with the (0, 0) point */
382 transform[0] = pastry->xScale / pastry->font->unitsPerEm;
383 transform[1] = 0.0;
384 transform[2] = 0.0;
385 transform[4] = pastry->xOffset - bbox[0];
386 if (pastry->flags & PASTRY_DOWNWARD_Y) {
387 transform[3] = -pastry->yScale / pastry->font->unitsPerEm;
388 transform[5] = bbox[3] - pastry->yOffset;
389 } else {
390 transform[3] = +pastry->yScale / pastry->font->unitsPerEm;
391 transform[5] = pastry->yOffset - bbox[1];
392 }
393
394 memset(&outl, 0, sizeof outl);
395 if (init_outline(&outl) < 0)
396 goto failure;
397
398 if (decode_outline(pastry->font, outline, 0, &outl) < 0)
399 goto failure;
400 if (render_outline(&outl, transform, image) < 0)
401 goto failure;
402
403 free_outline(&outl);
404 return 0;
405
406failure:
407 free_outline(&outl);
408 return -1;
409}
410
411/* sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX
412 * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW */
413#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4))
414
415/* copy of openbsd's reallocarray function from the stdlib
416 * a wrapper for realloc() that takes two size args like calloc()
417 * useful cuz it eliminates some integer overflow bugs that pmo */
418static void *
419realloc_array(void *optr, size_t nmemb, size_t size)
420{
421 if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
422 nmemb > 0 && SIZE_MAX / nmemb < size) {
423 errno = ENOMEM;
424 return NULL;
425 }
426 return PASTRY_REALLOC(optr, (size * nmemb));
427}
428
429static inline int
430fast_floor(double x)
431{
432 int i = (int) x;
433 return i - (i > x);
434}
435
436static inline double
437fast_abs(double x)
438{
439 return x < 0.0 ? -x : x;
440}
441
442static inline int
443fast_ceil(double x)
444{
445 int i = (int) x;
446 return i + (i < x);
447}
448
449// largest double strictly less than x; x must be finite and positive.
450// stands in for nextafter(x, 0.0) without pulling in libm.
451static inline double
452fast_prev(double x)
453{
454 union { double d; uint64_t u; } bits;
455 bits.d = x;
456 bits.u -= 1;
457 return bits.d;
458}
459
460#if defined(_WIN32)
461
462static int
463map_file(PASTRY_Font *font, const char *filename)
464{
465 HANDLE file;
466 DWORD high, low;
467
468 font->mapping = NULL;
469 font->memory = NULL;
470
471 file = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
472 if (file == INVALID_HANDLE_VALUE) {
473 return -1;
474 }
475
476 low = GetFileSize(file, &high);
477 if (low == INVALID_FILE_SIZE) {
478 CloseHandle(file);
479 return -1;
480 }
481
482 font->size = (size_t)high << (8 * sizeof(DWORD)) | low;
483
484 font->mapping = CreateFileMapping(file, NULL, PAGE_READONLY, high, low, NULL);
485 if (!font->mapping) {
486 CloseHandle(file);
487 return -1;
488 }
489
490 CloseHandle(file);
491
492 font->memory = MapViewOfFile(font->mapping, FILE_MAP_READ, 0, 0, 0);
493 if (!font->memory) {
494 CloseHandle(font->mapping);
495 font->mapping = NULL;
496 return -1;
497 }
498
499 return 0;
500}
501
502static void
503unmap_file(PASTRY_Font *font)
504{
505 if (font->memory) {
506 UnmapViewOfFile(font->memory);
507 font->memory = NULL;
508 }
509 if (font->mapping) {
510 CloseHandle(font->mapping);
511 font->mapping = NULL;
512 }
513}
514
515#elif defined(PASTRY_NO_MMAP)
516
517static int
518map_file(PASTRY_Font *font, const char *filename)
519{
520 FILE *f = fopen(filename, "rb");
521 if (!f) return -1;
522
523 if (fseek(f, 0, SEEK_END) != 0) {
524 fclose(f);
525 return -1;
526 }
527
528 long sz = ftell(f);
529 if (sz < 0) {
530 fclose(f);
531 return -1;
532 }
533 rewind(f);
534
535 // allocate and read
536 void *mem = PASTRY_MALLOC((size_t)sz);
537 if (!mem) {
538 fclose(f);
539 return -1;
540 }
541
542 size_t read_bytes = fread(mem, 1, (size_t)sz, f);
543 fclose(f);
544 if (read_bytes != (size_t)sz) {
545 PASTRY_FREE(mem);
546 return -1;
547 }
548
549 font->memory = mem;
550 font->size = (uint_fast32_t) sz;
551 font->source = SrcCopy;
552
553 return 0;
554}
555
556static void
557unmap_file(PASTRY_Font *font)
558{
559 if (font->memory) {
560 PASTRY_FREE(font->memory);
561 font->memory = NULL;
562 font->size = 0;
563 }
564}
565
566#else
567
568static int
569map_file(PASTRY_Font *font, const char *filename)
570{
571 struct stat info;
572 int fd;
573 font->memory = MAP_FAILED;
574 font->size = 0;
575 font->source = SrcMapping;
576 if ((fd = open(filename, O_RDONLY)) < 0) {
577 return -1;
578 }
579 if (fstat(fd, &info) < 0) {
580 close(fd);
581 return -1;
582 }
583 // gottem
584 assert(info.st_size > 0);
585 font->memory = mmap(NULL, (size_t) info.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
586 font->size = (uint_fast32_t) info.st_size;
587 close(fd);
588 return font->memory == MAP_FAILED ? -1 : 0;
589}
590
591static void
592unmap_file(PASTRY_Font *font)
593{
594 assert(font->memory != MAP_FAILED);
595 munmap((void *) (uintptr_t) font->memory, font->size);
596}
597
598#endif
599
600static int
601init_font(PASTRY_Font *font)
602{
603 uint_fast32_t scalerType, head, hhea;
604
605 if (!is_safe_offset(font, 0, 12))
606 return -1;
607 // check for a compatible scalerType (magic number). */
608 scalerType = getu32(font, 0);
609 if (scalerType != FILE_MAGIC_ONE && scalerType != FILE_MAGIC_TWO)
610 return -1;
611
612 if (gettable(font, "head", &head) < 0)
613 return -1;
614 if (!is_safe_offset(font, head, 54))
615 return -1;
616 font->unitsPerEm = getu16(font, head + 18);
617 font->locaFormat = geti16(font, head + 50);
618
619 if (gettable(font, "hhea", &hhea) < 0)
620 return -1;
621 if (!is_safe_offset(font, hhea, 36))
622 return -1;
623 font->numLongHmtx = getu16(font, hhea + 34);
624
625 return 0;
626}
627
628static Point
629midpoint(Point a, Point b)
630{
631 return (Point) {
632 0.5 * (a.x + b.x),
633 0.5 * (a.y + b.y)
634 };
635}
636
637// apply an affine linear transformation matrix to a set of points
638static void
639transform_points(unsigned int numPts, Point *points, double trf[6])
640{
641 assert(numPts == 0 || points != NULL);
642 assert(trf != NULL);
643 Point pt;
644 unsigned int i;
645 for (i = 0; i < numPts; ++i) {
646 pt = points[i];
647 points[i] = (Point) {
648 pt.x * trf[0] + pt.y * trf[2] + trf[4],
649 pt.x * trf[1] + pt.y * trf[3] + trf[5]
650 };
651 }
652}
653
654static void
655clip_points(unsigned int numPts, Point *points, int width, int height)
656{
657 assert(numPts == 0 || points != NULL);
658 assert(width > 0 && height > 0);
659 Point pt;
660 unsigned int i;
661
662 for (i = 0; i < numPts; ++i) {
663 pt = points[i];
664
665 if (pt.x < 0.0) {
666 points[i].x = 0.0;
667 }
668 if (pt.x >= width) {
669 points[i].x = fast_prev((double) width);
670 }
671 if (pt.y < 0.0) {
672 points[i].y = 0.0;
673 }
674 if (pt.y >= height) {
675 points[i].y = fast_prev((double) height);
676 }
677 }
678}
679
680static int
681init_outline(Outline *outl)
682{
683 assert(outl != NULL);
684 // add smaller initial allocations later (someday)
685 outl->numPoints = 0;
686 outl->capPoints = 64;
687 if (!(outl->points = PASTRY_MALLOC(outl->capPoints * sizeof *outl->points)))
688 return -1;
689 outl->numCurves = 0;
690 outl->capCurves = 64;
691 if (!(outl->curves = PASTRY_MALLOC(outl->capCurves * sizeof *outl->curves)))
692 return -1;
693 outl->numLines = 0;
694 outl->capLines = 64;
695 if (!(outl->lines = PASTRY_MALLOC(outl->capLines * sizeof *outl->lines)))
696 return -1;
697 return 0;
698}
699
700static void
701free_outline(Outline *outl)
702{
703 PASTRY_FREE(outl->points);
704 PASTRY_FREE(outl->curves);
705 PASTRY_FREE(outl->lines);
706}
707
708static int
709grow_points(Outline *outl)
710{
711 void *mem;
712 uint_fast16_t cap;
713 assert(outl->capPoints);
714 // since uint_fast16_t is used for capacities, be extra careful not to trigger int overflow
715 if (outl->capPoints > UINT16_MAX / 2)
716 return -1;
717 cap = (uint_fast16_t) (2U * outl->capPoints);
718 if (!(mem = realloc_array(outl->points, cap, sizeof *outl->points)))
719 return -1;
720 outl->capPoints = (uint_least16_t) cap;
721 outl->points = mem;
722 return 0;
723}
724
725static int
726grow_curves(Outline *outl)
727{
728 void *mem;
729 uint_fast16_t cap;
730 assert(outl->capCurves);
731 if (outl->capCurves > UINT16_MAX / 2)
732 return -1;
733 cap = (uint_fast16_t) (2U * outl->capCurves);
734 if (!(mem = realloc_array(outl->curves, cap, sizeof *outl->curves)))
735 return -1;
736 outl->capCurves = (uint_least16_t) cap;
737 outl->curves = mem;
738 return 0;
739}
740
741static int
742grow_lines(Outline *outl)
743{
744 void *mem;
745 uint_fast16_t cap;
746 assert(outl->capLines);
747 if (outl->capLines > UINT16_MAX / 2)
748 return -1;
749 cap = (uint_fast16_t) (2U * outl->capLines);
750 if (!(mem = realloc_array(outl->lines, cap, sizeof *outl->lines)))
751 return -1;
752 outl->capLines = (uint_least16_t) cap;
753 outl->lines = mem;
754 return 0;
755}
756
757static inline int
758is_safe_offset(PASTRY_Font *font, uint_fast32_t offset, uint_fast32_t margin)
759{
760 if (offset > font->size) return 0;
761 if (font->size - offset < margin) return 0;
762 return 1;
763}
764
765// like bsearch(), but returns next highest element if key could not be found`
766static void *
767csearch(const void *key, const void *base,
768 size_t nmemb, size_t size,
769 int (*compar)(const void *, const void *))
770{
771 const uint8_t *bytes = base, *sample;
772 size_t low = 0, high = nmemb - 1, mid;
773 if (!nmemb) return NULL;
774 while (low != high) {
775 mid = low + (high - low) / 2;
776 sample = bytes + mid * size;
777 if (compar(key, sample) > 0) {
778 low = mid + 1;
779 } else {
780 high = mid;
781 }
782 }
783 return (void *) ((uintptr_t)bytes + low * size);
784}
785
786// bsearch(), minus the libc. returns NULL when the key is absent.
787static void *
788binsearch(const void *key, const void *base,
789 size_t nmemb, size_t size,
790 int (*compar)(const void *, const void *))
791{
792 const uint8_t *bytes = base;
793 size_t low = 0, high = nmemb;
794 while (low < high) {
795 size_t mid = low + (high - low) / 2;
796 const uint8_t *sample = bytes + mid * size;
797 int cmp = compar(key, sample);
798 if (cmp == 0)
799 return (void *) (uintptr_t) sample;
800 if (cmp < 0)
801 high = mid;
802 else
803 low = mid + 1;
804 }
805 return NULL;
806}
807
808// used as comparison function for [bc]search()
809static int
810cmpu16(const void *a, const void *b)
811{
812 return memcmp(a, b, 2);
813}
814
815// used as comparison function for [bc]search()
816static int
817cmpu32(const void *a, const void *b)
818{
819 return memcmp(a, b, 4);
820}
821
822static inline uint_least8_t
823getu8(PASTRY_Font *font, uint_fast32_t offset)
824{
825 assert(offset + 1 <= font->size);
826 return *(font->memory + offset);
827}
828
829static inline int_least8_t
830geti8(PASTRY_Font *font, uint_fast32_t offset)
831{
832 return (int_least8_t) getu8(font, offset);
833}
834
835static inline uint_least16_t
836getu16(PASTRY_Font *font, uint_fast32_t offset)
837{
838 assert(offset + 2 <= font->size);
839 const uint8_t *base = font->memory + offset;
840 uint_least16_t b1 = base[0], b0 = base[1];
841 return (uint_least16_t) (b1 << 8 | b0);
842}
843
844static inline int16_t
845geti16(PASTRY_Font *font, uint_fast32_t offset)
846{
847 return (int_least16_t) getu16(font, offset);
848}
849
850static inline uint32_t
851getu32(PASTRY_Font *font, uint_fast32_t offset)
852{
853 assert(offset + 4 <= font->size);
854 const uint8_t *base = font->memory + offset;
855 uint_least32_t b3 = base[0], b2 = base[1], b1 = base[2], b0 = base[3];
856 return (uint_least32_t) (b3 << 24 | b2 << 16 | b1 << 8 | b0);
857}
858
859static int
860gettable(PASTRY_Font *font, const char tag[4], uint_fast32_t *offset)
861{
862 void *match;
863 unsigned int numTables;
864 // no need to bounds-check access to the first 12 bytes cuz it's already checked by init_font()
865 numTables = getu16(font, 4);
866 if (!is_safe_offset(font, 12, (uint_fast32_t) numTables * 16))
867 return -1;
868 if (!(match = binsearch(tag, font->memory + 12, numTables, 16, cmpu32)))
869 return -1;
870 *offset = getu32(font, (uint_fast32_t) ((uint8_t *) match - font->memory + 8));
871 return 0;
872}
873
874static int
875cmap_fmt4(PASTRY_Font *font, uint_fast32_t table, PASTRY_UChar charCode, PASTRY_Glyph *glyph)
876{
877 const uint8_t *segPtr;
878 uint_fast32_t segIdxX2;
879 uint_fast32_t endCodes, startCodes, idDeltas, idRangeOffsets, idOffset;
880 uint_fast16_t segCountX2, idRangeOffset, startCode, shortCode, idDelta, id;
881 uint8_t key[2] = { (uint8_t) (charCode >> 8), (uint8_t) charCode };
882 // cmap format 4 only supports the unicode bmp
883 if (charCode > 0xFFFF) {
884 *glyph = 0;
885 return 0;
886 }
887 shortCode = (uint_fast16_t) charCode;
888 if (!is_safe_offset(font, table, 8))
889 return -1;
890 segCountX2 = getu16(font, table);
891 if ((segCountX2 & 1) || !segCountX2)
892 return -1;
893 // find starting positions of the relevant arrays
894 endCodes = table + 8;
895 startCodes = endCodes + segCountX2 + 2;
896 idDeltas = startCodes + segCountX2;
897 idRangeOffsets = idDeltas + segCountX2;
898 if (!is_safe_offset(font, idRangeOffsets, segCountX2))
899 return -1;
900 /* find the segment that contains shortCode by binary searching over
901 * the highest codes in the segments. */
902 segPtr = csearch(key, font->memory + endCodes, segCountX2 / 2, 2, cmpu16);
903 segIdxX2 = (uint_fast32_t) (segPtr - (font->memory + endCodes));
904 // look up segment info from the arrays & short circuit if the spec requires
905 if ((startCode = getu16(font, startCodes + segIdxX2)) > shortCode)
906 return 0;
907 idDelta = getu16(font, idDeltas + segIdxX2);
908 if (!(idRangeOffset = getu16(font, idRangeOffsets + segIdxX2))) {
909 // intentional integer under/overflow
910 *glyph = (shortCode + idDelta) & 0xFFFF;
911 return 0;
912 }
913 // calculate offset into glyph array and determine ultimate value.
914 idOffset = idRangeOffsets + segIdxX2 + idRangeOffset + 2U * (unsigned int) (shortCode - startCode);
915 if (!is_safe_offset(font, idOffset, 2))
916 return -1;
917 id = getu16(font, idOffset);
918 // intentional integer under/overflow
919 *glyph = id ? (id + idDelta) & 0xFFFF : 0;
920 return 0;
921}
922
923static int
924cmap_fmt6(PASTRY_Font *font, uint_fast32_t table, PASTRY_UChar charCode, PASTRY_Glyph *glyph)
925{
926 unsigned int firstCode, entryCount;
927 // cmap format 6 only supports the unicode bmp
928 if (charCode > 0xFFFF) {
929 *glyph = 0;
930 return 0;
931 }
932 if (!is_safe_offset(font, table, 4))
933 return -1;
934 firstCode = getu16(font, table);
935 entryCount = getu16(font, table + 2);
936 if (!is_safe_offset(font, table, 4 + 2 * entryCount))
937 return -1;
938 if (charCode < firstCode)
939 return -1;
940 charCode -= firstCode;
941 if (!(charCode < entryCount))
942 return -1;
943 *glyph = getu16(font, table + 4 + 2 * charCode);
944 return 0;
945}
946
947static int
948cmap_fmt12_13(PASTRY_Font *font, uint_fast32_t table, PASTRY_UChar charCode, PASTRY_Glyph *glyph, int which)
949{
950 uint32_t len, numEntries;
951 uint_fast32_t i;
952
953 *glyph = 0;
954
955 // ensure entire header
956 if (!is_safe_offset(font, table, 16))
957 return -1;
958
959 len = getu32(font, table + 4);
960
961 // smol header is 16 bytes
962 if (len < 16)
963 return -1;
964
965 if (!is_safe_offset(font, table, len))
966 return -1;
967
968 numEntries = getu32(font, table + 12);
969
970 for (i = 0; i < numEntries; ++i) {
971 uint32_t firstCode, lastCode, glyphOffset;
972 firstCode = getu32(font, table + (i * 12) + 16);
973 lastCode = getu32(font, table + (i * 12) + 16 + 4);
974 if (charCode < firstCode || charCode > lastCode)
975 continue;
976 glyphOffset = getu32(font, table + (i * 12) + 16 + 8);
977 if (which == 12)
978 *glyph = (charCode-firstCode) + glyphOffset;
979 else
980 *glyph = glyphOffset;
981 return 0;
982 }
983
984 return 0;
985}
986
987// maps unicode code points to glyph indices
988static int
989glyph_id(PASTRY_Font *font, PASTRY_UChar charCode, PASTRY_Glyph *glyph)
990{
991 uint_fast32_t cmap, entry, table;
992 unsigned int idx, numEntries;
993 int type, format;
994
995 *glyph = 0;
996
997 if (gettable(font, "cmap", &cmap) < 0)
998 return -1;
999
1000 if (!is_safe_offset(font, cmap, 4))
1001 return -1;
1002 numEntries = getu16(font, cmap + 2);
1003
1004 if (!is_safe_offset(font, cmap, 4 + numEntries * 8))
1005 return -1;
1006
1007 // first look for a full repertoire map
1008 for (idx = 0; idx < numEntries; ++idx) {
1009 entry = cmap + 4 + idx * 8;
1010 type = getu16(font, entry) * 0100 + getu16(font, entry + 2);
1011 // complete unicode map
1012 if (type == 0004 || type == 0312) {
1013 table = cmap + getu32(font, entry + 4);
1014 if (!is_safe_offset(font, table, 8))
1015 return -1;
1016 /* Dispatch based on cmap format. */
1017 format = getu16(font, table);
1018 switch (format) {
1019 case 12:
1020 return cmap_fmt12_13(font, table, charCode, glyph, 12);
1021 default:
1022 return -1;
1023 }
1024 }
1025 }
1026
1027 // if no full repertoire cmap was found, try looking for a bmp map
1028 for (idx = 0; idx < numEntries; ++idx) {
1029 entry = cmap + 4 + idx * 8;
1030 type = getu16(font, entry) * 0100 + getu16(font, entry + 2);
1031 // unicode bmp
1032 if (type == 0003 || type == 0301) {
1033 table = cmap + getu32(font, entry + 4);
1034 if (!is_safe_offset(font, table, 6))
1035 return -1;
1036 // dispatch based on cmap format
1037 switch (getu16(font, table)) {
1038 case 4:
1039 return cmap_fmt4(font, table + 6, charCode, glyph);
1040 case 6:
1041 return cmap_fmt6(font, table + 6, charCode, glyph);
1042 default:
1043 return -1;
1044 }
1045 }
1046 }
1047
1048 return -1;
1049}
1050
1051static int
1052hor_metrics(PASTRY_Font *font, PASTRY_Glyph glyph, int *advanceWidth, int *leftSideBearing)
1053{
1054 uint_fast32_t hmtx, offset, boundary;
1055 if (gettable(font, "hmtx", &hmtx) < 0)
1056 return -1;
1057 if (glyph < font->numLongHmtx) {
1058 // glyph is inside long metrics segment
1059 offset = hmtx + 4 * glyph;
1060 if (!is_safe_offset(font, offset, 4))
1061 return -1;
1062 *advanceWidth = getu16(font, offset);
1063 *leftSideBearing = geti16(font, offset + 2);
1064 return 0;
1065 } else {
1066 // glyph is inside short metrics segment
1067 boundary = hmtx + 4U * (uint_fast32_t) font->numLongHmtx;
1068 if (boundary < 4)
1069 return -1;
1070
1071 offset = boundary - 4;
1072 if (!is_safe_offset(font, offset, 4))
1073 return -1;
1074 *advanceWidth = getu16(font, offset);
1075
1076 offset = boundary + 2 * (glyph - font->numLongHmtx);
1077 if (!is_safe_offset(font, offset, 2))
1078 return -1;
1079 *leftSideBearing = geti16(font, offset);
1080 return 0;
1081 }
1082}
1083
1084static int
1085glyph_bbox(const PASTRY *pastry, uint_fast32_t outline, int box[4])
1086{
1087 double xScale, yScale;
1088 // raad bounding box from the font file verbatim
1089 if (!is_safe_offset(pastry->font, outline, 10))
1090 return -1;
1091 box[0] = geti16(pastry->font, outline + 2);
1092 box[1] = geti16(pastry->font, outline + 4);
1093 box[2] = geti16(pastry->font, outline + 6);
1094 box[3] = geti16(pastry->font, outline + 8);
1095 if (box[2] <= box[0] || box[3] <= box[1])
1096 return -1;
1097 // transform bounding box into PASTRY coordinate space
1098 xScale = pastry->xScale / pastry->font->unitsPerEm;
1099 yScale = pastry->yScale / pastry->font->unitsPerEm;
1100 box[0] = fast_floor(box[0] * xScale + pastry->xOffset);
1101 box[1] = fast_floor(box[1] * yScale + pastry->yOffset);
1102 box[2] = fast_ceil (box[2] * xScale + pastry->xOffset);
1103 box[3] = fast_ceil (box[3] * yScale + pastry->yOffset);
1104 return 0;
1105}
1106
1107// returns the offset into the font that the glyph's outline is stored at
1108static int
1109outline_offset(PASTRY_Font *font, PASTRY_Glyph glyph, uint_fast32_t *offset)
1110{
1111 uint_fast32_t loca, glyf;
1112 uint_fast32_t base, this, next;
1113
1114 if (gettable(font, "loca", &loca) < 0)
1115 return -1;
1116 if (gettable(font, "glyf", &glyf) < 0)
1117 return -1;
1118
1119 if (font->locaFormat == 0) {
1120 base = loca + 2 * glyph;
1121
1122 if (!is_safe_offset(font, base, 4))
1123 return -1;
1124
1125 this = 2U * (uint_fast32_t) getu16(font, base);
1126 next = 2U * (uint_fast32_t) getu16(font, base + 2);
1127 } else {
1128 base = loca + 4 * glyph;
1129
1130 if (!is_safe_offset(font, base, 8))
1131 return -1;
1132
1133 this = getu32(font, base);
1134 next = getu32(font, base + 4);
1135 }
1136
1137 *offset = this == next ? 0 : glyf + this;
1138 return 0;
1139}
1140
1141// for a simple outline, determines each point of the outline with a set of flags
1142static int
1143simple_flags(PASTRY_Font *font, uint_fast32_t *offset, uint_fast16_t numPts, uint8_t *flags)
1144{
1145 assert(flags != NULL);
1146 uint_fast32_t off = *offset;
1147 uint_fast16_t i;
1148 uint8_t value = 0, repeat = 0;
1149 for (i = 0; i < numPts; ++i) {
1150 if (repeat) {
1151 --repeat;
1152 } else {
1153 if (!is_safe_offset(font, off, 1))
1154 return -1;
1155 value = getu8(font, off++);
1156 if (value & REPEAT_FLAG) {
1157 if (!is_safe_offset(font, off, 1))
1158 return -1;
1159 repeat = getu8(font, off++);
1160 }
1161 }
1162 flags[i] = value;
1163 }
1164 *offset = off;
1165 return 0;
1166}
1167
1168// for a simple outline, decodes both X and Y coordinates for each point of the outline
1169static int
1170simple_points(PASTRY_Font *font, uint_fast32_t offset, uint_fast16_t numPts, uint8_t *flags, Point *points)
1171{
1172 assert(flags != NULL);
1173 assert(points != NULL);
1174 long accum, value, bit;
1175 uint_fast16_t i;
1176
1177 accum = 0L;
1178 for (i = 0; i < numPts; ++i) {
1179 if (flags[i] & X_CHANGE_IS_SMALL) {
1180 if (!is_safe_offset(font, offset, 1))
1181 return -1;
1182 value = (long) getu8(font, offset++);
1183 bit = !!(flags[i] & X_CHANGE_IS_POSITIVE);
1184 accum -= (value ^ -bit) + bit;
1185 } else if (!(flags[i] & X_CHANGE_IS_ZERO)) {
1186 if (!is_safe_offset(font, offset, 2))
1187 return -1;
1188 accum += geti16(font, offset);
1189 offset += 2;
1190 }
1191 points[i].x = (double) accum;
1192 }
1193
1194 accum = 0L;
1195 for (i = 0; i < numPts; ++i) {
1196 if (flags[i] & Y_CHANGE_IS_SMALL) {
1197 if (!is_safe_offset(font, offset, 1))
1198 return -1;
1199 value = (long) getu8(font, offset++);
1200 bit = !!(flags[i] & Y_CHANGE_IS_POSITIVE);
1201 accum -= (value ^ -bit) + bit;
1202 } else if (!(flags[i] & Y_CHANGE_IS_ZERO)) {
1203 if (!is_safe_offset(font, offset, 2))
1204 return -1;
1205 accum += geti16(font, offset);
1206 offset += 2;
1207 }
1208 points[i].y = (double) accum;
1209 }
1210
1211 return 0;
1212}
1213
1214static int
1215decode_contour(uint8_t *flags, uint_fast16_t basePoint, uint_fast16_t count, Outline *outl)
1216{
1217 assert(basePoint + count <= outl->numPoints);
1218 uint_fast16_t i;
1219 uint_least16_t looseEnd, beg, ctrl, center, cur;
1220 unsigned int gotCtrl;
1221
1222 /* skip contours with less than two points, since my algo can't handle them and
1223 * they should appear invisible either way (because they don't have any area lmao) */
1224 if (count < 2) return 0;
1225
1226 assert(basePoint <= UINT16_MAX - count);
1227
1228 if (flags[0] & POINT_IS_ON_CURVE) {
1229 looseEnd = (uint_least16_t) basePoint++;
1230 ++flags;
1231 --count;
1232 } else if (flags[count - 1] & POINT_IS_ON_CURVE) {
1233 looseEnd = (uint_least16_t) (basePoint + --count);
1234 } else {
1235 if (outl->numPoints >= outl->capPoints && grow_points(outl) < 0)
1236 return -1;
1237
1238 looseEnd = outl->numPoints;
1239 outl->points[outl->numPoints++] = midpoint(
1240 outl->points[basePoint],
1241 outl->points[basePoint + count - 1]);
1242 }
1243 beg = looseEnd;
1244 gotCtrl = 0;
1245 for (i = 0; i < count; ++i) {
1246 // cur can't overflow because we ensure that basePoint + count < 0xFFFF before calling decode_contour()
1247 cur = (uint_least16_t) (basePoint + i);
1248 if (flags[i] & POINT_IS_ON_CURVE) {
1249 if (gotCtrl) {
1250 if (outl->numCurves >= outl->capCurves && grow_curves(outl) < 0)
1251 return -1;
1252 outl->curves[outl->numCurves++] = (Curve) {
1253 beg, cur, ctrl
1254 };
1255 } else {
1256 if (outl->numLines >= outl->capLines && grow_lines(outl) < 0)
1257 return -1;
1258 outl->lines[outl->numLines++] = (Line) {
1259 beg, cur
1260 };
1261 }
1262 beg = cur;
1263 gotCtrl = 0;
1264 } else {
1265 if (gotCtrl) {
1266 center = outl->numPoints;
1267 if (outl->numPoints >= outl->capPoints && grow_points(outl) < 0)
1268 return -1;
1269 outl->points[center] = midpoint(outl->points[ctrl], outl->points[cur]);
1270 ++outl->numPoints;
1271
1272 if (outl->numCurves >= outl->capCurves && grow_curves(outl) < 0)
1273 return -1;
1274 outl->curves[outl->numCurves++] = (Curve) {
1275 beg, center, ctrl
1276 };
1277
1278 beg = center;
1279 }
1280 ctrl = cur;
1281 gotCtrl = 1;
1282 }
1283 }
1284 if (gotCtrl) {
1285 if (outl->numCurves >= outl->capCurves && grow_curves(outl) < 0)
1286 return -1;
1287 outl->curves[outl->numCurves++] = (Curve) {
1288 beg, looseEnd, ctrl
1289 };
1290 } else {
1291 if (outl->numLines >= outl->capLines && grow_lines(outl) < 0)
1292 return -1;
1293 outl->lines[outl->numLines++] = (Line) {
1294 beg, looseEnd
1295 };
1296 }
1297
1298 return 0;
1299}
1300
1301static int
1302simple_outline(PASTRY_Font *font, uint_fast32_t offset, unsigned int numContours, Outline *outl)
1303{
1304 uint_fast16_t *endPts = NULL;
1305 uint8_t *flags = NULL;
1306 uint_fast16_t numPts;
1307 unsigned int i;
1308 uint_fast16_t beg;
1309
1310 assert(numContours > 0);
1311
1312 uint_fast16_t basePoint = outl->numPoints;
1313
1314 if (!is_safe_offset(font, offset, numContours * 2 + 2))
1315 goto failure;
1316 numPts = getu16(font, offset + (numContours - 1) * 2);
1317 if (numPts >= UINT16_MAX)
1318 goto failure;
1319 numPts++;
1320 if (outl->numPoints > UINT16_MAX - numPts)
1321 goto failure;
1322
1323 while (outl->capPoints < basePoint + numPts) {
1324 if (grow_points(outl) < 0)
1325 goto failure;
1326 }
1327
1328 PASTRY_STACK_ALLOC(endPts, uint_fast16_t, 16, numContours);
1329 if (endPts == NULL)
1330 goto failure;
1331 PASTRY_STACK_ALLOC(flags, uint8_t, 128, numPts);
1332 if (flags == NULL)
1333 goto failure;
1334
1335 for (i = 0; i < numContours; ++i) {
1336 endPts[i] = getu16(font, offset);
1337 offset += 2;
1338 }
1339 /* ensure that endPts are never falling
1340 * falling endPts have no sensible interpretation and idk probably only exist in bad inputs
1341 *
1342 * so... just bail? */
1343 for (i = 0; i < numContours - 1; ++i) {
1344 if (endPts[i + 1] < endPts[i] + 1)
1345 goto failure;
1346 }
1347 offset += 2U + getu16(font, offset);
1348
1349 if (simple_flags(font, &offset, numPts, flags) < 0)
1350 goto failure;
1351 if (simple_points(font, offset, numPts, flags, outl->points + basePoint) < 0)
1352 goto failure;
1353 outl->numPoints = (uint_least16_t) (outl->numPoints + numPts);
1354
1355 beg = 0;
1356 for (i = 0; i < numContours; ++i) {
1357 uint_fast16_t count = endPts[i] - beg + 1;
1358 if (decode_contour(flags + beg, basePoint + beg, count, outl) < 0)
1359 goto failure;
1360 beg = endPts[i] + 1;
1361 }
1362
1363 PASTRY_STACK_FREE(endPts);
1364 PASTRY_STACK_FREE(flags);
1365 return 0;
1366failure:
1367 PASTRY_STACK_FREE(endPts);
1368 PASTRY_STACK_FREE(flags);
1369 return -1;
1370}
1371
1372static int
1373compound_outline(PASTRY_Font *font, uint_fast32_t offset, int recDepth, Outline *outl)
1374{
1375 double local[6];
1376 uint_fast32_t outline;
1377 unsigned int flags, glyph, basePoint;
1378 // guard against compound glyphs that have themselves as component (wtf)
1379 if (recDepth >= 4)
1380 return -1;
1381 do {
1382 memset(local, 0, sizeof local);
1383 if (!is_safe_offset(font, offset, 4))
1384 return -1;
1385 flags = getu16(font, offset);
1386 glyph = getu16(font, offset + 2);
1387 offset += 4;
1388 // i refuse to implement point matching, and neither does stb_truetype for that matter, so fuck you
1389 if (!(flags & ACTUAL_XY_OFFSETS))
1390 return -1;
1391 // read additional X and Y offsets (in FUnits) of this component
1392 if (flags & OFFSETS_ARE_LARGE) {
1393 if (!is_safe_offset(font, offset, 4))
1394 return -1;
1395 local[4] = geti16(font, offset);
1396 local[5] = geti16(font, offset + 2);
1397 offset += 4;
1398 } else {
1399 if (!is_safe_offset(font, offset, 2))
1400 return -1;
1401 local[4] = geti8(font, offset);
1402 local[5] = geti8(font, offset + 1);
1403 offset += 2;
1404 }
1405 if (flags & GOT_A_SINGLE_SCALE) {
1406 if (!is_safe_offset(font, offset, 2))
1407 return -1;
1408 local[0] = geti16(font, offset) / 16384.0;
1409 local[3] = local[0];
1410 offset += 2;
1411 } else if (flags & GOT_AN_X_AND_Y_SCALE) {
1412 if (!is_safe_offset(font, offset, 4))
1413 return -1;
1414 local[0] = geti16(font, offset + 0) / 16384.0;
1415 local[3] = geti16(font, offset + 2) / 16384.0;
1416 offset += 4;
1417 } else if (flags & GOT_A_SCALE_MATRIX) {
1418 if (!is_safe_offset(font, offset, 8))
1419 return -1;
1420 local[0] = geti16(font, offset + 0) / 16384.0;
1421 local[1] = geti16(font, offset + 2) / 16384.0;
1422 local[2] = geti16(font, offset + 4) / 16384.0;
1423 local[3] = geti16(font, offset + 6) / 16384.0;
1424 offset += 8;
1425 } else {
1426 local[0] = 1.0;
1427 local[3] = 1.0;
1428 }
1429 /* at this point, apple more or less tells you to scale the matrix by its own L1
1430 * norm but stb_truetype scales by the L2 norm, and freetype doesn't scale at all
1431 * microsoft's spec doesn't even mention anything like this, it just tells you to
1432 * go fuck yourself
1433 *
1434 * hm idk it feels like nobody ever uses this feature */
1435 if (outline_offset(font, glyph, &outline) < 0)
1436 return -1;
1437 if (outline) {
1438 basePoint = outl->numPoints;
1439 if (decode_outline(font, outline, recDepth + 1, outl) < 0)
1440 return -1;
1441 transform_points(outl->numPoints - basePoint, outl->points + basePoint, local);
1442 }
1443 } while (flags & THERE_ARE_MORE_COMPONENTS);
1444
1445 return 0;
1446}
1447
1448static int
1449decode_outline(PASTRY_Font *font, uint_fast32_t offset, int recDepth, Outline *outl)
1450{
1451 int numContours;
1452 if (!is_safe_offset(font, offset, 10))
1453 return -1;
1454 numContours = geti16(font, offset);
1455 if (numContours > 0) {
1456 // glyph has a simple outline consisting of a number of contours
1457 return simple_outline(font, offset + 10, (unsigned int) numContours, outl);
1458 } else if (numContours < 0) {
1459 // glyph has a compound outline combined from mutiple other outlines
1460 return compound_outline(font, offset + 10, recDepth, outl);
1461 } else {
1462 return 0;
1463 }
1464}
1465
1466// weird thing to tell whether a given curve can be approximated closely enough by a line
1467static int
1468is_flat(Outline *outl, Curve curve)
1469{
1470 const double maxArea2 = 2.0;
1471 Point a = outl->points[curve.beg];
1472 Point b = outl->points[curve.ctrl];
1473 Point c = outl->points[curve.end];
1474 Point g = { b.x-a.x, b.y-a.y };
1475 Point h = { c.x-a.x, c.y-a.y };
1476 double area2 = fast_abs(g.x*h.y-h.x*g.y);
1477 return area2 <= maxArea2;
1478}
1479
1480static int
1481tessellate_curve(Curve curve, Outline *outl)
1482{
1483 /* i can conclude that this stack barely reaches a top height of 4 elements
1484 * even for the largest font sizes that i wanna support, and as space reqs
1485 * only grow logarithmically, 6 feels like it's more than enough */
1486#define STACK_SIZE 6
1487 Curve stack[STACK_SIZE];
1488 unsigned int top = 0;
1489 for (;;) {
1490 if (is_flat(outl, curve) || top >= STACK_SIZE) {
1491 if (outl->numLines >= outl->capLines && grow_lines(outl) < 0)
1492 return -1;
1493 outl->lines[outl->numLines++] = (Line) {
1494 curve.beg, curve.end
1495 };
1496 if (top == 0) break;
1497 curve = stack[--top];
1498 } else {
1499 uint_least16_t ctrl0 = outl->numPoints;
1500 if (outl->numPoints >= outl->capPoints && grow_points(outl) < 0)
1501 return -1;
1502 outl->points[ctrl0] = midpoint(outl->points[curve.beg], outl->points[curve.ctrl]);
1503 ++outl->numPoints;
1504
1505 uint_least16_t ctrl1 = outl->numPoints;
1506 if (outl->numPoints >= outl->capPoints && grow_points(outl) < 0)
1507 return -1;
1508 outl->points[ctrl1] = midpoint(outl->points[curve.ctrl], outl->points[curve.end]);
1509 ++outl->numPoints;
1510
1511 uint_least16_t pivot = outl->numPoints;
1512 if (outl->numPoints >= outl->capPoints && grow_points(outl) < 0)
1513 return -1;
1514 outl->points[pivot] = midpoint(outl->points[ctrl0], outl->points[ctrl1]);
1515 ++outl->numPoints;
1516
1517 stack[top++] = (Curve) {
1518 curve.beg, pivot, ctrl0
1519 };
1520 curve = (Curve) {
1521 pivot, curve.end, ctrl1
1522 };
1523 }
1524 }
1525 return 0;
1526#undef STACK_SIZE
1527}
1528
1529static int
1530tessellate_curves(Outline *outl)
1531{
1532 unsigned int i;
1533 for (i = 0; i < outl->numCurves; ++i) {
1534 if (tessellate_curve(outl->curves[i], outl) < 0)
1535 return -1;
1536 }
1537 return 0;
1538}
1539
1540// draws a line into the buffer, custom 2d raycasting algo is used
1541static void
1542draw_line(Raster buf, Point origin, Point goal)
1543{
1544 assert(buf.cells != NULL);
1545 assert(buf.width > 0 && buf.height > 0);
1546 Point delta;
1547 Point nextCrossing;
1548 Point crossingIncr;
1549 double halfDeltaX;
1550 double prevDistance = 0.0, nextDistance;
1551 double xAverage, yDifference;
1552 struct
1553 {
1554 int x, y;
1555 } pixel;
1556 struct
1557 {
1558 int x, y;
1559 } dir;
1560 int step, numSteps = 0;
1561 Cell *restrict cptr, cell;
1562
1563 delta.x = goal.x - origin.x;
1564 delta.y = goal.y - origin.y;
1565 dir.x = SIGN(delta.x);
1566 dir.y = SIGN(delta.y);
1567
1568 if (!dir.y) {
1569 return;
1570 }
1571
1572 crossingIncr.x = dir.x ? fast_abs(1.0 / delta.x) : 1.0;
1573 crossingIncr.y = fast_abs(1.0 / delta.y);
1574
1575 if (!dir.x) {
1576 pixel.x = fast_floor(origin.x);
1577 nextCrossing.x = 100.0;
1578 } else {
1579 if (dir.x > 0) {
1580 pixel.x = fast_floor(origin.x);
1581 nextCrossing.x = (origin.x - pixel.x) * crossingIncr.x;
1582 nextCrossing.x = crossingIncr.x - nextCrossing.x;
1583 numSteps += fast_ceil(goal.x) - fast_floor(origin.x) - 1;
1584 } else {
1585 pixel.x = fast_ceil(origin.x) - 1;
1586 nextCrossing.x = (origin.x - pixel.x) * crossingIncr.x;
1587 numSteps += fast_ceil(origin.x) - fast_floor(goal.x) - 1;
1588 }
1589 }
1590
1591 if (dir.y > 0) {
1592 pixel.y = fast_floor(origin.y);
1593 nextCrossing.y = (origin.y - pixel.y) * crossingIncr.y;
1594 nextCrossing.y = crossingIncr.y - nextCrossing.y;
1595 numSteps += fast_ceil(goal.y) - fast_floor(origin.y) - 1;
1596 } else {
1597 pixel.y = fast_ceil(origin.y) - 1;
1598 nextCrossing.y = (origin.y - pixel.y) * crossingIncr.y;
1599 numSteps += fast_ceil(origin.y) - fast_floor(goal.y) - 1;
1600 }
1601
1602 nextDistance = MIN(nextCrossing.x, nextCrossing.y);
1603 halfDeltaX = 0.5 * delta.x;
1604
1605 for (step = 0; step < numSteps; ++step) {
1606 xAverage = origin.x + (prevDistance + nextDistance) * halfDeltaX;
1607 yDifference = (nextDistance - prevDistance) * delta.y;
1608 cptr = &buf.cells[pixel.y * buf.width + pixel.x];
1609 cell = *cptr;
1610 cell.cover += yDifference;
1611 xAverage -= (double) pixel.x;
1612 cell.area += (1.0 - xAverage) * yDifference;
1613 *cptr = cell;
1614 prevDistance = nextDistance;
1615 int alongX = nextCrossing.x < nextCrossing.y;
1616 pixel.x += alongX ? dir.x : 0;
1617 pixel.y += alongX ? 0 : dir.y;
1618 nextCrossing.x += alongX ? crossingIncr.x : 0.0;
1619 nextCrossing.y += alongX ? 0.0 : crossingIncr.y;
1620 nextDistance = MIN(nextCrossing.x, nextCrossing.y);
1621 }
1622
1623 xAverage = origin.x + (prevDistance + 1.0) * halfDeltaX;
1624 yDifference = (1.0 - prevDistance) * delta.y;
1625 cptr = &buf.cells[pixel.y * buf.width + pixel.x];
1626 cell = *cptr;
1627 cell.cover += yDifference;
1628 xAverage -= (double) pixel.x;
1629 cell.area += (1.0 - xAverage) * yDifference;
1630 *cptr = cell;
1631}
1632
1633static void
1634draw_lines(Outline *outl, Raster buf)
1635{
1636 unsigned int i;
1637 for (i = 0; i < outl->numLines; ++i) {
1638 Line line = outl->lines[i];
1639 Point origin = outl->points[line.beg];
1640 Point goal = outl->points[line.end];
1641 draw_line(buf, origin, goal);
1642 }
1643}
1644
1645// integrate the values in the buffer to arrive at the final grayscale image
1646static void
1647post_process(Raster buf, uint8_t *image)
1648{
1649 Cell cell;
1650 double accum = 0.0, value;
1651 unsigned int i, num;
1652 num = (unsigned int) buf.width * (unsigned int) buf.height;
1653 for (i = 0; i < num; ++i) {
1654 cell = buf.cells[i];
1655 value = fast_abs(accum + cell.area);
1656 value = MIN(value, 1.0);
1657 value = value * 255.0 + 0.5;
1658 image[i] = (uint8_t) value;
1659 accum += cell.cover;
1660 }
1661}
1662
1663static int
1664render_outline(Outline *outl, double transform[6], PASTRY_Image image)
1665{
1666 assert(outl != NULL);
1667 assert(transform != NULL);
1668 assert(image.pixels != NULL);
1669 assert(image.width > 0 && image.height > 0);
1670 Cell *cells = NULL;
1671 Raster buf;
1672 unsigned int numPixels;
1673
1674 numPixels = (unsigned int) image.width * (unsigned int) image.height;
1675
1676 PASTRY_STACK_ALLOC(cells, Cell, 128 * 128, numPixels);
1677 if (!cells) {
1678 return -1;
1679 }
1680 memset(cells, 0, numPixels * sizeof *cells);
1681 buf.cells = cells;
1682 buf.width = image.width;
1683 buf.height = image.height;
1684
1685 transform_points(outl->numPoints, outl->points, transform);
1686
1687 clip_points(outl->numPoints, outl->points, image.width, image.height);
1688
1689 if (tessellate_curves(outl) < 0) {
1690 PASTRY_STACK_FREE(cells);
1691 return -1;
1692 }
1693
1694 draw_lines(outl, buf);
1695
1696 post_process(buf, image.pixels);
1697
1698 PASTRY_STACK_FREE(cells);
1699 return 0;
1700}