Skip to content

Instantly share code, notes, and snippets.

@n-yoda
Last active December 20, 2015 18:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save n-yoda/25ea623bffaebf233447 to your computer and use it in GitHub Desktop.
Save n-yoda/25ea623bffaebf233447 to your computer and use it in GitHub Desktop.
Render Font as Signed Distance Filed (WIP)
#include <cstdio>
#include <cstdlib>
#include <cstdint>
#include <cmath>
#include <memory>
#include <fstream>
#include <sstream>
extern "C" {
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_OUTLINE_H
const char* getErrorMessage(FT_Error err) {
#undef __FTERRORS_H__
#define FT_ERRORDEF( e, v, s ) case e: return s;
#define FT_ERROR_START_LIST
#define FT_ERROR_END_LIST
switch (err) {
#include FT_ERRORS_H
}
return "(Unknown error)";
}
}
void ExitOnError(FT_Error err) {
if (err) {
std::printf("%s\n", getErrorMessage(err));
exit(1);
}
}
double Length(const FT_Vector &v) {
double x = v.x;
double y = v.y;
return std::sqrt(x * x + y * y);
}
double Neighbor(const FT_Vector &a, const FT_Vector &b) {
return std::abs(a.x - b.x) <= 1 && std::abs(a.y - b.y) <= 1;
}
uint8_t Clamp(long x) {
return x <= 0 ? 0 : (x >= 255 ? 255 : x);
}
struct Data {
Data(long w, long h)
: first(true), start(), prev(),
width(w), height(h), img(new uint8_t[w*h]),
scale(1), distUnit(1), bbox() {
std::fill(&img[0], &img[w * h], 0);
}
bool first;
FT_Vector start;
FT_Vector prev;
long width;
long height;
long scale;
uint8_t distUnit;
uint8_t distZero;
FT_BBox bbox;
std::unique_ptr<uint8_t[]> img;
FT_Vector PosToPixel(const FT_Vector& v) const {
return FT_Vector {
.x = (v.x - bbox.xMin) / scale,
.y = (v.y - bbox.yMin) / scale,
};
}
bool ValidPixel(const FT_Vector& v) const {
return 0 <= v.x && v.x < width && 0 <= v.y && v.y < height;
}
void SetPixel(const FT_Vector& pixel, uint8_t value) {
img[pixel.y * width + pixel.x] = value;
}
uint8_t GetPixel(const FT_Vector& pixel) const {
return img[pixel.y * width + pixel.x];
}
void SetDist(const FT_Vector& pixel, long sd) {
long src = (long)GetPixel(pixel) - distZero;
if (std::abs(sd) < std::abs(src)) {
SetPixel(pixel, Clamp(sd + (long)distZero));
}
}
long CalcDist(const FT_Vector& a, const FT_Vector& b) const {
double len = Length({a.x - b.x, a.y - b.y});
long d = len * distUnit / scale;
return d;
}
};
struct SplitConic {
SplitConic(FT_Vector q0, FT_Vector c, FT_Vector q1)
: c0{(q0.x + c.x) / 2, (q0.y + c.y) / 2},
c1{(q1.x + c.x) / 2, (q1.y + c.y) / 2},
p1{(c0.x + c1.x) / 2, (c0.y + c1.y) / 2} {}
FT_Vector c0, c1, p1;
};
int MoveTo(const FT_Vector* to, void* user) {
Data *data = static_cast<Data*>(user);
if (data->first) {
std::printf("Move (%ld, %ld)\n", to->x, to->y);
} else {
std::printf("\nMove (%ld, %ld) -> (%ld, %ld)\n",
data->prev.x, data->prev.y, to->x, to->y);
}
data->first = false;
data->start = *to;
data->prev = *to;
return 0;
}
void FillLineRec(const FT_Vector& p, const FT_Vector& a,
const FT_Vector& b, long sign, Data& data) {
FT_Vector ia = data.PosToPixel(a);
FT_Vector ib = data.PosToPixel(b);
bool aValid = data.ValidPixel(ia);
bool bValid = data.ValidPixel(ib);
if (!aValid && !bValid) {
return;
} else if (Neighbor(ia, ib)) {
if (aValid) {
data.SetDist(ia, sign * data.CalcDist(p, a));
}
if (bValid) {
data.SetDist(ib, sign * data.CalcDist(p, b));
}
} else {
FT_Vector c {.x = (a.x + b.x) / 2, .y = (a.y + b.y) / 2};
FillLineRec(p, a, c, sign, data);
FillLineRec(p, c, b, sign, data);
}
}
void FillLine(const FT_Vector& p, const FT_Vector& n, long sign, Data& data) {
FT_Vector n2 = n;
while (Length(n2) <= (255 / data.distUnit) * data.scale) {
n2.x += n.x;
n2.y += n.y;
}
FT_Vector p2 {p.x + n2.x, p.y + n2.y};
FillLineRec(p, p, p2, sign, data);
}
void RenderLine(const FT_Vector& p0, const FT_Vector& p1,
const FT_Vector& n, Data& data) {
FT_Vector i0 = data.PosToPixel(p0);
FT_Vector i1 = data.PosToPixel(p1);
if (std::abs(i0.x - i1.x) <= 1 && std::abs(i0.y - i1.y) <= 1) {
FillLine(p0, n, -1, data);
FillLine(p1, n, -1, data);
FillLine(p0, {-n.x, -n.y}, 1, data);
FillLine(p1, {-n.x, -n.y}, 1, data);
} else {
FT_Vector p01 {.x = (p0.x + p1.x) / 2, .y = (p0.y + p1.y) / 2};
RenderLine(p0, p01, n, data);
RenderLine(p01, p1, n, data);
}
}
int LineTo(const FT_Vector* to, void* user) {
Data *data = static_cast<Data*>(user);
std::printf("Line (%ld, %ld) -> (%ld, %ld)\n",
data->prev.x, data->prev.y, to->x, to->y);
FT_Vector n{data->prev.y - to->y, to->x - data->prev.x};
RenderLine(data->prev, *to, n, *data);
data->prev = *to;
return 0;
}
void RenderConic(const FT_Vector& p0, const FT_Vector& c0,
const FT_Vector& p1, Data& data) {
FT_Vector i0 = data.PosToPixel(p0);
FT_Vector i1 = data.PosToPixel(p1);
if (std::abs(i0.x - i1.x) <= 1 && std::abs(i0.y - i1.y) <= 1) {
FillLine(p0, {p0.y - c0.y, c0.x - p0.x}, -1, data);
FillLine(p1, {c0.y - p1.y, p1.x - c0.x}, -1, data);
FillLine(p0, {c0.y - p0.y, p0.x - c0.x}, 1, data);
FillLine(p1, {p1.y - c0.y, c0.x - p1.x}, 1, data);
} else {
SplitConic split(p0, c0, p1);
RenderConic(p0, split.c0, split.p1, data);
RenderConic(split.p1, split.c1, p1, data);
}
}
int ConicTo(const FT_Vector* control, const FT_Vector* to, void* user) {
Data *data = static_cast<Data*>(user);
std::printf("Conic (%ld, %ld) -> (%ld, %ld)\n",
data->prev.x, data->prev.y, to->x, to->y);
RenderConic(data->prev, *control, *to, *data);
data->prev = *to;
return 0;
}
int CubicTo(const FT_Vector* control1, const FT_Vector* control2,
const FT_Vector* to, void* user) {
Data *data = static_cast<Data*>(user);
std::printf("Cubic (%ld, %ld) -> (%ld, %ld)\n",
data->prev.x, data->prev.y, to->x, to->y);
data->prev = *to;
return 0;
}
int main(int argc, char** args) {
// Initialize library
FT_Library lib;
ExitOnError(FT_Init_FreeType(&lib));
if (argc < 2) {
std::printf("Usage: ./glyph2dot font-path [index] [char]\n");
return 1;
}
// Load font
FT_Face face;
int faceIndex = argc >= 3 ? std::atoi(args[2]) : 0;
ExitOnError(FT_New_Face(lib, args[1], faceIndex, &face));
// Load glyph
char ch = argc >= 4 ? args[3][0] : 'A';
ExitOnError(FT_Load_Char(face, ch, FT_LOAD_NO_SCALE));
FT_Outline* outline = &face->glyph->outline;
FT_BBox bbox;
FT_Outline_Get_CBox(outline, &bbox);
long distUnit = 10;
int shift = 15;
long scale = 8;
long extent = (255 / distUnit) + ((255 % distUnit) ? 0 : 1);
bbox.xMin -= extent * scale;
bbox.xMax += extent * scale;
bbox.yMin -= extent * scale;
bbox.yMax += extent * scale;
std::printf("%ld, %ld %ld, %ld\n", bbox.xMin, bbox.yMin, bbox.xMax, bbox.yMax);
long width = bbox.xMax - bbox.xMin;
long height = bbox.yMax - bbox.yMin;
Data data(width / scale, height / scale);
data.distUnit = distUnit;
data.distZero = 127;
data.scale = (1 << shift) * scale;
data.bbox = bbox;
data.bbox.xMin <<= shift;
data.bbox.xMax <<= shift;
data.bbox.yMin <<= shift;
data.bbox.yMax <<= shift;
FT_Outline_Funcs funcs {MoveTo, LineTo, ConicTo, CubicTo, shift, 0};
// Render
FT_Outline_Decompose(outline, &funcs, &data);
std::printf("%ld, %ld\n", data.width, data.height);
// Output
std::stringstream name;
name << data.width << "." << data.height << ".bytes";
std::ofstream ofs(name.str(), std::ios::binary);
ofs.write(reinterpret_cast<char*>(data.img.get()), data.width * data.height);
// Release
FT_Done_Face(face);
FT_Done_FreeType(lib);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment