Browse Source

Fix for incorrect font coordinate scaling

Chlumsky 1 year ago
parent
commit
bc9f02e156
3 changed files with 143 additions and 62 deletions
  1. 61 37
      ext/import-font.cpp
  2. 22 7
      ext/import-font.h
  3. 60 18
      main.cpp

+ 61 - 37
ext/import-font.cpp

@@ -12,7 +12,6 @@
 
 namespace msdfgen {
 
-#define F26DOT6_TO_DOUBLE(x) (1/64.*double(x))
 #define F16DOT16_TO_DOUBLE(x) (1/65536.*double(x))
 #define DOUBLE_TO_F16DOT16(x) FT_Fixed(65536.*x)
 
@@ -35,14 +34,14 @@ class FontHandle {
     friend FontHandle *loadFont(FreetypeHandle *library, const char *filename);
     friend FontHandle *loadFontData(FreetypeHandle *library, const byte *data, int length);
     friend void destroyFont(FontHandle *font);
-    friend bool getFontMetrics(FontMetrics &metrics, FontHandle *font);
-    friend bool getFontWhitespaceWidth(double &spaceAdvance, double &tabAdvance, FontHandle *font);
+    friend bool getFontMetrics(FontMetrics &metrics, FontHandle *font, FontCoordinateScaling coordinateScaling);
+    friend bool getFontWhitespaceWidth(double &spaceAdvance, double &tabAdvance, FontHandle *font, FontCoordinateScaling coordinateScaling);
     friend bool getGlyphCount(unsigned &output, FontHandle *font);
     friend bool getGlyphIndex(GlyphIndex &glyphIndex, FontHandle *font, unicode_t unicode);
-    friend bool loadGlyph(Shape &output, FontHandle *font, GlyphIndex glyphIndex, double *advance);
-    friend bool loadGlyph(Shape &output, FontHandle *font, unicode_t unicode, double *advance);
-    friend bool getKerning(double &output, FontHandle *font, GlyphIndex glyphIndex0, GlyphIndex glyphIndex1);
-    friend bool getKerning(double &output, FontHandle *font, unicode_t unicode0, unicode_t unicode1);
+    friend bool loadGlyph(Shape &output, FontHandle *font, GlyphIndex glyphIndex, FontCoordinateScaling coordinateScaling, double *outAdvance);
+    friend bool loadGlyph(Shape &output, FontHandle *font, unicode_t unicode, FontCoordinateScaling coordinateScaling, double *outAdvance);
+    friend bool getKerning(double &output, FontHandle *font, GlyphIndex glyphIndex0, GlyphIndex glyphIndex1, FontCoordinateScaling coordinateScaling);
+    friend bool getKerning(double &output, FontHandle *font, unicode_t unicode0, unicode_t unicode1, FontCoordinateScaling coordinateScaling);
 #ifndef MSDFGEN_DISABLE_VARIABLE_FONTS
     friend bool setFontVariationAxis(FreetypeHandle *library, FontHandle *font, const char *name, double coordinate);
     friend bool listFontVariationAxes(std::vector<FontVariationAxis> &axes, FreetypeHandle *library, FontHandle *font);
@@ -54,26 +53,27 @@ class FontHandle {
 };
 
 struct FtContext {
+    double scale;
     Point2 position;
     Shape *shape;
     Contour *contour;
 };
 
-static Point2 ftPoint2(const FT_Vector &vector) {
-    return Point2(F26DOT6_TO_DOUBLE(vector.x), F26DOT6_TO_DOUBLE(vector.y));
+static Point2 ftPoint2(const FT_Vector &vector, double scale) {
+    return Point2(scale*vector.x, scale*vector.y);
 }
 
 static int ftMoveTo(const FT_Vector *to, void *user) {
     FtContext *context = reinterpret_cast<FtContext *>(user);
     if (!(context->contour && context->contour->edges.empty()))
         context->contour = &context->shape->addContour();
-    context->position = ftPoint2(*to);
+    context->position = ftPoint2(*to, context->scale);
     return 0;
 }
 
 static int ftLineTo(const FT_Vector *to, void *user) {
     FtContext *context = reinterpret_cast<FtContext *>(user);
-    Point2 endpoint = ftPoint2(*to);
+    Point2 endpoint = ftPoint2(*to, context->scale);
     if (endpoint != context->position) {
         context->contour->addEdge(EdgeHolder(context->position, endpoint));
         context->position = endpoint;
@@ -83,9 +83,9 @@ static int ftLineTo(const FT_Vector *to, void *user) {
 
 static int ftConicTo(const FT_Vector *control, const FT_Vector *to, void *user) {
     FtContext *context = reinterpret_cast<FtContext *>(user);
-    Point2 endpoint = ftPoint2(*to);
+    Point2 endpoint = ftPoint2(*to, context->scale);
     if (endpoint != context->position) {
-        context->contour->addEdge(EdgeHolder(context->position, ftPoint2(*control), endpoint));
+        context->contour->addEdge(EdgeHolder(context->position, ftPoint2(*control, context->scale), endpoint));
         context->position = endpoint;
     }
     return 0;
@@ -93,14 +93,26 @@ static int ftConicTo(const FT_Vector *control, const FT_Vector *to, void *user)
 
 static int ftCubicTo(const FT_Vector *control1, const FT_Vector *control2, const FT_Vector *to, void *user) {
     FtContext *context = reinterpret_cast<FtContext *>(user);
-    Point2 endpoint = ftPoint2(*to);
-    if (endpoint != context->position || crossProduct(ftPoint2(*control1)-endpoint, ftPoint2(*control2)-endpoint)) {
-        context->contour->addEdge(EdgeHolder(context->position, ftPoint2(*control1), ftPoint2(*control2), endpoint));
+    Point2 endpoint = ftPoint2(*to, context->scale);
+    if (endpoint != context->position || crossProduct(ftPoint2(*control1, context->scale)-endpoint, ftPoint2(*control2, context->scale)-endpoint)) {
+        context->contour->addEdge(EdgeHolder(context->position, ftPoint2(*control1, context->scale), ftPoint2(*control2, context->scale), endpoint));
         context->position = endpoint;
     }
     return 0;
 }
 
+static double getFontCoordinateScale(const FT_Face &face, FontCoordinateScaling coordinateScaling) {
+    switch (coordinateScaling) {
+        case FontCoordinateScaling::LEGACY:
+            return MSDFGEN_LEGACY_FONT_COORDINATE_SCALE;
+        case FontCoordinateScaling::KEEP_INTEGERS:
+            return 1;
+        case FontCoordinateScaling::EM_NORMALIZED:
+            return 1./(face->units_per_EM ? face->units_per_EM : 1);
+    }
+    return 1;
+}
+
 GlyphIndex::GlyphIndex(unsigned index) : index(index) { }
 
 unsigned GlyphIndex::getIndex() const {
@@ -129,10 +141,11 @@ FontHandle *adoptFreetypeFont(FT_Face ftFace) {
     return handle;
 }
 
-FT_Error readFreetypeOutline(Shape &output, FT_Outline *outline) {
+FT_Error readFreetypeOutline(Shape &output, FT_Outline *outline, double scale) {
     output.contours.clear();
     output.inverseYAxis = false;
     FtContext context = { };
+    context.scale = scale;
     context.shape = &output;
     FT_Outline_Funcs ftFunctions;
     ftFunctions.move_to = &ftMoveTo;
@@ -179,25 +192,27 @@ void destroyFont(FontHandle *font) {
     delete font;
 }
 
-bool getFontMetrics(FontMetrics &metrics, FontHandle *font) {
-    metrics.emSize = F26DOT6_TO_DOUBLE(font->face->units_per_EM);
-    metrics.ascenderY = F26DOT6_TO_DOUBLE(font->face->ascender);
-    metrics.descenderY = F26DOT6_TO_DOUBLE(font->face->descender);
-    metrics.lineHeight = F26DOT6_TO_DOUBLE(font->face->height);
-    metrics.underlineY = F26DOT6_TO_DOUBLE(font->face->underline_position);
-    metrics.underlineThickness = F26DOT6_TO_DOUBLE(font->face->underline_thickness);
+bool getFontMetrics(FontMetrics &metrics, FontHandle *font, FontCoordinateScaling coordinateScaling) {
+    double scale = getFontCoordinateScale(font->face, coordinateScaling);
+    metrics.emSize = scale*font->face->units_per_EM;
+    metrics.ascenderY = scale*font->face->ascender;
+    metrics.descenderY = scale*font->face->descender;
+    metrics.lineHeight = scale*font->face->height;
+    metrics.underlineY = scale*font->face->underline_position;
+    metrics.underlineThickness = scale*font->face->underline_thickness;
     return true;
 }
 
-bool getFontWhitespaceWidth(double &spaceAdvance, double &tabAdvance, FontHandle *font) {
+bool getFontWhitespaceWidth(double &spaceAdvance, double &tabAdvance, FontHandle *font, FontCoordinateScaling coordinateScaling) {
+    double scale = getFontCoordinateScale(font->face, coordinateScaling);
     FT_Error error = FT_Load_Char(font->face, ' ', FT_LOAD_NO_SCALE);
     if (error)
         return false;
-    spaceAdvance = F26DOT6_TO_DOUBLE(font->face->glyph->advance.x);
+    spaceAdvance = scale*font->face->glyph->advance.x;
     error = FT_Load_Char(font->face, '\t', FT_LOAD_NO_SCALE);
     if (error)
         return false;
-    tabAdvance = F26DOT6_TO_DOUBLE(font->face->glyph->advance.x);
+    tabAdvance = scale*font->face->glyph->advance.x;
     return true;
 }
 
@@ -211,33 +226,42 @@ bool getGlyphIndex(GlyphIndex &glyphIndex, FontHandle *font, unicode_t unicode)
     return glyphIndex.getIndex() != 0;
 }
 
-bool loadGlyph(Shape &output, FontHandle *font, GlyphIndex glyphIndex, double *advance) {
+bool loadGlyph(Shape &output, FontHandle *font, GlyphIndex glyphIndex, FontCoordinateScaling coordinateScaling, double *outAdvance) {
     if (!font)
         return false;
     FT_Error error = FT_Load_Glyph(font->face, glyphIndex.getIndex(), FT_LOAD_NO_SCALE);
     if (error)
         return false;
-    if (advance)
-        *advance = F26DOT6_TO_DOUBLE(font->face->glyph->advance.x);
-    return !readFreetypeOutline(output, &font->face->glyph->outline);
+    double scale = getFontCoordinateScale(font->face, coordinateScaling);
+    if (outAdvance)
+        *outAdvance = scale*font->face->glyph->advance.x;
+    return !readFreetypeOutline(output, &font->face->glyph->outline, scale);
+}
+
+bool loadGlyph(Shape &output, FontHandle *font, unicode_t unicode, FontCoordinateScaling coordinateScaling, double *outAdvance) {
+    return loadGlyph(output, font, GlyphIndex(FT_Get_Char_Index(font->face, unicode)), coordinateScaling, outAdvance);
+}
+
+bool loadGlyph(Shape &output, FontHandle *font, GlyphIndex glyphIndex, double *outAdvance) {
+    return loadGlyph(output, font, glyphIndex, FontCoordinateScaling::LEGACY, outAdvance);
 }
 
-bool loadGlyph(Shape &output, FontHandle *font, unicode_t unicode, double *advance) {
-    return loadGlyph(output, font, GlyphIndex(FT_Get_Char_Index(font->face, unicode)), advance);
+bool loadGlyph(Shape &output, FontHandle *font, unicode_t unicode, double *outAdvance) {
+    return loadGlyph(output, font, unicode, FontCoordinateScaling::LEGACY, outAdvance);
 }
 
-bool getKerning(double &output, FontHandle *font, GlyphIndex glyphIndex0, GlyphIndex glyphIndex1) {
+bool getKerning(double &output, FontHandle *font, GlyphIndex glyphIndex0, GlyphIndex glyphIndex1, FontCoordinateScaling coordinateScaling) {
     FT_Vector kerning;
     if (FT_Get_Kerning(font->face, glyphIndex0.getIndex(), glyphIndex1.getIndex(), FT_KERNING_UNSCALED, &kerning)) {
         output = 0;
         return false;
     }
-    output = F26DOT6_TO_DOUBLE(kerning.x);
+    output = getFontCoordinateScale(font->face, coordinateScaling)*kerning.x;
     return true;
 }
 
-bool getKerning(double &output, FontHandle *font, unicode_t unicode0, unicode_t unicode1) {
-    return getKerning(output, font, GlyphIndex(FT_Get_Char_Index(font->face, unicode0)), GlyphIndex(FT_Get_Char_Index(font->face, unicode1)));
+bool getKerning(double &output, FontHandle *font, unicode_t unicode0, unicode_t unicode1, FontCoordinateScaling coordinateScaling) {
+    return getKerning(output, font, GlyphIndex(FT_Get_Char_Index(font->face, unicode0)), GlyphIndex(FT_Get_Char_Index(font->face, unicode1)), coordinateScaling);
 }
 
 #ifndef MSDFGEN_DISABLE_VARIABLE_FONTS

+ 22 - 7
ext/import-font.h

@@ -5,6 +5,8 @@
 
 namespace msdfgen {
 
+#define MSDFGEN_LEGACY_FONT_COORDINATE_SCALE (1/64.)
+
 typedef unsigned unicode_t;
 
 class FreetypeHandle;
@@ -45,6 +47,16 @@ struct FontVariationAxis {
     double defaultValue;
 };
 
+/// The scaling applied to font glyph coordinates when loading a glyph
+enum class FontCoordinateScaling {
+    /// The incorrect legacy version that was in effect before version 1.12, coordinate values are divided by 64
+    LEGACY,
+    /// The coordinates are kept as the integer values native to the font file
+    KEEP_INTEGERS,
+    /// The coordinates will be normalized to the em size, i.e. 1 = 1 em
+    EM_NORMALIZED
+};
+
 /// Initializes the FreeType library.
 FreetypeHandle *initializeFreetype();
 /// Deinitializes the FreeType library.
@@ -54,7 +66,7 @@ void deinitializeFreetype(FreetypeHandle *library);
 /// Creates a FontHandle from FT_Face that was loaded by the user. destroyFont must still be called but will not affect the FT_Face.
 FontHandle *adoptFreetypeFont(FT_Face ftFace);
 /// Converts the geometry of FreeType's FT_Outline to a Shape object.
-FT_Error readFreetypeOutline(Shape &output, FT_Outline *outline);
+FT_Error readFreetypeOutline(Shape &output, FT_Outline *outline, double scale = MSDFGEN_LEGACY_FONT_COORDINATE_SCALE);
 #endif
 
 /// Loads a font file and returns its handle.
@@ -64,19 +76,22 @@ FontHandle *loadFontData(FreetypeHandle *library, const byte *data, int length);
 /// Unloads a font.
 void destroyFont(FontHandle *font);
 /// Outputs the metrics of a font.
-bool getFontMetrics(FontMetrics &metrics, FontHandle *font);
+bool getFontMetrics(FontMetrics &metrics, FontHandle *font, FontCoordinateScaling coordinateScaling = FontCoordinateScaling::LEGACY);
 /// Outputs the width of the space and tab characters.
-bool getFontWhitespaceWidth(double &spaceAdvance, double &tabAdvance, FontHandle *font);
+bool getFontWhitespaceWidth(double &spaceAdvance, double &tabAdvance, FontHandle *font, FontCoordinateScaling coordinateScaling = FontCoordinateScaling::LEGACY);
 /// Outputs the total number of glyphs available in the font.
 bool getGlyphCount(unsigned &output, FontHandle *font);
 /// Outputs the glyph index corresponding to the specified Unicode character.
 bool getGlyphIndex(GlyphIndex &glyphIndex, FontHandle *font, unicode_t unicode);
 /// Loads the geometry of a glyph from a font.
-bool loadGlyph(Shape &output, FontHandle *font, GlyphIndex glyphIndex, double *advance = NULL);
-bool loadGlyph(Shape &output, FontHandle *font, unicode_t unicode, double *advance = NULL);
+bool loadGlyph(Shape &output, FontHandle *font, GlyphIndex glyphIndex, FontCoordinateScaling coordinateScaling, double *outAdvance = NULL);
+bool loadGlyph(Shape &output, FontHandle *font, unicode_t unicode, FontCoordinateScaling coordinateScaling, double *outAdvance = NULL);
+// Legacy API - FontCoordinateScaling is LEGACY
+bool loadGlyph(Shape &output, FontHandle *font, GlyphIndex glyphIndex, double *outAdvance = NULL);
+bool loadGlyph(Shape &output, FontHandle *font, unicode_t unicode, double *outAdvance = NULL);
 /// Outputs the kerning distance adjustment between two specific glyphs.
-bool getKerning(double &output, FontHandle *font, GlyphIndex glyphIndex0, GlyphIndex glyphIndex1);
-bool getKerning(double &output, FontHandle *font, unicode_t unicode0, unicode_t unicode1);
+bool getKerning(double &output, FontHandle *font, GlyphIndex glyphIndex0, GlyphIndex glyphIndex1, FontCoordinateScaling coordinateScaling = FontCoordinateScaling::LEGACY);
+bool getKerning(double &output, FontHandle *font, unicode_t unicode0, unicode_t unicode1, FontCoordinateScaling coordinateScaling = FontCoordinateScaling::LEGACY);
 
 #ifndef MSDFGEN_DISABLE_VARIABLE_FONTS
 /// Sets a single variation axis of a variable font.

+ 60 - 18
main.cpp

@@ -402,6 +402,10 @@ static const char *const helpText =
         "\tSets the dimensions of the output image.\n"
     "  -edgecolors <sequence>\n"
         "\tOverrides automatic edge coloring with the specified color sequence.\n"
+#ifdef MSDFGEN_EXTENSIONS
+    "  -emnormalize\n"
+        "\tBefore applying scale, normalizes font glyph coordinates so that 1 = 1 em.\n"
+#endif
     "  -errorcorrection <mode>\n"
         "\tChanges the MSDF/MTSDF error correction mode. Use -errorcorrection help for a list of valid modes.\n"
     "  -errordeviationratio <ratio>\n"
@@ -426,6 +430,10 @@ static const char *const helpText =
         "\tDisplays this help.\n"
     "  -legacy\n"
         "\tUses the original (legacy) distance field algorithms.\n"
+#ifdef MSDFGEN_EXTENSIONS
+    "  -noemnormalize\n"
+        "\tRaw integer font glyph coordinates will be used. Without this option, legacy scaling will be applied.\n"
+#endif
 #ifdef MSDFGEN_USE_SKIA
     "  -nopreprocess\n"
         "\tDisables path preprocessing which resolves self-intersections and overlapping contours.\n"
@@ -547,6 +555,8 @@ int main(int argc, const char *const *argv) {
     bool glyphIndexSpecified = false;
     GlyphIndex glyphIndex;
     unicode_t unicode = 0;
+    FontCoordinateScaling fontCoordinateScaling = FontCoordinateScaling::LEGACY;
+    bool fontCoordinateScalingSpecified = false;
 #endif
 
     int width = 64, height = 64;
@@ -631,6 +641,21 @@ int main(int argc, const char *const *argv) {
             }
             continue;
         }
+        ARG_CASE("-noemnormalize", 0) {
+            fontCoordinateScaling = FontCoordinateScaling::KEEP_INTEGERS;
+            fontCoordinateScalingSpecified = true;
+            continue;
+        }
+        ARG_CASE("-emnormalize", 0) {
+            fontCoordinateScaling = FontCoordinateScaling::EM_NORMALIZED;
+            fontCoordinateScalingSpecified = true;
+            continue;
+        }
+        ARG_CASE("-legacyfontscaling", 0) {
+            fontCoordinateScaling = FontCoordinateScaling::LEGACY;
+            fontCoordinateScalingSpecified = true;
+            continue;
+        }
     #else
         ARG_CASE("-svg", 1) {
             ABORT("SVG input is not available in core-only version.");
@@ -668,6 +693,10 @@ int main(int argc, const char *const *argv) {
         }
         ARG_CASE("-legacy", 0) {
             legacyMode = true;
+        #ifdef MSDFGEN_EXTENSIONS
+            fontCoordinateScaling = FontCoordinateScaling::LEGACY;
+            fontCoordinateScalingSpecified = true;
+        #endif
             continue;
         }
         ARG_CASE("-nopreprocess", 0) {
@@ -987,28 +1016,40 @@ int main(int argc, const char *const *argv) {
         case FONT: case VAR_FONT: {
             if (!glyphIndexSpecified && !unicode)
                 ABORT("No character specified! Use -font <file.ttf/otf> <character code>. Character code can be a Unicode index (65, 0x41), a character in apostrophes ('A'), or a glyph index prefixed by g (g36, g0x24).");
-            FreetypeHandle *ft = initializeFreetype();
-            if (!ft)
-                return -1;
-            FontHandle *font = (
+            struct FreetypeFontGuard {
+                FreetypeHandle *ft;
+                FontHandle *font;
+                FreetypeFontGuard() : ft(), font() { }
+                ~FreetypeFontGuard() {
+                    if (ft) {
+                        if (font)
+                            destroyFont(font);
+                        deinitializeFreetype(ft);
+                    }
+                }
+            } guard;
+            if (!(guard.ft = initializeFreetype()))
+                ABORT("Failed to initialize FreeType library.");
+            if (!(guard.font = (
                 #ifndef MSDFGEN_DISABLE_VARIABLE_FONTS
-                    inputType == VAR_FONT ? loadVarFont(ft, input) :
+                    inputType == VAR_FONT ? loadVarFont(guard.ft, input) :
                 #endif
-                loadFont(ft, input)
-            );
-            if (!font) {
-                deinitializeFreetype(ft);
+                loadFont(guard.ft, input)
+            )))
                 ABORT("Failed to load font file.");
-            }
             if (unicode)
-                getGlyphIndex(glyphIndex, font, unicode);
-            if (!loadGlyph(shape, font, glyphIndex, &glyphAdvance)) {
-                destroyFont(font);
-                deinitializeFreetype(ft);
+                getGlyphIndex(glyphIndex, guard.font, unicode);
+            if (!loadGlyph(shape, guard.font, glyphIndex, fontCoordinateScaling, &glyphAdvance))
                 ABORT("Failed to load glyph from font file.");
+            if (!fontCoordinateScalingSpecified && (!autoFrame || scaleSpecified || rangeMode == RANGE_UNIT || mode == METRICS || printMetrics || shapeExport)) {
+                fputs(
+                    "Warning: Using legacy font coordinate conversion for compatibility reasons.\n"
+                    "         The scaling behavior in this configuration will likely change in a future version resulting in different output.\n"
+                    "         To silence this warning, use one of the following options:\n"
+                    "           -noemnormalize to switch to the correct native font coordinates,\n"
+                    "           -emnormalize to switch to coordinates normalized to 1 em, or\n"
+                    "           -legacyfontscaling to keep current behavior and make sure it will not change.\n", stderr);
             }
-            destroyFont(font);
-            deinitializeFreetype(ft);
             break;
         }
     #endif
@@ -1026,9 +1067,10 @@ int main(int argc, const char *const *argv) {
             FILE *file = fopen(input, "r");
             if (!file)
                 ABORT("Failed to load shape description file.");
-            if (!readShapeDescription(file, shape, &skipColoring))
-                ABORT("Parse error in shape description.");
+            bool readSuccessful = readShapeDescription(file, shape, &skipColoring);
             fclose(file);
+            if (!readSuccessful)
+                ABORT("Parse error in shape description.");
             break;
         }
         default:;