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;
}
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) {
if(projRight.Magnitude() < LENGTH_EPS) {
projRight = Vector::From(1, 0, 0);

View File

@ -36,20 +36,15 @@ static const VectorGlyph &GetVectorGlyph(char32_t chr) {
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.
#define FONT_SCALE(h) ((h)/22684.0)
#define FONT_SCALE(h) ((h)/(double)FONT_CAP_HEIGHT)
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)
{
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)
{
@ -58,9 +53,9 @@ double ssglStrWidth(const std::string &str, double h)
const VectorGlyph &glyph = GetVectorGlyph(chr);
if(glyph.baseCharacter != 0) {
const VectorGlyph &baseGlyph = GetVectorGlyph(glyph.baseCharacter);
width += max(glyph.width, baseGlyph.width);
width += max(glyph.advanceWidth, baseGlyph.advanceWidth);
} else {
width += glyph.width;
width += glyph.advanceWidth;
}
}
return width * FONT_SCALE(h) / SS.GW.scale;
@ -111,16 +106,57 @@ 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) {
int width = glyph.width;
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);
width = max(glyph.width, baseWidth);
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;
@ -132,16 +168,16 @@ int ssglDrawCharacter(const VectorGlyph &glyph, Vector t, Vector o, Vector u, Ve
if(pen_up) break;
pen_up = true;
} else {
Vector p = t;
p = p.Plus(u.ScaledBy((o.x + x) * scale));
p = p.Plus(v.ScaledBy((o.y + y) * scale));
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 width;
return advanceWidth;
}
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);
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 };
for(char32_t chr : ReadUTF8(str)) {
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 ProjectPoint4(Vector p, double *w);
Vector UnProjectPoint(Point2d p);
Vector UnProjectPoint3(Vector p);
void AnimateOnto(Quaternion quatf, Vector offsetf);
void AnimateOntoWorkplane(void);
Vector VectorFromProjs(Vector rightUpForward);

View File

@ -62,54 +62,45 @@ struct Glyph {
char32_t baseCharacter;
std::vector<Curve> curves;
void getBoundWidth(double *rminw, double *rmaxw) const {
if(curves.empty()) {
*rminw = 0.0;
*rmaxw = 0.0;
return;
}
double minw = curves[0].points[0].x;
double maxw = minw;
for(const Curve &c : curves) {
for(const Point &p : c.points) {
maxw = std::max(maxw, p.x);
minw = std::min(minw, p.x);
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);
}
}
}
*rminw = minw;
*rmaxw = maxw;
if(rminx) *rminx = minx;
if(rmaxx) *rmaxx = maxx;
}
void getBoundHeight(double *rminh, double *rmaxh) const {
if(curves.empty()) {
*rminh = 0.0;
*rmaxh = 0.0;
return;
}
double minh = curves[0].points[0].y;
double maxh = minh;
for(const Curve &c : curves) {
for(const Point &p : c.points) {
maxh = std::max(maxh, p.y);
minh = std::min(minh, p.y);
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);
}
}
}
*rminh = minh;
*rmaxh = maxh;
if(rminy) *rminy = miny;
if(rmaxy) *rmaxy = maxy;
}
double getWidth() const {
double maxw;
double minw;
getBoundWidth(&minw, &maxw);
return maxw - minw;
}
double getHeight() const {
double maxh;
double minh;
getBoundHeight(&minh, &maxh);
return maxh - minh;
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; }
@ -120,6 +111,11 @@ struct Font {
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;
@ -129,12 +125,12 @@ struct Font {
return;
}
glyphs[0].getBoundWidth(rminw, rmaxw);
glyphs[0].getBoundHeight(rminh, rmaxh);
glyphs[0].getHorizontalBounds(rminw, rmaxw);
glyphs[0].getVerticalBounds(rminh, rmaxh);
for(const Glyph &g : glyphs) {
double minw, minh, maxw, maxh;
g.getBoundWidth(&minw, &maxw);
g.getBoundHeight(&minh, &maxh);
g.getHorizontalBounds(&minw, &maxw);
g.getVerticalBounds(&minh, &maxh);
*rminw = std::min(*rminw, minw);
*rminh = std::min(*rminh, minh);
*rmaxw = std::max(*rmaxw, maxw);
@ -204,7 +200,7 @@ struct Font {
}
// Read line by line until we find a new letter:
Glyph *currentGlyph = NULL;
Glyph *currentGlyph = nullptr;
while(!gzeof(lfffont)) {
std::string line;
do {
@ -261,7 +257,7 @@ struct Font {
char32_t chr = std::stoi(line.substr(1), &closingPos, 16);;
if(line[closingPos + 1] != ']') {
std::cerr << "unrecognized character number: " << line << std::endl;
currentGlyph = NULL;
currentGlyph = nullptr;
continue;
}
@ -269,10 +265,10 @@ struct Font {
currentGlyph = &glyphs.back();
currentGlyph->character = chr;
currentGlyph->baseCharacter = 0;
} else if(currentGlyph != NULL) {
} else if(currentGlyph != nullptr) {
if (line[0] == 'C') {
// 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 {
// This is a series of curves.
currentGlyph->curves.emplace_back();
@ -321,6 +317,11 @@ struct Font {
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"
@ -330,10 +331,17 @@ struct Font {
"#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 width;\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"
@ -347,8 +355,8 @@ struct Font {
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";
ts << "\t" << (int)floor(p.x * scale) << ", " <<
(int)floor(p.y * scale) << ",\n";
index += 2;
}
ts << "\tUP, UP,\n";
@ -363,13 +371,19 @@ struct Font {
"\n"
"const VectorGlyph VectorFont[] = {\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) {
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((g.getWidth() + letterSpacing) * scale)) << ", ";
<< (int)floor(leftSideBearing * scale) << ", "
<< (int)floor(boundingWidth * scale) << ", "
<< (int)floor((leftSideBearing + boundingWidth +
letterSpacing) * scale) << ", ";
ts << "&VectorFontData[" << glyphIndexes[g.character] << "] },\n";
}