diff --git a/ext/import-font.cpp b/ext/import-font.cpp index 352f1bb..0844b25 100644 --- a/ext/import-font.cpp +++ b/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 &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(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(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(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(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, double *advance) { - return loadGlyph(output, font, GlyphIndex(FT_Get_Char_Index(font->face, unicode)), advance); +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 getKerning(double &output, FontHandle *font, GlyphIndex glyphIndex0, GlyphIndex glyphIndex1) { +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 *outAdvance) { + return loadGlyph(output, font, unicode, FontCoordinateScaling::LEGACY, outAdvance); +} + +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 diff --git a/ext/import-font.h b/ext/import-font.h index 1d796c9..eab678d 100644 --- a/ext/import-font.h +++ b/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. diff --git a/main.cpp b/main.cpp index 4d61dc8..9712a9b 100644 --- a/main.cpp +++ b/main.cpp @@ -402,6 +402,10 @@ static const char *const helpText = "\tSets the dimensions of the output image.\n" " -edgecolors \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 \n" "\tChanges the MSDF/MTSDF error correction mode. Use -errorcorrection help for a list of valid modes.\n" " -errordeviationratio \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 . 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:;