From 823a086e3c09d237c5489de2a05397c53697c8d7 Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Wed, 9 Aug 2023 22:43:46 +0200 Subject: [PATCH] Use a truetype font for the options menu, although a better one could probably be chosen to match the theme of doom --- src/CMakeLists.txt | 2 + src/common/fonts/schrift.cpp | 1570 +++++++++++++++++++++++++++++ src/common/fonts/schrift.h | 95 ++ src/common/fonts/ttffont.cpp | 145 +++ src/common/fonts/v_font.cpp | 8 +- wadsrc/static/newmenufont.license | 202 ++++ wadsrc/static/newmenufont.ttf | Bin 0 -> 73620 bytes 7 files changed, 2021 insertions(+), 1 deletion(-) create mode 100644 src/common/fonts/schrift.cpp create mode 100644 src/common/fonts/schrift.h create mode 100644 src/common/fonts/ttffont.cpp create mode 100644 wadsrc/static/newmenufont.license create mode 100644 wadsrc/static/newmenufont.ttf diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0bf6b7103..15ce6ea25 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -977,6 +977,8 @@ set (PCH_SOURCES common/fonts/specialfont.cpp common/fonts/font.cpp common/fonts/hexfont.cpp + common/fonts/ttffont.cpp + common/fonts/schrift.cpp common/fonts/v_font.cpp common/fonts/v_text.cpp common/textures/hw_ihwtexture.cpp diff --git a/src/common/fonts/schrift.cpp b/src/common/fonts/schrift.cpp new file mode 100644 index 000000000..84f92f221 --- /dev/null +++ b/src/common/fonts/schrift.cpp @@ -0,0 +1,1570 @@ +/* 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 +#include +#include +#include +#include +#include + +#if defined(_MSC_VER) +# define restrict __restrict +#endif + +#if defined(_WIN32) +# define WIN32_LEAN_AND_MEAN 1 +# include +#else +# define _POSIX_C_SOURCE 1 +# include +# include +# include +# include +#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)) +/* Allocate values on the stack if they are small enough, else spill to heap. */ +#define STACK_ALLOC(var, type, thresh, count) \ + type var##_stack_[thresh]; \ + var = (type*)( (count) <= (thresh) ? var##_stack_ : calloc(sizeof(type), count) ); +#define STACK_FREE(var) \ + if (var != var##_stack_) free(var); + +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 */ +static void *reallocarray(void *optr, size_t nmemb, size_t size); +static inline int fast_floor(double x); +static inline int fast_ceil (double x); +/* file loading */ +static int map_file (SFT_Font *font, const char *filename); +static void unmap_file(SFT_Font *font); +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, const char* tag, 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 = (SFT_Font*)calloc(1, sizeof *font))) { + return NULL; + } + font->memory = (const uint8_t*)mem; + font->size = (uint_fast32_t) size; + font->source = SrcUser; + if (init_font(font) < 0) { + sft_freefont(font); + return NULL; + } + return font; +} + +/* Loads a font from the file system. To do so, it has to map the entire font into memory. */ +SFT_Font * +sft_loadfile(char const *filename) +{ + SFT_Font *font; + if (!(font = (SFT_Font*)calloc(1, sizeof *font))) { + return NULL; + } + if (map_file(font, filename) < 0) { + free(font); + return NULL; + } + if (init_font(font) < 0) { + sft_freefont(font); + return NULL; + } + return font; +} + +void +sft_freefont(SFT_Font *font) +{ + if (!font) return; + /* Only unmap if we mapped it ourselves. */ + if (font->source == SrcMapping) + unmap_file(font); + 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. */ +static 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 = (const uint8_t*)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 + +static int +map_file(SFT_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; + } + /* FIXME do some basic validation on info.st_size maybe - it is signed for example, so it *could* be negative .. */ + 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(SFT_Font *font) +{ + assert(font->memory != MAP_FAILED); + munmap((void *) font->memory, font->size); +} + +#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 { + 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] = { + 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 = (Point*)malloc(outl->capPoints * sizeof *outl->points))) + return -1; + outl->numCurves = 0; + outl->capCurves = 64; + if (!(outl->curves = (Curve*)malloc(outl->capCurves * sizeof *outl->curves))) + return -1; + outl->numLines = 0; + outl->capLines = 64; + if (!(outl->lines = (Line*)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 = (Point*)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 = (Curve*)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 = (Line*)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 = (const uint8_t*)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, const char* tag, 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 = (const uint8_t*)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++] = { beg, cur, ctrl }; + } else { + if (outl->numLines >= outl->capLines && grow_lines(outl) < 0) + return -1; + outl->lines[outl->numLines++] = { 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++] = { 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++] = { beg, looseEnd, ctrl }; + } else { + if (outl->numLines >= outl->capLines && grow_lines(outl) < 0) + return -1; + outl->lines[outl->numLines++] = { 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; + 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; + } + + STACK_ALLOC(endPts, uint_fast16_t, 16, numContours); + if (endPts == NULL) + goto failure; + 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 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); + + 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; + } + + STACK_FREE(endPts); + STACK_FREE(flags); + return 0; +failure: + STACK_FREE(endPts); + STACK_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++] = { 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.beg, pivot, ctrl0 }; + 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; + + 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 (tesselate_curves(outl) < 0) { + STACK_FREE(cells); + return -1; + } + + draw_lines(outl, buf); + + post_process(buf, (uint8_t*)image.pixels); + + STACK_FREE(cells); + return 0; +} + diff --git a/src/common/fonts/schrift.h b/src/common/fonts/schrift.h new file mode 100644 index 000000000..93d20516a --- /dev/null +++ b/src/common/fonts/schrift.h @@ -0,0 +1,95 @@ +/* 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. */ + +#ifndef SCHRIFT_H +#define SCHRIFT_H 1 + +#include /* size_t */ +#include /* uint_fast32_t, uint_least32_t */ + +#ifdef __cplusplus +extern "C" { +#endif + +#define SFT_DOWNWARD_Y 0x01 + +typedef struct SFT SFT; +typedef struct SFT_Font SFT_Font; +typedef uint_least32_t SFT_UChar; /* Guaranteed to be compatible with char32_t. */ +typedef uint_fast32_t SFT_Glyph; +typedef struct SFT_LMetrics SFT_LMetrics; +typedef struct SFT_GMetrics SFT_GMetrics; +typedef struct SFT_Kerning SFT_Kerning; +typedef struct SFT_Image SFT_Image; + +struct SFT +{ + SFT_Font *font; + double xScale; + double yScale; + double xOffset; + double yOffset; + int flags; +}; + +struct SFT_LMetrics +{ + double ascender; + double descender; + double lineGap; +}; + +struct SFT_GMetrics +{ + double advanceWidth; + double leftSideBearing; + int yOffset; + int minWidth; + int minHeight; +}; + +struct SFT_Kerning +{ + double xShift; + double yShift; +}; + +struct SFT_Image +{ + void *pixels; + int width; + int height; +}; + +const char *sft_version(void); + +SFT_Font *sft_loadmem (const void *mem, size_t size); +SFT_Font *sft_loadfile(const char *filename); +void sft_freefont(SFT_Font *font); + +int sft_lmetrics(const SFT *sft, SFT_LMetrics *metrics); +int sft_lookup (const SFT *sft, SFT_UChar codepoint, SFT_Glyph *glyph); +int sft_gmetrics(const SFT *sft, SFT_Glyph glyph, SFT_GMetrics *metrics); +int sft_kerning (const SFT *sft, SFT_Glyph leftGlyph, SFT_Glyph rightGlyph, + SFT_Kerning *kerning); +int sft_render (const SFT *sft, SFT_Glyph glyph, SFT_Image image); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/src/common/fonts/ttffont.cpp b/src/common/fonts/ttffont.cpp new file mode 100644 index 000000000..f7e0f708c --- /dev/null +++ b/src/common/fonts/ttffont.cpp @@ -0,0 +1,145 @@ + +#include "engineerrors.h" +#include "textures.h" +#include "image.h" +#include "v_font.h" +#include "filesystem.h" +#include "utf8.h" +#include "sc_man.h" +#include "texturemanager.h" +#include "fontinternals.h" +#include "schrift.h" + +class FTTFGlyph : public FImageSource +{ +public: + FTTFGlyph(int width, int height, int leftoffset, int topoffset, TArray pixels, PalEntry* palette) + { + Width = width; + Height = height; + LeftOffset = leftoffset; + TopOffset = topoffset; + Pixels = std::move(pixels); + Palette = palette; + } + + PalettedPixels CreatePalettedPixels(int conversion) override + { + PalettedPixels OutPixels(Pixels.Size()); + memcpy(OutPixels.Data(), Pixels.Data(), Pixels.Size()); + return OutPixels; + } + + int CopyPixels(FBitmap* bmp, int conversion) + { + if (conversion == luminance) + conversion = normal; // luminance images have no use as an RGB source. + + auto ppix = CreatePalettedPixels(conversion); + bmp->CopyPixelData(0, 0, ppix.Data(), Width, Height, 1, Width, 0, Palette); + return 0; + } + +private: + TArray Pixels; + PalEntry* Palette = nullptr; +}; + +class FTTFFont : public FFont +{ +public: + FTTFFont(const char* fontname, int height, int lump) : FFont(lump) + { + auto lumpdata = fileSystem.GetFileData(lump); + + SFT sft = {}; + sft.xScale = height; + sft.yScale = height; + sft.flags = SFT_DOWNWARD_Y; + sft.font = sft_loadmem(lumpdata.Data(), lumpdata.Size()); + if (!sft.font) + I_FatalError("Could not load truetype font file"); + + SFT_LMetrics lmtx; + if (sft_lmetrics(&sft, &lmtx) < 0) + I_FatalError("Could not get truetype font metrics"); + + FontName = fontname; + FontHeight = (int)std::floor((lmtx.ascender + lmtx.descender + lmtx.lineGap)) + 2; + + { + SFT_Glyph gid; + SFT_GMetrics mtx; + if (sft_lookup(&sft, 32, &gid) < 0) + I_FatalError("Could not find the glyph id for the space character"); + + if (sft_gmetrics(&sft, gid, &mtx) < 0) + I_FatalError("Could not get the glyph metrics for the space character"); + + SpaceWidth = (int)std::round(mtx.advanceWidth); + } + + GlobalKerning = 0; + + FirstChar = 10; + LastChar = 255; + + for (int i = 0; i < 256; i++) + { + palette[i] = PalEntry(i, 255, 255, 255); + } + + Chars.Resize(LastChar - FirstChar + 1); + for (int i = FirstChar; i <= LastChar; i++) + { + SFT_Glyph gid; + if (sft_lookup(&sft, i, &gid) < 0) + continue; + + SFT_GMetrics mtx; + if (sft_gmetrics(&sft, gid, &mtx) < 0) + continue; + + if (mtx.minWidth <= 0 || mtx.minHeight <= 0) + continue; + + SFT_Image img = {}; + img.width = (mtx.minWidth + 3) & ~3; + img.height = mtx.minHeight; + + TArray pixels(img.width * img.height, true); + img.pixels = pixels.Data(); + if (sft_render(&sft, gid, img) < 0) + continue; + + Chars[i - FirstChar].OriginalPic = MakeGameTexture(new FImageTexture(CreateGlyph(lmtx, mtx, img.width, img.height, std::move(pixels))), nullptr, ETextureType::FontChar); + Chars[i - FirstChar].XMove = (int)std::floor(mtx.advanceWidth) + 1; + TexMan.AddGameTexture(Chars[i - FirstChar].OriginalPic); + } + } + + void LoadTranslations() override + { + int minlum = 0; + int maxlum = 255; + + Translations.Resize(NumTextColors); + for (int i = 0; i < NumTextColors; i++) + { + if (i == CR_UNTRANSLATED) Translations[i] = 0; + else Translations[i] = LuminosityTranslation(i * 2, minlum, maxlum); + } + } + + virtual FTTFGlyph* CreateGlyph(const SFT_LMetrics& lmtx, const SFT_GMetrics& mtx, int width, int height, TArray pixels) + { + return new FTTFGlyph(width, height, (int)std::round(-mtx.leftSideBearing), -mtx.yOffset - (int)std::round(lmtx.ascender + lmtx.descender + lmtx.lineGap * 0.5), std::move(pixels), palette); + } + + PalEntry palette[256]; +}; + +FFont* CreateTTFFont(const char* fontname, int height, int lump) +{ + return new FTTFFont(fontname, height, lump); +} diff --git a/src/common/fonts/v_font.cpp b/src/common/fonts/v_font.cpp index cb206a4ec..fed591756 100644 --- a/src/common/fonts/v_font.cpp +++ b/src/common/fonts/v_font.cpp @@ -870,11 +870,17 @@ void V_InitFonts() FFont *CreateHexLumpFont(const char *fontname, int lump); FFont *CreateHexLumpFont2(const char *fontname, int lump); + FFont* CreateTTFFont(const char* fontname, int height, int lump); auto lump = fileSystem.CheckNumForFullName("newconsolefont.hex", 0); // This is always loaded from gzdoom.pk3 to prevent overriding it with incomplete replacements. if (lump == -1) I_FatalError("newconsolefont.hex not found"); // This font is needed - do not start up without it. NewConsoleFont = CreateHexLumpFont("NewConsoleFont", lump); - NewSmallFont = CreateHexLumpFont2("NewSmallFont", lump); + //NewSmallFont = CreateHexLumpFont2("NewSmallFont", lump); + + lump = fileSystem.CheckNumForFullName("newmenufont.ttf", 0); // This is always loaded from gzdoom.pk3 to prevent overriding it with incomplete replacements. + if (lump == -1) I_FatalError("newmenufont.ttf not found"); // This font is needed - do not start up without it. + NewSmallFont = CreateTTFFont("NewSmallFont", 18, lump); + CurrentConsoleFont = NewConsoleFont; ConFont = V_GetFont("ConsoleFont", "CONFONT"); V_GetFont("IndexFont", "INDEXFON"); // detect potential replacements for this one. diff --git a/wadsrc/static/newmenufont.license b/wadsrc/static/newmenufont.license new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/wadsrc/static/newmenufont.license @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/wadsrc/static/newmenufont.ttf b/wadsrc/static/newmenufont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..6541e9d87e656f7b341c853abb4ced9a6397ef39 GIT binary patch literal 73620 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 z≠O<6;yLMm8P} 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 TX;D6Baau`6WkE(N=m