Browse Source

Added -exportsvg, RGBA & FL32 image output

Chlumsky 1 year ago
parent
commit
aa9478e9ae
12 changed files with 328 additions and 13 deletions
  1. 2 2
      README.md
  2. 79 0
      core/export-svg.cpp
  3. 11 0
      core/export-svg.h
  4. 2 0
      core/save-bmp.cpp
  5. 39 0
      core/save-fl32.cpp
  6. 12 0
      core/save-fl32.h
  7. 133 0
      core/save-rgba.cpp
  8. 16 0
      core/save-rgba.h
  9. 2 0
      core/save-tiff.cpp
  10. 28 10
      main.cpp
  11. 3 0
      msdfgen.h
  12. 1 1
      vcpkg.json

+ 2 - 2
README.md

@@ -67,7 +67,7 @@ The input can be specified as one of:
 The complete list of available options can be printed with **-help**.
 The complete list of available options can be printed with **-help**.
 Some of the important ones are:
 Some of the important ones are:
  - **-o \<filename\>** &ndash; specifies the output file name. The desired format will be deduced from the extension
  - **-o \<filename\>** &ndash; specifies the output file name. The desired format will be deduced from the extension
-   (png, bmp, tif, txt, bin). Otherwise, use -format.
+   (png, bmp, tiff, rgba, fl32, txt, bin). Otherwise, use -format.
  - **-dimensions \<width\> \<height\>** &ndash; specifies the dimensions of the output distance field (in pixels).
  - **-dimensions \<width\> \<height\>** &ndash; specifies the dimensions of the output distance field (in pixels).
  - **-range \<range\>**, **-pxrange \<range\>** &ndash; specifies the width of the range around the shape
  - **-range \<range\>**, **-pxrange \<range\>** &ndash; specifies the width of the range around the shape
    between the minimum and maximum representable signed distance in shape units or distance field pixels, respectivelly.
    between the minimum and maximum representable signed distance in shape units or distance field pixels, respectivelly.
@@ -107,7 +107,7 @@ in order to generate a distance field. Please note that all classes and function
    This can be performed automatically using the `edgeColoringSimple` (or other) heuristic, or manually by setting each edge's
    This can be performed automatically using the `edgeColoringSimple` (or other) heuristic, or manually by setting each edge's
    `color` member. Keep in mind that at least two color channels must be turned on in each edge.
    `color` member. Keep in mind that at least two color channels must be turned on in each edge.
  - Call `generateSDF`, `generatePSDF`, `generateMSDF`, or `generateMTSDF` to generate a distance field into a floating point
  - Call `generateSDF`, `generatePSDF`, `generateMSDF`, or `generateMTSDF` to generate a distance field into a floating point
-   `Bitmap` object. This can then be worked with further or saved to a file using `saveBmp`, `savePng`, or `saveTiff`.
+   `Bitmap` object. This can then be worked with further or saved to a file using `saveBmp`, `savePng`, `saveTiff`, etc.
  - You may also render an image from the distance field using `renderSDF`. Consider calling `simulate8bit`
  - You may also render an image from the distance field using `renderSDF`. Consider calling `simulate8bit`
    on the distance field beforehand to simulate the standard 8 bits/channel image format.
    on the distance field beforehand to simulate the standard 8 bits/channel image format.
 
 

+ 79 - 0
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
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);
+
+}

+ 2 - 0
core/save-bmp.cpp

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

+ 39 - 0
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
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
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
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
core/save-tiff.cpp

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

+ 28 - 10
main.cpp

@@ -30,7 +30,7 @@
 #define DEFAULT_IMAGE_EXTENSION "png"
 #define DEFAULT_IMAGE_EXTENSION "png"
 #define SAVE_DEFAULT_IMAGE_FORMAT savePng
 #define SAVE_DEFAULT_IMAGE_FORMAT savePng
 #else
 #else
-#define DEFAULT_IMAGE_EXTENSION "tif"
+#define DEFAULT_IMAGE_EXTENSION "tiff"
 #define SAVE_DEFAULT_IMAGE_FORMAT saveTiff
 #define SAVE_DEFAULT_IMAGE_FORMAT saveTiff
 #endif
 #endif
 
 
@@ -41,6 +41,8 @@ enum Format {
     PNG,
     PNG,
     BMP,
     BMP,
     TIFF,
     TIFF,
+    RGBA,
+    FL32,
     TEXT,
     TEXT,
     TEXT_FLOAT,
     TEXT_FLOAT,
     BINARY,
     BINARY,
@@ -49,7 +51,7 @@ enum Format {
 };
 };
 
 
 static bool is8bitFormat(Format format) {
 static bool is8bitFormat(Format format) {
-    return format == PNG || format == BMP || format == TEXT || format == BINARY;
+    return format == PNG || format == BMP || format == RGBA || format == TEXT || format == BINARY;
 }
 }
 
 
 static char toupper(char c) {
 static char toupper(char c) {
@@ -263,7 +265,9 @@ static const char *writeOutput(const BitmapConstRef<float, N> &bitmap, const cha
                 return "PNG format is not available in core-only version.";
                 return "PNG format is not available in core-only version.";
         #endif
         #endif
             else if (cmpExtension(filename, ".bmp")) format = BMP;
             else if (cmpExtension(filename, ".bmp")) format = BMP;
-            else if (cmpExtension(filename, ".tif") || cmpExtension(filename, ".tiff")) format = TIFF;
+            else if (cmpExtension(filename, ".tiff") || cmpExtension(filename, ".tif")) format = TIFF;
+            else if (cmpExtension(filename, ".rgba")) format = RGBA;
+            else if (cmpExtension(filename, ".fl32")) format = FL32;
             else if (cmpExtension(filename, ".txt")) format = TEXT;
             else if (cmpExtension(filename, ".txt")) format = TEXT;
             else if (cmpExtension(filename, ".bin")) format = BINARY;
             else if (cmpExtension(filename, ".bin")) format = BINARY;
             else
             else
@@ -275,6 +279,8 @@ static const char *writeOutput(const BitmapConstRef<float, N> &bitmap, const cha
         #endif
         #endif
             case BMP: return saveBmp(bitmap, filename) ? NULL : "Failed to write output BMP image.";
             case BMP: return saveBmp(bitmap, filename) ? NULL : "Failed to write output BMP image.";
             case TIFF: return saveTiff(bitmap, filename) ? NULL : "Failed to write output TIFF image.";
             case TIFF: return saveTiff(bitmap, filename) ? NULL : "Failed to write output TIFF image.";
+            case RGBA: return saveRgba(bitmap, filename) ? NULL : "Failed to write output RGBA image.";
+            case FL32: return saveFl32(bitmap, filename) ? NULL : "Failed to write output FL32 image.";
             case TEXT: case TEXT_FLOAT: {
             case TEXT: case TEXT_FLOAT: {
                 FILE *file = fopen(filename, "w");
                 FILE *file = fopen(filename, "w");
                 if (!file) return "Failed to write output text file.";
                 if (!file) return "Failed to write output text file.";
@@ -416,12 +422,14 @@ static const char *const helpText =
         "\tComputes and prints the distance field's estimated fill error to the standard output.\n"
         "\tComputes and prints the distance field's estimated fill error to the standard output.\n"
     "  -exportshape <filename.txt>\n"
     "  -exportshape <filename.txt>\n"
         "\tSaves the shape description into a text file that can be edited and loaded using -shapedesc.\n"
         "\tSaves the shape description into a text file that can be edited and loaded using -shapedesc.\n"
+    "  -exportsvg <filename.svg>\n"
+        "\tSaves the shape geometry into a simple SVG file.\n"
     "  -fillrule <nonzero / evenodd / positive / negative>\n"
     "  -fillrule <nonzero / evenodd / positive / negative>\n"
         "\tSets the fill rule for the scanline pass. Default is nonzero.\n"
         "\tSets the fill rule for the scanline pass. Default is nonzero.\n"
 #if defined(MSDFGEN_EXTENSIONS) && !defined(MSDFGEN_DISABLE_PNG)
 #if defined(MSDFGEN_EXTENSIONS) && !defined(MSDFGEN_DISABLE_PNG)
-    "  -format <png / bmp / tiff / text / textfloat / bin / binfloat / binfloatbe>\n"
+    "  -format <png / bmp / tiff / rgba / fl32 / text / textfloat / bin / binfloat / binfloatbe>\n"
 #else
 #else
-    "  -format <bmp / tiff / text / textfloat / bin / binfloat / binfloatbe>\n"
+    "  -format <bmp / tiff / rgba / fl32 / text / textfloat / bin / binfloat / binfloatbe>\n"
 #endif
 #endif
         "\tSpecifies the output format of the distance field. Otherwise it is chosen based on output file extension.\n"
         "\tSpecifies the output format of the distance field. Otherwise it is chosen based on output file extension.\n"
     "  -guessorder\n"
     "  -guessorder\n"
@@ -548,6 +556,7 @@ int main(int argc, const char *const *argv) {
     const char *input = NULL;
     const char *input = NULL;
     const char *output = "output." DEFAULT_IMAGE_EXTENSION;
     const char *output = "output." DEFAULT_IMAGE_EXTENSION;
     const char *shapeExport = NULL;
     const char *shapeExport = NULL;
+    const char *svgExport = NULL;
     const char *testRender = NULL;
     const char *testRender = NULL;
     const char *testRenderMulti = NULL;
     const char *testRenderMulti = NULL;
     bool outputSpecified = false;
     bool outputSpecified = false;
@@ -747,7 +756,9 @@ int main(int argc, const char *const *argv) {
                 fputs("PNG format is not available in core-only version.\n", stderr);
                 fputs("PNG format is not available in core-only version.\n", stderr);
         #endif
         #endif
             else if (ARG_IS("bmp")) SET_FORMAT(BMP, "bmp");
             else if (ARG_IS("bmp")) SET_FORMAT(BMP, "bmp");
-            else if (ARG_IS("tiff") || ARG_IS("tif")) SET_FORMAT(TIFF, "tif");
+            else if (ARG_IS("tiff") || ARG_IS("tif")) SET_FORMAT(TIFF, "tiff");
+            else if (ARG_IS("rgba")) SET_FORMAT(RGBA, "rgba");
+            else if (ARG_IS("fl32")) SET_FORMAT(FL32, "fl32");
             else if (ARG_IS("text") || ARG_IS("txt")) SET_FORMAT(TEXT, "txt");
             else if (ARG_IS("text") || ARG_IS("txt")) SET_FORMAT(TEXT, "txt");
             else if (ARG_IS("textfloat") || ARG_IS("txtfloat")) SET_FORMAT(TEXT_FLOAT, "txt");
             else if (ARG_IS("textfloat") || ARG_IS("txtfloat")) SET_FORMAT(TEXT_FLOAT, "txt");
             else if (ARG_IS("bin") || ARG_IS("binary")) SET_FORMAT(BINARY, "bin");
             else if (ARG_IS("bin") || ARG_IS("binary")) SET_FORMAT(BINARY, "bin");
@@ -919,6 +930,10 @@ int main(int argc, const char *const *argv) {
             shapeExport = argv[argPos++];
             shapeExport = argv[argPos++];
             continue;
             continue;
         }
         }
+        ARG_CASE("-exportsvg", 1) {
+            svgExport = argv[argPos++];
+            continue;
+        }
         ARG_CASE("-testrender", 3) {
         ARG_CASE("-testrender", 3) {
             unsigned w, h;
             unsigned w, h;
             testRender = argv[argPos++];
             testRender = argv[argPos++];
@@ -1041,7 +1056,7 @@ int main(int argc, const char *const *argv) {
                 getGlyphIndex(glyphIndex, guard.font, unicode);
                 getGlyphIndex(glyphIndex, guard.font, unicode);
             if (!loadGlyph(shape, guard.font, glyphIndex, fontCoordinateScaling, &glyphAdvance))
             if (!loadGlyph(shape, guard.font, glyphIndex, fontCoordinateScaling, &glyphAdvance))
                 ABORT("Failed to load glyph from font file.");
                 ABORT("Failed to load glyph from font file.");
-            if (!fontCoordinateScalingSpecified && (!autoFrame || scaleSpecified || rangeMode == RANGE_UNIT || mode == METRICS || printMetrics || shapeExport)) {
+            if (!fontCoordinateScalingSpecified && (!autoFrame || scaleSpecified || rangeMode == RANGE_UNIT || mode == METRICS || printMetrics || shapeExport || svgExport)) {
                 fputs(
                 fputs(
                     "Warning: Using legacy font coordinate conversion for compatibility reasons.\n"
                     "Warning: Using legacy font coordinate conversion for compatibility reasons.\n"
                     "         The implicit scaling behavior will likely change in a future version resulting in different output.\n"
                     "         The implicit scaling behavior will likely change in a future version resulting in different output.\n"
@@ -1104,7 +1119,7 @@ int main(int argc, const char *const *argv) {
 
 
     double avgScale = .5*(scale.x+scale.y);
     double avgScale = .5*(scale.x+scale.y);
     Shape::Bounds bounds = { };
     Shape::Bounds bounds = { };
-    if (autoFrame || mode == METRICS || printMetrics || orientation == GUESS)
+    if (autoFrame || mode == METRICS || printMetrics || orientation == GUESS || svgExport)
         bounds = shape.getBounds();
         bounds = shape.getBounds();
 
 
     if (outputDistanceShift) {
     if (outputDistanceShift) {
@@ -1278,13 +1293,16 @@ int main(int argc, const char *const *argv) {
 
 
     // Save output
     // Save output
     if (shapeExport) {
     if (shapeExport) {
-        FILE *file = fopen(shapeExport, "w");
-        if (file) {
+        if (FILE *file = fopen(shapeExport, "w")) {
             writeShapeDescription(file, shape);
             writeShapeDescription(file, shape);
             fclose(file);
             fclose(file);
         } else
         } else
             fputs("Failed to write shape export file.\n", stderr);
             fputs("Failed to write shape export file.\n", stderr);
     }
     }
+    if (svgExport) {
+        if (!saveSvgShape(shape, bounds, svgExport))
+            fputs("Failed to write shape SVG file.\n", stderr);
+    }
     const char *error = NULL;
     const char *error = NULL;
     switch (mode) {
     switch (mode) {
         case SINGLE:
         case SINGLE:

+ 3 - 0
msdfgen.h

@@ -36,7 +36,10 @@
 #include "core/sdf-error-estimation.h"
 #include "core/sdf-error-estimation.h"
 #include "core/save-bmp.h"
 #include "core/save-bmp.h"
 #include "core/save-tiff.h"
 #include "core/save-tiff.h"
+#include "core/save-rgba.h"
+#include "core/save-fl32.h"
 #include "core/shape-description.h"
 #include "core/shape-description.h"
+#include "core/export-svg.h"
 
 
 namespace msdfgen {
 namespace msdfgen {
 
 

+ 1 - 1
vcpkg.json

@@ -1,7 +1,7 @@
 {
 {
     "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/master/docs/vcpkg.schema.json",
     "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/master/docs/vcpkg.schema.json",
     "name": "msdfgen",
     "name": "msdfgen",
-    "version": "1.11.0",
+    "version": "1.12.0",
     "default-features": [
     "default-features": [
         "extensions",
         "extensions",
         "geometry-preprocessing",
         "geometry-preprocessing",