diff --git a/src/draw.cpp b/src/draw.cpp index 268e5a41..b4fdbc1d 100644 --- a/src/draw.cpp +++ b/src/draw.cpp @@ -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); diff --git a/src/glhelper.cpp b/src/glhelper.cpp index a46d56c1..a629a8e7 100644 --- a/src/glhelper.cpp +++ b/src/glhelper.cpp @@ -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); } } diff --git a/src/ui.h b/src/ui.h index 9762f315..1c5e07f2 100644 --- a/src/ui.h +++ b/src/ui.h @@ -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); diff --git a/tools/lff2c.cpp b/tools/lff2c.cpp index a38ac1e6..098fd0a0 100644 --- a/tools/lff2c.cpp +++ b/tools/lff2c.cpp @@ -62,54 +62,45 @@ struct Glyph { char32_t baseCharacter; std::vector 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 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"; }