diff --git a/CMakeLists.txt b/CMakeLists.txt index 27aca76b..fcfc96b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -183,7 +183,6 @@ endif() # components -add_subdirectory(tools) add_subdirectory(res) add_subdirectory(src) add_subdirectory(exposed) diff --git a/res/CMakeLists.txt b/res/CMakeLists.txt index db9db853..06ec0e1c 100644 --- a/res/CMakeLists.txt +++ b/res/CMakeLists.txt @@ -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 diff --git a/res/fonts/unicode.lff.gz b/res/fonts/unicode.lff.gz new file mode 100644 index 00000000..edea459c Binary files /dev/null and b/res/fonts/unicode.lff.gz differ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 58ef943e..9ec43b95 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -75,27 +75,6 @@ if(NOT WIN32) PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) endif() -# generated files - -# `$` 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 $ - ${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} $) diff --git a/src/dsc.h b/src/dsc.h index c61bbf5d..76d87b9d 100644 --- a/src/dsc.h +++ b/src/dsc.h @@ -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; diff --git a/src/fonts/unicode.lff.gz b/src/fonts/unicode.lff.gz deleted file mode 100644 index 67c78821..00000000 Binary files a/src/fonts/unicode.lff.gz and /dev/null differ diff --git a/src/glhelper.cpp b/src/glhelper.cpp index 6bf904c2..9dd1c083 100644 --- a/src/glhelper.cpp +++ b/src/glhelper.cpp @@ -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); +} + }; diff --git a/src/resource.cpp b/src/resource.cpp index 623b5a19..764a32e0 100644 --- a/src/resource.cpp +++ b/src/resource.cpp @@ -3,9 +3,10 @@ // // Copyright 2016 whitequark //----------------------------------------------------------------------------- -#include "solvespace.h" #include #include +#include +#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); +} + } diff --git a/src/resource.h b/src/resource.h index a2f888d3..96052cff 100644 --- a/src/resource.h +++ b/src/resource.h @@ -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 points; + }; + + struct Glyph { + std::vector contours; + double leftSideBearing; + double boundingWidth; + double advanceWidth; + }; + + std::string lffData; + std::map 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 diff --git a/src/util.cpp b/src/util.cpp index f1c50e4b..7a4b9420 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -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 }; } diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt deleted file mode 100644 index cfea02df..00000000 --- a/tools/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -add_executable(lff2c - lff2c.cpp) - -target_link_libraries(lff2c - ${ZLIB_LIBRARIES}) diff --git a/tools/lff2c.cpp b/tools/lff2c.cpp deleted file mode 100644 index 098fd0a0..00000000 --- a/tools/lff2c.cpp +++ /dev/null @@ -1,410 +0,0 @@ -#define _USE_MATH_DEFINES -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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 points; -}; - -struct Glyph { - char32_t character; - char32_t baseCharacter; - std::vector 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 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 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 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] << "
\n" << std::endl; - return 1; - } - - Font font; - font.readLff(argv[2]); - font.writeCppHeader(argv[1]); - - return 0; -}