
Grid fitting is performed only on glyph boundaries, since glyphs include curves converted to pwl, which would be mangled by per-point grid fitting. Grid fitting is only performed when the plane in which text is laid out is parallel to the viewing plane. Grid fitting is only performed when rendering for display; there are no devices with dpi low enough for grid fitting to become profitable, and in any case we cannot predict what the dpi would be anyway.
411 lines
13 KiB
C++
411 lines
13 KiB
C++
#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;
|
|
}
|