浏览代码

msdfgen: Update to 1.12

Rémi Verschelde 6 月之前
父节点
当前提交
c97c7b73e6
共有 41 个文件被更改,包括 1037 次插入362 次删除
  1. 1 1
      COPYRIGHT.txt
  2. 6 0
      modules/msdfgen/SCsub
  3. 6 0
      modules/text_server_adv/gdextension_build/SConstruct
  4. 1 0
      modules/text_server_adv/text_server_adv.cpp
  5. 6 0
      modules/text_server_fb/gdextension_build/SConstruct
  6. 1 0
      modules/text_server_fb/text_server_fb.cpp
  7. 1 1
      thirdparty/README.md
  8. 1 1
      thirdparty/msdfgen/LICENSE.txt
  9. 27 0
      thirdparty/msdfgen/core/DistanceMapping.cpp
  10. 36 0
      thirdparty/msdfgen/core/DistanceMapping.h
  11. 0 10
      thirdparty/msdfgen/core/EdgeHolder.cpp
  12. 5 5
      thirdparty/msdfgen/core/EdgeHolder.h
  13. 17 18
      thirdparty/msdfgen/core/MSDFErrorCorrection.cpp
  14. 3 4
      thirdparty/msdfgen/core/MSDFErrorCorrection.h
  15. 46 0
      thirdparty/msdfgen/core/Range.hpp
  16. 24 0
      thirdparty/msdfgen/core/SDFTransformation.h
  17. 22 4
      thirdparty/msdfgen/core/Shape.cpp
  18. 0 2
      thirdparty/msdfgen/core/Shape.h
  19. 2 2
      thirdparty/msdfgen/core/Vector2.hpp
  20. 9 2
      thirdparty/msdfgen/core/contour-combiners.cpp
  21. 72 42
      thirdparty/msdfgen/core/edge-coloring.cpp
  22. 38 39
      thirdparty/msdfgen/core/edge-segments.cpp
  23. 10 8
      thirdparty/msdfgen/core/edge-segments.h
  24. 51 51
      thirdparty/msdfgen/core/edge-selectors.cpp
  25. 14 14
      thirdparty/msdfgen/core/edge-selectors.h
  26. 79 0
      thirdparty/msdfgen/core/export-svg.cpp
  27. 11 0
      thirdparty/msdfgen/core/export-svg.h
  28. 34 16
      thirdparty/msdfgen/core/msdf-error-correction.cpp
  29. 14 6
      thirdparty/msdfgen/core/msdf-error-correction.h
  30. 102 56
      thirdparty/msdfgen/core/msdfgen.cpp
  31. 1 1
      thirdparty/msdfgen/core/pixel-conversion.hpp
  32. 140 54
      thirdparty/msdfgen/core/render-sdf.cpp
  33. 7 6
      thirdparty/msdfgen/core/render-sdf.h
  34. 2 0
      thirdparty/msdfgen/core/save-bmp.cpp
  35. 39 0
      thirdparty/msdfgen/core/save-fl32.cpp
  36. 12 0
      thirdparty/msdfgen/core/save-fl32.h
  37. 133 0
      thirdparty/msdfgen/core/save-rgba.cpp
  38. 16 0
      thirdparty/msdfgen/core/save-rgba.h
  39. 2 0
      thirdparty/msdfgen/core/save-tiff.cpp
  40. 18 5
      thirdparty/msdfgen/core/shape-description.cpp
  41. 28 14
      thirdparty/msdfgen/msdfgen.h

+ 1 - 1
COPYRIGHT.txt

@@ -469,7 +469,7 @@ License: BSD-2-clause
 
 Files: ./thirdparty/msdfgen/
 Comment: Multi-channel signed distance field generator
-Copyright: 2016-2022, Viktor Chlumsky
+Copyright: 2014-2024, Viktor Chlumsky
 License: Expat
 
 Files: ./thirdparty/nvapi/nvapi_minimal.h

+ 6 - 0
modules/msdfgen/SCsub

@@ -16,6 +16,7 @@ if env["builtin_msdfgen"]:
     thirdparty_dir = "#thirdparty/msdfgen/"
     thirdparty_sources = [
         "core/Contour.cpp",
+        "core/DistanceMapping.cpp",
         "core/EdgeHolder.cpp",
         "core/MSDFErrorCorrection.cpp",
         "core/Projection.cpp",
@@ -26,10 +27,15 @@ if env["builtin_msdfgen"]:
         "core/edge-segments.cpp",
         "core/edge-selectors.cpp",
         "core/equation-solver.cpp",
+        # "core/export-svg.cpp",
         "core/msdf-error-correction.cpp",
         "core/msdfgen.cpp",
         "core/rasterization.cpp",
         "core/render-sdf.cpp",
+        # "core/save-bmp.cpp",
+        # "core/save-fl32.cpp",
+        # "core/save-rgba.cpp",
+        # "core/save-tiff.cpp",
         "core/sdf-error-estimation.cpp",
         "core/shape-description.cpp",
     ]

+ 6 - 0
modules/text_server_adv/gdextension_build/SConstruct

@@ -123,6 +123,7 @@ if env["msdfgen_enabled"] and env["freetype_enabled"]:
     thirdparty_msdfgen_dir = "../../../thirdparty/msdfgen/"
     thirdparty_msdfgen_sources = [
         "core/Contour.cpp",
+        "core/DistanceMapping.cpp",
         "core/EdgeHolder.cpp",
         "core/MSDFErrorCorrection.cpp",
         "core/Projection.cpp",
@@ -133,10 +134,15 @@ if env["msdfgen_enabled"] and env["freetype_enabled"]:
         "core/edge-segments.cpp",
         "core/edge-selectors.cpp",
         "core/equation-solver.cpp",
+        # "core/export-svg.cpp",
         "core/msdf-error-correction.cpp",
         "core/msdfgen.cpp",
         "core/rasterization.cpp",
         "core/render-sdf.cpp",
+        # "core/save-bmp.cpp",
+        # "core/save-fl32.cpp",
+        # "core/save-rgba.cpp",
+        # "core/save-tiff.cpp",
         "core/sdf-error-estimation.cpp",
         "core/shape-description.cpp",
     ]

+ 1 - 0
modules/text_server_adv/text_server_adv.cpp

@@ -69,6 +69,7 @@ using namespace godot;
 #ifdef _MSC_VER
 #pragma warning(disable : 4458)
 #endif
+#include <core/EdgeHolder.h>
 #include <core/ShapeDistanceFinder.h>
 #include <core/contour-combiners.h>
 #include <core/edge-selectors.h>

+ 6 - 0
modules/text_server_fb/gdextension_build/SConstruct

@@ -118,6 +118,7 @@ if env["msdfgen_enabled"] and env["freetype_enabled"]:
     thirdparty_msdfgen_dir = "../../../thirdparty/msdfgen/"
     thirdparty_msdfgen_sources = [
         "core/Contour.cpp",
+        "core/DistanceMapping.cpp",
         "core/EdgeHolder.cpp",
         "core/MSDFErrorCorrection.cpp",
         "core/Projection.cpp",
@@ -128,10 +129,15 @@ if env["msdfgen_enabled"] and env["freetype_enabled"]:
         "core/edge-segments.cpp",
         "core/edge-selectors.cpp",
         "core/equation-solver.cpp",
+        # "core/export-svg.cpp",
         "core/msdf-error-correction.cpp",
         "core/msdfgen.cpp",
         "core/rasterization.cpp",
         "core/render-sdf.cpp",
+        # "core/save-bmp.cpp",
+        # "core/save-fl32.cpp",
+        # "core/save-rgba.cpp",
+        # "core/save-tiff.cpp",
         "core/sdf-error-estimation.cpp",
         "core/shape-description.cpp",
     ]

+ 1 - 0
modules/text_server_fb/text_server_fb.cpp

@@ -64,6 +64,7 @@ using namespace godot;
 #ifdef _MSC_VER
 #pragma warning(disable : 4458)
 #endif
+#include <core/EdgeHolder.h>
 #include <core/ShapeDistanceFinder.h>
 #include <core/contour-combiners.h>
 #include <core/edge-selectors.h>

+ 1 - 1
thirdparty/README.md

@@ -763,7 +763,7 @@ Collection of single-file libraries used in Godot components.
 ## msdfgen
 
 - Upstream: https://github.com/Chlumsky/msdfgen
-- Version: 1.11 (f12d7ca00091a632a289865b85c3f2e0bfc6542d, 2023)
+- Version: 1.12 (85e8b3d47b3d1a42e4a5ebda0a24fb1cc2e669e0, 2024)
 - License: MIT
 
 Files extracted from the upstream source:

+ 1 - 1
thirdparty/msdfgen/LICENSE.txt

@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2016 - 2023 Viktor Chlumsky
+Copyright (c) 2014 - 2024 Viktor Chlumsky
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal

+ 27 - 0
thirdparty/msdfgen/core/DistanceMapping.cpp

@@ -0,0 +1,27 @@
+
+#include "DistanceMapping.h"
+
+namespace msdfgen {
+
+DistanceMapping DistanceMapping::inverse(Range range) {
+    double rangeWidth = range.upper-range.lower;
+    return DistanceMapping(rangeWidth, range.lower/(rangeWidth ? rangeWidth : 1));
+}
+
+DistanceMapping::DistanceMapping() : scale(1), translate(0) { }
+
+DistanceMapping::DistanceMapping(Range range) : scale(1/(range.upper-range.lower)), translate(-range.lower) { }
+
+double DistanceMapping::operator()(double d) const {
+    return scale*(d+translate);
+}
+
+double DistanceMapping::operator()(Delta d) const {
+    return scale*d.value;
+}
+
+DistanceMapping DistanceMapping::inverse() const {
+    return DistanceMapping(1/scale, -scale*translate);
+}
+
+}

+ 36 - 0
thirdparty/msdfgen/core/DistanceMapping.h

@@ -0,0 +1,36 @@
+
+#pragma once
+
+#include "Range.hpp"
+
+namespace msdfgen {
+
+/// Linear transformation of signed distance values.
+class DistanceMapping {
+
+public:
+    /// Explicitly designates value as distance delta rather than an absolute distance.
+    class Delta {
+    public:
+        double value;
+        inline explicit Delta(double distanceDelta) : value(distanceDelta) { }
+        inline operator double() const { return value; }
+    };
+
+    static DistanceMapping inverse(Range range);
+
+    DistanceMapping();
+    DistanceMapping(Range range);
+    double operator()(double d) const;
+    double operator()(Delta d) const;
+    DistanceMapping inverse() const;
+
+private:
+    double scale;
+    double translate;
+
+    inline DistanceMapping(double scale, double translate) : scale(scale), translate(translate) { }
+
+};
+
+}

+ 0 - 10
thirdparty/msdfgen/core/EdgeHolder.cpp

@@ -9,16 +9,6 @@ void EdgeHolder::swap(EdgeHolder &a, EdgeHolder &b) {
     b.edgeSegment = tmp;
 }
 
-EdgeHolder::EdgeHolder() : edgeSegment(NULL) { }
-
-EdgeHolder::EdgeHolder(EdgeSegment *segment) : edgeSegment(segment) { }
-
-EdgeHolder::EdgeHolder(Point2 p0, Point2 p1, EdgeColor edgeColor) : edgeSegment(new LinearSegment(p0, p1, edgeColor)) { }
-
-EdgeHolder::EdgeHolder(Point2 p0, Point2 p1, Point2 p2, EdgeColor edgeColor) : edgeSegment(new QuadraticSegment(p0, p1, p2, edgeColor)) { }
-
-EdgeHolder::EdgeHolder(Point2 p0, Point2 p1, Point2 p2, Point2 p3, EdgeColor edgeColor) : edgeSegment(new CubicSegment(p0, p1, p2, p3, edgeColor)) { }
-
 EdgeHolder::EdgeHolder(const EdgeHolder &orig) : edgeSegment(orig.edgeSegment ? orig.edgeSegment->clone() : NULL) { }
 
 #ifdef MSDFGEN_USE_CPP11

+ 5 - 5
thirdparty/msdfgen/core/EdgeHolder.h

@@ -12,11 +12,11 @@ public:
     /// Swaps the edges held by a and b.
     static void swap(EdgeHolder &a, EdgeHolder &b);
 
-    EdgeHolder();
-    EdgeHolder(EdgeSegment *segment);
-    EdgeHolder(Point2 p0, Point2 p1, EdgeColor edgeColor = WHITE);
-    EdgeHolder(Point2 p0, Point2 p1, Point2 p2, EdgeColor edgeColor = WHITE);
-    EdgeHolder(Point2 p0, Point2 p1, Point2 p2, Point2 p3, EdgeColor edgeColor = WHITE);
+    inline EdgeHolder() : edgeSegment() { }
+    inline EdgeHolder(EdgeSegment *segment) : edgeSegment(segment) { }
+    inline EdgeHolder(Point2 p0, Point2 p1, EdgeColor edgeColor = WHITE) : edgeSegment(EdgeSegment::create(p0, p1, edgeColor)) { }
+    inline EdgeHolder(Point2 p0, Point2 p1, Point2 p2, EdgeColor edgeColor = WHITE) : edgeSegment(EdgeSegment::create(p0, p1, p2, edgeColor)) { }
+    inline EdgeHolder(Point2 p0, Point2 p1, Point2 p2, Point2 p3, EdgeColor edgeColor = WHITE) : edgeSegment(EdgeSegment::create(p0, p1, p2, p3, edgeColor)) { }
     EdgeHolder(const EdgeHolder &orig);
 #ifdef MSDFGEN_USE_CPP11
     EdgeHolder(EdgeHolder &&orig);

+ 17 - 18
thirdparty/msdfgen/core/MSDFErrorCorrection.cpp

@@ -74,7 +74,7 @@ public:
                 // Compute the evaluated distance (interpolated median) before and after error correction, as well as the exact shape distance.
                 float oldPSD = median(oldMSD[0], oldMSD[1], oldMSD[2]);
                 float newPSD = median(newMSD[0], newMSD[1], newMSD[2]);
-                float refPSD = float(parent->invRange*parent->distanceFinder.distance(parent->shapeCoord+tVector*parent->texelSize)+.5);
+                float refPSD = float(parent->distanceMapping(parent->distanceFinder.distance(parent->shapeCoord+tVector*parent->texelSize)));
                 // Compare the differences of the exact distance and the before and after distances.
                 return parent->minImproveRatio*fabsf(newPSD-refPSD) < double(fabsf(oldPSD-refPSD));
             }
@@ -87,24 +87,23 @@ public:
     Point2 shapeCoord, sdfCoord;
     const float *msd;
     bool protectedFlag;
-    inline ShapeDistanceChecker(const BitmapConstRef<float, N> &sdf, const Shape &shape, const Projection &projection, double invRange, double minImproveRatio) : distanceFinder(shape), sdf(sdf), invRange(invRange), minImproveRatio(minImproveRatio) {
+    inline ShapeDistanceChecker(const BitmapConstRef<float, N> &sdf, const Shape &shape, const Projection &projection, DistanceMapping distanceMapping, double minImproveRatio) : distanceFinder(shape), sdf(sdf), distanceMapping(distanceMapping), minImproveRatio(minImproveRatio) {
         texelSize = projection.unprojectVector(Vector2(1));
     }
     inline ArtifactClassifier classifier(const Vector2 &direction, double span) {
         return ArtifactClassifier(this, direction, span);
     }
 private:
-    ShapeDistanceFinder<ContourCombiner<PseudoDistanceSelector> > distanceFinder;
+    ShapeDistanceFinder<ContourCombiner<PerpendicularDistanceSelector> > distanceFinder;
     BitmapConstRef<float, N> sdf;
-    double invRange;
+    DistanceMapping distanceMapping;
     Vector2 texelSize;
     double minImproveRatio;
 };
 
 MSDFErrorCorrection::MSDFErrorCorrection() { }
 
-MSDFErrorCorrection::MSDFErrorCorrection(const BitmapRef<byte, 1> &stencil, const Projection &projection, double range) : stencil(stencil), projection(projection) {
-    invRange = 1/range;
+MSDFErrorCorrection::MSDFErrorCorrection(const BitmapRef<byte, 1> &stencil, const SDFTransformation &transformation) : stencil(stencil), transformation(transformation) {
     minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio;
     minImproveRatio = ErrorCorrectionConfig::defaultMinImproveRatio;
     memset(stencil.pixels, 0, sizeof(byte)*stencil.width*stencil.height);
@@ -127,7 +126,7 @@ void MSDFErrorCorrection::protectCorners(const Shape &shape) {
                 // If the color changes from prevEdge to edge, this is a corner.
                 if (!(commonColor&(commonColor-1))) {
                     // Find the four texels that envelop the corner and mark them as protected.
-                    Point2 p = projection.project((*edge)->point(0));
+                    Point2 p = transformation.project((*edge)->point(0));
                     if (shape.inverseYAxis)
                         p.y = stencil.height-p.y;
                     int l = (int) floor(p.x-.5);
@@ -191,7 +190,7 @@ template <int N>
 void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, N> &sdf) {
     float radius;
     // Horizontal texel pairs
-    radius = float(PROTECTION_RADIUS_TOLERANCE*projection.unprojectVector(Vector2(invRange, 0)).length());
+    radius = float(PROTECTION_RADIUS_TOLERANCE*transformation.unprojectVector(Vector2(transformation.distanceMapping(DistanceMapping::Delta(1)), 0)).length());
     for (int y = 0; y < sdf.height; ++y) {
         const float *left = sdf(0, y);
         const float *right = sdf(1, y);
@@ -207,7 +206,7 @@ void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, N> &sdf) {
         }
     }
     // Vertical texel pairs
-    radius = float(PROTECTION_RADIUS_TOLERANCE*projection.unprojectVector(Vector2(0, invRange)).length());
+    radius = float(PROTECTION_RADIUS_TOLERANCE*transformation.unprojectVector(Vector2(0, transformation.distanceMapping(DistanceMapping::Delta(1)))).length());
     for (int y = 0; y < sdf.height-1; ++y) {
         const float *bottom = sdf(0, y);
         const float *top = sdf(0, y+1);
@@ -223,7 +222,7 @@ void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, N> &sdf) {
         }
     }
     // Diagonal texel pairs
-    radius = float(PROTECTION_RADIUS_TOLERANCE*projection.unprojectVector(Vector2(invRange)).length());
+    radius = float(PROTECTION_RADIUS_TOLERANCE*transformation.unprojectVector(Vector2(transformation.distanceMapping(DistanceMapping::Delta(1)))).length());
     for (int y = 0; y < sdf.height-1; ++y) {
         const float *lb = sdf(0, y);
         const float *rb = sdf(1, y);
@@ -391,9 +390,9 @@ static bool hasDiagonalArtifact(const ArtifactClassifier &artifactClassifier, fl
 template <int N>
 void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, N> &sdf) {
     // Compute the expected deltas between values of horizontally, vertically, and diagonally adjacent texels.
-    double hSpan = minDeviationRatio*projection.unprojectVector(Vector2(invRange, 0)).length();
-    double vSpan = minDeviationRatio*projection.unprojectVector(Vector2(0, invRange)).length();
-    double dSpan = minDeviationRatio*projection.unprojectVector(Vector2(invRange)).length();
+    double hSpan = minDeviationRatio*transformation.unprojectVector(Vector2(transformation.distanceMapping(DistanceMapping::Delta(1)), 0)).length();
+    double vSpan = minDeviationRatio*transformation.unprojectVector(Vector2(0, transformation.distanceMapping(DistanceMapping::Delta(1)))).length();
+    double dSpan = minDeviationRatio*transformation.unprojectVector(Vector2(transformation.distanceMapping(DistanceMapping::Delta(1)))).length();
     // Inspect all texels.
     for (int y = 0; y < sdf.height; ++y) {
         for (int x = 0; x < sdf.width; ++x) {
@@ -419,14 +418,14 @@ void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, N> &sdf) {
 template <template <typename> class ContourCombiner, int N>
 void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, N> &sdf, const Shape &shape) {
     // Compute the expected deltas between values of horizontally, vertically, and diagonally adjacent texels.
-    double hSpan = minDeviationRatio*projection.unprojectVector(Vector2(invRange, 0)).length();
-    double vSpan = minDeviationRatio*projection.unprojectVector(Vector2(0, invRange)).length();
-    double dSpan = minDeviationRatio*projection.unprojectVector(Vector2(invRange)).length();
+    double hSpan = minDeviationRatio*transformation.unprojectVector(Vector2(transformation.distanceMapping(DistanceMapping::Delta(1)), 0)).length();
+    double vSpan = minDeviationRatio*transformation.unprojectVector(Vector2(0, transformation.distanceMapping(DistanceMapping::Delta(1)))).length();
+    double dSpan = minDeviationRatio*transformation.unprojectVector(Vector2(transformation.distanceMapping(DistanceMapping::Delta(1)))).length();
 #ifdef MSDFGEN_USE_OPENMP
     #pragma omp parallel
 #endif
     {
-        ShapeDistanceChecker<ContourCombiner, N> shapeDistanceChecker(sdf, shape, projection, invRange, minImproveRatio);
+        ShapeDistanceChecker<ContourCombiner, N> shapeDistanceChecker(sdf, shape, transformation, transformation.distanceMapping, minImproveRatio);
         bool rightToLeft = false;
         // Inspect all texels.
 #ifdef MSDFGEN_USE_OPENMP
@@ -439,7 +438,7 @@ void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, N> &sdf, const
                 if ((*stencil(x, row)&ERROR))
                     continue;
                 const float *c = sdf(x, row);
-                shapeDistanceChecker.shapeCoord = projection.unproject(Point2(x+.5, y+.5));
+                shapeDistanceChecker.shapeCoord = transformation.unproject(Point2(x+.5, y+.5));
                 shapeDistanceChecker.sdfCoord = Point2(x+.5, row+.5);
                 shapeDistanceChecker.msd = c;
                 shapeDistanceChecker.protectedFlag = (*stencil(x, row)&PROTECTED) != 0;

+ 3 - 4
thirdparty/msdfgen/core/MSDFErrorCorrection.h

@@ -1,7 +1,7 @@
 
 #pragma once
 
-#include "Projection.h"
+#include "SDFTransformation.h"
 #include "Shape.h"
 #include "BitmapRef.hpp"
 
@@ -20,7 +20,7 @@ public:
     };
 
     MSDFErrorCorrection();
-    explicit MSDFErrorCorrection(const BitmapRef<byte, 1> &stencil, const Projection &projection, double range);
+    explicit MSDFErrorCorrection(const BitmapRef<byte, 1> &stencil, const SDFTransformation &transformation);
     /// Sets the minimum ratio between the actual and maximum expected distance delta to be considered an error.
     void setMinDeviationRatio(double minDeviationRatio);
     /// Sets the minimum ratio between the pre-correction distance error and the post-correction distance error.
@@ -46,8 +46,7 @@ public:
 
 private:
     BitmapRef<byte, 1> stencil;
-    Projection projection;
-    double invRange;
+    SDFTransformation transformation;
     double minDeviationRatio;
     double minImproveRatio;
 

+ 46 - 0
thirdparty/msdfgen/core/Range.hpp

@@ -0,0 +1,46 @@
+
+#pragma once
+
+#include "base.h"
+
+namespace msdfgen {
+
+/**
+ * Represents the range between two real values.
+ * For example, the range of representable signed distances.
+ */
+struct Range {
+
+    double lower, upper;
+
+    inline Range(double symmetricalWidth = 0) : lower(-.5*symmetricalWidth), upper(.5*symmetricalWidth) { }
+
+    inline Range(double lowerBound, double upperBound) : lower(lowerBound), upper(upperBound) { }
+
+    inline Range &operator*=(double factor) {
+        lower *= factor;
+        upper *= factor;
+        return *this;
+    }
+
+    inline Range &operator/=(double divisor) {
+        lower /= divisor;
+        upper /= divisor;
+        return *this;
+    }
+
+    inline Range operator*(double factor) const {
+        return Range(lower*factor, upper*factor);
+    }
+
+    inline Range operator/(double divisor) const {
+        return Range(lower/divisor, upper/divisor);
+    }
+
+};
+
+inline Range operator*(double factor, const Range &range) {
+    return Range(factor*range.lower, factor*range.upper);
+}
+
+}

+ 24 - 0
thirdparty/msdfgen/core/SDFTransformation.h

@@ -0,0 +1,24 @@
+
+#pragma once
+
+#include "Projection.h"
+#include "DistanceMapping.h"
+
+namespace msdfgen {
+
+/**
+ * Full signed distance field transformation specifies both spatial transformation (Projection)
+ * as well as distance value transformation (DistanceMapping).
+ */
+class SDFTransformation : public Projection {
+
+public:
+    DistanceMapping distanceMapping;
+
+    inline SDFTransformation() { }
+
+    inline SDFTransformation(const Projection &projection, const DistanceMapping &distanceMapping) : Projection(projection), distanceMapping(distanceMapping) { }
+
+};
+
+}

+ 22 - 4
thirdparty/msdfgen/core/Shape.cpp

@@ -4,6 +4,8 @@
 #include <cstdlib>
 #include "arithmetics.hpp"
 
+#define DECONVERGE_OVERSHOOT 1.11111111111111111 // moves control points slightly more than necessary to account for floating-point errors
+
 namespace msdfgen {
 
 Shape::Shape() : inverseYAxis(false) { }
@@ -39,13 +41,23 @@ bool Shape::validate() const {
     return true;
 }
 
-static void deconvergeEdge(EdgeHolder &edgeHolder, int param) {
+static void deconvergeEdge(EdgeHolder &edgeHolder, int param, Vector2 vector) {
     switch (edgeHolder->type()) {
         case (int) QuadraticSegment::EDGE_TYPE:
             edgeHolder = static_cast<const QuadraticSegment *>(&*edgeHolder)->convertToCubic();
             // fallthrough
         case (int) CubicSegment::EDGE_TYPE:
-            static_cast<CubicSegment *>(&*edgeHolder)->deconverge(param, MSDFGEN_DECONVERGENCE_FACTOR);
+            {
+                Point2 *p = static_cast<CubicSegment *>(&*edgeHolder)->p;
+                switch (param) {
+                    case 0:
+                        p[1] += (p[1]-p[0]).length()*vector;
+                        break;
+                    case 1:
+                        p[2] += (p[2]-p[3]).length()*vector;
+                        break;
+                }
+            }
     }
 }
 
@@ -59,13 +71,19 @@ void Shape::normalize() {
             contour->edges.push_back(EdgeHolder(parts[1]));
             contour->edges.push_back(EdgeHolder(parts[2]));
         } else {
+            // Push apart convergent edge segments
             EdgeHolder *prevEdge = &contour->edges.back();
             for (std::vector<EdgeHolder>::iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
                 Vector2 prevDir = (*prevEdge)->direction(1).normalize();
                 Vector2 curDir = (*edge)->direction(0).normalize();
                 if (dotProduct(prevDir, curDir) < MSDFGEN_CORNER_DOT_EPSILON-1) {
-                    deconvergeEdge(*prevEdge, 1);
-                    deconvergeEdge(*edge, 0);
+                    double factor = DECONVERGE_OVERSHOOT*sqrt(1-(MSDFGEN_CORNER_DOT_EPSILON-1)*(MSDFGEN_CORNER_DOT_EPSILON-1))/(MSDFGEN_CORNER_DOT_EPSILON-1);
+                    Vector2 axis = factor*(curDir-prevDir).normalize();
+                    // Determine curve ordering using third-order derivative (t = 0) of crossProduct((*prevEdge)->point(1-t)-p0, (*edge)->point(t)-p0) where p0 is the corner (*edge)->point(0)
+                    if (crossProduct((*prevEdge)->directionChange(1), (*edge)->direction(0))+crossProduct((*edge)->directionChange(0), (*prevEdge)->direction(1)) < 0)
+                        axis = -axis;
+                    deconvergeEdge(*prevEdge, 1, axis.getOrthogonal(true));
+                    deconvergeEdge(*edge, 0, axis.getOrthogonal(false));
                 }
                 prevEdge = &*edge;
             }

+ 0 - 2
thirdparty/msdfgen/core/Shape.h

@@ -9,8 +9,6 @@ namespace msdfgen {
 
 // Threshold of the dot product of adjacent edge directions to be considered convergent.
 #define MSDFGEN_CORNER_DOT_EPSILON .000001
-// The proportional amount by which a curve's control point will be adjusted to eliminate convergent corners.
-#define MSDFGEN_DECONVERGENCE_FACTOR .000001
 
 /// Vector shape representation.
 class Shape {

+ 2 - 2
thirdparty/msdfgen/core/Vector2.hpp

@@ -24,8 +24,8 @@ struct Vector2 {
     }
 
     /// Sets individual elements of the vector.
-    inline void set(double x, double y) {
-        this->x = x, this->y = y;
+    inline void set(double newX, double newY) {
+        x = newX, y = newY;
     }
 
     /// Returns the vector's squared length.

+ 9 - 2
thirdparty/msdfgen/core/contour-combiners.cpp

@@ -16,6 +16,13 @@ static void initDistance(MultiDistance &distance) {
     distance.b = -DBL_MAX;
 }
 
+static void initDistance(MultiAndTrueDistance &distance) {
+    distance.r = -DBL_MAX;
+    distance.g = -DBL_MAX;
+    distance.b = -DBL_MAX;
+    distance.a = -DBL_MAX;
+}
+
 static double resolveDistance(double distance) {
     return distance;
 }
@@ -43,7 +50,7 @@ typename SimpleContourCombiner<EdgeSelector>::DistanceType SimpleContourCombiner
 }
 
 template class SimpleContourCombiner<TrueDistanceSelector>;
-template class SimpleContourCombiner<PseudoDistanceSelector>;
+template class SimpleContourCombiner<PerpendicularDistanceSelector>;
 template class SimpleContourCombiner<MultiDistanceSelector>;
 template class SimpleContourCombiner<MultiAndTrueDistanceSelector>;
 
@@ -127,7 +134,7 @@ typename OverlappingContourCombiner<EdgeSelector>::DistanceType OverlappingConto
 }
 
 template class OverlappingContourCombiner<TrueDistanceSelector>;
-template class OverlappingContourCombiner<PseudoDistanceSelector>;
+template class OverlappingContourCombiner<PerpendicularDistanceSelector>;
 template class OverlappingContourCombiner<MultiDistanceSelector>;
 template class OverlappingContourCombiner<MultiAndTrueDistanceSelector>;
 

+ 72 - 42
thirdparty/msdfgen/core/edge-coloring.cpp

@@ -11,6 +11,15 @@
 
 namespace msdfgen {
 
+/**
+ * For each position < n, this function will return -1, 0, or 1,
+ * depending on whether the position is closer to the beginning, middle, or end, respectively.
+ * It is guaranteed that the output will be balanced in that the total for positions 0 through n-1 will be zero.
+ */
+static int symmetricalTrichotomy(int position, int n) {
+    return int(3+2.875*position/(n-1)-1.4375+.5)-3;
+}
+
 static bool isCorner(const Vector2 &aDir, const Vector2 &bDir, double crossThreshold) {
     return dotProduct(aDir, bDir) <= 0 || fabs(crossProduct(aDir, bDir)) > crossThreshold;
 }
@@ -26,30 +35,45 @@ static double estimateEdgeLength(const EdgeSegment *edge) {
     return len;
 }
 
-static void switchColor(EdgeColor &color, unsigned long long &seed, EdgeColor banned = BLACK) {
+static int seedExtract2(unsigned long long &seed) {
+    int v = int(seed)&1;
+    seed >>= 1;
+    return v;
+}
+
+static int seedExtract3(unsigned long long &seed) {
+    int v = int(seed%3);
+    seed /= 3;
+    return v;
+}
+
+static EdgeColor initColor(unsigned long long &seed) {
+    static const EdgeColor colors[3] = { CYAN, MAGENTA, YELLOW };
+    return colors[seedExtract3(seed)];
+}
+
+static void switchColor(EdgeColor &color, unsigned long long &seed) {
+    int shifted = color<<(1+seedExtract2(seed));
+    color = EdgeColor((shifted|shifted>>3)&WHITE);
+}
+
+static void switchColor(EdgeColor &color, unsigned long long &seed, EdgeColor banned) {
     EdgeColor combined = EdgeColor(color&banned);
-    if (combined == RED || combined == GREEN || combined == BLUE) {
+    if (combined == RED || combined == GREEN || combined == BLUE)
         color = EdgeColor(combined^WHITE);
-        return;
-    }
-    if (color == BLACK || color == WHITE) {
-        static const EdgeColor start[3] = { CYAN, MAGENTA, YELLOW };
-        color = start[seed%3];
-        seed /= 3;
-        return;
-    }
-    int shifted = color<<(1+(seed&1));
-    color = EdgeColor((shifted|shifted>>3)&WHITE);
-    seed >>= 1;
+    else
+        switchColor(color, seed);
 }
 
 void edgeColoringSimple(Shape &shape, double angleThreshold, unsigned long long seed) {
     double crossThreshold = sin(angleThreshold);
+    EdgeColor color = initColor(seed);
     std::vector<int> corners;
     for (std::vector<Contour>::iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour) {
-        // Identify corners
-        corners.clear();
-        if (!contour->edges.empty()) {
+        if (contour->edges.empty())
+            continue;
+        { // Identify corners
+            corners.clear();
             Vector2 prevDirection = contour->edges.back()->direction(1);
             int index = 0;
             for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge, ++index) {
@@ -60,19 +84,24 @@ void edgeColoringSimple(Shape &shape, double angleThreshold, unsigned long long
         }
 
         // Smooth contour
-        if (corners.empty())
+        if (corners.empty()) {
+            switchColor(color, seed);
             for (std::vector<EdgeHolder>::iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge)
-                (*edge)->color = WHITE;
+                (*edge)->color = color;
+        }
         // "Teardrop" case
         else if (corners.size() == 1) {
-            EdgeColor colors[3] = { WHITE, WHITE };
-            switchColor(colors[0], seed);
-            switchColor(colors[2] = colors[0], seed);
+            EdgeColor colors[3];
+            switchColor(color, seed);
+            colors[0] = color;
+            colors[1] = WHITE;
+            switchColor(color, seed);
+            colors[2] = color;
             int corner = corners[0];
             if (contour->edges.size() >= 3) {
                 int m = (int) contour->edges.size();
                 for (int i = 0; i < m; ++i)
-                    contour->edges[(corner+i)%m]->color = (colors+1)[int(3+2.875*i/(m-1)-1.4375+.5)-3];
+                    contour->edges[(corner+i)%m]->color = colors[1+symmetricalTrichotomy(i, m)];
             } else if (contour->edges.size() >= 1) {
                 // Less than three edge segments for three colors => edges must be split
                 EdgeSegment *parts[7] = { };
@@ -98,7 +127,6 @@ void edgeColoringSimple(Shape &shape, double angleThreshold, unsigned long long
             int spline = 0;
             int start = corners[0];
             int m = (int) contour->edges.size();
-            EdgeColor color = WHITE;
             switchColor(color, seed);
             EdgeColor initialColor = color;
             for (int i = 0; i < m; ++i) {
@@ -123,12 +151,14 @@ struct EdgeColoringInkTrapCorner {
 void edgeColoringInkTrap(Shape &shape, double angleThreshold, unsigned long long seed) {
     typedef EdgeColoringInkTrapCorner Corner;
     double crossThreshold = sin(angleThreshold);
+    EdgeColor color = initColor(seed);
     std::vector<Corner> corners;
     for (std::vector<Contour>::iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour) {
-        // Identify corners
+        if (contour->edges.empty())
+            continue;
         double splineLength = 0;
-        corners.clear();
-        if (!contour->edges.empty()) {
+        { // Identify corners
+            corners.clear();
             Vector2 prevDirection = contour->edges.back()->direction(1);
             int index = 0;
             for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge, ++index) {
@@ -143,19 +173,24 @@ void edgeColoringInkTrap(Shape &shape, double angleThreshold, unsigned long long
         }
 
         // Smooth contour
-        if (corners.empty())
+        if (corners.empty()) {
+            switchColor(color, seed);
             for (std::vector<EdgeHolder>::iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge)
-                (*edge)->color = WHITE;
+                (*edge)->color = color;
+        }
         // "Teardrop" case
         else if (corners.size() == 1) {
-            EdgeColor colors[3] = { WHITE, WHITE };
-            switchColor(colors[0], seed);
-            switchColor(colors[2] = colors[0], seed);
+            EdgeColor colors[3];
+            switchColor(color, seed);
+            colors[0] = color;
+            colors[1] = WHITE;
+            switchColor(color, seed);
+            colors[2] = color;
             int corner = corners[0].index;
             if (contour->edges.size() >= 3) {
                 int m = (int) contour->edges.size();
                 for (int i = 0; i < m; ++i)
-                    contour->edges[(corner+i)%m]->color = (colors+1)[int(3+2.875*i/(m-1)-1.4375+.5)-3];
+                    contour->edges[(corner+i)%m]->color = colors[1+symmetricalTrichotomy(i, m)];
             } else if (contour->edges.size() >= 1) {
                 // Less than three edge segments for three colors => edges must be split
                 EdgeSegment *parts[7] = { };
@@ -191,7 +226,6 @@ void edgeColoringInkTrap(Shape &shape, double angleThreshold, unsigned long long
                     }
                 }
             }
-            EdgeColor color = WHITE;
             EdgeColor initialColor = BLACK;
             for (int i = 0; i < cornerCount; ++i) {
                 if (!corners[i].minor) {
@@ -271,23 +305,19 @@ static void colorSecondDegreeGraph(int *coloring, const int *const *edgeMatrix,
                 color = 1;
                 break;
             case 3:
-                color = (int) seed&1;
-                seed >>= 1;
+                color = seedExtract2(seed); // 0 or 1
                 break;
             case 4:
                 color = 2;
                 break;
             case 5:
-                color = ((int) seed+1&1)<<1;
-                seed >>= 1;
+                color = (int) !seedExtract2(seed)<<1; // 2 or 0
                 break;
             case 6:
-                color = ((int) seed&1)+1;
-                seed >>= 1;
+                color = seedExtract2(seed)+1; // 1 or 2
                 break;
             case 7:
-                color = int((seed+i)%3);
-                seed /= 3;
+                color = (seedExtract3(seed)+i)%3; // 0 or 1 or 2
                 break;
         }
         coloring[i] = color;
@@ -394,7 +424,7 @@ void edgeColoringByDistance(Shape &shape, double angleThreshold, unsigned long l
                     for (int i = 0; i < m; ++i) {
                         if (i == m/2)
                             splineStarts.push_back((int) edgeSegments.size());
-                        if (int(3+2.875*i/(m-1)-1.4375+.5)-3)
+                        if (symmetricalTrichotomy(i, m))
                             edgeSegments.push_back(&*contour->edges[(corner+i)%m]);
                         else
                             contour->edges[(corner+i)%m]->color = WHITE;

+ 38 - 39
thirdparty/msdfgen/core/edge-segments.cpp

@@ -6,15 +6,34 @@
 
 namespace msdfgen {
 
-void EdgeSegment::distanceToPseudoDistance(SignedDistance &distance, Point2 origin, double param) const {
+EdgeSegment *EdgeSegment::create(Point2 p0, Point2 p1, EdgeColor edgeColor) {
+    return new LinearSegment(p0, p1, edgeColor);
+}
+
+EdgeSegment *EdgeSegment::create(Point2 p0, Point2 p1, Point2 p2, EdgeColor edgeColor) {
+    if (!crossProduct(p1-p0, p2-p1))
+        return new LinearSegment(p0, p2, edgeColor);
+    return new QuadraticSegment(p0, p1, p2, edgeColor);
+}
+
+EdgeSegment *EdgeSegment::create(Point2 p0, Point2 p1, Point2 p2, Point2 p3, EdgeColor edgeColor) {
+    Vector2 p12 = p2-p1;
+    if (!crossProduct(p1-p0, p12) && !crossProduct(p12, p3-p2))
+        return new LinearSegment(p0, p3, edgeColor);
+    if ((p12 = 1.5*p1-.5*p0) == 1.5*p2-.5*p3)
+        return new QuadraticSegment(p0, p12, p3, edgeColor);
+    return new CubicSegment(p0, p1, p2, p3, edgeColor);
+}
+
+void EdgeSegment::distanceToPerpendicularDistance(SignedDistance &distance, Point2 origin, double param) const {
     if (param < 0) {
         Vector2 dir = direction(0).normalize();
         Vector2 aq = origin-point(0);
         double ts = dotProduct(aq, dir);
         if (ts < 0) {
-            double pseudoDistance = crossProduct(aq, dir);
-            if (fabs(pseudoDistance) <= fabs(distance.distance)) {
-                distance.distance = pseudoDistance;
+            double perpendicularDistance = crossProduct(aq, dir);
+            if (fabs(perpendicularDistance) <= fabs(distance.distance)) {
+                distance.distance = perpendicularDistance;
                 distance.dot = 0;
             }
         }
@@ -23,9 +42,9 @@ void EdgeSegment::distanceToPseudoDistance(SignedDistance &distance, Point2 orig
         Vector2 bq = origin-point(1);
         double ts = dotProduct(bq, dir);
         if (ts > 0) {
-            double pseudoDistance = crossProduct(bq, dir);
-            if (fabs(pseudoDistance) <= fabs(distance.distance)) {
-                distance.distance = pseudoDistance;
+            double perpendicularDistance = crossProduct(bq, dir);
+            if (fabs(perpendicularDistance) <= fabs(distance.distance)) {
+                distance.distance = perpendicularDistance;
                 distance.dot = 0;
             }
         }
@@ -38,18 +57,12 @@ LinearSegment::LinearSegment(Point2 p0, Point2 p1, EdgeColor edgeColor) : EdgeSe
 }
 
 QuadraticSegment::QuadraticSegment(Point2 p0, Point2 p1, Point2 p2, EdgeColor edgeColor) : EdgeSegment(edgeColor) {
-    if (p1 == p0 || p1 == p2)
-        p1 = 0.5*(p0+p2);
     p[0] = p0;
     p[1] = p1;
     p[2] = p2;
 }
 
 CubicSegment::CubicSegment(Point2 p0, Point2 p1, Point2 p2, Point2 p3, EdgeColor edgeColor) : EdgeSegment(edgeColor) {
-    if ((p1 == p0 || p1 == p3) && (p2 == p0 || p2 == p3)) {
-        p1 = mix(p0, p3, 1/3.);
-        p2 = mix(p0, p3, 2/3.);
-    }
     p[0] = p0;
     p[1] = p1;
     p[2] = p2;
@@ -486,43 +499,29 @@ void CubicSegment::moveEndPoint(Point2 to) {
     p[3] = to;
 }
 
-void LinearSegment::splitInThirds(EdgeSegment *&part1, EdgeSegment *&part2, EdgeSegment *&part3) const {
-    part1 = new LinearSegment(p[0], point(1/3.), color);
-    part2 = new LinearSegment(point(1/3.), point(2/3.), color);
-    part3 = new LinearSegment(point(2/3.), p[1], color);
+void LinearSegment::splitInThirds(EdgeSegment *&part0, EdgeSegment *&part1, EdgeSegment *&part2) const {
+    part0 = new LinearSegment(p[0], point(1/3.), color);
+    part1 = new LinearSegment(point(1/3.), point(2/3.), color);
+    part2 = new LinearSegment(point(2/3.), p[1], color);
 }
 
-void QuadraticSegment::splitInThirds(EdgeSegment *&part1, EdgeSegment *&part2, EdgeSegment *&part3) const {
-    part1 = new QuadraticSegment(p[0], mix(p[0], p[1], 1/3.), point(1/3.), color);
-    part2 = new QuadraticSegment(point(1/3.), mix(mix(p[0], p[1], 5/9.), mix(p[1], p[2], 4/9.), .5), point(2/3.), color);
-    part3 = new QuadraticSegment(point(2/3.), mix(p[1], p[2], 2/3.), p[2], color);
+void QuadraticSegment::splitInThirds(EdgeSegment *&part0, EdgeSegment *&part1, EdgeSegment *&part2) const {
+    part0 = new QuadraticSegment(p[0], mix(p[0], p[1], 1/3.), point(1/3.), color);
+    part1 = new QuadraticSegment(point(1/3.), mix(mix(p[0], p[1], 5/9.), mix(p[1], p[2], 4/9.), .5), point(2/3.), color);
+    part2 = new QuadraticSegment(point(2/3.), mix(p[1], p[2], 2/3.), p[2], color);
 }
 
-void CubicSegment::splitInThirds(EdgeSegment *&part1, EdgeSegment *&part2, EdgeSegment *&part3) const {
-    part1 = new CubicSegment(p[0], p[0] == p[1] ? p[0] : mix(p[0], p[1], 1/3.), mix(mix(p[0], p[1], 1/3.), mix(p[1], p[2], 1/3.), 1/3.), point(1/3.), color);
-    part2 = new CubicSegment(point(1/3.),
+void CubicSegment::splitInThirds(EdgeSegment *&part0, EdgeSegment *&part1, EdgeSegment *&part2) const {
+    part0 = new CubicSegment(p[0], p[0] == p[1] ? p[0] : mix(p[0], p[1], 1/3.), mix(mix(p[0], p[1], 1/3.), mix(p[1], p[2], 1/3.), 1/3.), point(1/3.), color);
+    part1 = new CubicSegment(point(1/3.),
         mix(mix(mix(p[0], p[1], 1/3.), mix(p[1], p[2], 1/3.), 1/3.), mix(mix(p[1], p[2], 1/3.), mix(p[2], p[3], 1/3.), 1/3.), 2/3.),
         mix(mix(mix(p[0], p[1], 2/3.), mix(p[1], p[2], 2/3.), 2/3.), mix(mix(p[1], p[2], 2/3.), mix(p[2], p[3], 2/3.), 2/3.), 1/3.),
         point(2/3.), color);
-    part3 = new CubicSegment(point(2/3.), mix(mix(p[1], p[2], 2/3.), mix(p[2], p[3], 2/3.), 2/3.), p[2] == p[3] ? p[3] : mix(p[2], p[3], 2/3.), p[3], color);
+    part2 = new CubicSegment(point(2/3.), mix(mix(p[1], p[2], 2/3.), mix(p[2], p[3], 2/3.), 2/3.), p[2] == p[3] ? p[3] : mix(p[2], p[3], 2/3.), p[3], color);
 }
 
 EdgeSegment *QuadraticSegment::convertToCubic() const {
     return new CubicSegment(p[0], mix(p[0], p[1], 2/3.), mix(p[1], p[2], 1/3.), p[2], color);
 }
 
-void CubicSegment::deconverge(int param, double amount) {
-    Vector2 dir = direction(param);
-    Vector2 normal = dir.getOrthonormal();
-    double h = dotProduct(directionChange(param)-dir, normal);
-    switch (param) {
-        case 0:
-            p[1] += amount*(dir+sign(h)*sqrt(fabs(h))*normal);
-            break;
-        case 1:
-            p[2] -= amount*(dir-sign(h)*sqrt(fabs(h))*normal);
-            break;
-    }
-}
-
 }

+ 10 - 8
thirdparty/msdfgen/core/edge-segments.h

@@ -17,6 +17,10 @@ class EdgeSegment {
 public:
     EdgeColor color;
 
+    static EdgeSegment *create(Point2 p0, Point2 p1, EdgeColor edgeColor = WHITE);
+    static EdgeSegment *create(Point2 p0, Point2 p1, Point2 p2, EdgeColor edgeColor = WHITE);
+    static EdgeSegment *create(Point2 p0, Point2 p1, Point2 p2, Point2 p3, EdgeColor edgeColor = WHITE);
+
     EdgeSegment(EdgeColor edgeColor = WHITE) : color(edgeColor) { }
     virtual ~EdgeSegment() { }
     /// Creates a copy of the edge segment.
@@ -33,8 +37,8 @@ public:
     virtual Vector2 directionChange(double param) const = 0;
     /// Returns the minimum signed distance between origin and the edge.
     virtual SignedDistance signedDistance(Point2 origin, double &param) const = 0;
-    /// Converts a previously retrieved signed distance from origin to pseudo-distance.
-    virtual void distanceToPseudoDistance(SignedDistance &distance, Point2 origin, double param) const;
+    /// Converts a previously retrieved signed distance from origin to perpendicular distance.
+    virtual void distanceToPerpendicularDistance(SignedDistance &distance, Point2 origin, double param) const;
     /// Outputs a list of (at most three) intersections (their X coordinates) with an infinite horizontal scanline at y and returns how many there are.
     virtual int scanlineIntersections(double x[3], int dy[3], double y) const = 0;
     /// Adjusts the bounding box to fit the edge segment.
@@ -47,7 +51,7 @@ public:
     /// Moves the end point of the edge segment.
     virtual void moveEndPoint(Point2 to) = 0;
     /// Splits the edge segments into thirds which together represent the original edge.
-    virtual void splitInThirds(EdgeSegment *&part1, EdgeSegment *&part2, EdgeSegment *&part3) const = 0;
+    virtual void splitInThirds(EdgeSegment *&part0, EdgeSegment *&part1, EdgeSegment *&part2) const = 0;
 
 };
 
@@ -76,7 +80,7 @@ public:
     void reverse();
     void moveStartPoint(Point2 to);
     void moveEndPoint(Point2 to);
-    void splitInThirds(EdgeSegment *&part1, EdgeSegment *&part2, EdgeSegment *&part3) const;
+    void splitInThirds(EdgeSegment *&part0, EdgeSegment *&part1, EdgeSegment *&part2) const;
 
 };
 
@@ -105,7 +109,7 @@ public:
     void reverse();
     void moveStartPoint(Point2 to);
     void moveEndPoint(Point2 to);
-    void splitInThirds(EdgeSegment *&part1, EdgeSegment *&part2, EdgeSegment *&part3) const;
+    void splitInThirds(EdgeSegment *&part0, EdgeSegment *&part1, EdgeSegment *&part2) const;
 
     EdgeSegment *convertToCubic() const;
 
@@ -135,9 +139,7 @@ public:
     void reverse();
     void moveStartPoint(Point2 to);
     void moveEndPoint(Point2 to);
-    void splitInThirds(EdgeSegment *&part1, EdgeSegment *&part2, EdgeSegment *&part3) const;
-
-    void deconverge(int param, double amount);
+    void splitInThirds(EdgeSegment *&part0, EdgeSegment *&part1, EdgeSegment *&part2) const;
 
 };
 

+ 51 - 51
thirdparty/msdfgen/core/edge-selectors.cpp

@@ -36,48 +36,48 @@ TrueDistanceSelector::DistanceType TrueDistanceSelector::distance() const {
     return minDistance.distance;
 }
 
-PseudoDistanceSelectorBase::EdgeCache::EdgeCache() : absDistance(0), aDomainDistance(0), bDomainDistance(0), aPseudoDistance(0), bPseudoDistance(0) { }
+PerpendicularDistanceSelectorBase::EdgeCache::EdgeCache() : absDistance(0), aDomainDistance(0), bDomainDistance(0), aPerpendicularDistance(0), bPerpendicularDistance(0) { }
 
-bool PseudoDistanceSelectorBase::getPseudoDistance(double &distance, const Vector2 &ep, const Vector2 &edgeDir) {
+bool PerpendicularDistanceSelectorBase::getPerpendicularDistance(double &distance, const Vector2 &ep, const Vector2 &edgeDir) {
     double ts = dotProduct(ep, edgeDir);
     if (ts > 0) {
-        double pseudoDistance = crossProduct(ep, edgeDir);
-        if (fabs(pseudoDistance) < fabs(distance)) {
-            distance = pseudoDistance;
+        double perpendicularDistance = crossProduct(ep, edgeDir);
+        if (fabs(perpendicularDistance) < fabs(distance)) {
+            distance = perpendicularDistance;
             return true;
         }
     }
     return false;
 }
 
-PseudoDistanceSelectorBase::PseudoDistanceSelectorBase() : minNegativePseudoDistance(-fabs(minTrueDistance.distance)), minPositivePseudoDistance(fabs(minTrueDistance.distance)), nearEdge(NULL), nearEdgeParam(0) { }
+PerpendicularDistanceSelectorBase::PerpendicularDistanceSelectorBase() : minNegativePerpendicularDistance(-fabs(minTrueDistance.distance)), minPositivePerpendicularDistance(fabs(minTrueDistance.distance)), nearEdge(NULL), nearEdgeParam(0) { }
 
-void PseudoDistanceSelectorBase::reset(double delta) {
+void PerpendicularDistanceSelectorBase::reset(double delta) {
     minTrueDistance.distance += nonZeroSign(minTrueDistance.distance)*delta;
-    minNegativePseudoDistance = -fabs(minTrueDistance.distance);
-    minPositivePseudoDistance = fabs(minTrueDistance.distance);
+    minNegativePerpendicularDistance = -fabs(minTrueDistance.distance);
+    minPositivePerpendicularDistance = fabs(minTrueDistance.distance);
     nearEdge = NULL;
     nearEdgeParam = 0;
 }
 
-bool PseudoDistanceSelectorBase::isEdgeRelevant(const EdgeCache &cache, const EdgeSegment *edge, const Point2 &p) const {
+bool PerpendicularDistanceSelectorBase::isEdgeRelevant(const EdgeCache &cache, const EdgeSegment *edge, const Point2 &p) const {
     double delta = DISTANCE_DELTA_FACTOR*(p-cache.point).length();
     return (
         cache.absDistance-delta <= fabs(minTrueDistance.distance) ||
         fabs(cache.aDomainDistance) < delta ||
         fabs(cache.bDomainDistance) < delta ||
-        (cache.aDomainDistance > 0 && (cache.aPseudoDistance < 0 ?
-            cache.aPseudoDistance+delta >= minNegativePseudoDistance :
-            cache.aPseudoDistance-delta <= minPositivePseudoDistance
+        (cache.aDomainDistance > 0 && (cache.aPerpendicularDistance < 0 ?
+            cache.aPerpendicularDistance+delta >= minNegativePerpendicularDistance :
+            cache.aPerpendicularDistance-delta <= minPositivePerpendicularDistance
         )) ||
-        (cache.bDomainDistance > 0 && (cache.bPseudoDistance < 0 ?
-            cache.bPseudoDistance+delta >= minNegativePseudoDistance :
-            cache.bPseudoDistance-delta <= minPositivePseudoDistance
+        (cache.bDomainDistance > 0 && (cache.bPerpendicularDistance < 0 ?
+            cache.bPerpendicularDistance+delta >= minNegativePerpendicularDistance :
+            cache.bPerpendicularDistance-delta <= minPositivePerpendicularDistance
         ))
     );
 }
 
-void PseudoDistanceSelectorBase::addEdgeTrueDistance(const EdgeSegment *edge, const SignedDistance &distance, double param) {
+void PerpendicularDistanceSelectorBase::addEdgeTrueDistance(const EdgeSegment *edge, const SignedDistance &distance, double param) {
     if (distance < minTrueDistance) {
         minTrueDistance = distance;
         nearEdge = edge;
@@ -85,47 +85,47 @@ void PseudoDistanceSelectorBase::addEdgeTrueDistance(const EdgeSegment *edge, co
     }
 }
 
-void PseudoDistanceSelectorBase::addEdgePseudoDistance(double distance) {
-    if (distance <= 0 && distance > minNegativePseudoDistance)
-        minNegativePseudoDistance = distance;
-    if (distance >= 0 && distance < minPositivePseudoDistance)
-        minPositivePseudoDistance = distance;
+void PerpendicularDistanceSelectorBase::addEdgePerpendicularDistance(double distance) {
+    if (distance <= 0 && distance > minNegativePerpendicularDistance)
+        minNegativePerpendicularDistance = distance;
+    if (distance >= 0 && distance < minPositivePerpendicularDistance)
+        minPositivePerpendicularDistance = distance;
 }
 
-void PseudoDistanceSelectorBase::merge(const PseudoDistanceSelectorBase &other) {
+void PerpendicularDistanceSelectorBase::merge(const PerpendicularDistanceSelectorBase &other) {
     if (other.minTrueDistance < minTrueDistance) {
         minTrueDistance = other.minTrueDistance;
         nearEdge = other.nearEdge;
         nearEdgeParam = other.nearEdgeParam;
     }
-    if (other.minNegativePseudoDistance > minNegativePseudoDistance)
-        minNegativePseudoDistance = other.minNegativePseudoDistance;
-    if (other.minPositivePseudoDistance < minPositivePseudoDistance)
-        minPositivePseudoDistance = other.minPositivePseudoDistance;
+    if (other.minNegativePerpendicularDistance > minNegativePerpendicularDistance)
+        minNegativePerpendicularDistance = other.minNegativePerpendicularDistance;
+    if (other.minPositivePerpendicularDistance < minPositivePerpendicularDistance)
+        minPositivePerpendicularDistance = other.minPositivePerpendicularDistance;
 }
 
-double PseudoDistanceSelectorBase::computeDistance(const Point2 &p) const {
-    double minDistance = minTrueDistance.distance < 0 ? minNegativePseudoDistance : minPositivePseudoDistance;
+double PerpendicularDistanceSelectorBase::computeDistance(const Point2 &p) const {
+    double minDistance = minTrueDistance.distance < 0 ? minNegativePerpendicularDistance : minPositivePerpendicularDistance;
     if (nearEdge) {
         SignedDistance distance = minTrueDistance;
-        nearEdge->distanceToPseudoDistance(distance, p, nearEdgeParam);
+        nearEdge->distanceToPerpendicularDistance(distance, p, nearEdgeParam);
         if (fabs(distance.distance) < fabs(minDistance))
             minDistance = distance.distance;
     }
     return minDistance;
 }
 
-SignedDistance PseudoDistanceSelectorBase::trueDistance() const {
+SignedDistance PerpendicularDistanceSelectorBase::trueDistance() const {
     return minTrueDistance;
 }
 
-void PseudoDistanceSelector::reset(const Point2 &p) {
+void PerpendicularDistanceSelector::reset(const Point2 &p) {
     double delta = DISTANCE_DELTA_FACTOR*(p-this->p).length();
-    PseudoDistanceSelectorBase::reset(delta);
+    PerpendicularDistanceSelectorBase::reset(delta);
     this->p = p;
 }
 
-void PseudoDistanceSelector::addEdge(EdgeCache &cache, const EdgeSegment *prevEdge, const EdgeSegment *edge, const EdgeSegment *nextEdge) {
+void PerpendicularDistanceSelector::addEdge(EdgeCache &cache, const EdgeSegment *prevEdge, const EdgeSegment *edge, const EdgeSegment *nextEdge) {
     if (isEdgeRelevant(cache, edge, p)) {
         double param;
         SignedDistance distance = edge->signedDistance(p, param);
@@ -143,22 +143,22 @@ void PseudoDistanceSelector::addEdge(EdgeCache &cache, const EdgeSegment *prevEd
         double bdd = -dotProduct(bp, (bDir+nextDir).normalize(true));
         if (add > 0) {
             double pd = distance.distance;
-            if (getPseudoDistance(pd, ap, -aDir))
-                addEdgePseudoDistance(pd = -pd);
-            cache.aPseudoDistance = pd;
+            if (getPerpendicularDistance(pd, ap, -aDir))
+                addEdgePerpendicularDistance(pd = -pd);
+            cache.aPerpendicularDistance = pd;
         }
         if (bdd > 0) {
             double pd = distance.distance;
-            if (getPseudoDistance(pd, bp, bDir))
-                addEdgePseudoDistance(pd);
-            cache.bPseudoDistance = pd;
+            if (getPerpendicularDistance(pd, bp, bDir))
+                addEdgePerpendicularDistance(pd);
+            cache.bPerpendicularDistance = pd;
         }
         cache.aDomainDistance = add;
         cache.bDomainDistance = bdd;
     }
 }
 
-PseudoDistanceSelector::DistanceType PseudoDistanceSelector::distance() const {
+PerpendicularDistanceSelector::DistanceType PerpendicularDistanceSelector::distance() const {
     return computeDistance(p);
 }
 
@@ -197,28 +197,28 @@ void MultiDistanceSelector::addEdge(EdgeCache &cache, const EdgeSegment *prevEdg
         double bdd = -dotProduct(bp, (bDir+nextDir).normalize(true));
         if (add > 0) {
             double pd = distance.distance;
-            if (PseudoDistanceSelectorBase::getPseudoDistance(pd, ap, -aDir)) {
+            if (PerpendicularDistanceSelectorBase::getPerpendicularDistance(pd, ap, -aDir)) {
                 pd = -pd;
                 if (edge->color&RED)
-                    r.addEdgePseudoDistance(pd);
+                    r.addEdgePerpendicularDistance(pd);
                 if (edge->color&GREEN)
-                    g.addEdgePseudoDistance(pd);
+                    g.addEdgePerpendicularDistance(pd);
                 if (edge->color&BLUE)
-                    b.addEdgePseudoDistance(pd);
+                    b.addEdgePerpendicularDistance(pd);
             }
-            cache.aPseudoDistance = pd;
+            cache.aPerpendicularDistance = pd;
         }
         if (bdd > 0) {
             double pd = distance.distance;
-            if (PseudoDistanceSelectorBase::getPseudoDistance(pd, bp, bDir)) {
+            if (PerpendicularDistanceSelectorBase::getPerpendicularDistance(pd, bp, bDir)) {
                 if (edge->color&RED)
-                    r.addEdgePseudoDistance(pd);
+                    r.addEdgePerpendicularDistance(pd);
                 if (edge->color&GREEN)
-                    g.addEdgePseudoDistance(pd);
+                    g.addEdgePerpendicularDistance(pd);
                 if (edge->color&BLUE)
-                    b.addEdgePseudoDistance(pd);
+                    b.addEdgePerpendicularDistance(pd);
             }
-            cache.bPseudoDistance = pd;
+            cache.bPerpendicularDistance = pd;
         }
         cache.aDomainDistance = add;
         cache.bDomainDistance = bdd;

+ 14 - 14
thirdparty/msdfgen/core/edge-selectors.h

@@ -38,40 +38,40 @@ private:
 
 };
 
-class PseudoDistanceSelectorBase {
+class PerpendicularDistanceSelectorBase {
 
 public:
     struct EdgeCache {
         Point2 point;
         double absDistance;
         double aDomainDistance, bDomainDistance;
-        double aPseudoDistance, bPseudoDistance;
+        double aPerpendicularDistance, bPerpendicularDistance;
 
         EdgeCache();
     };
 
-    static bool getPseudoDistance(double &distance, const Vector2 &ep, const Vector2 &edgeDir);
+    static bool getPerpendicularDistance(double &distance, const Vector2 &ep, const Vector2 &edgeDir);
 
-    PseudoDistanceSelectorBase();
+    PerpendicularDistanceSelectorBase();
     void reset(double delta);
     bool isEdgeRelevant(const EdgeCache &cache, const EdgeSegment *edge, const Point2 &p) const;
     void addEdgeTrueDistance(const EdgeSegment *edge, const SignedDistance &distance, double param);
-    void addEdgePseudoDistance(double distance);
-    void merge(const PseudoDistanceSelectorBase &other);
+    void addEdgePerpendicularDistance(double distance);
+    void merge(const PerpendicularDistanceSelectorBase &other);
     double computeDistance(const Point2 &p) const;
     SignedDistance trueDistance() const;
 
 private:
     SignedDistance minTrueDistance;
-    double minNegativePseudoDistance;
-    double minPositivePseudoDistance;
+    double minNegativePerpendicularDistance;
+    double minPositivePerpendicularDistance;
     const EdgeSegment *nearEdge;
     double nearEdgeParam;
 
 };
 
-/// Selects the nearest edge by its pseudo-distance.
-class PseudoDistanceSelector : public PseudoDistanceSelectorBase {
+/// Selects the nearest edge by its perpendicular distance.
+class PerpendicularDistanceSelector : public PerpendicularDistanceSelectorBase {
 
 public:
     typedef double DistanceType;
@@ -85,12 +85,12 @@ private:
 
 };
 
-/// Selects the nearest edge for each of the three channels by its pseudo-distance.
+/// Selects the nearest edge for each of the three channels by its perpendicular distance.
 class MultiDistanceSelector {
 
 public:
     typedef MultiDistance DistanceType;
-    typedef PseudoDistanceSelectorBase::EdgeCache EdgeCache;
+    typedef PerpendicularDistanceSelectorBase::EdgeCache EdgeCache;
 
     void reset(const Point2 &p);
     void addEdge(EdgeCache &cache, const EdgeSegment *prevEdge, const EdgeSegment *edge, const EdgeSegment *nextEdge);
@@ -100,11 +100,11 @@ public:
 
 private:
     Point2 p;
-    PseudoDistanceSelectorBase r, g, b;
+    PerpendicularDistanceSelectorBase r, g, b;
 
 };
 
-/// Selects the nearest edge for each of the three color channels by its pseudo-distance and by true distance for the alpha channel.
+/// Selects the nearest edge for each of the three color channels by its perpendicular distance and by true distance for the alpha channel.
 class MultiAndTrueDistanceSelector : public MultiDistanceSelector {
 
 public:

+ 79 - 0
thirdparty/msdfgen/core/export-svg.cpp

@@ -0,0 +1,79 @@
+
+#include "export-svg.h"
+
+#include <cstdio>
+#include "edge-segments.h"
+
+namespace msdfgen {
+
+static void writeSvgCoord(FILE *f, Point2 coord) {
+    fprintf(f, "%.17g %.17g", coord.x, coord.y);
+}
+
+static void writeSvgPathDef(FILE *f, const Shape &shape) {
+    bool beginning = true;
+    for (const Contour &c : shape.contours) {
+        if (c.edges.empty())
+            continue;
+        if (beginning)
+            beginning = false;
+        else
+            fputc(' ', f);
+        fputs("M ", f);
+        writeSvgCoord(f, c.edges[0]->controlPoints()[0]);
+        for (const EdgeHolder &e : c.edges) {
+            const Point2 *cp = e->controlPoints();
+            switch (e->type()) {
+                case (int) LinearSegment::EDGE_TYPE:
+                    fputs(" L ", f);
+                    writeSvgCoord(f, cp[1]);
+                    break;
+                case (int) QuadraticSegment::EDGE_TYPE:
+                    fputs(" Q ", f);
+                    writeSvgCoord(f, cp[1]);
+                    fputc(' ', f);
+                    writeSvgCoord(f, cp[2]);
+                    break;
+                case (int) CubicSegment::EDGE_TYPE:
+                    fputs(" C ", f);
+                    writeSvgCoord(f, cp[1]);
+                    fputc(' ', f);
+                    writeSvgCoord(f, cp[2]);
+                    fputc(' ', f);
+                    writeSvgCoord(f, cp[3]);
+                    break;
+            }
+        }
+        fputs(" Z", f);
+    }
+}
+
+bool saveSvgShape(const Shape &shape, const char *filename) {
+    if (FILE *f = fopen(filename, "w")) {
+        fputs("<svg xmlns=\"http://www.w3.org/2000/svg\"><path", f);
+        if (!shape.inverseYAxis)
+            fputs(" transform=\"scale(1 -1)\"", f);
+        fputs(" d=\"", f);
+        writeSvgPathDef(f, shape);
+        fputs("\"/></svg>\n", f);
+        fclose(f);
+        return true;
+    }
+    return false;
+}
+
+bool saveSvgShape(const Shape &shape, const Shape::Bounds &bounds, const char *filename) {
+    if (FILE *f = fopen(filename, "w")) {
+        fprintf(f, "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"%.17g %.17g %.17g %.17g\"><path", bounds.l, bounds.b, bounds.r-bounds.l, bounds.t-bounds.b);
+        if (!shape.inverseYAxis)
+            fprintf(f, " transform=\"translate(0 %.17g) scale(1 -1)\"", bounds.b+bounds.t);
+        fputs(" d=\"", f);
+        writeSvgPathDef(f, shape);
+        fputs("\"/></svg>\n", f);
+        fclose(f);
+        return true;
+    }
+    return false;
+}
+
+}

+ 11 - 0
thirdparty/msdfgen/core/export-svg.h

@@ -0,0 +1,11 @@
+
+#pragma once
+
+#include "Shape.h"
+
+namespace msdfgen {
+
+bool saveSvgShape(const Shape &shape, const char *filename);
+bool saveSvgShape(const Shape &shape, const Shape::Bounds &bounds, const char *filename);
+
+}

+ 34 - 16
thirdparty/msdfgen/core/msdf-error-correction.cpp

@@ -10,7 +10,7 @@
 namespace msdfgen {
 
 template <int N>
-static void msdfErrorCorrectionInner(const BitmapRef<float, N> &sdf, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config) {
+static void msdfErrorCorrectionInner(const BitmapRef<float, N> &sdf, const Shape &shape, const SDFTransformation &transformation, const MSDFGeneratorConfig &config) {
     if (config.errorCorrection.mode == ErrorCorrectionConfig::DISABLED)
         return;
     Bitmap<byte, 1> stencilBuffer;
@@ -19,7 +19,7 @@ static void msdfErrorCorrectionInner(const BitmapRef<float, N> &sdf, const Shape
     BitmapRef<byte, 1> stencil;
     stencil.pixels = config.errorCorrection.buffer ? config.errorCorrection.buffer : (byte *) stencilBuffer;
     stencil.width = sdf.width, stencil.height = sdf.height;
-    MSDFErrorCorrection ec(stencil, projection, range);
+    MSDFErrorCorrection ec(stencil, transformation);
     ec.setMinDeviationRatio(config.errorCorrection.minDeviationRatio);
     ec.setMinImproveRatio(config.errorCorrection.minImproveRatio);
     switch (config.errorCorrection.mode) {
@@ -49,9 +49,9 @@ static void msdfErrorCorrectionInner(const BitmapRef<float, N> &sdf, const Shape
 }
 
 template <int N>
-static void msdfErrorCorrectionShapeless(const BitmapRef<float, N> &sdf, const Projection &projection, double range, double minDeviationRatio, bool protectAll) {
+static void msdfErrorCorrectionShapeless(const BitmapRef<float, N> &sdf, const SDFTransformation &transformation, double minDeviationRatio, bool protectAll) {
     Bitmap<byte, 1> stencilBuffer(sdf.width, sdf.height);
-    MSDFErrorCorrection ec(stencilBuffer, projection, range);
+    MSDFErrorCorrection ec(stencilBuffer, transformation);
     ec.setMinDeviationRatio(minDeviationRatio);
     if (protectAll)
         ec.protectAll();
@@ -59,25 +59,43 @@ static void msdfErrorCorrectionShapeless(const BitmapRef<float, N> &sdf, const P
     ec.apply(sdf);
 }
 
-void msdfErrorCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config) {
-    msdfErrorCorrectionInner(sdf, shape, projection, range, config);
+void msdfErrorCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape, const SDFTransformation &transformation, const MSDFGeneratorConfig &config) {
+    msdfErrorCorrectionInner(sdf, shape, transformation, config);
 }
-void msdfErrorCorrection(const BitmapRef<float, 4> &sdf, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config) {
-    msdfErrorCorrectionInner(sdf, shape, projection, range, config);
+void msdfErrorCorrection(const BitmapRef<float, 4> &sdf, const Shape &shape, const SDFTransformation &transformation, const MSDFGeneratorConfig &config) {
+    msdfErrorCorrectionInner(sdf, shape, transformation, config);
+}
+void msdfErrorCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape, const Projection &projection, Range range, const MSDFGeneratorConfig &config) {
+    msdfErrorCorrectionInner(sdf, shape, SDFTransformation(projection, range), config);
+}
+void msdfErrorCorrection(const BitmapRef<float, 4> &sdf, const Shape &shape, const Projection &projection, Range range, const MSDFGeneratorConfig &config) {
+    msdfErrorCorrectionInner(sdf, shape, SDFTransformation(projection, range), config);
 }
 
-void msdfFastDistanceErrorCorrection(const BitmapRef<float, 3> &sdf, const Projection &projection, double range, double minDeviationRatio) {
-    msdfErrorCorrectionShapeless(sdf, projection, range, minDeviationRatio, false);
+void msdfFastDistanceErrorCorrection(const BitmapRef<float, 3> &sdf, const SDFTransformation &transformation, double minDeviationRatio) {
+    msdfErrorCorrectionShapeless(sdf, transformation, minDeviationRatio, false);
+}
+void msdfFastDistanceErrorCorrection(const BitmapRef<float, 4> &sdf, const SDFTransformation &transformation, double minDeviationRatio) {
+    msdfErrorCorrectionShapeless(sdf, transformation, minDeviationRatio, false);
 }
-void msdfFastDistanceErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, double range, double minDeviationRatio) {
-    msdfErrorCorrectionShapeless(sdf, projection, range, minDeviationRatio, false);
+void msdfFastDistanceErrorCorrection(const BitmapRef<float, 3> &sdf, const Projection &projection, Range range, double minDeviationRatio) {
+    msdfErrorCorrectionShapeless(sdf, SDFTransformation(projection, range), minDeviationRatio, false);
+}
+void msdfFastDistanceErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, Range range, double minDeviationRatio) {
+    msdfErrorCorrectionShapeless(sdf, SDFTransformation(projection, range), minDeviationRatio, false);
 }
 
-void msdfFastEdgeErrorCorrection(const BitmapRef<float, 3> &sdf, const Projection &projection, double range, double minDeviationRatio) {
-    msdfErrorCorrectionShapeless(sdf, projection, range, minDeviationRatio, true);
+void msdfFastEdgeErrorCorrection(const BitmapRef<float, 3> &sdf, const SDFTransformation &transformation, double minDeviationRatio) {
+    msdfErrorCorrectionShapeless(sdf, transformation, minDeviationRatio, true);
+}
+void msdfFastEdgeErrorCorrection(const BitmapRef<float, 4> &sdf, const SDFTransformation &transformation, double minDeviationRatio) {
+    msdfErrorCorrectionShapeless(sdf, transformation, minDeviationRatio, true);
+}
+void msdfFastEdgeErrorCorrection(const BitmapRef<float, 3> &sdf, const Projection &projection, Range range, double minDeviationRatio) {
+    msdfErrorCorrectionShapeless(sdf, SDFTransformation(projection, range), minDeviationRatio, true);
 }
-void msdfFastEdgeErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, double range, double minDeviationRatio) {
-    msdfErrorCorrectionShapeless(sdf, projection, range, minDeviationRatio, true);
+void msdfFastEdgeErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, Range range, double minDeviationRatio) {
+    msdfErrorCorrectionShapeless(sdf, SDFTransformation(projection, range), minDeviationRatio, true);
 }
 
 

+ 14 - 6
thirdparty/msdfgen/core/msdf-error-correction.h

@@ -2,7 +2,9 @@
 #pragma once
 
 #include "Vector2.hpp"
+#include "Range.hpp"
 #include "Projection.h"
+#include "SDFTransformation.h"
 #include "Shape.h"
 #include "BitmapRef.hpp"
 #include "generator-config.h"
@@ -10,16 +12,22 @@
 namespace msdfgen {
 
 /// Predicts potential artifacts caused by the interpolation of the MSDF and corrects them by converting nearby texels to single-channel.
-void msdfErrorCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config = MSDFGeneratorConfig());
-void msdfErrorCorrection(const BitmapRef<float, 4> &sdf, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config = MSDFGeneratorConfig());
+void msdfErrorCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape, const SDFTransformation &transformation, const MSDFGeneratorConfig &config = MSDFGeneratorConfig());
+void msdfErrorCorrection(const BitmapRef<float, 4> &sdf, const Shape &shape, const SDFTransformation &transformation, const MSDFGeneratorConfig &config = MSDFGeneratorConfig());
+void msdfErrorCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape, const Projection &projection, Range range, const MSDFGeneratorConfig &config = MSDFGeneratorConfig());
+void msdfErrorCorrection(const BitmapRef<float, 4> &sdf, const Shape &shape, const Projection &projection, Range range, const MSDFGeneratorConfig &config = MSDFGeneratorConfig());
 
 /// Applies the simplified error correction to all discontiunous distances (INDISCRIMINATE mode). Does not need shape or translation.
-void msdfFastDistanceErrorCorrection(const BitmapRef<float, 3> &sdf, const Projection &projection, double range, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio);
-void msdfFastDistanceErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, double range, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio);
+void msdfFastDistanceErrorCorrection(const BitmapRef<float, 3> &sdf, const SDFTransformation &transformation, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio);
+void msdfFastDistanceErrorCorrection(const BitmapRef<float, 4> &sdf, const SDFTransformation &transformation, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio);
+void msdfFastDistanceErrorCorrection(const BitmapRef<float, 3> &sdf, const Projection &projection, Range range, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio);
+void msdfFastDistanceErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, Range range, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio);
 
 /// Applies the simplified error correction to edges only (EDGE_ONLY mode). Does not need shape or translation.
-void msdfFastEdgeErrorCorrection(const BitmapRef<float, 3> &sdf, const Projection &projection, double range, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio);
-void msdfFastEdgeErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, double range, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio);
+void msdfFastEdgeErrorCorrection(const BitmapRef<float, 3> &sdf, const SDFTransformation &transformation, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio);
+void msdfFastEdgeErrorCorrection(const BitmapRef<float, 4> &sdf, const SDFTransformation &transformation, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio);
+void msdfFastEdgeErrorCorrection(const BitmapRef<float, 3> &sdf, const Projection &projection, Range range, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio);
+void msdfFastEdgeErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, Range range, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio);
 
 /// The original version of the error correction algorithm.
 void msdfErrorCorrection_legacy(const BitmapRef<float, 3> &output, const Vector2 &threshold);

+ 102 - 56
thirdparty/msdfgen/core/msdfgen.cpp

@@ -13,45 +13,45 @@ class DistancePixelConversion;
 
 template <>
 class DistancePixelConversion<double> {
-    double invRange;
+    DistanceMapping mapping;
 public:
     typedef BitmapRef<float, 1> BitmapRefType;
-    inline explicit DistancePixelConversion(double range) : invRange(1/range) { }
+    inline explicit DistancePixelConversion(DistanceMapping mapping) : mapping(mapping) { }
     inline void operator()(float *pixels, double distance) const {
-        *pixels = float(invRange*distance+.5);
+        *pixels = float(mapping(distance));
     }
 };
 
 template <>
 class DistancePixelConversion<MultiDistance> {
-    double invRange;
+    DistanceMapping mapping;
 public:
     typedef BitmapRef<float, 3> BitmapRefType;
-    inline explicit DistancePixelConversion(double range) : invRange(1/range) { }
+    inline explicit DistancePixelConversion(DistanceMapping mapping) : mapping(mapping) { }
     inline void operator()(float *pixels, const MultiDistance &distance) const {
-        pixels[0] = float(invRange*distance.r+.5);
-        pixels[1] = float(invRange*distance.g+.5);
-        pixels[2] = float(invRange*distance.b+.5);
+        pixels[0] = float(mapping(distance.r));
+        pixels[1] = float(mapping(distance.g));
+        pixels[2] = float(mapping(distance.b));
     }
 };
 
 template <>
 class DistancePixelConversion<MultiAndTrueDistance> {
-    double invRange;
+    DistanceMapping mapping;
 public:
     typedef BitmapRef<float, 4> BitmapRefType;
-    inline explicit DistancePixelConversion(double range) : invRange(1/range) { }
+    inline explicit DistancePixelConversion(DistanceMapping mapping) : mapping(mapping) { }
     inline void operator()(float *pixels, const MultiAndTrueDistance &distance) const {
-        pixels[0] = float(invRange*distance.r+.5);
-        pixels[1] = float(invRange*distance.g+.5);
-        pixels[2] = float(invRange*distance.b+.5);
-        pixels[3] = float(invRange*distance.a+.5);
+        pixels[0] = float(mapping(distance.r));
+        pixels[1] = float(mapping(distance.g));
+        pixels[2] = float(mapping(distance.b));
+        pixels[3] = float(mapping(distance.a));
     }
 };
 
 template <class ContourCombiner>
-void generateDistanceField(const typename DistancePixelConversion<typename ContourCombiner::DistanceType>::BitmapRefType &output, const Shape &shape, const Projection &projection, double range) {
-    DistancePixelConversion<typename ContourCombiner::DistanceType> distancePixelConversion(range);
+void generateDistanceField(const typename DistancePixelConversion<typename ContourCombiner::DistanceType>::BitmapRefType &output, const Shape &shape, const SDFTransformation &transformation) {
+    DistancePixelConversion<typename ContourCombiner::DistanceType> distancePixelConversion(transformation.distanceMapping);
 #ifdef MSDFGEN_USE_OPENMP
     #pragma omp parallel
 #endif
@@ -65,7 +65,7 @@ void generateDistanceField(const typename DistancePixelConversion<typename Conto
             int row = shape.inverseYAxis ? output.height-y-1 : y;
             for (int col = 0; col < output.width; ++col) {
                 int x = rightToLeft ? output.width-col-1 : col;
-                Point2 p = projection.unproject(Point2(x+.5, y+.5));
+                Point2 p = transformation.unproject(Point2(x+.5, y+.5));
                 typename ContourCombiner::DistanceType distance = distanceFinder.distance(p);
                 distancePixelConversion(output(x, row), distance);
             }
@@ -74,57 +74,96 @@ void generateDistanceField(const typename DistancePixelConversion<typename Conto
     }
 }
 
-void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, double range, const GeneratorConfig &config) {
+void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, const SDFTransformation &transformation, const GeneratorConfig &config) {
     if (config.overlapSupport)
-        generateDistanceField<OverlappingContourCombiner<TrueDistanceSelector> >(output, shape, projection, range);
+        generateDistanceField<OverlappingContourCombiner<TrueDistanceSelector> >(output, shape, transformation);
     else
-        generateDistanceField<SimpleContourCombiner<TrueDistanceSelector> >(output, shape, projection, range);
+        generateDistanceField<SimpleContourCombiner<TrueDistanceSelector> >(output, shape, transformation);
 }
 
-void generatePseudoSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, double range, const GeneratorConfig &config) {
+void generatePSDF(const BitmapRef<float, 1> &output, const Shape &shape, const SDFTransformation &transformation, const GeneratorConfig &config) {
     if (config.overlapSupport)
-        generateDistanceField<OverlappingContourCombiner<PseudoDistanceSelector> >(output, shape, projection, range);
+        generateDistanceField<OverlappingContourCombiner<PerpendicularDistanceSelector> >(output, shape, transformation);
     else
-        generateDistanceField<SimpleContourCombiner<PseudoDistanceSelector> >(output, shape, projection, range);
+        generateDistanceField<SimpleContourCombiner<PerpendicularDistanceSelector> >(output, shape, transformation);
 }
 
-void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config) {
+void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, const SDFTransformation &transformation, const MSDFGeneratorConfig &config) {
     if (config.overlapSupport)
-        generateDistanceField<OverlappingContourCombiner<MultiDistanceSelector> >(output, shape, projection, range);
+        generateDistanceField<OverlappingContourCombiner<MultiDistanceSelector> >(output, shape, transformation);
     else
-        generateDistanceField<SimpleContourCombiner<MultiDistanceSelector> >(output, shape, projection, range);
-    msdfErrorCorrection(output, shape, projection, range, config);
+        generateDistanceField<SimpleContourCombiner<MultiDistanceSelector> >(output, shape, transformation);
+    msdfErrorCorrection(output, shape, transformation, config);
 }
 
-void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config) {
+void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, const SDFTransformation &transformation, const MSDFGeneratorConfig &config) {
     if (config.overlapSupport)
-        generateDistanceField<OverlappingContourCombiner<MultiAndTrueDistanceSelector> >(output, shape, projection, range);
+        generateDistanceField<OverlappingContourCombiner<MultiAndTrueDistanceSelector> >(output, shape, transformation);
     else
-        generateDistanceField<SimpleContourCombiner<MultiAndTrueDistanceSelector> >(output, shape, projection, range);
-    msdfErrorCorrection(output, shape, projection, range, config);
+        generateDistanceField<SimpleContourCombiner<MultiAndTrueDistanceSelector> >(output, shape, transformation);
+    msdfErrorCorrection(output, shape, transformation, config);
+}
+
+void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, Range range, const GeneratorConfig &config) {
+    if (config.overlapSupport)
+        generateDistanceField<OverlappingContourCombiner<TrueDistanceSelector> >(output, shape, SDFTransformation(projection, range));
+    else
+        generateDistanceField<SimpleContourCombiner<TrueDistanceSelector> >(output, shape, SDFTransformation(projection, range));
+}
+
+void generatePSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, Range range, const GeneratorConfig &config) {
+    if (config.overlapSupport)
+        generateDistanceField<OverlappingContourCombiner<PerpendicularDistanceSelector> >(output, shape, SDFTransformation(projection, range));
+    else
+        generateDistanceField<SimpleContourCombiner<PerpendicularDistanceSelector> >(output, shape, SDFTransformation(projection, range));
+}
+
+void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, const Projection &projection, Range range, const MSDFGeneratorConfig &config) {
+    if (config.overlapSupport)
+        generateDistanceField<OverlappingContourCombiner<MultiDistanceSelector> >(output, shape, SDFTransformation(projection, range));
+    else
+        generateDistanceField<SimpleContourCombiner<MultiDistanceSelector> >(output, shape, SDFTransformation(projection, range));
+    msdfErrorCorrection(output, shape, SDFTransformation(projection, range), config);
+}
+
+void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, const Projection &projection, Range range, const MSDFGeneratorConfig &config) {
+    if (config.overlapSupport)
+        generateDistanceField<OverlappingContourCombiner<MultiAndTrueDistanceSelector> >(output, shape, SDFTransformation(projection, range));
+    else
+        generateDistanceField<SimpleContourCombiner<MultiAndTrueDistanceSelector> >(output, shape, SDFTransformation(projection, range));
+    msdfErrorCorrection(output, shape, SDFTransformation(projection, range), config);
 }
 
 // Legacy API
 
-void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport) {
+void generatePseudoSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, Range range, const GeneratorConfig &config) {
+    generatePSDF(output, shape, SDFTransformation(projection, range), config);
+}
+
+void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport) {
     generateSDF(output, shape, Projection(scale, translate), range, GeneratorConfig(overlapSupport));
 }
 
-void generatePseudoSDF(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport) {
-    generatePseudoSDF(output, shape, Projection(scale, translate), range, GeneratorConfig(overlapSupport));
+void generatePSDF(const BitmapRef<float, 1> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport) {
+    generatePSDF(output, shape, Projection(scale, translate), range, GeneratorConfig(overlapSupport));
+}
+
+void generatePseudoSDF(const BitmapRef<float, 1> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport) {
+    generatePSDF(output, shape, Projection(scale, translate), range, GeneratorConfig(overlapSupport));
 }
 
-void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, const ErrorCorrectionConfig &errorCorrectionConfig, bool overlapSupport) {
+void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, const ErrorCorrectionConfig &errorCorrectionConfig, bool overlapSupport) {
     generateMSDF(output, shape, Projection(scale, translate), range, MSDFGeneratorConfig(overlapSupport, errorCorrectionConfig));
 }
 
-void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, const ErrorCorrectionConfig &errorCorrectionConfig, bool overlapSupport) {
+void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, const ErrorCorrectionConfig &errorCorrectionConfig, bool overlapSupport) {
     generateMTSDF(output, shape, Projection(scale, translate), range, MSDFGeneratorConfig(overlapSupport, errorCorrectionConfig));
 }
 
 // Legacy version
 
-void generateSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) {
+void generateSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate) {
+    DistanceMapping distanceMapping(range);
 #ifdef MSDFGEN_USE_OPENMP
     #pragma omp parallel for
 #endif
@@ -140,12 +179,13 @@ void generateSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, d
                     if (distance < minDistance)
                         minDistance = distance;
                 }
-            *output(x, row) = float(minDistance.distance/range+.5);
+            *output(x, row) = float(distanceMapping(minDistance.distance));
         }
     }
 }
 
-void generatePseudoSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) {
+void generatePSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate) {
+    DistanceMapping distanceMapping(range);
 #ifdef MSDFGEN_USE_OPENMP
     #pragma omp parallel for
 #endif
@@ -167,13 +207,18 @@ void generatePseudoSDF_legacy(const BitmapRef<float, 1> &output, const Shape &sh
                     }
                 }
             if (nearEdge)
-                (*nearEdge)->distanceToPseudoDistance(minDistance, p, nearParam);
-            *output(x, row) = float(minDistance.distance/range+.5);
+                (*nearEdge)->distanceToPerpendicularDistance(minDistance, p, nearParam);
+            *output(x, row) = float(distanceMapping(minDistance.distance));
         }
     }
 }
 
-void generateMSDF_legacy(const BitmapRef<float, 3> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, ErrorCorrectionConfig errorCorrectionConfig) {
+void generatePseudoSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate) {
+    generatePSDF_legacy(output, shape, range, scale, translate);
+}
+
+void generateMSDF_legacy(const BitmapRef<float, 3> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, ErrorCorrectionConfig errorCorrectionConfig) {
+    DistanceMapping distanceMapping(range);
 #ifdef MSDFGEN_USE_OPENMP
     #pragma omp parallel for
 #endif
@@ -212,14 +257,14 @@ void generateMSDF_legacy(const BitmapRef<float, 3> &output, const Shape &shape,
                 }
 
             if (r.nearEdge)
-                (*r.nearEdge)->distanceToPseudoDistance(r.minDistance, p, r.nearParam);
+                (*r.nearEdge)->distanceToPerpendicularDistance(r.minDistance, p, r.nearParam);
             if (g.nearEdge)
-                (*g.nearEdge)->distanceToPseudoDistance(g.minDistance, p, g.nearParam);
+                (*g.nearEdge)->distanceToPerpendicularDistance(g.minDistance, p, g.nearParam);
             if (b.nearEdge)
-                (*b.nearEdge)->distanceToPseudoDistance(b.minDistance, p, b.nearParam);
-            output(x, row)[0] = float(r.minDistance.distance/range+.5);
-            output(x, row)[1] = float(g.minDistance.distance/range+.5);
-            output(x, row)[2] = float(b.minDistance.distance/range+.5);
+                (*b.nearEdge)->distanceToPerpendicularDistance(b.minDistance, p, b.nearParam);
+            output(x, row)[0] = float(distanceMapping(r.minDistance.distance));
+            output(x, row)[1] = float(distanceMapping(g.minDistance.distance));
+            output(x, row)[2] = float(distanceMapping(b.minDistance.distance));
         }
     }
 
@@ -227,7 +272,8 @@ void generateMSDF_legacy(const BitmapRef<float, 3> &output, const Shape &shape,
     msdfErrorCorrection(output, shape, Projection(scale, translate), range, MSDFGeneratorConfig(false, errorCorrectionConfig));
 }
 
-void generateMTSDF_legacy(const BitmapRef<float, 4> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, ErrorCorrectionConfig errorCorrectionConfig) {
+void generateMTSDF_legacy(const BitmapRef<float, 4> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, ErrorCorrectionConfig errorCorrectionConfig) {
+    DistanceMapping distanceMapping(range);
 #ifdef MSDFGEN_USE_OPENMP
     #pragma omp parallel for
 #endif
@@ -269,15 +315,15 @@ void generateMTSDF_legacy(const BitmapRef<float, 4> &output, const Shape &shape,
                 }
 
             if (r.nearEdge)
-                (*r.nearEdge)->distanceToPseudoDistance(r.minDistance, p, r.nearParam);
+                (*r.nearEdge)->distanceToPerpendicularDistance(r.minDistance, p, r.nearParam);
             if (g.nearEdge)
-                (*g.nearEdge)->distanceToPseudoDistance(g.minDistance, p, g.nearParam);
+                (*g.nearEdge)->distanceToPerpendicularDistance(g.minDistance, p, g.nearParam);
             if (b.nearEdge)
-                (*b.nearEdge)->distanceToPseudoDistance(b.minDistance, p, b.nearParam);
-            output(x, row)[0] = float(r.minDistance.distance/range+.5);
-            output(x, row)[1] = float(g.minDistance.distance/range+.5);
-            output(x, row)[2] = float(b.minDistance.distance/range+.5);
-            output(x, row)[3] = float(minDistance.distance/range+.5);
+                (*b.nearEdge)->distanceToPerpendicularDistance(b.minDistance, p, b.nearParam);
+            output(x, row)[0] = float(distanceMapping(r.minDistance.distance));
+            output(x, row)[1] = float(distanceMapping(g.minDistance.distance));
+            output(x, row)[2] = float(distanceMapping(b.minDistance.distance));
+            output(x, row)[3] = float(distanceMapping(minDistance.distance));
         }
     }
 

+ 1 - 1
thirdparty/msdfgen/core/pixel-conversion.hpp

@@ -6,7 +6,7 @@
 namespace msdfgen {
 
 inline byte pixelFloatToByte(float x) {
-    return byte(clamp(256.f*x, 255.f));
+    return byte(~int(255.5f-255.f*clamp(x)));
 }
 
 inline float pixelByteToFloat(byte x) {

+ 140 - 54
thirdparty/msdfgen/core/render-sdf.cpp

@@ -2,89 +2,175 @@
 #include "render-sdf.h"
 
 #include "arithmetics.hpp"
+#include "DistanceMapping.h"
 #include "pixel-conversion.hpp"
 #include "bitmap-interpolation.hpp"
 
 namespace msdfgen {
 
-static float distVal(float dist, double pxRange, float midValue) {
-    if (!pxRange)
-        return (float) (dist > midValue);
-    return (float) clamp((dist-midValue)*pxRange+.5);
+static float distVal(float dist, DistanceMapping mapping) {
+    return (float) clamp(mapping(dist)+.5);
 }
 
-void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 1> &sdf, double pxRange, float midValue) {
+void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 1> &sdf, Range sdfPxRange, float sdThreshold) {
     Vector2 scale((double) sdf.width/output.width, (double) sdf.height/output.height);
-    pxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height);
-    for (int y = 0; y < output.height; ++y)
-        for (int x = 0; x < output.width; ++x) {
-            float sd;
-            interpolate(&sd, sdf, scale*Point2(x+.5, y+.5));
-            *output(x, y) = distVal(sd, pxRange, midValue);
+    if (sdfPxRange.lower == sdfPxRange.upper) {
+        for (int y = 0; y < output.height; ++y) {
+            for (int x = 0; x < output.width; ++x) {
+                float sd;
+                interpolate(&sd, sdf, scale*Point2(x+.5, y+.5));
+                *output(x, y) = float(sd >= sdThreshold);
+            }
         }
+    } else {
+        sdfPxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height);
+        DistanceMapping distanceMapping = DistanceMapping::inverse(sdfPxRange);
+        float sdBias = .5f-sdThreshold;
+        for (int y = 0; y < output.height; ++y) {
+            for (int x = 0; x < output.width; ++x) {
+                float sd;
+                interpolate(&sd, sdf, scale*Point2(x+.5, y+.5));
+                *output(x, y) = distVal(sd+sdBias, distanceMapping);
+            }
+        }
+    }
+
 }
 
-void renderSDF(const BitmapRef<float, 3> &output, const BitmapConstRef<float, 1> &sdf, double pxRange, float midValue) {
+void renderSDF(const BitmapRef<float, 3> &output, const BitmapConstRef<float, 1> &sdf, Range sdfPxRange, float sdThreshold) {
     Vector2 scale((double) sdf.width/output.width, (double) sdf.height/output.height);
-    pxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height);
-    for (int y = 0; y < output.height; ++y)
-        for (int x = 0; x < output.width; ++x) {
-            float sd;
-            interpolate(&sd, sdf, scale*Point2(x+.5, y+.5));
-            float v = distVal(sd, pxRange, midValue);
-            output(x, y)[0] = v;
-            output(x, y)[1] = v;
-            output(x, y)[2] = v;
+    if (sdfPxRange.lower == sdfPxRange.upper) {
+        for (int y = 0; y < output.height; ++y) {
+            for (int x = 0; x < output.width; ++x) {
+                float sd;
+                interpolate(&sd, sdf, scale*Point2(x+.5, y+.5));
+                float v = float(sd >= sdThreshold);
+                output(x, y)[0] = v;
+                output(x, y)[1] = v;
+                output(x, y)[2] = v;
+            }
         }
+    } else {
+        sdfPxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height);
+        DistanceMapping distanceMapping = DistanceMapping::inverse(sdfPxRange);
+        float sdBias = .5f-sdThreshold;
+        for (int y = 0; y < output.height; ++y) {
+            for (int x = 0; x < output.width; ++x) {
+                float sd;
+                interpolate(&sd, sdf, scale*Point2(x+.5, y+.5));
+                float v = distVal(sd+sdBias, distanceMapping);
+                output(x, y)[0] = v;
+                output(x, y)[1] = v;
+                output(x, y)[2] = v;
+            }
+        }
+    }
 }
 
-void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 3> &sdf, double pxRange, float midValue) {
+void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 3> &sdf, Range sdfPxRange, float sdThreshold) {
     Vector2 scale((double) sdf.width/output.width, (double) sdf.height/output.height);
-    pxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height);
-    for (int y = 0; y < output.height; ++y)
-        for (int x = 0; x < output.width; ++x) {
-            float sd[3];
-            interpolate(sd, sdf, scale*Point2(x+.5, y+.5));
-            *output(x, y) = distVal(median(sd[0], sd[1], sd[2]), pxRange, midValue);
+    if (sdfPxRange.lower == sdfPxRange.upper) {
+        for (int y = 0; y < output.height; ++y) {
+            for (int x = 0; x < output.width; ++x) {
+                float sd[3];
+                interpolate(sd, sdf, scale*Point2(x+.5, y+.5));
+                *output(x, y) = float(median(sd[0], sd[1], sd[2]) >= sdThreshold);
+            }
+        }
+    } else {
+        sdfPxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height);
+        DistanceMapping distanceMapping = DistanceMapping::inverse(sdfPxRange);
+        float sdBias = .5f-sdThreshold;
+        for (int y = 0; y < output.height; ++y) {
+            for (int x = 0; x < output.width; ++x) {
+                float sd[3];
+                interpolate(sd, sdf, scale*Point2(x+.5, y+.5));
+                *output(x, y) = distVal(median(sd[0], sd[1], sd[2])+sdBias, distanceMapping);
+            }
         }
+    }
 }
 
-void renderSDF(const BitmapRef<float, 3> &output, const BitmapConstRef<float, 3> &sdf, double pxRange, float midValue) {
+void renderSDF(const BitmapRef<float, 3> &output, const BitmapConstRef<float, 3> &sdf, Range sdfPxRange, float sdThreshold) {
     Vector2 scale((double) sdf.width/output.width, (double) sdf.height/output.height);
-    pxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height);
-    for (int y = 0; y < output.height; ++y)
-        for (int x = 0; x < output.width; ++x) {
-            float sd[3];
-            interpolate(sd, sdf, scale*Point2(x+.5, y+.5));
-            output(x, y)[0] = distVal(sd[0], pxRange, midValue);
-            output(x, y)[1] = distVal(sd[1], pxRange, midValue);
-            output(x, y)[2] = distVal(sd[2], pxRange, midValue);
+    if (sdfPxRange.lower == sdfPxRange.upper) {
+        for (int y = 0; y < output.height; ++y) {
+            for (int x = 0; x < output.width; ++x) {
+                float sd[3];
+                interpolate(sd, sdf, scale*Point2(x+.5, y+.5));
+                output(x, y)[0] = float(sd[0] >= sdThreshold);
+                output(x, y)[1] = float(sd[1] >= sdThreshold);
+                output(x, y)[2] = float(sd[2] >= sdThreshold);
+            }
         }
+    } else {
+        sdfPxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height);
+        DistanceMapping distanceMapping = DistanceMapping::inverse(sdfPxRange);
+        float sdBias = .5f-sdThreshold;
+        for (int y = 0; y < output.height; ++y) {
+            for (int x = 0; x < output.width; ++x) {
+                float sd[3];
+                interpolate(sd, sdf, scale*Point2(x+.5, y+.5));
+                output(x, y)[0] = distVal(sd[0]+sdBias, distanceMapping);
+                output(x, y)[1] = distVal(sd[1]+sdBias, distanceMapping);
+                output(x, y)[2] = distVal(sd[2]+sdBias, distanceMapping);
+            }
+        }
+    }
 }
 
-void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 4> &sdf, double pxRange, float midValue) {
+void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 4> &sdf, Range sdfPxRange, float sdThreshold) {
     Vector2 scale((double) sdf.width/output.width, (double) sdf.height/output.height);
-    pxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height);
-    for (int y = 0; y < output.height; ++y)
-        for (int x = 0; x < output.width; ++x) {
-            float sd[4];
-            interpolate(sd, sdf, scale*Point2(x+.5, y+.5));
-            *output(x, y) = distVal(median(sd[0], sd[1], sd[2]), pxRange, midValue);
+    if (sdfPxRange.lower == sdfPxRange.upper) {
+        for (int y = 0; y < output.height; ++y) {
+            for (int x = 0; x < output.width; ++x) {
+                float sd[4];
+                interpolate(sd, sdf, scale*Point2(x+.5, y+.5));
+                *output(x, y) = float(median(sd[0], sd[1], sd[2]) >= sdThreshold);
+            }
+        }
+    } else {
+        sdfPxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height);
+        DistanceMapping distanceMapping = DistanceMapping::inverse(sdfPxRange);
+        float sdBias = .5f-sdThreshold;
+        for (int y = 0; y < output.height; ++y) {
+            for (int x = 0; x < output.width; ++x) {
+                float sd[4];
+                interpolate(sd, sdf, scale*Point2(x+.5, y+.5));
+                *output(x, y) = distVal(median(sd[0], sd[1], sd[2])+sdBias, distanceMapping);
+            }
         }
+    }
 }
 
-void renderSDF(const BitmapRef<float, 4> &output, const BitmapConstRef<float, 4> &sdf, double pxRange, float midValue) {
+void renderSDF(const BitmapRef<float, 4> &output, const BitmapConstRef<float, 4> &sdf, Range sdfPxRange, float sdThreshold) {
     Vector2 scale((double) sdf.width/output.width, (double) sdf.height/output.height);
-    pxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height);
-    for (int y = 0; y < output.height; ++y)
-        for (int x = 0; x < output.width; ++x) {
-            float sd[4];
-            interpolate(sd, sdf, scale*Point2(x+.5, y+.5));
-            output(x, y)[0] = distVal(sd[0], pxRange, midValue);
-            output(x, y)[1] = distVal(sd[1], pxRange, midValue);
-            output(x, y)[2] = distVal(sd[2], pxRange, midValue);
-            output(x, y)[3] = distVal(sd[3], pxRange, midValue);
+    if (sdfPxRange.lower == sdfPxRange.upper) {
+        for (int y = 0; y < output.height; ++y) {
+            for (int x = 0; x < output.width; ++x) {
+                float sd[4];
+                interpolate(sd, sdf, scale*Point2(x+.5, y+.5));
+                output(x, y)[0] = float(sd[0] >= sdThreshold);
+                output(x, y)[1] = float(sd[1] >= sdThreshold);
+                output(x, y)[2] = float(sd[2] >= sdThreshold);
+                output(x, y)[3] = float(sd[3] >= sdThreshold);
+            }
+        }
+    } else {
+        sdfPxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height);
+        DistanceMapping distanceMapping = DistanceMapping::inverse(sdfPxRange);
+        float sdBias = .5f-sdThreshold;
+        for (int y = 0; y < output.height; ++y) {
+            for (int x = 0; x < output.width; ++x) {
+                float sd[4];
+                interpolate(sd, sdf, scale*Point2(x+.5, y+.5));
+                output(x, y)[0] = distVal(sd[0]+sdBias, distanceMapping);
+                output(x, y)[1] = distVal(sd[1]+sdBias, distanceMapping);
+                output(x, y)[2] = distVal(sd[2]+sdBias, distanceMapping);
+                output(x, y)[3] = distVal(sd[3]+sdBias, distanceMapping);
+            }
         }
+    }
 }
 
 void simulate8bit(const BitmapRef<float, 1> &bitmap) {

+ 7 - 6
thirdparty/msdfgen/core/render-sdf.h

@@ -2,17 +2,18 @@
 #pragma once
 
 #include "Vector2.hpp"
+#include "Range.hpp"
 #include "BitmapRef.hpp"
 
 namespace msdfgen {
 
 /// Reconstructs the shape's appearance into output from the distance field sdf.
-void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 1> &sdf, double pxRange = 0, float midValue = .5f);
-void renderSDF(const BitmapRef<float, 3> &output, const BitmapConstRef<float, 1> &sdf, double pxRange = 0, float midValue = .5f);
-void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 3> &sdf, double pxRange = 0, float midValue = .5f);
-void renderSDF(const BitmapRef<float, 3> &output, const BitmapConstRef<float, 3> &sdf, double pxRange = 0, float midValue = .5f);
-void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 4> &sdf, double pxRange = 0, float midValue = .5f);
-void renderSDF(const BitmapRef<float, 4> &output, const BitmapConstRef<float, 4> &sdf, double pxRange = 0, float midValue = .5f);
+void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 1> &sdf, Range sdfPxRange = 0, float sdThreshold = .5f);
+void renderSDF(const BitmapRef<float, 3> &output, const BitmapConstRef<float, 1> &sdf, Range sdfPxRange = 0, float sdThreshold = .5f);
+void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 3> &sdf, Range sdfPxRange = 0, float sdThreshold = .5f);
+void renderSDF(const BitmapRef<float, 3> &output, const BitmapConstRef<float, 3> &sdf, Range sdfPxRange = 0, float sdThreshold = .5f);
+void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 4> &sdf, Range sdfPxRange = 0, float sdThreshold = .5f);
+void renderSDF(const BitmapRef<float, 4> &output, const BitmapConstRef<float, 4> &sdf, Range sdfPxRange = 0, float sdThreshold = .5f);
 
 /// Snaps the values of the floating-point bitmaps into one of the 256 values representable in a standard 8-bit bitmap.
 void simulate8bit(const BitmapRef<float, 1> &bitmap);

+ 2 - 0
thirdparty/msdfgen/core/save-bmp.cpp

@@ -8,10 +8,12 @@
 #ifdef MSDFGEN_USE_CPP11
     #include <cstdint>
 #else
+namespace msdfgen {
     typedef int int32_t;
     typedef unsigned uint32_t;
     typedef unsigned short uint16_t;
     typedef unsigned char uint8_t;
+}
 #endif
 
 #include "pixel-conversion.hpp"

+ 39 - 0
thirdparty/msdfgen/core/save-fl32.cpp

@@ -0,0 +1,39 @@
+
+#include "save-fl32.h"
+
+#include <cstdio>
+
+namespace msdfgen {
+
+// Requires byte reversal for floats on big-endian platform
+#ifndef __BIG_ENDIAN__
+
+template <int N>
+bool saveFl32(const BitmapConstRef<float, N> &bitmap, const char *filename) {
+    if (FILE *f = fopen(filename, "wb")) {
+        byte header[16] = { byte('F'), byte('L'), byte('3'), byte('2') };
+        header[4] = byte(bitmap.height);
+        header[5] = byte(bitmap.height>>8);
+        header[6] = byte(bitmap.height>>16);
+        header[7] = byte(bitmap.height>>24);
+        header[8] = byte(bitmap.width);
+        header[9] = byte(bitmap.width>>8);
+        header[10] = byte(bitmap.width>>16);
+        header[11] = byte(bitmap.width>>24);
+        header[12] = byte(N);
+        fwrite(header, 1, 16, f);
+        fwrite(bitmap.pixels, sizeof(float), N*bitmap.width*bitmap.height, f);
+        fclose(f);
+        return true;
+    }
+    return false;
+}
+
+template bool saveFl32(const BitmapConstRef<float, 1> &bitmap, const char *filename);
+template bool saveFl32(const BitmapConstRef<float, 2> &bitmap, const char *filename);
+template bool saveFl32(const BitmapConstRef<float, 3> &bitmap, const char *filename);
+template bool saveFl32(const BitmapConstRef<float, 4> &bitmap, const char *filename);
+
+#endif
+
+}

+ 12 - 0
thirdparty/msdfgen/core/save-fl32.h

@@ -0,0 +1,12 @@
+
+#pragma once
+
+#include "BitmapRef.hpp"
+
+namespace msdfgen {
+
+/// Saves the bitmap as an uncompressed floating-point FL32 file, which can be decoded trivially.
+template <int N>
+bool saveFl32(const BitmapConstRef<float, N> &bitmap, const char *filename);
+
+}

+ 133 - 0
thirdparty/msdfgen/core/save-rgba.cpp

@@ -0,0 +1,133 @@
+
+#include "save-rgba.h"
+
+#include <cstdio>
+#include "pixel-conversion.hpp"
+
+namespace msdfgen {
+
+class RgbaFileOutput {
+    FILE *file;
+
+public:
+    RgbaFileOutput(const char *filename, unsigned width, unsigned height) {
+        if ((file = fopen(filename, "wb"))) {
+            byte header[12] = { byte('R'), byte('G'), byte('B'), byte('A') };
+            header[4] = byte(width>>24);
+            header[5] = byte(width>>16);
+            header[6] = byte(width>>8);
+            header[7] = byte(width);
+            header[8] = byte(height>>24);
+            header[9] = byte(height>>16);
+            header[10] = byte(height>>8);
+            header[11] = byte(height);
+            fwrite(header, 1, 12, file);
+        }
+    }
+
+    ~RgbaFileOutput() {
+        if (file)
+            fclose(file);
+    }
+
+    void writePixel(const byte rgba[4]) {
+        fwrite(rgba, 1, 4, file);
+    }
+
+    operator FILE *() {
+        return file;
+    }
+
+};
+
+bool saveRgba(const BitmapConstRef<byte, 1> &bitmap, const char *filename) {
+    RgbaFileOutput output(filename, bitmap.width, bitmap.height);
+    if (output) {
+        byte rgba[4] = { byte(0), byte(0), byte(0), byte(0xff) };
+        for (int y = bitmap.height; y--;) {
+            for (const byte *p = bitmap(0, y), *end = p+bitmap.width; p < end; ++p) {
+                rgba[0] = rgba[1] = rgba[2] = *p;
+                output.writePixel(rgba);
+            }
+        }
+        return true;
+    }
+    return false;
+}
+
+bool saveRgba(const BitmapConstRef<byte, 3> &bitmap, const char *filename) {
+    RgbaFileOutput output(filename, bitmap.width, bitmap.height);
+    if (output) {
+        byte rgba[4] = { byte(0), byte(0), byte(0), byte(0xff) };
+        for (int y = bitmap.height; y--;) {
+            for (const byte *p = bitmap(0, y), *end = p+3*bitmap.width; p < end; p += 3) {
+                rgba[0] = p[0], rgba[1] = p[1], rgba[2] = p[2];
+                output.writePixel(rgba);
+            }
+        }
+        return true;
+    }
+    return false;
+}
+
+bool saveRgba(const BitmapConstRef<byte, 4> &bitmap, const char *filename) {
+    RgbaFileOutput output(filename, bitmap.width, bitmap.height);
+    if (output) {
+        for (int y = bitmap.height; y--;)
+            fwrite(bitmap(0, y), 1, 4*bitmap.width, output);
+        return true;
+    }
+    return false;
+}
+
+bool saveRgba(const BitmapConstRef<float, 1> &bitmap, const char *filename) {
+    RgbaFileOutput output(filename, bitmap.width, bitmap.height);
+    if (output) {
+        byte rgba[4] = { byte(0), byte(0), byte(0), byte(0xff) };
+        for (int y = bitmap.height; y--;) {
+            for (const float *p = bitmap(0, y), *end = p+bitmap.width; p < end; ++p) {
+                rgba[0] = rgba[1] = rgba[2] = pixelFloatToByte(*p);
+                output.writePixel(rgba);
+            }
+        }
+        return true;
+    }
+    return false;
+}
+
+bool saveRgba(const BitmapConstRef<float, 3> &bitmap, const char *filename) {
+    RgbaFileOutput output(filename, bitmap.width, bitmap.height);
+    if (output) {
+        byte rgba[4] = { byte(0), byte(0), byte(0), byte(0xff) };
+        for (int y = bitmap.height; y--;) {
+            for (const float *p = bitmap(0, y), *end = p+3*bitmap.width; p < end; p += 3) {
+                rgba[0] = pixelFloatToByte(p[0]);
+                rgba[1] = pixelFloatToByte(p[1]);
+                rgba[2] = pixelFloatToByte(p[2]);
+                output.writePixel(rgba);
+            }
+        }
+        return true;
+    }
+    return false;
+}
+
+bool saveRgba(const BitmapConstRef<float, 4> &bitmap, const char *filename) {
+    RgbaFileOutput output(filename, bitmap.width, bitmap.height);
+    if (output) {
+        byte rgba[4];
+        for (int y = bitmap.height; y--;) {
+            for (const float *p = bitmap(0, y), *end = p+4*bitmap.width; p < end; p += 4) {
+                rgba[0] = pixelFloatToByte(p[0]);
+                rgba[1] = pixelFloatToByte(p[1]);
+                rgba[2] = pixelFloatToByte(p[2]);
+                rgba[3] = pixelFloatToByte(p[3]);
+                output.writePixel(rgba);
+            }
+        }
+        return true;
+    }
+    return false;
+}
+
+}

+ 16 - 0
thirdparty/msdfgen/core/save-rgba.h

@@ -0,0 +1,16 @@
+
+#pragma once
+
+#include "BitmapRef.hpp"
+
+namespace msdfgen {
+
+/// Saves the bitmap as a simple RGBA file, which can be decoded trivially.
+bool saveRgba(const BitmapConstRef<byte, 1> &bitmap, const char *filename);
+bool saveRgba(const BitmapConstRef<byte, 3> &bitmap, const char *filename);
+bool saveRgba(const BitmapConstRef<byte, 4> &bitmap, const char *filename);
+bool saveRgba(const BitmapConstRef<float, 1> &bitmap, const char *filename);
+bool saveRgba(const BitmapConstRef<float, 3> &bitmap, const char *filename);
+bool saveRgba(const BitmapConstRef<float, 4> &bitmap, const char *filename);
+
+}

+ 2 - 0
thirdparty/msdfgen/core/save-tiff.cpp

@@ -8,10 +8,12 @@
 #ifdef MSDFGEN_USE_CPP11
     #include <cstdint>
 #else
+namespace msdfgen {
     typedef int int32_t;
     typedef unsigned uint32_t;
     typedef unsigned short uint16_t;
     typedef unsigned char uint8_t;
+}
 #endif
 
 namespace msdfgen {

+ 18 - 5
thirdparty/msdfgen/core/shape-description.cpp

@@ -2,6 +2,8 @@
 #define _CRT_SECURE_NO_WARNINGS
 #include "shape-description.h"
 
+#include <cstdlib>
+
 namespace msdfgen {
 
 int readCharF(FILE *input) {
@@ -25,14 +27,25 @@ int readCharS(const char **input) {
 }
 
 int readCoordF(FILE *input, Point2 &coord) {
-    return fscanf(input, "%lf,%lf", &coord.x, &coord.y);
+    return fscanf(input, "%lf , %lf", &coord.x, &coord.y);
 }
 
 int readCoordS(const char **input, Point2 &coord) {
-    int read = 0;
-    int result = sscanf(*input, "%lf,%lf%n", &coord.x, &coord.y, &read);
-    *input += read;
-    return result;
+    char *end = NULL;
+    coord.x = strtod(*input, &end);
+    if (end <= *input)
+        return 0;
+    *input = end;
+    while (**input == ' ' || **input == '\t' || **input == '\n' || **input == '\r')
+        ++*input;
+    if (**input != ',')
+        return 1;
+    ++*input;
+    coord.y = strtod(*input, &end);
+    if (end <= *input)
+        return 1;
+    *input = end;
+    return 2;
 }
 
 static bool writeCoord(FILE *output, Point2 coord) {

+ 28 - 14
thirdparty/msdfgen/msdfgen.h

@@ -4,7 +4,7 @@
 /*
  * MULTI-CHANNEL SIGNED DISTANCE FIELD GENERATOR
  * ---------------------------------------------
- * A utility by Viktor Chlumsky, (c) 2014 - 2023
+ * A utility by Viktor Chlumsky, (c) 2014 - 2024
  *
  * The technique used to generate multi-channel distance fields in this code
  * has been developed by Viktor Chlumsky in 2014 for his master's thesis,
@@ -18,7 +18,10 @@
 #include "core/base.h"
 #include "core/arithmetics.hpp"
 #include "core/Vector2.hpp"
+#include "core/Range.hpp"
 #include "core/Projection.h"
+#include "core/DistanceMapping.h"
+#include "core/SDFTransformation.h"
 #include "core/Scanline.h"
 #include "core/Shape.h"
 #include "core/BitmapRef.hpp"
@@ -33,32 +36,43 @@
 #include "core/sdf-error-estimation.h"
 #include "core/save-bmp.h"
 #include "core/save-tiff.h"
+#include "core/save-rgba.h"
+#include "core/save-fl32.h"
 #include "core/shape-description.h"
+#include "core/export-svg.h"
 
 namespace msdfgen {
 
 /// Generates a conventional single-channel signed distance field.
-void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, double range, const GeneratorConfig &config = GeneratorConfig());
+void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, const SDFTransformation &transformation, const GeneratorConfig &config = GeneratorConfig());
 
-/// Generates a single-channel signed pseudo-distance field.
-void generatePseudoSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, double range, const GeneratorConfig &config = GeneratorConfig());
+/// Generates a single-channel signed perpendicular distance field.
+void generatePSDF(const BitmapRef<float, 1> &output, const Shape &shape, const SDFTransformation &transformation, const GeneratorConfig &config = GeneratorConfig());
 
 /// Generates a multi-channel signed distance field. Edge colors must be assigned first! (See edgeColoringSimple)
-void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config = MSDFGeneratorConfig());
+void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, const SDFTransformation &transformation, const MSDFGeneratorConfig &config = MSDFGeneratorConfig());
 
 /// Generates a multi-channel signed distance field with true distance in the alpha channel. Edge colors must be assigned first.
-void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config = MSDFGeneratorConfig());
+void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, const SDFTransformation &transformation, const MSDFGeneratorConfig &config = MSDFGeneratorConfig());
 
 // Old version of the function API's kept for backwards compatibility
-void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport = true);
-void generatePseudoSDF(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport = true);
-void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, const ErrorCorrectionConfig &errorCorrectionConfig = ErrorCorrectionConfig(), bool overlapSupport = true);
-void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, const ErrorCorrectionConfig &errorCorrectionConfig = ErrorCorrectionConfig(), bool overlapSupport = true);
+void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, Range range, const GeneratorConfig &config = GeneratorConfig());
+void generatePSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, Range range, const GeneratorConfig &config = GeneratorConfig());
+void generatePseudoSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, Range range, const GeneratorConfig &config = GeneratorConfig());
+void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, const Projection &projection, Range range, const MSDFGeneratorConfig &config = MSDFGeneratorConfig());
+void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, const Projection &projection, Range range, const MSDFGeneratorConfig &config = MSDFGeneratorConfig());
+
+void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport = true);
+void generatePSDF(const BitmapRef<float, 1> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport = true);
+void generatePseudoSDF(const BitmapRef<float, 1> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport = true);
+void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, const ErrorCorrectionConfig &errorCorrectionConfig = ErrorCorrectionConfig(), bool overlapSupport = true);
+void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, const ErrorCorrectionConfig &errorCorrectionConfig = ErrorCorrectionConfig(), bool overlapSupport = true);
 
 // Original simpler versions of the previous functions, which work well under normal circumstances, but cannot deal with overlapping contours.
-void generateSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate);
-void generatePseudoSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate);
-void generateMSDF_legacy(const BitmapRef<float, 3> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, ErrorCorrectionConfig errorCorrectionConfig = ErrorCorrectionConfig());
-void generateMTSDF_legacy(const BitmapRef<float, 4> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, ErrorCorrectionConfig errorCorrectionConfig = ErrorCorrectionConfig());
+void generateSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate);
+void generatePSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate);
+void generatePseudoSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate);
+void generateMSDF_legacy(const BitmapRef<float, 3> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, ErrorCorrectionConfig errorCorrectionConfig = ErrorCorrectionConfig());
+void generateMTSDF_legacy(const BitmapRef<float, 4> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, ErrorCorrectionConfig errorCorrectionConfig = ErrorCorrectionConfig());
 
 }