/* 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