NotificationModule/src/gui/schrift.c
2023-01-26 14:29:07 +01:00

1463 lines
46 KiB
C

/* This file is part of libschrift.
*
* © 2019-2022 Thomas Oltmann and contributors
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
#include <assert.h>
#include <errno.h>
#include <math.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
// #define _POSIX_C_SOURCE 1
#include <fcntl.h>
//# include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#endif
#include "schrift.h"
#define SCHRIFT_VERSION "0.10.2"
#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
/* macros */
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define SIGN(x) (((x) > 0) - ((x) < 0))
enum { SrcMapping,
SrcUser };
/* structs */
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 SFT_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;
};
/* function declarations */
/* generic utility functions */
void *reallocarray(void *optr, size_t nmemb, size_t size);
static inline int fast_floor(double x);
static inline int fast_ceil(double x);
static int init_font(SFT_Font *font);
/* simple mathematical operations */
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' data structure management */
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);
/* TTF parsing utilities */
static inline int is_safe_offset(SFT_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 int cmpu16(const void *a, const void *b);
static int cmpu32(const void *a, const void *b);
static inline uint_least8_t getu8(SFT_Font *font, uint_fast32_t offset);
static inline int_least8_t geti8(SFT_Font *font, uint_fast32_t offset);
static inline uint_least16_t getu16(SFT_Font *font, uint_fast32_t offset);
static inline int_least16_t geti16(SFT_Font *font, uint_fast32_t offset);
static inline uint_least32_t getu32(SFT_Font *font, uint_fast32_t offset);
static int gettable(SFT_Font *font, char tag[4], uint_fast32_t *offset);
/* codepoint to glyph id translation */
static int cmap_fmt4(SFT_Font *font, uint_fast32_t table, SFT_UChar charCode, uint_fast32_t *glyph);
static int cmap_fmt6(SFT_Font *font, uint_fast32_t table, SFT_UChar charCode, uint_fast32_t *glyph);
static int glyph_id(SFT_Font *font, SFT_UChar charCode, uint_fast32_t *glyph);
/* glyph metrics lookup */
static int hor_metrics(SFT_Font *font, uint_fast32_t glyph, int *advanceWidth, int *leftSideBearing);
static int glyph_bbox(const SFT *sft, uint_fast32_t outline, int box[4]);
/* decoding outlines */
static int outline_offset(SFT_Font *font, uint_fast32_t glyph, uint_fast32_t *offset);
static int simple_flags(SFT_Font *font, uint_fast32_t *offset, uint_fast16_t numPts, uint8_t *flags);
static int simple_points(SFT_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(SFT_Font *font, uint_fast32_t offset, unsigned int numContours, Outline *outl);
static int compound_outline(SFT_Font *font, uint_fast32_t offset, int recDepth, Outline *outl);
static int decode_outline(SFT_Font *font, uint_fast32_t offset, int recDepth, Outline *outl);
/* tesselation */
static int is_flat(Outline *outl, Curve curve);
static int tesselate_curve(Curve curve, Outline *outl);
static int tesselate_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], SFT_Image image);
/* function implementations */
const char *
sft_version(void) {
return SCHRIFT_VERSION;
}
/* Loads a font from a user-supplied memory range. */
SFT_Font *
sft_loadmem(const void *mem, size_t size) {
SFT_Font *font;
if (size > UINT32_MAX) {
return NULL;
}
if (!(font = malloc(sizeof *font))) {
return NULL;
}
memset(font, 0, sizeof *font);
font->memory = mem;
font->size = (uint_fast32_t) size;
font->source = SrcUser;
if (init_font(font) < 0) {
sft_freefont(font);
return NULL;
}
return font;
}
void sft_freefont(SFT_Font *font) {
if (!font) return;
free(font);
}
int sft_lmetrics(const SFT *sft, SFT_LMetrics *metrics) {
double factor;
uint_fast32_t hhea;
memset(metrics, 0, sizeof *metrics);
if (gettable(sft->font, "hhea", &hhea) < 0)
return -1;
if (!is_safe_offset(sft->font, hhea, 36))
return -1;
factor = sft->yScale / sft->font->unitsPerEm;
metrics->ascender = geti16(sft->font, hhea + 4) * factor;
metrics->descender = geti16(sft->font, hhea + 6) * factor;
metrics->lineGap = geti16(sft->font, hhea + 8) * factor;
return 0;
}
int sft_lookup(const SFT *sft, SFT_UChar codepoint, SFT_Glyph *glyph) {
return glyph_id(sft->font, codepoint, glyph);
}
int sft_gmetrics(const SFT *sft, SFT_Glyph glyph, SFT_GMetrics *metrics) {
int adv, lsb;
double xScale = sft->xScale / sft->font->unitsPerEm;
uint_fast32_t outline;
int bbox[4];
memset(metrics, 0, sizeof *metrics);
if (hor_metrics(sft->font, glyph, &adv, &lsb) < 0)
return -1;
metrics->advanceWidth = adv * xScale;
metrics->leftSideBearing = lsb * xScale + sft->xOffset;
if (outline_offset(sft->font, glyph, &outline) < 0)
return -1;
if (!outline)
return 0;
if (glyph_bbox(sft, outline, bbox) < 0)
return -1;
metrics->minWidth = bbox[2] - bbox[0] + 1;
metrics->minHeight = bbox[3] - bbox[1] + 1;
metrics->yOffset = sft->flags & SFT_DOWNWARD_Y ? -bbox[3] : bbox[1];
return 0;
}
int sft_kerning(const SFT *sft, SFT_Glyph leftGlyph, SFT_Glyph rightGlyph,
SFT_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(sft->font, "kern", &offset) < 0)
return 0;
/* Read kern table header. */
if (!is_safe_offset(sft->font, offset, 4))
return -1;
if (getu16(sft->font, offset) != 0)
return 0;
numTables = getu16(sft->font, offset + 2);
offset += 4;
while (numTables > 0) {
/* Read subtable header. */
if (!is_safe_offset(sft->font, offset, 6))
return -1;
length = getu16(sft->font, offset + 2);
format = getu8(sft->font, offset + 4);
flags = getu8(sft->font, offset + 5);
offset += 6;
if (format == 0 && (flags & HORIZONTAL_KERNING) && !(flags & MINIMUM_KERNING)) {
/* Read format 0 header. */
if (!is_safe_offset(sft->font, offset, 8))
return -1;
numPairs = getu16(sft->font, offset);
offset += 8;
/* Look up character code pair via binary search. */
key[0] = (leftGlyph >> 8) & 0xFF;
key[1] = leftGlyph & 0xFF;
key[2] = (rightGlyph >> 8) & 0xFF;
key[3] = rightGlyph & 0xFF;
if ((match = bsearch(key, sft->font->memory + offset,
numPairs, 6, cmpu32)) != NULL) {
value = geti16(sft->font, (uint_fast32_t) ((uint8_t *) match - sft->font->memory + 4));
if (flags & CROSS_STREAM_KERNING) {
kerning->yShift += value;
} else {
kerning->xShift += value;
}
}
}
offset += length;
--numTables;
}
kerning->xShift = kerning->xShift / sft->font->unitsPerEm * sft->xScale;
kerning->yShift = kerning->yShift / sft->font->unitsPerEm * sft->yScale;
return 0;
}
int sft_render(const SFT *sft, SFT_Glyph glyph, SFT_Image image) {
uint_fast32_t outline;
double transform[6];
int bbox[4];
Outline outl;
if (outline_offset(sft->font, glyph, &outline) < 0)
return -1;
if (!outline)
return 0;
if (glyph_bbox(sft, outline, bbox) < 0)
return -1;
/* Set up the transformation matrix such that
* the transformed bounding boxes min corner lines
* up with the (0, 0) point. */
transform[0] = sft->xScale / sft->font->unitsPerEm;
transform[1] = 0.0;
transform[2] = 0.0;
transform[4] = sft->xOffset - bbox[0];
if (sft->flags & SFT_DOWNWARD_Y) {
transform[3] = -sft->yScale / sft->font->unitsPerEm;
transform[5] = bbox[3] - sft->yOffset;
} else {
transform[3] = +sft->yScale / sft->font->unitsPerEm;
transform[5] = sft->yOffset - bbox[1];
}
memset(&outl, 0, sizeof outl);
if (init_outline(&outl) < 0)
goto failure;
if (decode_outline(sft->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;
}
/* This is 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))
/* OpenBSD's reallocarray() standard libary function.
* A wrapper for realloc() that takes two size args like calloc().
* Useful because it eliminates common integer overflow bugs. */
void *
reallocarray(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 realloc(optr, size * nmemb);
}
/* TODO maybe we should use long here instead of int. */
static inline int
fast_floor(double x) {
int i = (int) x;
return i - (i > x);
}
static inline int
fast_ceil(double x) {
int i = (int) x;
return i + (i < x);
}
#if defined(_WIN32)
static int
map_file(SFT_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(SFT_Font *font) {
if (font->memory) {
UnmapViewOfFile(font->memory);
font->memory = NULL;
}
if (font->mapping) {
CloseHandle(font->mapping);
font->mapping = NULL;
}
}
#else
#endif
static int
init_font(SFT_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)};
}
/* Applies an affine linear transformation matrix to a set of points. */
static void
transform_points(unsigned int numPts, Point *points, double trf[6]) {
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) {
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 = nextafter(width, 0.0);
}
if (pt.y < 0.0) {
points[i].y = 0.0;
}
if (pt.y >= height) {
points[i].y = nextafter(height, 0.0);
}
}
}
static int
init_outline(Outline *outl) {
/* TODO Smaller initial allocations */
outl->numPoints = 0;
outl->capPoints = 64;
if (!(outl->points = malloc(outl->capPoints * sizeof *outl->points)))
return -1;
outl->numCurves = 0;
outl->capCurves = 64;
if (!(outl->curves = malloc(outl->capCurves * sizeof *outl->curves)))
return -1;
outl->numLines = 0;
outl->capLines = 64;
if (!(outl->lines = malloc(outl->capLines * sizeof *outl->lines)))
return -1;
return 0;
}
static void
free_outline(Outline *outl) {
free(outl->points);
free(outl->curves);
free(outl->lines);
}
static int
grow_points(Outline *outl) {
void *mem;
uint_fast16_t cap;
assert(outl->capPoints);
/* Since we use uint_fast16_t for capacities, we have to be extra careful not to trigger integer overflow. */
if (outl->capPoints > UINT16_MAX / 2)
return -1;
cap = (uint_fast16_t) (2U * outl->capPoints);
if (!(mem = reallocarray(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 = reallocarray(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 = reallocarray(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(SFT_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 the 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 (uint8_t *) bytes + low * size;
}
/* Used as a comparison function for [bc]search(). */
static int
cmpu16(const void *a, const void *b) {
return memcmp(a, b, 2);
}
/* Used as a 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(SFT_Font *font, uint_fast32_t offset) {
assert(offset + 1 <= font->size);
return *(font->memory + offset);
}
static inline int_least8_t
geti8(SFT_Font *font, uint_fast32_t offset) {
return (int_least8_t) getu8(font, offset);
}
static inline uint_least16_t
getu16(SFT_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(SFT_Font *font, uint_fast32_t offset) {
return (int_least16_t) getu16(font, offset);
}
static inline uint32_t
getu32(SFT_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(SFT_Font *font, char tag[4], uint_fast32_t *offset) {
void *match;
unsigned int numTables;
/* No need to bounds-check access to the first 12 bytes - this gets already checked by init_font(). */
numTables = getu16(font, 4);
if (!is_safe_offset(font, 12, (uint_fast32_t) numTables * 16))
return -1;
if (!(match = bsearch(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(SFT_Font *font, uint_fast32_t table, SFT_UChar charCode, SFT_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- and 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- and overflow. */
*glyph = id ? (id + idDelta) & 0xFFFF : 0;
return 0;
}
static int
cmap_fmt6(SFT_Font *font, uint_fast32_t table, SFT_UChar charCode, SFT_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(SFT_Font *font, uint_fast32_t table, SFT_UChar charCode, SFT_Glyph *glyph, int which) {
uint32_t len, numEntries;
uint_fast32_t i;
*glyph = 0;
/* check that the entire header is present */
if (!is_safe_offset(font, table, 16))
return -1;
len = getu32(font, table + 4);
/* A minimal 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(SFT_Font *font, SFT_UChar charCode, SFT_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'/non-BMP 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(SFT_Font *font, SFT_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 SFT *sft, uint_fast32_t outline, int box[4]) {
double xScale, yScale;
/* Read the bounding box from the font file verbatim. */
if (!is_safe_offset(sft->font, outline, 10))
return -1;
box[0] = geti16(sft->font, outline + 2);
box[1] = geti16(sft->font, outline + 4);
box[2] = geti16(sft->font, outline + 6);
box[3] = geti16(sft->font, outline + 8);
if (box[2] <= box[0] || box[3] <= box[1])
return -1;
/* Transform the bounding box into SFT coordinate space. */
xScale = sft->xScale / sft->font->unitsPerEm;
yScale = sft->yScale / sft->font->unitsPerEm;
box[0] = (int) floor(box[0] * xScale + sft->xOffset);
box[1] = (int) floor(box[1] * yScale + sft->yOffset);
box[2] = (int) ceil(box[2] * xScale + sft->xOffset);
box[3] = (int) ceil(box[3] * yScale + sft->yOffset);
return 0;
}
/* Returns the offset into the font that the glyph's outline is stored at. */
static int
outline_offset(SFT_Font *font, SFT_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(SFT_Font *font, uint_fast32_t *offset, uint_fast16_t numPts, uint8_t *flags) {
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(SFT_Font *font, uint_fast32_t offset, uint_fast16_t numPts, uint8_t *flags, Point *points) {
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) {
uint_fast16_t i;
uint_least16_t looseEnd, beg, ctrl, center, cur;
unsigned int gotCtrl;
/* Skip contours with less than two points, since the following algorithm can't handle them and
* they should appear invisible either way (because they don't have any area). */
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);
/* NOTE clang-analyzer will often flag this and another piece of code because it thinks that flags and
* outl->points + basePoint don't always get properly initialized -- even when you explicitly loop over both
* and set every element to zero (but not when you use memset). This is a known clang-analyzer bug:
* http://clang-developers.42468.n3.nabble.com/StaticAnalyzer-False-positive-with-loop-handling-td4053875.html */
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(SFT_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;
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;
}
endPts = calloc(sizeof(uint_fast16_t), numContours);
if (endPts == NULL) {
goto failure;
}
flags = calloc(sizeof(uint8_t), 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 most likely only occur in malicious input.
* Therefore, we bail, should we ever encounter such input. */
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);
uint_fast16_t 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;
}
free(endPts);
free(flags);
return 0;
failure:
free(endPts);
free(flags);
return -1;
}
static int
compound_outline(SFT_Font *font, uint_fast32_t offset, int recDepth, Outline *outl) {
double local[6];
uint_fast32_t outline;
unsigned int flags, glyph, basePoint;
/* Guard against infinite recursion (compound glyphs that have themselves as component). */
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;
/* We don't implement point matching, and neither does stb_truetype for that matter. */
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's spec more or less tells you to scale the matrix by its own L1 norm.
* But stb_truetype scales by the L2 norm. And FreeType2 doesn't scale at all.
* Furthermore, Microsoft's spec doesn't even mention anything like this.
* It's almost as if nobody ever uses this feature anyway. */
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(SFT_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;
}
}
/* A heuristic 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 = fabs(g.x * h.y - h.x * g.y);
return area2 <= maxArea2;
}
static int
tesselate_curve(Curve curve, Outline *outl) {
/* From my tests I can conclude that this stack barely reaches a top height
* of 4 elements even for the largest font sizes I'm willing to support. And
* as space requirements should only grow logarithmically, I think 10 is
* more than enough. */
#define STACK_SIZE 10
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
tesselate_curves(Outline *outl) {
unsigned int i;
for (i = 0; i < outl->numCurves; ++i) {
if (tesselate_curve(outl->curves[i], outl) < 0)
return -1;
}
return 0;
}
/* Draws a line into the buffer. Uses a custom 2D raycasting algorithm to do so. */
static void
draw_line(Raster buf, Point origin, Point goal) {
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 ? fabs(1.0 / delta.x) : 1.0;
crossingIncr.y = fabs(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 = fabs(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], SFT_Image image) {
Cell *cells = NULL;
Raster buf;
unsigned int numPixels;
numPixels = (unsigned int) image.width * (unsigned int) image.height;
cells = calloc(sizeof(Cell), 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 (tesselate_curves(outl) < 0) {
free(cells);
return -1;
}
draw_lines(outl, buf);
post_process(buf, image.pixels);
free(cells);
return 0;
}