diff --git a/msdf.h b/msdf.h new file mode 100644 index 00000000..fdb36562 --- /dev/null +++ b/msdf.h @@ -0,0 +1,1395 @@ +/* msdf + Handles multi-channel signed distance field bitmap + generation from given ttf (stb_truetype.h) font. + https://github.com/exezin/msdf-c + Depends on stb_truetype.h to load the ttf file. + This is in an unstable state, ymmv. + Based on the C++ implementation by Viktor Chlumský. + https://github.com/Chlumsky/msdfgen +*/ + +#ifndef MSDF_H +#define MSDF_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + int glyphIdx; + int left_bearing; + int advance; + int ix0, ix1; + int iy0, iy1; +} ex_metrics_t; + +typedef struct msdf_Result { + int glyphIdx; + int left_bearing; + int advance; + float* rgb; + int width; + int height; + int yOffset; +} msdf_Result; + +typedef struct msdf_AllocCtx { + void* (*alloc)(size_t size, void* ctx); + // free is optional and will not be called if it is null (useful for area allocators that free everything at once) + void (*free)(void* ptr, void* ctx); + void* ctx; +} msdf_AllocCtx; + +/* + Generates a bitmap from the specified glyph index of a stbtt font + + Returned result is 1 for success or 0 in case of an error + */ +int msdf_genGlyph(msdf_Result* result, stbtt_fontinfo *font, int stbttGlyphIndex, uint32_t borderWidth, float scale, float range, msdf_AllocCtx* alloc); + +#ifdef __cplusplus +} +#endif + +#ifdef MSDF_IMPLEMENTATION +// pixel at (x, y) in bitmap (arr) +#define msdf_pixelAt(x, y, w, arr) ((msdf_Vec3){arr[(3 * (((y)*w) + x))], arr[(3 * (((y)*w) + x)) + 1], arr[(3 * (((y)*w) + x)) + 2]}) + +#define msdf_max(x, y) (((x) > (y)) ? (x) : (y)) +#define msdf_min(x, y) (((x) < (y)) ? (x) : (y)) + +#define MSDF_INF -1e24 +#define MSDF_EDGE_THRESHOLD 0.02 + +#ifndef MSDF_PI +#define MSDF_PI 3.14159265358979323846 +#endif + +typedef float msdf_Vec2[2]; +typedef float msdf_Vec3[3]; + +typedef struct +{ + double dist; + double d; +} msdf_signedDistance; + +// the possible types: +// STBTT_vmove = start of a contour +// STBTT_vline = linear segment +// STBTT_vcurve = quadratic segment +// STBTT_vcubic = cubic segment +typedef struct +{ + int color; + msdf_Vec2 p[4]; + int type; +} msdf_EdgeSegment; + +// defines what color channel an edge belongs to +typedef enum +{ + msdf_edgeColor_black = 0, + msdf_edgeColor_red = 1, + msdf_edgeColor_green = 2, + msdf_edgeColor_yellow = 3, + msdf_edgeColor_blue = 4, + msdf_edgeColor_magenta = 5, + msdf_edgeColor_cyan = 6, + msdf_edgeColor_white = 7 +} msdf_edgeColor; + +static double msdf_median(double a, double b, double c) +{ + return msdf_max(msdf_min(a, b), msdf_min(msdf_max(a, b), c)); +} + +static int msdf_nonZeroSign(double n) +{ + return 2 * (n > 0) - 1; +} + +static double msdf_cross(msdf_Vec2 a, msdf_Vec2 b) +{ + return a[0] * b[1] - a[1] * b[0]; +} + +static void msdf_v2Scale(msdf_Vec2 r, msdf_Vec2 const v, float const s) +{ + int i; + for (i = 0; i < 2; ++i) + r[i] = v[i] * s; +} + +static float msdf_v2MulInner(msdf_Vec2 const a, msdf_Vec2 const b) +{ + float p = 0.; + int i; + for (i = 0; i < 2; ++i) + p += b[i] * a[i]; + return p; +} + +static float msdf_v2Leng(msdf_Vec2 const v) +{ + return sqrtf(msdf_v2MulInner(v, v)); +} + +static void msdf_v2Norm(msdf_Vec2 r, msdf_Vec2 const v) +{ + float k = 1.0 / msdf_v2Leng(v); + msdf_v2Scale(r, v, k); +} + +static void msdf_v2Sub(msdf_Vec2 r, msdf_Vec2 const a, msdf_Vec2 const b) +{ + int i; + for (i = 0; i < 2; ++i) + r[i] = a[i] - b[i]; +} + +int msdf_solveQuadratic(double x[2], double a, double b, double c) +{ + if (fabs(a) < 1e-14) + { + if (fabs(b) < 1e-14) + { + if (c == 0) + return -1; + return 0; + } + x[0] = -c / b; + return 1; + } + + double dscr = b * b - 4 * a * c; + if (dscr > 0) + { + dscr = sqrt(dscr); + x[0] = (-b + dscr) / (2 * a); + x[1] = (-b - dscr) / (2 * a); + return 2; + } + else if (dscr == 0) + { + x[0] = -b / (2 * a); + return 1; + } + else + { + return 0; + } +} + +int msdf_solveCubicNormed(double *x, double a, double b, double c) +{ + double a2 = a * a; + double q = (a2 - 3 * b) / 9; + double r = (a * (2 * a2 - 9 * b) + 27 * c) / 54; + double r2 = r * r; + double q3 = q * q * q; + double A, B; + if (r2 < q3) + { + double t = r / sqrt(q3); + if (t < -1) + t = -1; + if (t > 1) + t = 1; + t = acos(t); + a /= 3; + q = -2 * sqrt(q); + x[0] = q * cos(t / 3) - a; + x[1] = q * cos((t + 2 * MSDF_PI) / 3) - a; + x[2] = q * cos((t - 2 * MSDF_PI) / 3) - a; + return 3; + } + else + { + A = -pow(fabs(r) + sqrt(r2 - q3), 1 / 3.); + if (r < 0) + A = -A; + B = A == 0 ? 0 : q / A; + a /= 3; + x[0] = (A + B) - a; + x[1] = -0.5 * (A + B) - a; + x[2] = 0.5 * sqrt(3.) * (A - B); + if (fabs(x[2]) < 1e-14) + return 2; + return 1; + } +} + +int msdf_solveCubic(double x[3], double a, double b, double c, double d) +{ + if (fabs(a) < 1e-14) + return msdf_solveQuadratic(x, b, c, d); + + return msdf_solveCubicNormed(x, b / a, c / a, d / a); +} + +void msdf_getOrtho(msdf_Vec2 r, msdf_Vec2 const v, int polarity, int allow_zero) +{ + double len = msdf_v2Leng(v); + + if (len == 0) + { + if (polarity) + { + r[0] = 0; + r[1] = !allow_zero; + } + else + { + r[0] = 0; + r[1] = -!allow_zero; + } + return; + } + + if (polarity) + { + r[0] = -v[1] / len; + r[1] = v[0] / len; + } + else + { + r[0] = v[1] / len; + r[1] = -v[0] / len; + } +} + +int msdf_pixelClash(const msdf_Vec3 a, const msdf_Vec3 b, double threshold) +{ + int aIn = (a[0] > .5f) + (a[1] > .5f) + (a[2] > .5f) >= 2; + int bIn = (b[0] > .5f) + (b[1] > .5f) + (b[2] > .5f) >= 2; + if (aIn != bIn) + return 0; + if ((a[0] > .5f && a[1] > .5f && a[2] > .5f) || (a[0] < .5f && a[1] < .5f && a[2] < .5f) || (b[0] > .5f && b[1] > .5f && b[2] > .5f) || (b[0] < .5f && b[1] < .5f && b[2] < .5f)) + return 0; + float aa, ab, ba, bb, ac, bc; + if ((a[0] > .5f) != (b[0] > .5f) && (a[0] < .5f) != (b[0] < .5f)) + { + aa = a[0], ba = b[0]; + if ((a[1] > .5f) != (b[1] > .5f) && (a[1] < .5f) != (b[1] < .5f)) + { + ab = a[1], bb = b[1]; + ac = a[2], bc = b[2]; + } + else if ((a[2] > .5f) != (b[2] > .5f) && (a[2] < .5f) != (b[2] < .5f)) + { + ab = a[2], bb = b[2]; + ac = a[1], bc = b[1]; + } + else + { + return 0; + } + } + else if ((a[1] > .5f) != (b[1] > .5f) && (a[1] < .5f) != (b[1] < .5f) && (a[2] > .5f) != (b[2] > .5f) && (a[2] < .5f) != (b[2] < .5f)) + { + aa = a[1], ba = b[1]; + ab = a[2], bb = b[2]; + ac = a[0], bc = b[0]; + } + else + { + return 0; + } + return (fabsf(aa - ba) >= threshold) && (fabsf(ab - bb) >= threshold) && fabsf(ac - .5f) >= fabsf(bc - .5f); +} + +void msdf_mix(msdf_Vec2 r, msdf_Vec2 a, msdf_Vec2 b, double weight) +{ + r[0] = (1 - weight) * a[0] + weight * b[0]; + r[1] = (1 - weight) * a[1] + weight * b[1]; +} + +void msdf_linearDirection(msdf_Vec2 r, msdf_EdgeSegment *e, double param) +{ + r[0] = e->p[1][0] - e->p[0][0]; + r[1] = e->p[1][1] - e->p[0][1]; +} + +void msdf_quadraticDirection(msdf_Vec2 r, msdf_EdgeSegment *e, double param) +{ + msdf_Vec2 a, b; + msdf_v2Sub(a, e->p[1], e->p[0]); + msdf_v2Sub(b, e->p[2], e->p[1]); + msdf_mix(r, a, b, param); +} + +void msdf_cubicDirection(msdf_Vec2 r, msdf_EdgeSegment *e, double param) +{ + msdf_Vec2 a, b, c, d, t; + msdf_v2Sub(a, e->p[1], e->p[0]); + msdf_v2Sub(b, e->p[2], e->p[1]); + msdf_mix(c, a, b, param); + msdf_v2Sub(a, e->p[3], e->p[2]); + msdf_mix(d, b, a, param); + msdf_mix(t, c, d, param); + + if (!t[0] && !t[1]) + { + if (param == 0) + { + r[0] = e->p[2][0] - e->p[0][0]; + r[1] = e->p[2][1] - e->p[0][1]; + return; + } + if (param == 1) + { + r[0] = e->p[3][0] - e->p[1][0]; + r[1] = e->p[3][1] - e->p[1][1]; + return; + } + } + + r[0] = t[0]; + r[1] = t[1]; +} + +void msdf_direction(msdf_Vec2 r, msdf_EdgeSegment *e, double param) +{ + switch (e->type) + { + case STBTT_vline: + { + msdf_linearDirection(r, e, param); + break; + } + case STBTT_vcurve: + { + msdf_quadraticDirection(r, e, param); + break; + } + case STBTT_vcubic: + { + msdf_cubicDirection(r, e, param); + break; + } + } +} + +void msdf_linearPoint(msdf_Vec2 r, msdf_EdgeSegment *e, double param) +{ + msdf_mix(r, e->p[0], e->p[1], param); +} + +void msdf_quadraticPoint(msdf_Vec2 r, msdf_EdgeSegment *e, double param) +{ + msdf_Vec2 a, b; + msdf_mix(a, e->p[0], e->p[1], param); + msdf_mix(b, e->p[1], e->p[2], param); + msdf_mix(r, a, b, param); +} + +void msdf_cubicPoint(msdf_Vec2 r, msdf_EdgeSegment *e, double param) +{ + msdf_Vec2 p12, a, b, c, d; + msdf_mix(p12, e->p[1], e->p[2], param); + + msdf_mix(a, e->p[0], e->p[1], param); + msdf_mix(b, a, p12, param); + + msdf_mix(c, e->p[2], e->p[3], param); + msdf_mix(d, p12, c, param); + + msdf_mix(r, b, d, param); +} + +void msdf_point(msdf_Vec2 r, msdf_EdgeSegment *e, double param) +{ + switch (e->type) + { + case STBTT_vline: + { + msdf_linearPoint(r, e, param); + break; + } + case STBTT_vcurve: + { + msdf_quadraticPoint(r, e, param); + break; + } + case STBTT_vcubic: + { + msdf_cubicPoint(r, e, param); + break; + } + } +} + +// linear edge signed distance +msdf_signedDistance msdf_linearDist(msdf_EdgeSegment *e, msdf_Vec2 origin, double *param) +{ + msdf_Vec2 aq, ab, eq; + msdf_v2Sub(aq, origin, e->p[0]); + msdf_v2Sub(ab, e->p[1], e->p[0]); + *param = msdf_v2MulInner(aq, ab) / msdf_v2MulInner(ab, ab); + msdf_v2Sub(eq, e->p[*param > .5], origin); + + double endpoint_distance = msdf_v2Leng(eq); + if (*param > 0 && *param < 1) + { + msdf_Vec2 ab_ortho; + msdf_getOrtho(ab_ortho, ab, 0, 0); + double ortho_dist = msdf_v2MulInner(ab_ortho, aq); + if (fabs(ortho_dist) < endpoint_distance) + return (msdf_signedDistance){ortho_dist, 0}; + } + + msdf_v2Norm(ab, ab); + msdf_v2Norm(eq, eq); + double dist = msdf_nonZeroSign(msdf_cross(aq, ab)) * endpoint_distance; + double d = fabs(msdf_v2MulInner(ab, eq)); + return (msdf_signedDistance){dist, d}; +} + +// quadratic edge signed distance +msdf_signedDistance msdf_quadraticDist(msdf_EdgeSegment *e, msdf_Vec2 origin, double *param) +{ + msdf_Vec2 qa, ab, br; + msdf_v2Sub(qa, e->p[0], origin); + msdf_v2Sub(ab, e->p[1], e->p[0]); + br[0] = e->p[0][0] + e->p[2][0] - e->p[1][0] - e->p[1][0]; + br[1] = e->p[0][1] + e->p[2][1] - e->p[1][1] - e->p[1][1]; + + double a = msdf_v2MulInner(br, br); + double b = 3 * msdf_v2MulInner(ab, br); + double c = 2 * msdf_v2MulInner(ab, ab) + msdf_v2MulInner(qa, br); + double d = msdf_v2MulInner(qa, ab); + double t[3]; + int solutions = msdf_solveCubic(t, a, b, c, d); + + // distance from a + double min_distance = msdf_nonZeroSign(msdf_cross(ab, qa)) * msdf_v2Leng(qa); + *param = -msdf_v2MulInner(qa, ab) / msdf_v2MulInner(ab, ab); + { + msdf_Vec2 a, b; + msdf_v2Sub(a, e->p[2], e->p[1]); + msdf_v2Sub(b, e->p[2], origin); + + // distance from b + double distance = msdf_nonZeroSign(msdf_cross(a, b)) * msdf_v2Leng(b); + if (fabs(distance) < fabs(min_distance)) + { + min_distance = distance; + + msdf_v2Sub(a, origin, e->p[1]); + msdf_v2Sub(b, e->p[2], e->p[1]); + *param = msdf_v2MulInner(a, b) / msdf_v2MulInner(b, b); + } + } + + for (int i = 0; i < solutions; ++i) + { + if (t[i] > 0 && t[i] < 1) + { + // end_point = p[0]+2*t[i]*ab+t[i]*t[i]*br; + msdf_Vec2 end_point, a, b; + end_point[0] = e->p[0][0] + 2 * t[i] * ab[0] + t[i] * t[i] * br[0]; + end_point[1] = e->p[0][1] + 2 * t[i] * ab[1] + t[i] * t[i] * br[1]; + + msdf_v2Sub(a, e->p[2], e->p[0]); + msdf_v2Sub(b, end_point, origin); + double distance = msdf_nonZeroSign(msdf_cross(a, b)) * msdf_v2Leng(b); + if (fabs(distance) <= fabs(min_distance)) + { + min_distance = distance; + *param = t[i]; + } + } + } + + if (*param >= 0 && *param <= 1) + return (msdf_signedDistance){min_distance, 0}; + + msdf_Vec2 aa, bb; + msdf_v2Norm(ab, ab); + msdf_v2Norm(qa, qa); + msdf_v2Sub(aa, e->p[2], e->p[1]); + msdf_v2Norm(aa, aa); + msdf_v2Sub(bb, e->p[2], origin); + msdf_v2Norm(bb, bb); + + if (*param < .5) + return (msdf_signedDistance){min_distance, fabs(msdf_v2MulInner(ab, qa))}; + else + return (msdf_signedDistance){min_distance, fabs(msdf_v2MulInner(aa, bb))}; +} + +// cubic edge signed distance +msdf_signedDistance msdf_cubicDist(msdf_EdgeSegment *e, msdf_Vec2 origin, double *param) +{ + msdf_Vec2 qa, ab, br, as; + msdf_v2Sub(qa, e->p[0], origin); + msdf_v2Sub(ab, e->p[1], e->p[0]); + br[0] = e->p[2][0] - e->p[1][0] - ab[0]; + br[1] = e->p[2][1] - e->p[1][1] - ab[1]; + as[0] = (e->p[3][0] - e->p[2][0]) - (e->p[2][0] - e->p[1][0]) - br[0]; + as[1] = (e->p[3][1] - e->p[2][1]) - (e->p[2][1] - e->p[1][1]) - br[1]; + + msdf_Vec2 ep_dir; + msdf_direction(ep_dir, e, 0); + + // distance from a + double min_distance = msdf_nonZeroSign(msdf_cross(ep_dir, qa)) * msdf_v2Leng(qa); + *param = -msdf_v2MulInner(qa, ep_dir) / msdf_v2MulInner(ep_dir, ep_dir); + { + msdf_Vec2 a; + msdf_v2Sub(a, e->p[3], origin); + + msdf_direction(ep_dir, e, 1); + // distance from b + double distance = msdf_nonZeroSign(msdf_cross(ep_dir, a)) * msdf_v2Leng(a); + if (fabs(distance) < fabs(min_distance)) + { + min_distance = distance; + + a[0] = origin[0] + ep_dir[0] - e->p[3][0]; + a[1] = origin[1] + ep_dir[1] - e->p[3][1]; + *param = msdf_v2MulInner(a, ep_dir) / msdf_v2MulInner(ep_dir, ep_dir); + } + } + + const int search_starts = 4; + for (int i = 0; i <= search_starts; ++i) + { + double t = (double)i / search_starts; + for (int step = 0;; ++step) + { + msdf_Vec2 qpt; + msdf_point(qpt, e, t); + msdf_v2Sub(qpt, qpt, origin); + msdf_Vec2 d; + msdf_direction(d, e, t); + double distance = msdf_nonZeroSign(msdf_cross(d, qpt)) * msdf_v2Leng(qpt); + if (fabs(distance) < fabs(min_distance)) + { + min_distance = distance; + *param = t; + } + if (step == search_starts) + break; + + msdf_Vec2 d1, d2; + d1[0] = 3 * as[0] * t * t + 6 * br[0] * t + 3 * ab[0]; + d1[1] = 3 * as[1] * t * t + 6 * br[1] * t + 3 * ab[1]; + d2[0] = 6 * as[0] * t + 6 * br[0]; + d2[1] = 6 * as[1] * t + 6 * br[1]; + + t -= msdf_v2MulInner(qpt, d1) / (msdf_v2MulInner(d1, d1) + msdf_v2MulInner(qpt, d2)); + if (t < 0 || t > 1) + break; + } + } + + if (*param >= 0 && *param <= 1) + return (msdf_signedDistance){min_distance, 0}; + + msdf_Vec2 d0, d1; + msdf_direction(d0, e, 0); + msdf_direction(d1, e, 1); + msdf_v2Norm(d0, d0); + msdf_v2Norm(d1, d1); + msdf_v2Norm(qa, qa); + msdf_Vec2 a; + msdf_v2Sub(a, e->p[3], origin); + msdf_v2Norm(a, a); + + if (*param < .5) + return (msdf_signedDistance){min_distance, fabs(msdf_v2MulInner(d0, qa))}; + else + return (msdf_signedDistance){min_distance, fabs(msdf_v2MulInner(d1, a))}; +} + +void msdf_distToPseudo(msdf_signedDistance *distance, msdf_Vec2 origin, double param, msdf_EdgeSegment *e) { + if (param < 0) { + msdf_Vec2 dir, p; + msdf_direction(dir, e, 0); + msdf_v2Norm(dir, dir); + msdf_Vec2 aq = {origin[0], origin[1]}; + msdf_point(p, e, 0); + msdf_v2Sub(aq, origin, p); + double ts = msdf_v2MulInner(aq, dir); + if (ts < 0) { + double pseudo_dist = msdf_cross(aq, dir); + if (fabs(pseudo_dist) <= fabs(distance->dist)) { + distance->dist = pseudo_dist; + distance->d = 0; + } + } + } else if (param > 1) { + msdf_Vec2 dir, p; + msdf_direction(dir, e, 1); + msdf_v2Norm(dir, dir); + msdf_Vec2 bq = {origin[0], origin[1]}; + msdf_point(p, e, 1); + msdf_v2Sub(bq, origin, p); + double ts = msdf_v2MulInner(bq, dir); + if (ts > 0) { + double pseudo_dist = msdf_cross(bq, dir); + if (fabs(pseudo_dist) <= fabs(distance->dist)) { + distance->dist = pseudo_dist; + distance->d = 0; + } + } + } +} + +int msdf_signedCompare(msdf_signedDistance a, msdf_signedDistance b) { + return fabs(a.dist) < fabs(b.dist) || (fabs(a.dist) == fabs(b.dist) && a.d < b.d); +} + +int msdf_isCorner(msdf_Vec2 a, msdf_Vec2 b, double threshold) { + return msdf_v2MulInner(a, b) <= 0 || fabs(msdf_cross(a, b)) > threshold; +} + +void msdf_switchColor(msdf_edgeColor *color, unsigned long long *seed, msdf_edgeColor banned) +{ + msdf_edgeColor combined = *color & banned; + if (combined == msdf_edgeColor_red || combined == msdf_edgeColor_green || combined == msdf_edgeColor_blue) { + *color = (msdf_edgeColor)(combined ^ msdf_edgeColor_white); + return; + } + + if (*color == msdf_edgeColor_black || *color == msdf_edgeColor_white) { + static const msdf_edgeColor start[3] = {msdf_edgeColor_cyan, msdf_edgeColor_magenta, msdf_edgeColor_yellow}; + *color = start[*seed & 3]; + *seed /= 3; + return; + } + + int shifted = *color << (1 + (*seed & 1)); + *color = (msdf_edgeColor)((shifted | shifted >> 3) & msdf_edgeColor_white); + *seed >>= 1; +} + +void msdf_linearSplit(msdf_EdgeSegment *e, msdf_EdgeSegment *p1, msdf_EdgeSegment *p2, msdf_EdgeSegment *p3) +{ + msdf_Vec2 p; + + msdf_point(p, e, 1 / 3.0); + memcpy(&p1->p[0], e->p[0], sizeof(msdf_Vec2)); + memcpy(&p1->p[1], p, sizeof(msdf_Vec2)); + p1->color = e->color; + + msdf_point(p, e, 1 / 3.0); + memcpy(&p2->p[0], p, sizeof(msdf_Vec2)); + msdf_point(p, e, 2 / 3.0); + memcpy(&p2->p[1], p, sizeof(msdf_Vec2)); + p2->color = e->color; + + msdf_point(p, e, 2 / 3.0); + memcpy(&p3->p[0], p, sizeof(msdf_Vec2)); + msdf_point(p, e, 2 / 3.0); + memcpy(&p3->p[1], e->p[1], sizeof(msdf_Vec2)); + p3->color = e->color; +} + +void msdf_quadraticSplit(msdf_EdgeSegment *e, msdf_EdgeSegment *p1, msdf_EdgeSegment *p2, msdf_EdgeSegment *p3) +{ + msdf_Vec2 p, a, b; + + memcpy(&p1->p[0], e->p[0], sizeof(msdf_Vec2)); + msdf_mix(p, e->p[0], e->p[1], 1 / 3.0); + memcpy(&p1->p[1], p, sizeof(msdf_Vec2)); + msdf_point(p, e, 1 / 3.0); + memcpy(&p1->p[2], p, sizeof(msdf_Vec2)); + p1->color = e->color; + + msdf_point(p, e, 1 / 3.0); + memcpy(&p2->p[0], p, sizeof(msdf_Vec2)); + msdf_mix(a, e->p[0], e->p[1], 5 / 9.0); + msdf_mix(b, e->p[1], e->p[2], 4 / 9.0); + msdf_mix(p, a, b, 0.5); + memcpy(&p2->p[1], p, sizeof(msdf_Vec2)); + msdf_point(p, e, 2 / 3.0); + memcpy(&p2->p[2], p, sizeof(msdf_Vec2)); + p2->color = e->color; + + msdf_point(p, e, 2 / 3.0); + memcpy(&p3->p[0], p, sizeof(msdf_Vec2)); + msdf_mix(p, e->p[1], e->p[2], 2 / 3.0); + memcpy(&p3->p[1], p, sizeof(msdf_Vec2)); + memcpy(&p3->p[2], e->p[2], sizeof(msdf_Vec2)); + p3->color = e->color; +} + +void msdf_cubicSplit(msdf_EdgeSegment *e, msdf_EdgeSegment *p1, msdf_EdgeSegment *p2, msdf_EdgeSegment *p3) +{ + msdf_Vec2 p, a, b, c, d; + + memcpy(&p1->p[0], e->p[0], sizeof(msdf_Vec2)); // p1 0 + if (e->p[0] == e->p[1]) { + memcpy(&p1->p[1], e->p[0], sizeof(msdf_Vec2)); // ? p1 1 + } else { + msdf_mix(p, e->p[0], e->p[1], 1 / 3.0); + memcpy(&p1->p[1], p, sizeof(msdf_Vec2)); // ? p1 1 + } + msdf_mix(a, e->p[0], e->p[1], 1 / 3.0); + msdf_mix(b, e->p[1], e->p[2], 1 / 3.0); + msdf_mix(p, a, b, 1 / 3.0); + memcpy(&p1->p[2], p, sizeof(msdf_Vec2)); // p1 2 + msdf_point(p, e, 1 / 3.0); + memcpy(&p1->p[3], p, sizeof(msdf_Vec2)); // p1 3 + p1->color = e->color; + + msdf_point(p, e, 1 / 3.0); + memcpy(&p2->p[0], p, sizeof(msdf_Vec2)); // p2 0 + msdf_mix(a, e->p[0], e->p[1], 1 / 3.0); + msdf_mix(b, e->p[1], e->p[2], 1 / 3.0); + msdf_mix(c, a, b, 1 / 3.0); + msdf_mix(a, e->p[1], e->p[2], 1 / 3.0); + msdf_mix(b, e->p[2], e->p[3], 1 / 3.0); + msdf_mix(d, a, b, 1 / 3.0); + msdf_mix(p, c, d, 2 / 3.0); + memcpy(&p2->p[1], p, sizeof(msdf_Vec2)); // p2 1 + msdf_mix(a, e->p[0], e->p[1], 2 / 3.0); + msdf_mix(b, e->p[1], e->p[2], 2 / 3.0); + msdf_mix(c, a, b, 2 / 3.0); + msdf_mix(a, e->p[1], e->p[2], 2 / 3.0); + msdf_mix(b, e->p[2], e->p[3], 2 / 3.0); + msdf_mix(d, a, b, 2 / 3.0); + msdf_mix(p, c, d, 1 / 3.0); + memcpy(&p2->p[2], p, sizeof(msdf_Vec2)); // p2 2 + msdf_point(p, e, 2 / 3.0); + memcpy(&p2->p[3], p, sizeof(msdf_Vec2)); // p2 3 + p2->color = e->color; + + msdf_point(p, e, 2 / 3.0); + memcpy(&p3->p[0], p, sizeof(msdf_Vec2)); // p3 0 + + msdf_mix(a, e->p[1], e->p[2], 2 / 3.0); + msdf_mix(b, e->p[2], e->p[3], 2 / 3.0); + msdf_mix(p, a, b, 2 / 3.0); + memcpy(&p3->p[1], p, sizeof(msdf_Vec2)); // p3 1 + + if (e->p[2] == e->p[3]) { + memcpy(&p3->p[2], e->p[3], sizeof(msdf_Vec2)); // ? p3 2 + } else { + msdf_mix(p, e->p[2], e->p[3], 2 / 3.0); + memcpy(&p3->p[2], p, sizeof(msdf_Vec2)); // ? p3 2 + } + + memcpy(&p3->p[3], e->p[3], sizeof(msdf_Vec2)); // p3 3 +} + +void msdf_edgeSplit(msdf_EdgeSegment *e, msdf_EdgeSegment *p1, msdf_EdgeSegment *p2, msdf_EdgeSegment *p3) +{ + switch (e->type) { + case STBTT_vline: { + msdf_linearSplit(e, p1, p2, p3); + break; + } + case STBTT_vcurve: { + msdf_quadraticSplit(e, p1, p2, p3); + break; + } + case STBTT_vcubic: { + msdf_cubicSplit(e, p1, p2, p3); + break; + } + } +} + +double msdf_shoelace(const msdf_Vec2 a, const msdf_Vec2 b) +{ + return (b[0] - a[0]) * (a[1] + b[1]); +} + + +void* msdf__alloc(size_t size, void* ctx) { + return malloc(size); +} +void msdf__free(void* ptr, void* ctx) { + free(ptr); +} + +int msdf_genGlyph(msdf_Result* result, stbtt_fontinfo *font, int stbttGlyphIndex, uint32_t borderWidth, float scale, float range, msdf_AllocCtx* alloc) { + msdf_AllocCtx allocCtx; + + if (alloc) { + allocCtx = *alloc; + } else { + allocCtx.alloc = msdf__alloc; + allocCtx.free = msdf__free; + allocCtx.ctx = NULL; + } + + //char f = c; + // Funit to pixel scale + //float scale = stbtt_ScaleForMappingEmToPixels(font, h); + int glyphIdx = stbttGlyphIndex; + // get glyph bounding box (scaled later) + int ix0, iy0, ix1, iy1; + float xoff = .0, yoff = .0; + stbtt_GetGlyphBox(font, glyphIdx, &ix0, &iy0, &ix1, &iy1); + + float glyphWidth = ix1 - ix0; + float glyphHeight = iy1 - iy0; + float borderWidthF32 = borderWidth; + float wF32 = ceilf(glyphWidth * scale); + float hF32 = ceilf(glyphHeight * scale); + wF32 += 2.f * borderWidth; + hF32 += 2.f * borderWidth; + int w = wF32; + int h = hF32; + + float* bitmap = (float*) allocCtx.alloc(w * h * 3 * sizeof(float), allocCtx.ctx); + memset(bitmap, 0x0, w * h * 3 * sizeof(float)); + + // em scale + //scale = stbtt_ScaleForMappingEmToPixels(font, h); + + //if (autofit) + //{ + + // calculate new height + //float newh = h + (h - (iy1 - iy0) * scale) - 4; + + // calculate new scale + // see 'stbtt_ScaleForMappingEmToPixels' in stb_truetype.h + //uint8_t *p = font->data + font->head + 18; + //int unitsPerEm = p[0] * 256 + p[1]; + //scale = ((float)h) / ((float)unitsPerEm); + + // make sure we are centered + //xoff = .0; + //yoff = .0; + //} + + // get left offset and advance + //int left_bearing, advance; + //stbtt_GetGlyphHMetrics(font, glyphIdx, &advance, &left_bearing); + //left_bearing *= scale; + + int32_t glyphOrgX = ix0 * scale; + int32_t glyphOrgY = iy0 * scale; + + int32_t borderWidthX = borderWidth; + int32_t borderWidthY = borderWidth; + + // org 8,8 + // - bord 4,4 + // erg: 4,4 + + // calculate offset for centering glyph on bitmap + + //glyphOrgX >= 2 ? (glyphOrgX) : (); + int32_t translateX = (glyphOrgX - borderWidth);//borderWidth + ((w / 2) - ((ix1 - ix0) * scale) / 2 - leftBearingScaled); + int32_t translateY = (glyphOrgY - borderWidth);//borderWidth + ((h / 2) - ((iy1 - iy0) * scale) / 2 - ((float) iy0) * scale); + //translateY = 8; + // set the glyph metrics + // (pre-scale them) + + #if 0 + if (metrics) + { + metrics->left_bearing = left_bearing; + metrics->advance = advance * scale; + metrics->ix0 = ix0 * scale; + metrics->ix1 = ix1 * scale; + metrics->iy0 = iy0 * scale; + metrics->iy1 = iy1 * scale; + metrics->glyphIdx = glyphIdx; + } + #endif + + stbtt_vertex *verts; + int num_verts = stbtt_GetGlyphShape(font, glyphIdx, &verts); + + // figure out how many contours exist + int contour_count = 0; + for (int i = 0; i < num_verts; i++) { + if (verts[i].type == STBTT_vmove) { + contour_count++; + } + } + + if (contour_count == 0) { + return 0; + } + + // determin what vertices belong to what contours + typedef struct { + size_t start, end; + } msdf_Indices; + msdf_Indices *contours = allocCtx.alloc(sizeof(msdf_Indices) * contour_count, allocCtx.ctx); + int j = 0; + for (int i = 0; i <= num_verts; i++) { + if (verts[i].type == STBTT_vmove) { + if (i > 0) { + contours[j].end = i; + j++; + } + + contours[j].start = i; + } else if (i >= num_verts) { + contours[j].end = i; + } + } + + typedef struct { + msdf_signedDistance min_distance; + msdf_EdgeSegment *near_edge; + double near_param; + } msdf_EdgePoint; + + typedef struct { + msdf_EdgeSegment *edges; + size_t edge_count; + } msdf_Contour; + + // process verts into series of contour-specific edge lists + msdf_Vec2 initial = {0, 0}; // fix this? + msdf_Contour *contour_data = allocCtx.alloc(sizeof(msdf_Contour) * contour_count, allocCtx.ctx); + double cscale = 64.0; + for (int i = 0; i < contour_count; i++) { + size_t count = contours[i].end - contours[i].start; + contour_data[i].edges = allocCtx.alloc(sizeof(msdf_EdgeSegment) * count, allocCtx.ctx); + contour_data[i].edge_count = 0; + + size_t k = 0; + for (int j = contours[i].start; j < contours[i].end; j++) { + msdf_EdgeSegment *e = &contour_data[i].edges[k]; + stbtt_vertex *v = &verts[j]; + e->type = v->type; + e->color = msdf_edgeColor_white; + + switch (v->type) { + case STBTT_vmove: { + msdf_Vec2 p = {v->x / cscale, v->y / cscale}; + memcpy(&initial, p, sizeof(msdf_Vec2)); + break; + } + + case STBTT_vline: { + msdf_Vec2 p = {v->x / cscale, v->y / cscale}; + memcpy(&e->p[0], initial, sizeof(msdf_Vec2)); + memcpy(&e->p[1], p, sizeof(msdf_Vec2)); + memcpy(&initial, p, sizeof(msdf_Vec2)); + contour_data[i].edge_count++; + k++; + break; + } + + case STBTT_vcurve: { + msdf_Vec2 p = {v->x / cscale, v->y / cscale}; + msdf_Vec2 c = {v->cx / cscale, v->cy / cscale}; + memcpy(&e->p[0], initial, sizeof(msdf_Vec2)); + memcpy(&e->p[1], c, sizeof(msdf_Vec2)); + memcpy(&e->p[2], p, sizeof(msdf_Vec2)); + + if ((e->p[0][0] == e->p[1][0] && e->p[0][1] == e->p[1][1]) || + (e->p[1][0] == e->p[2][0] && e->p[1][1] == e->p[2][1])) + { + e->p[1][0] = 0.5 * (e->p[0][0] + e->p[2][0]); + e->p[1][1] = 0.5 * (e->p[0][1] + e->p[2][1]); + } + + memcpy(&initial, p, sizeof(msdf_Vec2)); + contour_data[i].edge_count++; + k++; + break; + } + + case STBTT_vcubic: { + msdf_Vec2 p = {v->x / cscale, v->y / cscale}; + msdf_Vec2 c = {v->cx / cscale, v->cy / cscale}; + msdf_Vec2 c1 = {v->cx1 / cscale, v->cy1 / cscale}; + memcpy(&e->p[0], initial, sizeof(msdf_Vec2)); + memcpy(&e->p[1], c, sizeof(msdf_Vec2)); + memcpy(&e->p[2], c1, sizeof(msdf_Vec2)); + memcpy(&e->p[3], p, sizeof(msdf_Vec2)); + memcpy(&initial, p, sizeof(msdf_Vec2)); + contour_data[i].edge_count++; + k++; + break; + } + } + } + } + + // calculate edge-colors + uint64_t seed = 0; + double anglethreshold = 3.0; + double crossthreshold = sin(anglethreshold); + size_t corner_count = 0; + for (int i = 0; i < contour_count; ++i) { + for (int j = 0; j < contour_data[i].edge_count; ++j) { + corner_count++; + } + } + + int *corners = allocCtx.alloc(sizeof(int) * corner_count, allocCtx.ctx); + int cornerIndex = 0; + for (int i = 0; i < contour_count; ++i) { + + if (contour_data[i].edge_count > 0) { + msdf_Vec2 prev_dir, dir; + msdf_direction(prev_dir, &contour_data[i].edges[contour_data[i].edge_count - 1], 1); + + int index = 0; + for (int j = 0; j < contour_data[i].edge_count; ++j, ++index) { + msdf_EdgeSegment *e = &contour_data[i].edges[j]; + msdf_direction(dir, e, 0); + msdf_v2Norm(dir, dir); + msdf_v2Norm(prev_dir, prev_dir); + if (msdf_isCorner(prev_dir, dir, crossthreshold)) { + corners[cornerIndex++] = index; + } + msdf_direction(prev_dir, e, 1); + } + } + + if (cornerIndex == 0) { + for (int j = 0; j < contour_data[i].edge_count; ++j) { + contour_data[i].edges[j].color = msdf_edgeColor_white; + } + } else if (cornerIndex == 1) { + msdf_edgeColor colors[3] = {msdf_edgeColor_white, msdf_edgeColor_white}; + msdf_switchColor(&colors[0], &seed, msdf_edgeColor_black); + colors[2] = colors[0]; + msdf_switchColor(&colors[2], &seed, msdf_edgeColor_black); + + int corner = corners[0]; + if (contour_data[i].edge_count >= 3) { + int m = contour_data[i].edge_count; + for (int j = 0; j < m; ++j) { + contour_data[i].edges[(corner + j) % m].color = (colors + 1)[(int)(3 + 2.875 * i / (m - 1) - 1.4375 + .5) - 3]; + } + } else if (contour_data[i].edge_count >= 1) { + msdf_EdgeSegment *parts[7] = {NULL}; + msdf_edgeSplit(&contour_data[i].edges[0], parts[0 + 3 * corner], parts[1 + 3 * corner], parts[2 + 3 * corner]); + if (contour_data[i].edge_count >= 2) { + msdf_edgeSplit(&contour_data[i].edges[1], parts[3 - 3 * corner], parts[4 - 3 * corner], parts[5 - 3 * corner]); + parts[0]->color = parts[1]->color = colors[0]; + parts[2]->color = parts[3]->color = colors[1]; + parts[4]->color = parts[5]->color = colors[2]; + } else { + parts[0]->color = colors[0]; + parts[1]->color = colors[1]; + parts[2]->color = colors[2]; + } + if (allocCtx.free) { + allocCtx.free(contour_data[i].edges, allocCtx.ctx); + } + contour_data[i].edges = allocCtx.alloc(sizeof(msdf_EdgeSegment) * 7, allocCtx.ctx); + contour_data[i].edge_count = 0; + int index = 0; + for (int j = 0; parts[j]; ++j) { + memcpy(&contour_data[i].edges[index++], &parts[j], sizeof(msdf_EdgeSegment)); + contour_data[i].edge_count++; + } + } + } else { + int spline = 0; + int start = corners[0]; + int m = contour_data[i].edge_count; + msdf_edgeColor color = msdf_edgeColor_white; + msdf_switchColor(&color, &seed, msdf_edgeColor_black); + msdf_edgeColor initial_color = color; + for (int j = 0; j < m; ++j) { + int index = (start + j) % m; + if (spline + 1 < corner_count && corners[spline + 1] == index) { + ++spline; + + msdf_edgeColor s = (msdf_edgeColor)((spline == corner_count - 1) * initial_color); + msdf_switchColor(&color, &seed, s); + } + contour_data[i].edges[index].color = color; + } + } + } + + if (allocCtx.free) { + allocCtx.free(corners, allocCtx.ctx); + } + + // normalize shape + for (int i = 0; i < contour_count; i++) { + if (contour_data[i].edge_count == 1) { + msdf_EdgeSegment *parts[3] = {0}; + msdf_edgeSplit(&contour_data[i].edges[0], parts[0], parts[1], parts[2]); + if (allocCtx.free) { + allocCtx.free(contour_data[i].edges, allocCtx.ctx); + } + contour_data[i].edges = allocCtx.alloc(sizeof(msdf_EdgeSegment) * 3, allocCtx.ctx); + contour_data[i].edge_count = 3; + for (int j = 0; j < 3; j++) { + memcpy(&contour_data[i].edges[j], &parts[j], sizeof(msdf_EdgeSegment)); + } + } + } + + // calculate windings + int *windings = allocCtx.alloc(sizeof(int) * contour_count, allocCtx.ctx); + for (int i = 0; i < contour_count; i++) { + size_t edge_count = contour_data[i].edge_count; + if (edge_count == 0) { + windings[i] = 0; + continue; + } + + double total = 0; + + if (edge_count == 1) { + msdf_Vec2 a, b, c; + msdf_point(a, &contour_data[i].edges[0], 0); + msdf_point(b, &contour_data[i].edges[0], 1 / 3.0); + msdf_point(c, &contour_data[i].edges[0], 2 / 3.0); + total += msdf_shoelace(a, b); + total += msdf_shoelace(b, c); + total += msdf_shoelace(c, a); + } else if (edge_count == 2) { + msdf_Vec2 a, b, c, d; + msdf_point(a, &contour_data[i].edges[0], 0); + msdf_point(b, &contour_data[i].edges[0], 0.5); + msdf_point(c, &contour_data[i].edges[1], 0); + msdf_point(d, &contour_data[i].edges[1], 0.5); + total += msdf_shoelace(a, b); + total += msdf_shoelace(b, c); + total += msdf_shoelace(c, d); + total += msdf_shoelace(d, a); + } else { + msdf_Vec2 prev; + msdf_point(prev, &contour_data[i].edges[edge_count - 1], 0); + for (int j = 0; j < edge_count; j++) { + msdf_Vec2 cur; + msdf_point(cur, &contour_data[i].edges[j], 0); + total += msdf_shoelace(prev, cur); + memcpy(prev, cur, sizeof(msdf_Vec2)); + } + } + + windings[i] = ((0 < total) - (total < 0)); // sign + } + + typedef struct { + double r, g, b; + double med; + } msdf_MultiDistance; + + msdf_MultiDistance *contour_sd; + contour_sd = allocCtx.alloc(sizeof(msdf_MultiDistance) * contour_count, allocCtx.ctx); + + float invRange = 1.0 / range; + + for (int y = 0; y < h; ++y) { + int row = iy0 > iy1 ? y : h - y - 1; + for (int x = 0; x < w; ++x) { + float a64 = 64.0; + msdf_Vec2 p = {(translateX + x + xoff) / (scale * a64), (translateY + y + yoff) / (scale * a64)}; + //p[0] = ; + //p[1] = ; + msdf_EdgePoint sr, sg, sb; + sr.near_edge = sg.near_edge = sb.near_edge = NULL; + sr.near_param = sg.near_param = sb.near_param = 0; + sr.min_distance.dist = sg.min_distance.dist = sb.min_distance.dist = MSDF_INF; + sr.min_distance.d = sg.min_distance.d = sb.min_distance.d = 1; + double d = fabs(MSDF_INF); + double neg_dist = -MSDF_INF; + double pos_dist = MSDF_INF; + int winding = 0; + + // calculate distance to contours from current point (and if its inside or outside of the shape?) + for (int j = 0; j < contour_count; ++j) { + msdf_EdgePoint r, g, b; + r.near_edge = g.near_edge = b.near_edge = NULL; + r.near_param = g.near_param = b.near_param = 0; + r.min_distance.dist = g.min_distance.dist = b.min_distance.dist = MSDF_INF; + r.min_distance.d = g.min_distance.d = b.min_distance.d = 1; + + for (int k = 0; k < contour_data[j].edge_count; ++k) { + msdf_EdgeSegment *e = &contour_data[j].edges[k]; + double param; + msdf_signedDistance distance; + distance.dist = MSDF_INF; + distance.d = 1; + + // calculate signed distance + switch (e->type) { + case STBTT_vline: { + distance = msdf_linearDist(e, p, ¶m); + break; + } + case STBTT_vcurve: { + distance = msdf_quadraticDist(e, p, ¶m); + break; + } + case STBTT_vcubic: { + distance = msdf_cubicDist(e, p, ¶m); + break; + } + } + + if (e->color & msdf_edgeColor_red && msdf_signedCompare(distance, r.min_distance)) { + r.min_distance = distance; + r.near_edge = e; + r.near_param = param; + } + if (e->color & msdf_edgeColor_green && msdf_signedCompare(distance, g.min_distance)) { + g.min_distance = distance; + g.near_edge = e; + g.near_param = param; + } + if (e->color & msdf_edgeColor_blue && msdf_signedCompare(distance, b.min_distance)) { + b.min_distance = distance; + b.near_edge = e; + b.near_param = param; + } + } + + if (msdf_signedCompare(r.min_distance, sr.min_distance)) { + sr = r; + } + if (msdf_signedCompare(g.min_distance, sg.min_distance)) { + sg = g; + } + if (msdf_signedCompare(b.min_distance, sb.min_distance)) { + sb = b; + } + + double med_min_dist = fabs(msdf_median(r.min_distance.dist, g.min_distance.dist, b.min_distance.dist)); + + if (med_min_dist < d) { + d = med_min_dist; + winding = -windings[j]; + } + + if (r.near_edge) { + msdf_distToPseudo(&r.min_distance, p, r.near_param, r.near_edge); + } + if (g.near_edge) { + msdf_distToPseudo(&g.min_distance, p, g.near_param, g.near_edge); + } + if (b.near_edge) { + msdf_distToPseudo(&b.min_distance, p, b.near_param, b.near_edge); + } + + med_min_dist = msdf_median(r.min_distance.dist, g.min_distance.dist, b.min_distance.dist); + contour_sd[j].r = r.min_distance.dist; + contour_sd[j].g = g.min_distance.dist; + contour_sd[j].b = b.min_distance.dist; + contour_sd[j].med = med_min_dist; + + if (windings[j] > 0 && med_min_dist >= 0 && fabs(med_min_dist) < fabs(pos_dist)) { + pos_dist = med_min_dist; + } + if (windings[j] < 0 && med_min_dist <= 0 && fabs(med_min_dist) < fabs(neg_dist)) { + neg_dist = med_min_dist; + } + } + + if (sr.near_edge) { + msdf_distToPseudo(&sr.min_distance, p, sr.near_param, sr.near_edge); + } + if (sg.near_edge) { + msdf_distToPseudo(&sg.min_distance, p, sg.near_param, sg.near_edge); + } + if (sb.near_edge) { + msdf_distToPseudo(&sb.min_distance, p, sb.near_param, sb.near_edge); + } + + msdf_MultiDistance msd; + msd.r = msd.g = msd.b = msd.med = MSDF_INF; + if (pos_dist >= 0 && fabs(pos_dist) <= fabs(neg_dist)) { + msd.med = MSDF_INF; + winding = 1; + for (int i = 0; i < contour_count; ++i) { + if (windings[i] > 0 && contour_sd[i].med > msd.med && fabs(contour_sd[i].med) < fabs(neg_dist)) { + msd = contour_sd[i]; + } + } + } else if (neg_dist <= 0 && fabs(neg_dist) <= fabs(pos_dist)) { + msd.med = -MSDF_INF; + winding = -1; + for (int i = 0; i < contour_count; ++i) { + if (windings[i] < 0 && contour_sd[i].med < msd.med && fabs(contour_sd[i].med) < fabs(pos_dist)) { + msd = contour_sd[i]; + } + } + } + + for (int i = 0; i < contour_count; ++i) { + if (windings[i] != winding && fabs(contour_sd[i].med) < fabs(msd.med)) { + msd = contour_sd[i]; + } + } + + if (msdf_median(sr.min_distance.dist, sg.min_distance.dist, sb.min_distance.dist) == msd.med) { + msd.r = sr.min_distance.dist; + msd.g = sg.min_distance.dist; + msd.b = sb.min_distance.dist; + } + + size_t index = 3 * ((row * w) + x); + + float mr = ((float)msd.r) * invRange + 0.5f; + float mg = ((float)msd.g) * invRange + 0.5f; + float mb = ((float)msd.b) * invRange + 0.5f; + bitmap[index + 0] = mr; + bitmap[index + 1] = mg; + bitmap[index + 2] = mb; + + } + } + + if (allocCtx.free) { + for (int i = 0; i < contour_count; i++) { + allocCtx.free(contour_data[i].edges, allocCtx.ctx); + } + allocCtx.free(contour_data, allocCtx.ctx); + allocCtx.free(contour_sd, allocCtx.ctx); + allocCtx.free(contours, allocCtx.ctx); + allocCtx.free(windings, allocCtx.ctx); + allocCtx.free(verts, allocCtx.ctx); + } + + // msdf error correction + typedef struct { + int x, y; + } msdf_Clash; + msdf_Clash *clashes = allocCtx.alloc(sizeof(msdf_Clash) * w * h, allocCtx.ctx); + size_t cindex = 0; + + double tx = MSDF_EDGE_THRESHOLD / (scale * range); + double ty = MSDF_EDGE_THRESHOLD / (scale * range); + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + if ((x > 0 && msdf_pixelClash(msdf_pixelAt(x, y, w, bitmap), msdf_pixelAt(msdf_max(x - 1, 0), y, w, bitmap), tx)) || (x < w - 1 && msdf_pixelClash(msdf_pixelAt(x, y, w, bitmap), msdf_pixelAt(msdf_min(x + 1, w - 1), y, w, bitmap), tx)) || (y > 0 && msdf_pixelClash(msdf_pixelAt(x, y, w, bitmap), msdf_pixelAt(x, msdf_max(y - 1, 0), w, bitmap), ty)) || (y < h - 1 && msdf_pixelClash(msdf_pixelAt(x, y, w, bitmap), msdf_pixelAt(x, msdf_min(y + 1, h - 1), w, bitmap), ty))) { + clashes[cindex].x = x; + clashes[cindex++].y = y; + } + } + } + + for (int i = 0; i < cindex; i++) { + size_t index = 3 * ((clashes[i].y * w) + clashes[i].x); + float med = msdf_median(bitmap[index], bitmap[index + 1], bitmap[index + 2]); + bitmap[index + 0] = med; + bitmap[index + 1] = med; + bitmap[index + 2] = med; + } + + if (allocCtx.free) { + allocCtx.free(clashes, allocCtx.ctx); + } + + result->glyphIdx = glyphIdx; + result->rgb = bitmap; + result->width = w; + result->height = h; + result->yOffset = translateY; + + return 1; +} +#endif +#endif // MSDF_H diff --git a/sdl_gpu.cm b/sdl_gpu.cm index f0b59a87..de606ded 100644 --- a/sdl_gpu.cm +++ b/sdl_gpu.cm @@ -1247,7 +1247,7 @@ function _render_text(cmd_buffer, pass, drawable, camera, target) { // staef font has 'texture' property which is pixel blob + dims. We need to upload it to GPU if not already. var font_tex = _get_font_texture(font, is_sdf) - pass.bind_fragment_samplers(0, [{texture: font_tex, sampler: is_sdf ? _sampler_linear : _sampler_nearest}]) + pass.bind_fragment_samplers(0, [{texture: font_tex, sampler: _sampler_nearest}]) cmd_buffer.push_vertex_uniform_data(0, proj) pass.draw_indexed(num_indices, 1, 0, 0, 0) } diff --git a/staef.c b/staef.c index 29126007..a6c4b777 100644 --- a/staef.c +++ b/staef.c @@ -11,6 +11,9 @@ #include #include +#define MSDF_IMPLEMENTATION +#include "msdf.h" + #define STB_TRUETYPE_IMPLEMENTATION #define STB_TRUETYPE_NO_STDIO #include "stb_truetype.h" @@ -113,7 +116,7 @@ struct sFont *MakeFont(void *ttf_buffer, size_t len, int height, int is_sdf) { int row_height = 0; int pad = 5; // padding for SDF float onedge_value = 127.5f; // 128ish - float pixel_dist_scale = 32.0f; // Distance field range + float pixel_dist_scale = 150.f; // Distance field range for (unsigned char c = 32; c < 127; c++) { int glyph_index = c - 29; // Simple ASCII mapping? verify if font has proper map @@ -124,7 +127,14 @@ struct sFont *MakeFont(void *ttf_buffer, size_t len, int height, int is_sdf) { int width, height, xoff, yoff; unsigned char *sdf = stbtt_GetGlyphSDF(&fontinfo, scale, g, pad, (unsigned char)onedge_value, pixel_dist_scale, &width, &height, &xoff, &yoff); - if (!sdf) continue; + if (!sdf) { + // Handle invisible characters (space) + int advance, lsb; + stbtt_GetGlyphHMetrics(&fontinfo, g, &advance, &lsb); + newfont->Characters[c].advance = advance * scale; + // Keep quad/uv as 0 + continue; + } if (x + width + 1 > packsize) { x = 0; @@ -135,7 +145,6 @@ struct sFont *MakeFont(void *ttf_buffer, size_t len, int height, int is_sdf) { if (y + height + 1 > packsize) { // Out of space free(sdf); - // Continue or break? Missing chars better than crash continue; } @@ -151,15 +160,36 @@ struct sFont *MakeFont(void *ttf_buffer, size_t len, int height, int is_sdf) { // Store character info rect uv; uv.x = (float)x / packsize; - uv.y = (float)y / packsize; + uv.y = (float)(y + height) / packsize; // Bottom (Top of glyph in SDF?) + // To match bitmap path which produces UPSIDE DOWN results if not flipped: + // Bitmap path: uv.h = Negative. + // So UVs are (y1, y0). (Top Texture is at Bottom vertex). + // Here we want to match that. + // Top Vertex (v0) should get Bottom Texture. + // Bottom Vertex (v1) should get Top Texture. + + // Let's make uv.h negative. + // Top Texture is at 'y'. Bottom Texture is at 'y+height'. + // uv.y = (y+height)/size. + // uv.h = -height/size. + // v0 (Top Vert) = uv.y = y+height (Bottom Tex). + // v1 (Bottom Vert) = uv.y + uv.h = y (Top Tex). + // This flips the texture on Y. + uv.w = (float)width / packsize; - uv.h = (float)height / packsize; + uv.h = -(float)height / packsize; newfont->Characters[c].uv = uv; rect quad; quad.x = (float)xoff; - quad.y = (float)yoff; - quad.w = (float)width; // Includes padding + // Bitmap path: quad.y = -yoff2. (Bottom in Y-Up). + // SDF path: yoff is Top in Y-Down (-Top in Y-Up). + // height is height. + // We want Bottom in Y-Up. + // Bottom = - (yoff + height). + + quad.y = (float)(-yoff - height); + quad.w = (float)width; quad.h = (float)height; newfont->Characters[c].quad = quad;