Perform grid fitting on the builtin vector font.

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.
pull/4/head
EvilSpirit 2016-04-05 17:12:14 +06:00 committed by whitequark
parent bd6c4c0cbd
commit e74ccb3010
4 changed files with 141 additions and 74 deletions

View File

@ -429,6 +429,19 @@ Vector GraphicsWindow::UnProjectPoint(Point2d p) {
return orig; return orig;
} }
Vector GraphicsWindow::UnProjectPoint3(Vector p) {
p.z = p.z / (scale - p.z * SS.CameraTangent() * scale);
double w = 1 + p.z * SS.CameraTangent() * scale;
p.x *= w / scale;
p.y *= w / scale;
Vector orig = offset.ScaledBy(-1);
orig = orig.Plus(projRight.ScaledBy(p.x)).Plus(
projUp. ScaledBy(p.y).Plus(
projRight.Cross(projUp). ScaledBy(p.z)));
return orig;
}
void GraphicsWindow::NormalizeProjectionVectors(void) { void GraphicsWindow::NormalizeProjectionVectors(void) {
if(projRight.Magnitude() < LENGTH_EPS) { if(projRight.Magnitude() < LENGTH_EPS) {
projRight = Vector::From(1, 0, 0); projRight = Vector::From(1, 0, 0);

View File

@ -36,20 +36,15 @@ static const VectorGlyph &GetVectorGlyph(char32_t chr) {
return GetVectorGlyph(0xfffd); // replacement character return GetVectorGlyph(0xfffd); // replacement character
} }
// The internal font metrics are as follows:
// * Cap height (measured on "A"): 22684
// * Ascender (measured on "h"): 22684
// * Descender (measured on "p"): -7562
// * Font size (ascender+descender): 30246
// Internally and in the UI, the vector font is sized using cap height. // Internally and in the UI, the vector font is sized using cap height.
#define FONT_SCALE(h) ((h)/22684.0) #define FONT_SCALE(h) ((h)/(double)FONT_CAP_HEIGHT)
double ssglStrCapHeight(double h) double ssglStrCapHeight(double h)
{ {
return /*cap height*/22684.0 * FONT_SCALE(h) / SS.GW.scale; return FONT_CAP_HEIGHT * FONT_SCALE(h) / SS.GW.scale;
} }
double ssglStrFontSize(double h) double ssglStrFontSize(double h)
{ {
return /*font size*/30246.0 * FONT_SCALE(h) / SS.GW.scale; return FONT_SIZE * FONT_SCALE(h) / SS.GW.scale;
} }
double ssglStrWidth(const std::string &str, double h) double ssglStrWidth(const std::string &str, double h)
{ {
@ -58,9 +53,9 @@ double ssglStrWidth(const std::string &str, double h)
const VectorGlyph &glyph = GetVectorGlyph(chr); const VectorGlyph &glyph = GetVectorGlyph(chr);
if(glyph.baseCharacter != 0) { if(glyph.baseCharacter != 0) {
const VectorGlyph &baseGlyph = GetVectorGlyph(glyph.baseCharacter); const VectorGlyph &baseGlyph = GetVectorGlyph(glyph.baseCharacter);
width += max(glyph.width, baseGlyph.width); width += max(glyph.advanceWidth, baseGlyph.advanceWidth);
} else { } else {
width += glyph.width; width += glyph.advanceWidth;
} }
} }
return width * FONT_SCALE(h) / SS.GW.scale; return width * FONT_SCALE(h) / SS.GW.scale;
@ -111,16 +106,57 @@ static void LineDrawCallback(void *fndata, Vector a, Vector b)
glEnd(); 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, int ssglDrawCharacter(const VectorGlyph &glyph, Vector t, Vector o, Vector u, Vector v,
double scale, ssglLineFn *fn, void *fndata) { double scale, ssglLineFn *fn, void *fndata, bool gridFit) {
int width = glyph.width; int advanceWidth = glyph.advanceWidth;
if(glyph.baseCharacter != 0) { if(glyph.baseCharacter != 0) {
const VectorGlyph &baseGlyph = GetVectorGlyph(glyph.baseCharacter); const VectorGlyph &baseGlyph = GetVectorGlyph(glyph.baseCharacter);
int baseWidth = ssglDrawCharacter(baseGlyph, t, o, u, v, scale, fn, fndata); int baseWidth = ssglDrawCharacter(baseGlyph, t, o, u, v, scale, fn, fndata, gridFit);
width = max(glyph.width, baseWidth); 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; const int16_t *data = glyph.data;
bool pen_up = true; bool pen_up = true;
Vector prevp; Vector prevp;
@ -132,16 +168,16 @@ int ssglDrawCharacter(const VectorGlyph &glyph, Vector t, Vector o, Vector u, Ve
if(pen_up) break; if(pen_up) break;
pen_up = true; pen_up = true;
} else { } else {
Vector p = t; Vector p = tt;
p = p.Plus(u.ScaledBy((o.x + x) * scale)); p = p.Plus(tu.ScaledBy(x - offsetX));
p = p.Plus(v.ScaledBy((o.y + y) * scale)); p = p.Plus(tv.ScaledBy(y));
if(!pen_up) fn(fndata, prevp, p); if(!pen_up) fn(fndata, prevp, p);
prevp = p; prevp = p;
pen_up = false; pen_up = false;
} }
} }
return width; return advanceWidth;
} }
void ssglWriteText(const std::string &str, double h, Vector t, Vector u, Vector v, void ssglWriteText(const std::string &str, double h, Vector t, Vector u, Vector v,
@ -151,11 +187,14 @@ void ssglWriteText(const std::string &str, double h, Vector t, Vector u, Vector
u = u.WithMagnitude(1); u = u.WithMagnitude(1);
v = v.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; double scale = FONT_SCALE(h) / SS.GW.scale;
Vector o = { 3840.0, 3840.0 }; Vector o = { 3840.0, 3840.0 };
for(char32_t chr : ReadUTF8(str)) { for(char32_t chr : ReadUTF8(str)) {
const VectorGlyph &glyph = GetVectorGlyph(chr); const VectorGlyph &glyph = GetVectorGlyph(chr);
o.x += ssglDrawCharacter(glyph, t, o, u, v, scale, fn, fndata); o.x += ssglDrawCharacter(glyph, t, o, u, v, scale, fn, fndata, gridFit);
} }
} }

View File

@ -514,6 +514,7 @@ public:
Vector ProjectPoint3(Vector p); Vector ProjectPoint3(Vector p);
Vector ProjectPoint4(Vector p, double *w); Vector ProjectPoint4(Vector p, double *w);
Vector UnProjectPoint(Point2d p); Vector UnProjectPoint(Point2d p);
Vector UnProjectPoint3(Vector p);
void AnimateOnto(Quaternion quatf, Vector offsetf); void AnimateOnto(Quaternion quatf, Vector offsetf);
void AnimateOntoWorkplane(void); void AnimateOntoWorkplane(void);
Vector VectorFromProjs(Vector rightUpForward); Vector VectorFromProjs(Vector rightUpForward);

View File

@ -62,54 +62,45 @@ struct Glyph {
char32_t baseCharacter; char32_t baseCharacter;
std::vector<Curve> curves; std::vector<Curve> curves;
void getBoundWidth(double *rminw, double *rmaxw) const { void getHorizontalBounds(double *rminx, double *rmaxx) const {
if(curves.empty()) { double minx = 0;
*rminw = 0.0; double maxx = 0;
*rmaxw = 0.0; if(!curves.empty()) {
return; minx = curves[0].points[0].x;
} maxx = minx;
double minw = curves[0].points[0].x;
double maxw = minw;
for(const Curve &c : curves) { for(const Curve &c : curves) {
for(const Point &p : c.points) { for(const Point &p : c.points) {
maxw = std::max(maxw, p.x); maxx = std::max(maxx, p.x);
minw = std::min(minw, p.x); minx = std::min(minx, p.x);
} }
} }
*rminw = minw; }
*rmaxw = maxw; if(rminx) *rminx = minx;
if(rmaxx) *rmaxx = maxx;
} }
void getBoundHeight(double *rminh, double *rmaxh) const { void getVerticalBounds(double *rminy, double *rmaxy) const {
if(curves.empty()) { double miny = 0;
*rminh = 0.0; double maxy = 0;
*rmaxh = 0.0; if(!curves.empty()) {
return; miny = curves[0].points[0].y;
} maxy = miny;
double minh = curves[0].points[0].y;
double maxh = minh;
for(const Curve &c : curves) { for(const Curve &c : curves) {
for(const Point &p : c.points) { for(const Point &p : c.points) {
maxh = std::max(maxh, p.y); maxy = std::max(maxy, p.y);
minh = std::min(minh, p.y); miny = std::min(miny, p.y);
} }
} }
*rminh = minh; }
*rmaxh = maxh; if(rminy) *rminy = miny;
if(rmaxy) *rmaxy = maxy;
} }
double getWidth() const { void getHorizontalMetrics(double *leftSideBearing, double *boundingWidth) const {
double maxw; double minx, maxx;
double minw; getHorizontalBounds(&minx, &maxx);
getBoundWidth(&minw, &maxw); *leftSideBearing = minx;
return maxw - minw; *boundingWidth = maxx - minx;
}
double getHeight() const {
double maxh;
double minh;
getBoundHeight(&minh, &maxh);
return maxh - minh;
} }
bool operator<(const Glyph &o) const { return character < o.character; } bool operator<(const Glyph &o) const { return character < o.character; }
@ -120,6 +111,11 @@ struct Font {
double wordSpacing; double wordSpacing;
std::vector<Glyph> glyphs; 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) { void getGlyphBound(double *rminw, double *rminh, double *rmaxw, double *rmaxh) {
if(glyphs.empty()) { if(glyphs.empty()) {
*rminw = 0.0; *rminw = 0.0;
@ -129,12 +125,12 @@ struct Font {
return; return;
} }
glyphs[0].getBoundWidth(rminw, rmaxw); glyphs[0].getHorizontalBounds(rminw, rmaxw);
glyphs[0].getBoundHeight(rminh, rmaxh); glyphs[0].getVerticalBounds(rminh, rmaxh);
for(const Glyph &g : glyphs) { for(const Glyph &g : glyphs) {
double minw, minh, maxw, maxh; double minw, minh, maxw, maxh;
g.getBoundWidth(&minw, &maxw); g.getHorizontalBounds(&minw, &maxw);
g.getBoundHeight(&minh, &maxh); g.getVerticalBounds(&minh, &maxh);
*rminw = std::min(*rminw, minw); *rminw = std::min(*rminw, minw);
*rminh = std::min(*rminh, minh); *rminh = std::min(*rminh, minh);
*rmaxw = std::max(*rmaxw, maxw); *rmaxw = std::max(*rmaxw, maxw);
@ -204,7 +200,7 @@ struct Font {
} }
// Read line by line until we find a new letter: // Read line by line until we find a new letter:
Glyph *currentGlyph = NULL; Glyph *currentGlyph = nullptr;
while(!gzeof(lfffont)) { while(!gzeof(lfffont)) {
std::string line; std::string line;
do { do {
@ -261,7 +257,7 @@ struct Font {
char32_t chr = std::stoi(line.substr(1), &closingPos, 16);; char32_t chr = std::stoi(line.substr(1), &closingPos, 16);;
if(line[closingPos + 1] != ']') { if(line[closingPos + 1] != ']') {
std::cerr << "unrecognized character number: " << line << std::endl; std::cerr << "unrecognized character number: " << line << std::endl;
currentGlyph = NULL; currentGlyph = nullptr;
continue; continue;
} }
@ -269,10 +265,10 @@ struct Font {
currentGlyph = &glyphs.back(); currentGlyph = &glyphs.back();
currentGlyph->character = chr; currentGlyph->character = chr;
currentGlyph->baseCharacter = 0; currentGlyph->baseCharacter = 0;
} else if(currentGlyph != NULL) { } else if(currentGlyph != nullptr) {
if (line[0] == 'C') { if (line[0] == 'C') {
// This is a reference to another glyph. // This is a reference to another glyph.
currentGlyph->baseCharacter = std::stoi(line.substr(1), NULL, 16); currentGlyph->baseCharacter = std::stoi(line.substr(1), nullptr, 16);
} else { } else {
// This is a series of curves. // This is a series of curves.
currentGlyph->curves.emplace_back(); currentGlyph->curves.emplace_back();
@ -321,6 +317,11 @@ struct Font {
double size = 32766.0; double size = 32766.0;
double scale = size / std::max({ fabs(maxX), fabs(minX), fabs(maxY), fabs(minY) }); 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 // We use tabs for indentation here to make compilation slightly faster
ts << ts <<
"/**** This is a generated file - do not edit ****/\n\n" "/**** This is a generated file - do not edit ****/\n\n"
@ -330,10 +331,17 @@ struct Font {
"#define PEN_UP 32767\n" "#define PEN_UP 32767\n"
"#define UP PEN_UP\n" "#define UP PEN_UP\n"
"\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" "struct VectorGlyph {\n"
"\tchar32_t character;\n" "\tchar32_t character;\n"
"\tchar32_t baseCharacter;\n" "\tchar32_t baseCharacter;\n"
"\tint width;\n" "\tint leftSideBearing;\n"
"\tint boundingWidth;\n"
"\tint advanceWidth;\n"
"\tconst int16_t *data;\n" "\tconst int16_t *data;\n"
"};\n" "};\n"
"\n" "\n"
@ -347,8 +355,8 @@ struct Font {
glyphIndexes[g.character] = index; glyphIndexes[g.character] = index;
for(const Curve &c : g.curves) { for(const Curve &c : g.curves) {
for(const Point &p : c.points) { for(const Point &p : c.points) {
ts << "\t" << int(floor(p.x * scale)) << ", " << ts << "\t" << (int)floor(p.x * scale) << ", " <<
int(floor(p.y * scale)) << ",\n"; (int)floor(p.y * scale) << ",\n";
index += 2; index += 2;
} }
ts << "\tUP, UP,\n"; ts << "\tUP, UP,\n";
@ -363,13 +371,19 @@ struct Font {
"\n" "\n"
"const VectorGlyph VectorFont[] = {\n" "const VectorGlyph VectorFont[] = {\n"
"\t// U+20\n" "\t// U+20\n"
"\t{ 32, 0, " << int(floor(wordSpacing * scale)) << ", &VectorFontData[0] },\n"; "\t{ 32, 0, 0, 0, " << (int)floor(wordSpacing * scale) << ", &VectorFontData[0] },\n";
for(const Glyph &g : glyphs) { for(const Glyph &g : glyphs) {
double leftSideBearing, boundingWidth;
g.getHorizontalMetrics(&leftSideBearing, &boundingWidth);
ts << "\t// U+" << std::hex << g.character << std::dec << "\n"; ts << "\t// U+" << std::hex << g.character << std::dec << "\n";
ts << "\t{ " << g.character << ", " ts << "\t{ " << g.character << ", "
<< g.baseCharacter << ", " << g.baseCharacter << ", "
<< int(floor((g.getWidth() + letterSpacing) * scale)) << ", "; << (int)floor(leftSideBearing * scale) << ", "
<< (int)floor(boundingWidth * scale) << ", "
<< (int)floor((leftSideBearing + boundingWidth +
letterSpacing) * scale) << ", ";
ts << "&VectorFontData[" << glyphIndexes[g.character] << "] },\n"; ts << "&VectorFontData[" << glyphIndexes[g.character] << "] },\n";
} }