Move vector font to res/fonts/; remove lff2c.

This commit integrates the vector font in the resource system, so
that cross-compilation would be easier and a custom font could be
used without recompilation.

The font handling code was carefully written to lazily load glyphs;
as possible; in practice this means that startup is less than 15ms
slower after this commit, most of it spent in inflate().

This also reduces executable size and makes compilation of
glhelper.cpp much faster.
pull/10/head
whitequark 2016-04-24 22:35:23 +00:00
parent 645c2d90ac
commit fc79642788
12 changed files with 426 additions and 605 deletions

View File

@ -183,7 +183,6 @@ endif()
# components
add_subdirectory(tools)
add_subdirectory(res)
add_subdirectory(src)
add_subdirectory(exposed)

View File

@ -176,7 +176,8 @@ add_resources(
fonts/private/4-stipple-dot.png
fonts/private/5-stipple-dash-long.png
fonts/private/6-stipple-dash.png
fonts/private/7-stipple-zigzag.png)
fonts/private/7-stipple-zigzag.png
fonts/unicode.lff.gz)
# Third, distribute the resources.
add_custom_target(resources

BIN
res/fonts/unicode.lff.gz Normal file

Binary file not shown.

View File

@ -75,27 +75,6 @@ if(NOT WIN32)
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
endif()
# generated files
# `$<TARGET_FILE:tool>` allows us to use binfmt support on Linux
# without special-casing anything; running `tool.exe` would succeed
# but unlike Windows, Linux does not have the machinery to map
# an invocation of `tool` to an executable `tool.exe` in $PATH.
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/generated)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/generated/vectorfont.table.h
COMMAND $<TARGET_FILE:lff2c>
${CMAKE_CURRENT_BINARY_DIR}/generated/vectorfont.table.h
${CMAKE_CURRENT_SOURCE_DIR}/fonts/unicode.lff.gz
DEPENDS lff2c
${CMAKE_CURRENT_SOURCE_DIR}/fonts/unicode.lff.gz
VERBATIM)
set(generated_HEADERS
${CMAKE_CURRENT_BINARY_DIR}/generated/vectorfont.table.h)
# platform dependencies
if(WIN32)
@ -212,7 +191,6 @@ add_executable(solvespace WIN32 MACOSX_BUNDLE
${libslvs_SOURCES}
${util_SOURCES}
${platform_SOURCES}
${generated_HEADERS}
${solvespace_HEADERS}
${solvespace_SOURCES}
$<TARGET_PROPERTY:resources,EXTRA_SOURCES>)

View File

@ -126,6 +126,7 @@ public:
double x, y;
static Point2d From(double x, double y);
static Point2d FromPolar(double r, double a);
Point2d Plus(const Point2d &b) const;
Point2d Minus(const Point2d &b) const;
@ -134,6 +135,8 @@ public:
double Dot(Point2d p) const;
double DistanceTo(const Point2d &p) const;
double DistanceToLine(const Point2d &p0, const Point2d &dp, bool segment) const;
double Angle() const;
double AngleTo(const Point2d &p) const;
double Magnitude(void) const;
double MagSquared(void) const;
Point2d WithMagnitude(double v) const;

Binary file not shown.

View File

@ -7,74 +7,9 @@
namespace SolveSpace {
// A vector font.
#include "generated/vectorfont.table.h"
static bool ColorLocked;
static bool DepthOffsetLocked;
static const VectorGlyph &GetVectorGlyph(char32_t chr) {
int first = 0;
int last = sizeof(VectorFont) / sizeof(VectorGlyph);
while(first <= last) {
int mid = (first + last) / 2;
char32_t midChr = VectorFont[mid].character;
if(midChr > chr) {
last = mid - 1; // and first stays the same
continue;
}
if(midChr < chr) {
first = mid + 1; // and last stays the same
continue;
}
return VectorFont[mid];
}
return GetVectorGlyph(0xfffd); // replacement character
}
// Internally and in the UI, the vector font is sized using cap height.
#define FONT_SCALE(h) ((h)/(double)FONT_CAP_HEIGHT)
double ssglStrCapHeight(double h)
{
return FONT_CAP_HEIGHT * FONT_SCALE(h) / SS.GW.scale;
}
double ssglStrFontSize(double h)
{
return FONT_SIZE * FONT_SCALE(h) / SS.GW.scale;
}
double ssglStrWidth(const std::string &str, double h)
{
int width = 0;
for(char32_t chr : ReadUTF8(str)) {
const VectorGlyph &glyph = GetVectorGlyph(chr);
if(glyph.baseCharacter != 0) {
const VectorGlyph &baseGlyph = GetVectorGlyph(glyph.baseCharacter);
width += max(glyph.advanceWidth, baseGlyph.advanceWidth);
} else {
width += glyph.advanceWidth;
}
}
return width * FONT_SCALE(h) / SS.GW.scale;
}
void ssglWriteTextRefCenter(const std::string &str, double h, Vector t, Vector u, Vector v,
ssglLineFn *fn, void *fndata)
{
u = u.WithMagnitude(1);
v = v.WithMagnitude(1);
double scale = FONT_SCALE(h)/SS.GW.scale;
double fh = ssglStrCapHeight(h);
double fw = ssglStrWidth(str, h);
t = t.Plus(u.ScaledBy(-fw/2));
t = t.Plus(v.ScaledBy(-fh/2));
// Apply additional offset to get an exact center alignment.
t = t.Plus(v.ScaledBy(-4608*scale));
ssglWriteText(str, h, t, u, v, fn, fndata);
}
void ssglLineWidth(GLfloat width) {
// Intel GPUs with Mesa on *nix render thin lines poorly.
static bool workaroundChecked, workaroundEnabled;
@ -102,98 +37,6 @@ static void LineDrawCallback(void *fndata, Vector a, Vector b)
glEnd();
}
Vector pixelAlign(Vector v) {
v = SS.GW.ProjectPoint3(v);
v.x = floor(v.x) + 0.5;
v.y = floor(v.y) + 0.5;
v = SS.GW.UnProjectPoint3(v);
return v;
}
int ssglDrawCharacter(const VectorGlyph &glyph, Vector t, Vector o, Vector u, Vector v,
double scale, ssglLineFn *fn, void *fndata, bool gridFit) {
int advanceWidth = glyph.advanceWidth;
if(glyph.baseCharacter != 0) {
const VectorGlyph &baseGlyph = GetVectorGlyph(glyph.baseCharacter);
int baseWidth = ssglDrawCharacter(baseGlyph, t, o, u, v, scale, fn, fndata, gridFit);
advanceWidth = max(glyph.advanceWidth, baseWidth);
}
int actualWidth, offsetX;
if(gridFit) {
o.x += glyph.leftSideBearing;
offsetX = glyph.leftSideBearing;
actualWidth = glyph.boundingWidth;
if(actualWidth == 0) {
// Dot, "i", etc.
actualWidth = 1;
}
} else {
offsetX = 0;
actualWidth = advanceWidth;
}
Vector tt = t;
tt = tt.Plus(u.ScaledBy(o.x * scale));
tt = tt.Plus(v.ScaledBy(o.y * scale));
Vector tu = tt;
tu = tu.Plus(u.ScaledBy(actualWidth * scale));
Vector tv = tt;
tv = tv.Plus(v.ScaledBy(FONT_CAP_HEIGHT * scale));
if(gridFit) {
tt = pixelAlign(tt);
tu = pixelAlign(tu);
tv = pixelAlign(tv);
}
tu = tu.Minus(tt).ScaledBy(1.0 / actualWidth);
tv = tv.Minus(tt).ScaledBy(1.0 / FONT_CAP_HEIGHT);
const int16_t *data = glyph.data;
bool pen_up = true;
Vector prevp;
while(true) {
int16_t x = *data++;
int16_t y = *data++;
if(x == PEN_UP && y == PEN_UP) {
if(pen_up) break;
pen_up = true;
} else {
Vector p = tt;
p = p.Plus(tu.ScaledBy(x - offsetX));
p = p.Plus(tv.ScaledBy(y));
if(!pen_up) fn(fndata, prevp, p);
prevp = p;
pen_up = false;
}
}
return advanceWidth;
}
void ssglWriteText(const std::string &str, double h, Vector t, Vector u, Vector v,
ssglLineFn *fn, void *fndata)
{
if(!fn) fn = LineDrawCallback;
u = u.WithMagnitude(1);
v = v.WithMagnitude(1);
// Perform grid-fitting only when the text is parallel to the view plane.
bool gridFit = !SS.exportMode && u.Equals(SS.GW.projRight) && v.Equals(SS.GW.projUp);
double scale = FONT_SCALE(h) / SS.GW.scale;
Vector o = { 3840.0, 3840.0, 0.0 };
for(char32_t chr : ReadUTF8(str)) {
const VectorGlyph &glyph = GetVectorGlyph(chr);
o.x += ssglDrawCharacter(glyph, t, o, u, v, scale, fn, fndata, gridFit);
}
}
void ssglVertex3v(Vector u)
{
glVertex3f((GLfloat)u.x, (GLfloat)u.y, (GLfloat)u.z);
@ -841,4 +684,139 @@ void ssglBitmapText(const std::string &str, Vector p)
glDisable(GL_TEXTURE_2D);
}
//-----------------------------------------------------------------------------
// Bitmap font rendering
//-----------------------------------------------------------------------------
static VectorFont BuiltinVectorFont;
static void LoadVectorFont() {
if(!BuiltinVectorFont.IsEmpty()) return;
BuiltinVectorFont = VectorFont::From(LoadStringFromGzip("fonts/unicode.lff.gz"));
}
// Internally and in the UI, the vector font is sized using cap height.
#define FONT_SCALE(h) ((h)/(double)BuiltinVectorFont.capHeight)
double ssglStrCapHeight(double h)
{
return BuiltinVectorFont.capHeight *
FONT_SCALE(h) / SS.GW.scale;
}
double ssglStrFontSize(double h)
{
return (BuiltinVectorFont.ascender - BuiltinVectorFont.descender) *
FONT_SCALE(h) / SS.GW.scale;
}
double ssglStrWidth(const std::string &str, double h)
{
LoadVectorFont();
int width = 0;
for(char32_t codepoint : ReadUTF8(str)) {
width += BuiltinVectorFont.GetGlyph(codepoint).advanceWidth;
}
return width * FONT_SCALE(h) / SS.GW.scale;
}
static Vector PixelAlign(Vector v) {
v = SS.GW.ProjectPoint3(v);
v.x = floor(v.x) + 0.5;
v.y = floor(v.y) + 0.5;
v = SS.GW.UnProjectPoint3(v);
return v;
}
static int DrawCharacter(const VectorFont::Glyph &glyph, Vector t, Vector o, Vector u, Vector v,
double scale, ssglLineFn *fn, void *fndata, bool gridFit) {
int advanceWidth = glyph.advanceWidth;
int actualWidth, offsetX;
if(gridFit) {
o.x += glyph.leftSideBearing;
offsetX = glyph.leftSideBearing;
actualWidth = glyph.boundingWidth;
if(actualWidth == 0) {
// Dot, "i", etc.
actualWidth = 1;
}
} else {
offsetX = 0;
actualWidth = advanceWidth;
}
Vector tt = t;
tt = tt.Plus(u.ScaledBy(o.x * scale));
tt = tt.Plus(v.ScaledBy(o.y * scale));
Vector tu = tt;
tu = tu.Plus(u.ScaledBy(actualWidth * scale));
Vector tv = tt;
tv = tv.Plus(v.ScaledBy(BuiltinVectorFont.capHeight * scale));
if(gridFit) {
tt = PixelAlign(tt);
tu = PixelAlign(tu);
tv = PixelAlign(tv);
}
tu = tu.Minus(tt).ScaledBy(1.0 / actualWidth);
tv = tv.Minus(tt).ScaledBy(1.0 / BuiltinVectorFont.capHeight);
for(const VectorFont::Contour &contour : glyph.contours) {
Vector prevp;
bool penUp = true;
for(const Point2d &pt : contour.points) {
Vector p = tt;
p = p.Plus(tu.ScaledBy(pt.x - offsetX));
p = p.Plus(tv.ScaledBy(pt.y));
if(!penUp) fn(fndata, prevp, p);
prevp = p;
penUp = false;
}
}
return advanceWidth;
}
void ssglWriteText(const std::string &str, double h, Vector t, Vector u, Vector v,
ssglLineFn *fn, void *fndata)
{
LoadVectorFont();
if(!fn) fn = LineDrawCallback;
u = u.WithMagnitude(1);
v = v.WithMagnitude(1);
// Perform grid-fitting only when the text is parallel to the view plane.
bool gridFit = !SS.exportMode && u.Equals(SS.GW.projRight) && v.Equals(SS.GW.projUp);
double scale = FONT_SCALE(h) / SS.GW.scale;
Vector o = {};
for(char32_t codepoint : ReadUTF8(str)) {
o.x += DrawCharacter(BuiltinVectorFont.GetGlyph(codepoint),
t, o, u, v, scale, fn, fndata, gridFit);
}
}
void ssglWriteTextRefCenter(const std::string &str, double h, Vector t, Vector u, Vector v,
ssglLineFn *fn, void *fndata)
{
LoadVectorFont();
u = u.WithMagnitude(1);
v = v.WithMagnitude(1);
double fh = ssglStrCapHeight(h);
double fw = ssglStrWidth(str, h);
t = t.Plus(u.ScaledBy(-fw/2));
t = t.Plus(v.ScaledBy(-fh/2));
ssglWriteText(str, h, t, u, v, fn, fndata);
}
};

View File

@ -3,9 +3,10 @@
//
// Copyright 2016 whitequark
//-----------------------------------------------------------------------------
#include "solvespace.h"
#include <zlib.h>
#include <png.h>
#include <regex>
#include "solvespace.h"
namespace SolveSpace {
@ -175,18 +176,39 @@ public:
return ASCIIReader({ str.cbegin(), str.cend() });
}
size_t LengthToEOL() {
bool AtEnd() const {
return pos == end;
}
size_t CountUntilEOL() const {
return std::find(pos, end, '\n') - pos;
}
void ReadChar(char c) {
if(pos == end) oops();
if(*pos++ != c) oops();
void SkipUntilEOL() {
pos = std::find(pos, end, '\n');
}
char ReadChar() {
if(AtEnd()) oops();
return *pos++;
}
bool TryChar(char c) {
if(AtEnd()) oops();
if(*pos == c) {
pos++;
return true;
} else {
return false;
}
}
void ExpectChar(char c) {
if(ReadChar() != c) oops();
}
uint8_t Read4HexBits() {
if(pos == end) oops();
char c = *pos++;
char c = ReadChar();
if(c >= '0' && c <= '9') {
return c - '0';
} else if(c >= 'a' && c <= 'f') {
@ -207,6 +229,23 @@ public:
l = Read8HexBits();
return (h << 8) + l;
}
double ReadDoubleString() {
char *endptr;
double d = strtod(&*pos, &endptr);
if(&*pos == endptr) oops();
pos += endptr - &*pos;
return d;
}
bool ReadRegex(const std::regex &re, std::smatch *m) {
if(std::regex_search(pos, end, *m, re, std::regex_constants::match_continuous)) {
pos = (*m)[0].second;
return true;
} else {
return false;
}
}
};
//-----------------------------------------------------------------------------
@ -273,7 +312,7 @@ const BitmapFont::Glyph &BitmapFont::GetGlyph(char32_t codepoint) {
// Read the codepoint.
ASCIIReader reader = { mid, unifontData.cend() };
char32_t foundCodepoint = reader.Read16HexBits();
reader.ReadChar(':');
reader.ExpectChar(':');
if(foundCodepoint > codepoint) {
last = mid - 1;
@ -294,7 +333,7 @@ const BitmapFont::Glyph &BitmapFont::GetGlyph(char32_t codepoint) {
// Read glyph bits.
unsigned short glyphBits[16];
int glyphLength = reader.LengthToEOL();
int glyphLength = reader.CountUntilEOL();
if(glyphLength == 4 * 16) {
glyph.advanceCells = 2;
for(size_t i = 0; i < 16; i++) {
@ -340,4 +379,201 @@ bool BitmapFont::LocateGlyph(char32_t codepoint,
return textureUpdated;
}
//-----------------------------------------------------------------------------
// Vector font manipulation
//-----------------------------------------------------------------------------
const static int ARC_POINTS = 8;
static void MakePwlArc(VectorFont::Contour *contour, bool isReversed,
const Point2d &cp, double radius, double a1, double a2) {
if (radius < LENGTH_EPS) return;
double aSign = 1.0;
if(isReversed) {
if(a1 <= a2 + LENGTH_EPS) a1 += 2.0 * M_PI;
aSign = -1.0;
} else {
if(a2 <= a1 + LENGTH_EPS) a2 += 2.0 * M_PI;
}
double aStep = aSign * fabs(a2 - a1) / (double)ARC_POINTS;
for(int i = 0; i <= ARC_POINTS; i++) {
contour->points.emplace_back(cp.Plus(Point2d::FromPolar(radius, a1 + aStep * i)));
}
}
static void MakePwlBulge(VectorFont::Contour *contour, const Point2d &v, double bulge) {
bool reversed = bulge < 0.0;
double alpha = atan(bulge) * 4.0;
const Point2d &point = contour->points.back();
Point2d middle = point.Plus(v).ScaledBy(0.5);
double dist = point.DistanceTo(v) / 2.0;
double angle = point.AngleTo(v);
// alpha can't be 0.0 at this point
double radius = fabs(dist / sin(alpha / 2.0));
double wu = fabs(radius*radius - dist*dist);
double h = sqrt(wu);
if(bulge > 0.0) {
angle += M_PI_2;
} else {
angle -= M_PI_2;
}
if (fabs(alpha) > M_PI) {
h = -h;
}
Point2d center = Point2d::FromPolar(h, angle).Plus(middle);
double a1 = center.AngleTo(point);
double a2 = center.AngleTo(v);
MakePwlArc(contour, reversed, center, radius, a1, a2);
}
static void GetGlyphBBox(const VectorFont::Glyph &glyph,
double *rminx, double *rmaxx, double *rminy, double *rmaxy) {
double minx = 0.0, maxx = 0.0, miny = 0.0, maxy = 0.0;
if(!glyph.contours.empty()) {
const Point2d &start = glyph.contours[0].points[0];
minx = maxx = start.x;
miny = maxy = start.y;
for(const VectorFont::Contour &c : glyph.contours) {
for(const Point2d &p : c.points) {
maxx = std::max(maxx, p.x);
minx = std::min(minx, p.x);
maxy = std::max(maxy, p.y);
miny = std::min(miny, p.y);
}
}
}
if(rminx) *rminx = minx;
if(rmaxx) *rmaxx = maxx;
if(rminy) *rminy = miny;
if(rmaxy) *rmaxy = maxy;
}
VectorFont VectorFont::From(std::string &&lffData) {
VectorFont font = {};
font.lffData = std::move(lffData);
ASCIIReader reader = ASCIIReader::From(font.lffData);
std::smatch m;
while(reader.ReadRegex(std::regex("#\\s*(\\w+)\\s*:\\s*(.+?)\n"), &m)) {
std::string name = m.str(1),
value = m.str(2);
std::transform(name.begin(), name.end(), name.begin(), ::tolower);
if (name == "letterspacing") {
font.rightSideBearing = std::stod(value);
} else if (name == "wordspacing") {
Glyph space = {};
space.advanceWidth = std::stod(value);
font.glyphs.emplace(' ', std::move(space));
}
}
GetGlyphBBox(font.GetGlyph('A'), nullptr, nullptr, nullptr, &font.capHeight);
GetGlyphBBox(font.GetGlyph('h'), nullptr, nullptr, nullptr, &font.ascender);
GetGlyphBBox(font.GetGlyph('p'), nullptr, nullptr, &font.descender, nullptr);
return font;
}
const VectorFont::Glyph &VectorFont::GetGlyph(char32_t codepoint) {
auto it = glyphs.find(codepoint);
if(it != glyphs.end()) {
return (*it).second;
}
auto firstGlyph = std::find(lffData.cbegin(), lffData.cend(), '[');
if(firstGlyph == lffData.cend()) oops();
// Find the serialized representation in the (sorted) lff file.
auto first = firstGlyph,
last = lffData.cend();
while(first <= last) {
auto mid = first + (last - first) / 2;
while(mid > first) {
if(*mid == '[' && *(mid - 1) == '\n') break;
mid--;
}
// Read the codepoint.
ASCIIReader reader = { mid, lffData.cend() };
reader.ExpectChar('[');
char32_t foundCodepoint = reader.Read16HexBits();
reader.ExpectChar(']');
reader.SkipUntilEOL();
if(foundCodepoint > codepoint) {
last = mid - 1;
continue; // and first stays the same
}
if(foundCodepoint < codepoint) {
first = mid + 1;
while(first != lffData.cend()) {
if(*first == '[' && *(first - 1) == '\n') break;
first++;
}
continue; // and last stays the same
}
// Found the codepoint.
VectorFont::Glyph glyph = {};
// Read glyph contours.
while(!reader.AtEnd()) {
if(reader.TryChar('\n')) {
// Skip.
} else if(reader.TryChar('[')) {
// End of glyph.
if(glyph.contours.back().points.empty()) {
// Remove an useless empty contour, if any.
glyph.contours.pop_back();
}
break;
} else if(reader.TryChar('C')) {
// Another character is referenced in this one.
char32_t baseCodepoint = reader.Read16HexBits();
const VectorFont::Glyph &baseGlyph = GetGlyph(baseCodepoint);
std::copy(baseGlyph.contours.begin(), baseGlyph.contours.end(),
std::back_inserter(glyph.contours));
} else {
Contour contour;
do {
Point2d p;
p.x = reader.ReadDoubleString();
reader.ExpectChar(',');
p.y = reader.ReadDoubleString();
if(reader.TryChar(',')) {
// Point with a bulge.
reader.ExpectChar('A');
double bulge = reader.ReadDoubleString();
MakePwlBulge(&contour, p, bulge);
} else {
// Just a point.
contour.points.emplace_back(std::move(p));
}
} while(reader.TryChar(';'));
reader.ExpectChar('\n');
glyph.contours.emplace_back(std::move(contour));
}
}
// Calculate metrics.
GetGlyphBBox(glyph, &glyph.leftSideBearing, &glyph.boundingWidth, nullptr, nullptr);
glyph.advanceWidth = glyph.leftSideBearing + glyph.boundingWidth + rightSideBearing;
it = glyphs.emplace(codepoint, std::move(glyph)).first;
return (*it).second;
}
// Glyph doesn't exist; return replacement glyph instead.
if(codepoint == 0xfffd) oops();
return GetGlyph(0xfffd);
}
}

View File

@ -7,6 +7,7 @@
#ifndef __RESOURCE_H
#define __RESOURCE_H
class Point2d;
class Pixmap;
// Only the following function is platform-specific.
@ -61,4 +62,31 @@ public:
void AddGlyph(char32_t codepoint, const Pixmap &pixmap);
};
class VectorFont {
public:
struct Contour {
std::vector<Point2d> points;
};
struct Glyph {
std::vector<Contour> contours;
double leftSideBearing;
double boundingWidth;
double advanceWidth;
};
std::string lffData;
std::map<char32_t, Glyph> glyphs;
double rightSideBearing;
double capHeight;
double ascender;
double descender;
static VectorFont From(std::string &&lffData);
bool IsEmpty() const { return lffData.empty(); }
const Glyph &GetGlyph(char32_t codepoint);
};
#endif

View File

@ -938,6 +938,19 @@ Point2d Point2d::From(double x, double y) {
return { x, y };
}
Point2d Point2d::FromPolar(double r, double a) {
return { r * cos(a), r * sin(a) };
}
double Point2d::Angle() const {
double a = atan2(y, x);
return M_PI + remainder(a - M_PI, 2 * M_PI);
}
double Point2d::AngleTo(const Point2d &p) const {
return p.Minus(*this).Angle();
}
Point2d Point2d::Plus(const Point2d &b) const {
return { x + b.x, y + b.y };
}

View File

@ -1,5 +0,0 @@
add_executable(lff2c
lff2c.cpp)
target_link_libraries(lff2c
${ZLIB_LIBRARIES})

View File

@ -1,410 +0,0 @@
#define _USE_MATH_DEFINES
#include <zlib.h>
#include <cmath>
#include <cctype>
#include <algorithm>
#include <vector>
#include <string>
#include <map>
#include <iostream>
#include <fstream>
#include <sstream>
#define TOLERANCE 1e-6
double correctAngle(double a) {
return M_PI + remainder(a - M_PI, 2 * M_PI);
}
struct Point {
double x;
double y;
Point operator+(const Point &o) const { return { x + o.x, y + o.y }; }
Point operator-(const Point &o) const { return { x - o.x, y - o.y }; }
Point operator*(const Point &o) const { return { x * o.x, y * o.y }; }
Point operator/(const Point &o) const { return { x / o.x, y / o.y }; }
Point operator*(double k) const { return { x * k, y * k }; }
Point operator/(double k) const { return { x / k, y / k }; }
double length() const{
return sqrt(x * x + y * y);
}
double angle() const {
return correctAngle(atan2(y, x));
}
double distanceTo(const Point &v) const {
return (*this - v).length();
}
double angleTo(const Point &v) const {
return (v - *this).angle();
}
static Point polar(double radius, double angle) {
return { radius * cos(angle), radius * sin(angle) };
}
bool operator==(const Point &o) const { return x == o.x && y == o.y; }
bool operator!=(const Point &o) const { return x != o.x || y != o.y; }
};
struct Curve {
std::vector<Point> points;
};
struct Glyph {
char32_t character;
char32_t baseCharacter;
std::vector<Curve> curves;
void getHorizontalBounds(double *rminx, double *rmaxx) const {
double minx = 0;
double maxx = 0;
if(!curves.empty()) {
minx = curves[0].points[0].x;
maxx = minx;
for(const Curve &c : curves) {
for(const Point &p : c.points) {
maxx = std::max(maxx, p.x);
minx = std::min(minx, p.x);
}
}
}
if(rminx) *rminx = minx;
if(rmaxx) *rmaxx = maxx;
}
void getVerticalBounds(double *rminy, double *rmaxy) const {
double miny = 0;
double maxy = 0;
if(!curves.empty()) {
miny = curves[0].points[0].y;
maxy = miny;
for(const Curve &c : curves) {
for(const Point &p : c.points) {
maxy = std::max(maxy, p.y);
miny = std::min(miny, p.y);
}
}
}
if(rminy) *rminy = miny;
if(rmaxy) *rmaxy = maxy;
}
void getHorizontalMetrics(double *leftSideBearing, double *boundingWidth) const {
double minx, maxx;
getHorizontalBounds(&minx, &maxx);
*leftSideBearing = minx;
*boundingWidth = maxx - minx;
}
bool operator<(const Glyph &o) const { return character < o.character; }
};
struct Font {
double letterSpacing;
double wordSpacing;
std::vector<Glyph> glyphs;
const Glyph &findGlyph(char32_t character) {
return *std::find_if(glyphs.begin(), glyphs.end(),
[&](const Glyph &g) { return g.character == character; });
}
void getGlyphBound(double *rminw, double *rminh, double *rmaxw, double *rmaxh) {
if(glyphs.empty()) {
*rminw = 0.0;
*rmaxw = 0.0;
*rminh = 0.0;
*rmaxh = 0.0;
return;
}
glyphs[0].getHorizontalBounds(rminw, rmaxw);
glyphs[0].getVerticalBounds(rminh, rmaxh);
for(const Glyph &g : glyphs) {
double minw, minh, maxw, maxh;
g.getHorizontalBounds(&minw, &maxw);
g.getVerticalBounds(&minh, &maxh);
*rminw = std::min(*rminw, minw);
*rminh = std::min(*rminh, minh);
*rmaxw = std::max(*rmaxw, maxw);
*rmaxh = std::max(*rmaxh, maxh);
}
}
void createArc(Curve &curve, const Point &cp, double radius,
double a1, double a2, bool reversed) {
if (radius < 1e-6) return;
double aSign = 1.0;
if(reversed) {
if(a1 <= a2 + TOLERANCE) a1 += 2.0 * M_PI;
aSign = -1.0;
} else {
if(a2 <= a1 + TOLERANCE) a2 += 2.0 * M_PI;
}
// Angle Step (rad)
double da = fabs(a2 - a1);
int numPoints = 8;
double aStep = aSign * da / double(numPoints);
for(int i = 0; i <= numPoints; i++) {
curve.points.push_back(cp + Point::polar(radius, a1 + aStep * i));
}
}
void createBulge(const Point &v, double bulge, Curve &curve) {
bool reversed = bulge < 0.0;
double alpha = atan(bulge) * 4.0;
Point &point = curve.points.back();
Point middle = (point + v) / 2.0;
double dist = point.distanceTo(v) / 2.0;
double angle = point.angleTo(v);
// alpha can't be 0.0 at this point
double radius = fabs(dist / sin(alpha / 2.0));
double wu = fabs(radius*radius - dist*dist);
double h = sqrt(wu);
if(bulge > 0.0) {
angle += M_PI_2;
} else {
angle -= M_PI_2;
}
if (fabs(alpha) > M_PI) {
h *= -1.0;
}
Point center = Point::polar(h, angle);
center = center + middle;
double a1 = center.angleTo(point);
double a2 = center.angleTo(v);
createArc(curve, center, radius, a1, a2, reversed);
}
void readLff(const std::string &path) {
gzFile lfffont = gzopen(path.c_str(), "rb");
if(!lfffont) {
std::cerr << path << ": gzopen failed" << std::endl;
std::exit(1);
}
// Read line by line until we find a new letter:
Glyph *currentGlyph = nullptr;
while(!gzeof(lfffont)) {
std::string line;
do {
char buf[128] = {0};
if(!gzgets(lfffont, buf, sizeof(buf)))
break;
line += buf;
} while(line.back() != '\n');
if(line.empty() || line[0] == '\n') {
continue;
} else if(line[0] == '#') {
// This is comment or metadata.
std::istringstream ss(line.substr(1));
std::vector<std::string> tokens;
while(!ss.eof()) {
std::string token;
std::getline(ss, token, ':');
std::istringstream(token) >> token; // trim
if(!token.empty())
tokens.push_back(token);
}
// If not in form of "a:b" then it's not metadata, just a comment.
if (tokens.size() != 2)
continue;
std::string &identifier = tokens[0];
std::string &value = tokens[1];
std::transform(identifier.begin(), identifier.end(), identifier.begin(),
::tolower);
if (identifier == "letterspacing") {
std::istringstream(value) >> letterSpacing;
} else if (identifier == "wordspacing") {
std::istringstream(value) >> wordSpacing;
} else if (identifier == "linespacingfactor") {
/* don't care */
} else if (identifier == "author") {
/* don't care */
} else if (identifier == "name") {
/* don't care */
} else if (identifier == "license") {
/* don't care */
} else if (identifier == "encoding") {
/* don't care */
} else if (identifier == "created") {
/* don't care */
}
} else if(line[0] == '[') {
// This is a glyph.
size_t closingPos;
char32_t chr = std::stoi(line.substr(1), &closingPos, 16);;
if(line[closingPos + 1] != ']') {
std::cerr << "unrecognized character number: " << line << std::endl;
currentGlyph = nullptr;
continue;
}
glyphs.emplace_back();
currentGlyph = &glyphs.back();
currentGlyph->character = chr;
currentGlyph->baseCharacter = 0;
} else if(currentGlyph != nullptr) {
if (line[0] == 'C') {
// This is a reference to another glyph.
currentGlyph->baseCharacter = std::stoi(line.substr(1), nullptr, 16);
} else {
// This is a series of curves.
currentGlyph->curves.emplace_back();
Curve &curve = currentGlyph->curves.back();
std::stringstream ss(line);
while (!ss.eof()) {
std::string vertex;
std::getline(ss, vertex, ';');
std::stringstream ssv(vertex);
std::string coord;
Point p;
if(!std::getline(ssv, coord, ',')) continue;
p.x = std::stod(coord);
if(!std::getline(ssv, coord, ',')) continue;
p.y = std::stod(coord);
if(!std::getline(ssv, coord, ',') || coord[0] != 'A') {
// This is just a point.
curve.points.push_back(p);
} else {
// This is a point with a bulge.
double bulge = std::stod(coord.substr(1));
createBulge(p, bulge, curve);
}
}
}
} else {
std::cerr << "unrecognized line: " << line << std::endl;
}
}
gzclose(lfffont);
}
void writeCppHeader(const std::string &hName) {
std::sort(glyphs.begin(), glyphs.end());
std::ofstream ts(hName, std::ios::out);
double minX, minY, maxX, maxY;
getGlyphBound(&minX, &minY, &maxX, &maxY);
double size = 32766.0;
double scale = size / std::max({ fabs(maxX), fabs(minX), fabs(maxY), fabs(minY) });
double capHeight, ascender, descender;
findGlyph('A').getVerticalBounds(nullptr, &capHeight);
findGlyph('h').getVerticalBounds(nullptr, &ascender);
findGlyph('p').getVerticalBounds(&descender, nullptr);
// We use tabs for indentation here to make compilation slightly faster
ts <<
"/**** This is a generated file - do not edit ****/\n\n"
"#ifndef __VECTORFONT_TABLE_H\n"
"#define __VECTORFONT_TABLE_H\n"
"\n"
"#define PEN_UP 32767\n"
"#define UP PEN_UP\n"
"\n"
"#define FONT_CAP_HEIGHT ((int16_t)" << (int)floor(capHeight * scale) << ")\n" <<
"#define FONT_ASCENDER ((int16_t)" << (int)floor(ascender * scale) << ")\n" <<
"#define FONT_DESCENDER ((int16_t)" << (int)floor(descender * scale) << ")\n" <<
"#define FONT_SIZE (FONT_ASCENDER-FONT_DESCENDER)\n"
"\n"
"struct VectorGlyph {\n"
"\tchar32_t character;\n"
"\tchar32_t baseCharacter;\n"
"\tint leftSideBearing;\n"
"\tint boundingWidth;\n"
"\tint advanceWidth;\n"
"\tconst int16_t *data;\n"
"};\n"
"\n"
"const int16_t VectorFontData[] = {\n"
"\tUP, UP,\n";
std::map<char32_t, size_t> glyphIndexes;
size_t index = 2;
for(const Glyph &g : glyphs) {
ts << "\t// U+" << std::hex << g.character << std::dec << "\n";
glyphIndexes[g.character] = index;
for(const Curve &c : g.curves) {
for(const Point &p : c.points) {
ts << "\t" << (int)floor(p.x * scale) << ", " <<
(int)floor(p.y * scale) << ",\n";
index += 2;
}
ts << "\tUP, UP,\n";
index += 2;
}
ts << "\tUP, UP,\n"; // end-of-glyph marker
index += 2;
}
ts <<
"};\n"
"\n"
"const VectorGlyph VectorFont[] = {\n"
"\t// U+20\n"
"\t{ 32, 0, 0, 0, " << (int)floor(wordSpacing * scale) << ", &VectorFontData[0] },\n";
for(const Glyph &g : glyphs) {
double leftSideBearing, boundingWidth;
g.getHorizontalMetrics(&leftSideBearing, &boundingWidth);
ts << "\t// U+" << std::hex << g.character << std::dec << "\n";
ts << "\t{ " << g.character << ", "
<< g.baseCharacter << ", "
<< (int)floor(leftSideBearing * scale) << ", "
<< (int)floor(boundingWidth * scale) << ", "
<< (int)floor((leftSideBearing + boundingWidth +
letterSpacing) * scale) << ", ";
ts << "&VectorFontData[" << glyphIndexes[g.character] << "] },\n";
}
ts <<
"};\n"
"\n"
"#undef UP\n"
"\n"
"#endif\n";
}
};
int main(int argc, char** argv) {
if(argc != 3) {
std::cerr << "Usage: " << argv[0] << " <header out> <lff in>\n" << std::endl;
return 1;
}
Font font;
font.readLff(argv[2]);
font.writeCppHeader(argv[1]);
return 0;
}