1064 lines
44 KiB
C++
1064 lines
44 KiB
C++
|
|
/*
|
|
* MULTI-CHANNEL SIGNED DISTANCE FIELD ATLAS GENERATOR - standalone console program
|
|
* --------------------------------------------------------------------------------
|
|
* A utility by Viktor Chlumsky, (c) 2020 - 2023
|
|
*/
|
|
|
|
#ifdef MSDF_ATLAS_STANDALONE
|
|
|
|
#define _USE_MATH_DEFINES
|
|
#include <cstdio>
|
|
#include <cmath>
|
|
#include <cstring>
|
|
#include <cassert>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
#include <thread>
|
|
|
|
#include "msdf-atlas-gen.h"
|
|
|
|
using namespace msdf_atlas;
|
|
|
|
#define DEFAULT_ANGLE_THRESHOLD 3.0
|
|
#define DEFAULT_MITER_LIMIT 1.0
|
|
#define DEFAULT_PIXEL_RANGE 2.0
|
|
#define SDF_ERROR_ESTIMATE_PRECISION 19
|
|
#define GLYPH_FILL_RULE msdfgen::FILL_NONZERO
|
|
#define LCG_MULTIPLIER 6364136223846793005ull
|
|
#define LCG_INCREMENT 1442695040888963407ull
|
|
|
|
#define STRINGIZE_(x) #x
|
|
#define STRINGIZE(x) STRINGIZE_(x)
|
|
#define MSDF_ATLAS_VERSION_STRING STRINGIZE(MSDF_ATLAS_VERSION)
|
|
#define MSDFGEN_VERSION_STRING STRINGIZE(MSDFGEN_VERSION)
|
|
#ifdef MSDF_ATLAS_VERSION_UNDERLINE
|
|
#define VERSION_UNDERLINE STRINGIZE(MSDF_ATLAS_VERSION_UNDERLINE)
|
|
#else
|
|
#define VERSION_UNDERLINE "--------"
|
|
#endif
|
|
|
|
#ifdef MSDFGEN_USE_SKIA
|
|
#define TITLE_SUFFIX " & Skia"
|
|
#define EXTRA_UNDERLINE "-------"
|
|
#else
|
|
#define TITLE_SUFFIX
|
|
#define EXTRA_UNDERLINE
|
|
#endif
|
|
|
|
static const char * const versionText =
|
|
"MSDF-Atlas-Gen v" MSDF_ATLAS_VERSION_STRING "\n"
|
|
" with MSDFgen v" MSDFGEN_VERSION_STRING TITLE_SUFFIX "\n"
|
|
"(c) 2020 - " STRINGIZE(MSDF_ATLAS_COPYRIGHT_YEAR) " Viktor Chlumsky";
|
|
|
|
static const char * const helpText = R"(
|
|
MSDF Atlas Generator by Viktor Chlumsky v)" MSDF_ATLAS_VERSION_STRING R"( (with MSDFgen v)" MSDFGEN_VERSION_STRING TITLE_SUFFIX R"()
|
|
----------------------------------------------------------------)" VERSION_UNDERLINE EXTRA_UNDERLINE R"(
|
|
|
|
INPUT SPECIFICATION
|
|
-font <filename.ttf/otf>
|
|
Specifies the input TrueType / OpenType font file. A font specification is required.
|
|
-varfont <filename.ttf/otf?var0=value0&var1=value1>
|
|
Specifies an input variable font file and configures its variables.
|
|
-charset <filename>
|
|
Specifies the input character set. Refer to the documentation for format of charset specification. Defaults to ASCII.
|
|
-glyphset <filename>
|
|
Specifies the set of input glyphs as glyph indices within the font file.
|
|
-fontscale <scale>
|
|
Specifies the scale to be applied to the glyph geometry of the font.
|
|
-fontname <name>
|
|
Specifies a name for the font that will be propagated into the output files as metadata.
|
|
-and
|
|
Separates multiple inputs to be combined into a single atlas.
|
|
|
|
ATLAS CONFIGURATION
|
|
-type <hardmask / softmask / sdf / psdf / msdf / mtsdf>
|
|
Selects the type of atlas to be generated.
|
|
-format <png / bmp / tiff / text / textfloat / bin / binfloat / binfloatbe>
|
|
Selects the format for the atlas image output. Some image formats may be incompatible with embedded output formats.
|
|
-dimensions <width> <height>
|
|
Sets the atlas to have fixed dimensions (width x height).
|
|
-pots / -potr / -square / -square2 / -square4
|
|
Picks the minimum atlas dimensions that fit all glyphs and satisfy the selected constraint:
|
|
power of two square / ... rectangle / any square / square with side divisible by 2 / ... 4
|
|
-yorigin <bottom / top>
|
|
Determines whether the Y-axis is oriented upwards (bottom origin, default) or downwards (top origin).
|
|
|
|
OUTPUT SPECIFICATION - one or more can be specified
|
|
-imageout <filename.*>
|
|
Saves the atlas as an image file with the specified format. Layout data must be stored separately.
|
|
-json <filename.json>
|
|
Writes the atlas's layout data, as well as other metrics into a structured JSON file.
|
|
-csv <filename.csv>
|
|
Writes the layout data of the glyphs into a simple CSV file.)"
|
|
#ifndef MSDF_ATLAS_NO_ARTERY_FONT
|
|
R"(
|
|
-arfont <filename.arfont>
|
|
Stores the atlas and its layout data as an Artery Font file. Supported formats: png, bin, binfloat.)"
|
|
#endif
|
|
R"(
|
|
-shadronpreview <filename.shadron> <sample text>
|
|
Generates a Shadron script that uses the generated atlas to draw a sample text as a preview.
|
|
|
|
GLYPH CONFIGURATION
|
|
-size <EM size>
|
|
Specifies the size of the glyphs in the atlas bitmap in pixels per EM.
|
|
-minsize <EM size>
|
|
Specifies the minimum size. The largest possible size that fits the same atlas dimensions will be used.
|
|
-emrange <EM range>
|
|
Specifies the SDF distance range in EM's.
|
|
-pxrange <pixel range>
|
|
Specifies the SDF distance range in output pixels. The default value is 2.
|
|
-nokerning
|
|
Disables inclusion of kerning pair table in output files.
|
|
|
|
DISTANCE FIELD GENERATOR SETTINGS
|
|
-angle <angle>
|
|
Specifies the minimum angle between adjacent edges to be considered a corner. Append D for degrees. (msdf / mtsdf only)
|
|
-coloringstrategy <simple / inktrap / distance>
|
|
Selects the strategy of the edge coloring heuristic.
|
|
-errorcorrection <mode>
|
|
Changes the MSDF/MTSDF error correction mode. Use -errorcorrection help for a list of valid modes.
|
|
-errordeviationratio <ratio>
|
|
Sets the minimum ratio between the actual and maximum expected distance delta to be considered an error.
|
|
-errorimproveratio <ratio>
|
|
Sets the minimum ratio between the pre-correction distance error and the post-correction distance error.
|
|
-miterlimit <value>
|
|
Sets the miter limit that limits the extension of each glyph's bounding box due to very sharp corners. (psdf / msdf / mtsdf only))"
|
|
#ifdef MSDFGEN_USE_SKIA
|
|
R"(
|
|
-overlap
|
|
Switches to distance field generator with support for overlapping contours.
|
|
-nopreprocess
|
|
Disables path preprocessing which resolves self-intersections and overlapping contours.
|
|
-scanline
|
|
Performs an additional scanline pass to fix the signs of the distances.)"
|
|
#else
|
|
R"(
|
|
-nooverlap
|
|
Disables resolution of overlapping contours.
|
|
-noscanline
|
|
Disables the scanline pass, which corrects the distance field's signs according to the non-zero fill rule.)"
|
|
#endif
|
|
R"(
|
|
-seed <N>
|
|
Sets the initial seed for the edge coloring heuristic.
|
|
-threads <N>
|
|
Sets the number of threads for the parallel computation. (0 = auto)
|
|
)";
|
|
|
|
static const char *errorCorrectionHelpText = R"(
|
|
ERROR CORRECTION MODES
|
|
auto-fast
|
|
Detects inversion artifacts and distance errors that do not affect edges by range testing.
|
|
auto-full
|
|
Detects inversion artifacts and distance errors that do not affect edges by exact distance evaluation.
|
|
auto-mixed (default)
|
|
Detects inversions by distance evaluation and distance errors that do not affect edges by range testing.
|
|
disabled
|
|
Disables error correction.
|
|
distance-fast
|
|
Detects distance errors by range testing. Does not care if edges and corners are affected.
|
|
distance-full
|
|
Detects distance errors by exact distance evaluation. Does not care if edges and corners are affected, slow.
|
|
edge-fast
|
|
Detects inversion artifacts only by range testing.
|
|
edge-full
|
|
Detects inversion artifacts only by exact distance evaluation.
|
|
help
|
|
Displays this help.
|
|
)";
|
|
|
|
static char toupper(char c) {
|
|
return c >= 'a' && c <= 'z' ? c-'a'+'A' : c;
|
|
}
|
|
|
|
static bool parseUnsigned(unsigned &value, const char *arg) {
|
|
static char c;
|
|
return sscanf(arg, "%u%c", &value, &c) == 1;
|
|
}
|
|
|
|
static bool parseUnsignedLL(unsigned long long &value, const char *arg) {
|
|
static char c;
|
|
return sscanf(arg, "%llu%c", &value, &c) == 1;
|
|
}
|
|
|
|
static bool parseDouble(double &value, const char *arg) {
|
|
static char c;
|
|
return sscanf(arg, "%lf%c", &value, &c) == 1;
|
|
}
|
|
|
|
static bool parseAngle(double &value, const char *arg) {
|
|
char c1, c2;
|
|
int result = sscanf(arg, "%lf%c%c", &value, &c1, &c2);
|
|
if (result == 1)
|
|
return true;
|
|
if (result == 2 && (c1 == 'd' || c1 == 'D')) {
|
|
value *= M_PI/180;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool cmpExtension(const char *path, const char *ext) {
|
|
for (const char *a = path+strlen(path)-1, *b = ext+strlen(ext)-1; b >= ext; --a, --b)
|
|
if (a < path || toupper(*a) != toupper(*b))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
static msdfgen::FontHandle * loadVarFont(msdfgen::FreetypeHandle *library, const char *filename) {
|
|
std::string buffer;
|
|
while (*filename && *filename != '?')
|
|
buffer.push_back(*filename++);
|
|
msdfgen::FontHandle *font = msdfgen::loadFont(library, buffer.c_str());
|
|
if (font && *filename++ == '?') {
|
|
do {
|
|
buffer.clear();
|
|
while (*filename && *filename != '=')
|
|
buffer.push_back(*filename++);
|
|
if (*filename == '=') {
|
|
double value = 0;
|
|
int skip = 0;
|
|
if (sscanf(++filename, "%lf%n", &value, &skip) == 1) {
|
|
msdfgen::setFontVariationAxis(library, font, buffer.c_str(), value);
|
|
filename += skip;
|
|
}
|
|
}
|
|
} while (*filename++ == '&');
|
|
}
|
|
return font;
|
|
}
|
|
|
|
struct FontInput {
|
|
const char *fontFilename;
|
|
bool variableFont;
|
|
GlyphIdentifierType glyphIdentifierType;
|
|
const char *charsetFilename;
|
|
double fontScale;
|
|
const char *fontName;
|
|
};
|
|
|
|
struct Configuration {
|
|
ImageType imageType;
|
|
ImageFormat imageFormat;
|
|
YDirection yDirection;
|
|
int width, height;
|
|
double emSize;
|
|
double pxRange;
|
|
double angleThreshold;
|
|
double miterLimit;
|
|
void (*edgeColoring)(msdfgen::Shape &, double, unsigned long long);
|
|
bool expensiveColoring;
|
|
unsigned long long coloringSeed;
|
|
GeneratorAttributes generatorAttributes;
|
|
bool preprocessGeometry;
|
|
bool kerning;
|
|
int threadCount;
|
|
const char *arteryFontFilename;
|
|
const char *imageFilename;
|
|
const char *jsonFilename;
|
|
const char *csvFilename;
|
|
const char *shadronPreviewFilename;
|
|
const char *shadronPreviewText;
|
|
};
|
|
|
|
template <typename T, typename S, int N, GeneratorFunction<S, N> GEN_FN>
|
|
static bool makeAtlas(const std::vector<GlyphGeometry> &glyphs, const std::vector<FontGeometry> &fonts, const Configuration &config) {
|
|
ImmediateAtlasGenerator<S, N, GEN_FN, BitmapAtlasStorage<T, N> > generator(config.width, config.height);
|
|
generator.setAttributes(config.generatorAttributes);
|
|
generator.setThreadCount(config.threadCount);
|
|
generator.generate(glyphs.data(), glyphs.size());
|
|
msdfgen::BitmapConstRef<T, N> bitmap = (msdfgen::BitmapConstRef<T, N>) generator.atlasStorage();
|
|
|
|
bool success = true;
|
|
|
|
if (config.imageFilename) {
|
|
if (saveImage(bitmap, config.imageFormat, config.imageFilename, config.yDirection))
|
|
puts("Atlas image file saved.");
|
|
else {
|
|
success = false;
|
|
puts("Failed to save the atlas as an image file.");
|
|
}
|
|
}
|
|
|
|
#ifndef MSDF_ATLAS_NO_ARTERY_FONT
|
|
if (config.arteryFontFilename) {
|
|
ArteryFontExportProperties arfontProps;
|
|
arfontProps.fontSize = config.emSize;
|
|
arfontProps.pxRange = config.pxRange;
|
|
arfontProps.imageType = config.imageType;
|
|
arfontProps.imageFormat = config.imageFormat;
|
|
arfontProps.yDirection = config.yDirection;
|
|
if (exportArteryFont<float>(fonts.data(), fonts.size(), bitmap, config.arteryFontFilename, arfontProps))
|
|
puts("Artery Font file generated.");
|
|
else {
|
|
success = false;
|
|
puts("Failed to generate Artery Font file.");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return success;
|
|
}
|
|
|
|
int main(int argc, const char * const *argv) {
|
|
#define ABORT(msg) { puts(msg); return 1; }
|
|
|
|
int result = 0;
|
|
std::vector<FontInput> fontInputs;
|
|
FontInput fontInput = { };
|
|
Configuration config = { };
|
|
fontInput.glyphIdentifierType = GlyphIdentifierType::UNICODE_CODEPOINT;
|
|
fontInput.fontScale = -1;
|
|
config.imageType = ImageType::MSDF;
|
|
config.imageFormat = ImageFormat::UNSPECIFIED;
|
|
config.yDirection = YDirection::BOTTOM_UP;
|
|
config.edgeColoring = msdfgen::edgeColoringInkTrap;
|
|
config.kerning = true;
|
|
const char *imageFormatName = nullptr;
|
|
int fixedWidth = -1, fixedHeight = -1;
|
|
config.preprocessGeometry = (
|
|
#ifdef MSDFGEN_USE_SKIA
|
|
true
|
|
#else
|
|
false
|
|
#endif
|
|
);
|
|
config.generatorAttributes.config.overlapSupport = !config.preprocessGeometry;
|
|
config.generatorAttributes.scanlinePass = !config.preprocessGeometry;
|
|
double minEmSize = 0;
|
|
enum {
|
|
/// Range specified in EMs
|
|
RANGE_EM,
|
|
/// Range specified in output pixels
|
|
RANGE_PIXEL,
|
|
} rangeMode = RANGE_PIXEL;
|
|
double rangeValue = 0;
|
|
TightAtlasPacker::DimensionsConstraint atlasSizeConstraint = TightAtlasPacker::DimensionsConstraint::MULTIPLE_OF_FOUR_SQUARE;
|
|
config.angleThreshold = DEFAULT_ANGLE_THRESHOLD;
|
|
config.miterLimit = DEFAULT_MITER_LIMIT;
|
|
config.threadCount = 0;
|
|
|
|
// Parse command line
|
|
int argPos = 1;
|
|
bool suggestHelp = false;
|
|
bool explicitErrorCorrectionMode = false;
|
|
while (argPos < argc) {
|
|
const char *arg = argv[argPos];
|
|
#define ARG_CASE(s, p) if (!strcmp(arg, s) && argPos+(p) < argc)
|
|
|
|
// Accept arguments prefixed with -- instead of -
|
|
if (arg[0] == '-' && arg[1] == '-')
|
|
++arg;
|
|
|
|
ARG_CASE("-type", 1) {
|
|
arg = argv[++argPos];
|
|
if (!strcmp(arg, "hardmask"))
|
|
config.imageType = ImageType::HARD_MASK;
|
|
else if (!strcmp(arg, "softmask"))
|
|
config.imageType = ImageType::SOFT_MASK;
|
|
else if (!strcmp(arg, "sdf"))
|
|
config.imageType = ImageType::SDF;
|
|
else if (!strcmp(arg, "psdf"))
|
|
config.imageType = ImageType::PSDF;
|
|
else if (!strcmp(arg, "msdf"))
|
|
config.imageType = ImageType::MSDF;
|
|
else if (!strcmp(arg, "mtsdf"))
|
|
config.imageType = ImageType::MTSDF;
|
|
else
|
|
ABORT("Invalid atlas type. Valid types are: hardmask, softmask, sdf, psdf, msdf, mtsdf");
|
|
++argPos;
|
|
continue;
|
|
}
|
|
ARG_CASE("-format", 1) {
|
|
arg = argv[++argPos];
|
|
if (!strcmp(arg, "png"))
|
|
config.imageFormat = ImageFormat::PNG;
|
|
else if (!strcmp(arg, "bmp"))
|
|
config.imageFormat = ImageFormat::BMP;
|
|
else if (!strcmp(arg, "tiff"))
|
|
config.imageFormat = ImageFormat::TIFF;
|
|
else if (!strcmp(arg, "text"))
|
|
config.imageFormat = ImageFormat::TEXT;
|
|
else if (!strcmp(arg, "textfloat"))
|
|
config.imageFormat = ImageFormat::TEXT_FLOAT;
|
|
else if (!strcmp(arg, "bin"))
|
|
config.imageFormat = ImageFormat::BINARY;
|
|
else if (!strcmp(arg, "binfloat"))
|
|
config.imageFormat = ImageFormat::BINARY_FLOAT;
|
|
else if (!strcmp(arg, "binfloatbe"))
|
|
config.imageFormat = ImageFormat::BINARY_FLOAT_BE;
|
|
else
|
|
ABORT("Invalid image format. Valid formats are: png, bmp, tiff, text, textfloat, bin, binfloat");
|
|
imageFormatName = arg;
|
|
++argPos;
|
|
continue;
|
|
}
|
|
ARG_CASE("-font", 1) {
|
|
fontInput.fontFilename = argv[++argPos];
|
|
fontInput.variableFont = false;
|
|
++argPos;
|
|
continue;
|
|
}
|
|
ARG_CASE("-varfont", 1) {
|
|
fontInput.fontFilename = argv[++argPos];
|
|
fontInput.variableFont = true;
|
|
++argPos;
|
|
continue;
|
|
}
|
|
ARG_CASE("-charset", 1) {
|
|
fontInput.charsetFilename = argv[++argPos];
|
|
fontInput.glyphIdentifierType = GlyphIdentifierType::UNICODE_CODEPOINT;
|
|
++argPos;
|
|
continue;
|
|
}
|
|
ARG_CASE("-glyphset", 1) {
|
|
fontInput.charsetFilename = argv[++argPos];
|
|
fontInput.glyphIdentifierType = GlyphIdentifierType::GLYPH_INDEX;
|
|
++argPos;
|
|
continue;
|
|
}
|
|
ARG_CASE("-fontscale", 1) {
|
|
double fs;
|
|
if (!(parseDouble(fs, argv[++argPos]) && fs > 0))
|
|
ABORT("Invalid font scale argument. Use -fontscale <font scale> with a positive real number.");
|
|
fontInput.fontScale = fs;
|
|
++argPos;
|
|
continue;
|
|
}
|
|
ARG_CASE("-fontname", 1) {
|
|
fontInput.fontName = argv[++argPos];
|
|
++argPos;
|
|
continue;
|
|
}
|
|
ARG_CASE("-and", 0) {
|
|
if (!fontInput.fontFilename && !fontInput.charsetFilename && fontInput.fontScale < 0)
|
|
ABORT("No font, character set, or font scale specified before -and separator.");
|
|
if (!fontInputs.empty() && !memcmp(&fontInputs.back(), &fontInput, sizeof(FontInput)))
|
|
ABORT("No changes between subsequent inputs. A different font, character set, or font scale must be set inbetween -and separators.");
|
|
fontInputs.push_back(fontInput);
|
|
fontInput.fontName = nullptr;
|
|
++argPos;
|
|
continue;
|
|
}
|
|
#ifndef MSDF_ATLAS_NO_ARTERY_FONT
|
|
ARG_CASE("-arfont", 1) {
|
|
config.arteryFontFilename = argv[++argPos];
|
|
++argPos;
|
|
continue;
|
|
}
|
|
#endif
|
|
ARG_CASE("-imageout", 1) {
|
|
config.imageFilename = argv[++argPos];
|
|
++argPos;
|
|
continue;
|
|
}
|
|
ARG_CASE("-json", 1) {
|
|
config.jsonFilename = argv[++argPos];
|
|
++argPos;
|
|
continue;
|
|
}
|
|
ARG_CASE("-csv", 1) {
|
|
config.csvFilename = argv[++argPos];
|
|
++argPos;
|
|
continue;
|
|
}
|
|
ARG_CASE("-shadronpreview", 2) {
|
|
config.shadronPreviewFilename = argv[++argPos];
|
|
config.shadronPreviewText = argv[++argPos];
|
|
++argPos;
|
|
continue;
|
|
}
|
|
ARG_CASE("-dimensions", 2) {
|
|
unsigned w, h;
|
|
if (!(parseUnsigned(w, argv[argPos+1]) && parseUnsigned(h, argv[argPos+2]) && w && h))
|
|
ABORT("Invalid atlas dimensions. Use -dimensions <width> <height> with two positive integers.");
|
|
fixedWidth = w, fixedHeight = h;
|
|
argPos += 3;
|
|
continue;
|
|
}
|
|
ARG_CASE("-pots", 0) {
|
|
atlasSizeConstraint = TightAtlasPacker::DimensionsConstraint::POWER_OF_TWO_SQUARE;
|
|
fixedWidth = -1, fixedHeight = -1;
|
|
++argPos;
|
|
continue;
|
|
}
|
|
ARG_CASE("-potr", 0) {
|
|
atlasSizeConstraint = TightAtlasPacker::DimensionsConstraint::POWER_OF_TWO_RECTANGLE;
|
|
fixedWidth = -1, fixedHeight = -1;
|
|
++argPos;
|
|
continue;
|
|
}
|
|
ARG_CASE("-square", 0) {
|
|
atlasSizeConstraint = TightAtlasPacker::DimensionsConstraint::SQUARE;
|
|
fixedWidth = -1, fixedHeight = -1;
|
|
++argPos;
|
|
continue;
|
|
}
|
|
ARG_CASE("-square2", 0) {
|
|
atlasSizeConstraint = TightAtlasPacker::DimensionsConstraint::EVEN_SQUARE;
|
|
fixedWidth = -1, fixedHeight = -1;
|
|
++argPos;
|
|
continue;
|
|
}
|
|
ARG_CASE("-square4", 0) {
|
|
atlasSizeConstraint = TightAtlasPacker::DimensionsConstraint::MULTIPLE_OF_FOUR_SQUARE;
|
|
fixedWidth = -1, fixedHeight = -1;
|
|
++argPos;
|
|
continue;
|
|
}
|
|
ARG_CASE("-yorigin", 1) {
|
|
arg = argv[++argPos];
|
|
if (!strcmp(arg, "bottom"))
|
|
config.yDirection = YDirection::BOTTOM_UP;
|
|
else if (!strcmp(arg, "top"))
|
|
config.yDirection = YDirection::TOP_DOWN;
|
|
else
|
|
ABORT("Invalid Y-axis origin. Use bottom or top.");
|
|
++argPos;
|
|
continue;
|
|
}
|
|
ARG_CASE("-size", 1) {
|
|
double s;
|
|
if (!(parseDouble(s, argv[++argPos]) && s > 0))
|
|
ABORT("Invalid EM size argument. Use -size <EM size> with a positive real number.");
|
|
config.emSize = s;
|
|
++argPos;
|
|
continue;
|
|
}
|
|
ARG_CASE("-minsize", 1) {
|
|
double s;
|
|
if (!(parseDouble(s, argv[++argPos]) && s > 0))
|
|
ABORT("Invalid minimum EM size argument. Use -minsize <EM size> with a positive real number.");
|
|
minEmSize = s;
|
|
++argPos;
|
|
continue;
|
|
}
|
|
ARG_CASE("-emrange", 1) {
|
|
double r;
|
|
if (!(parseDouble(r, argv[++argPos]) && r >= 0))
|
|
ABORT("Invalid range argument. Use -emrange <EM range> with a positive real number.");
|
|
rangeMode = RANGE_EM;
|
|
rangeValue = r;
|
|
++argPos;
|
|
continue;
|
|
}
|
|
ARG_CASE("-pxrange", 1) {
|
|
double r;
|
|
if (!(parseDouble(r, argv[++argPos]) && r >= 0))
|
|
ABORT("Invalid range argument. Use -pxrange <pixel range> with a positive real number.");
|
|
rangeMode = RANGE_PIXEL;
|
|
rangeValue = r;
|
|
++argPos;
|
|
continue;
|
|
}
|
|
ARG_CASE("-angle", 1) {
|
|
double at;
|
|
if (!parseAngle(at, argv[argPos+1]))
|
|
ABORT("Invalid angle threshold. Use -angle <min angle> with a positive real number less than PI or a value in degrees followed by 'd' below 180d.");
|
|
config.angleThreshold = at;
|
|
argPos += 2;
|
|
continue;
|
|
}
|
|
ARG_CASE("-errorcorrection", 1) {
|
|
msdfgen::ErrorCorrectionConfig &ec = config.generatorAttributes.config.errorCorrection;
|
|
if (!strcmp(argv[argPos+1], "disabled") || !strcmp(argv[argPos+1], "0") || !strcmp(argv[argPos+1], "none")) {
|
|
ec.mode = msdfgen::ErrorCorrectionConfig::DISABLED;
|
|
ec.distanceCheckMode = msdfgen::ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
|
|
} else if (!strcmp(argv[argPos+1], "default") || !strcmp(argv[argPos+1], "auto") || !strcmp(argv[argPos+1], "auto-mixed") || !strcmp(argv[argPos+1], "mixed")) {
|
|
ec.mode = msdfgen::ErrorCorrectionConfig::EDGE_PRIORITY;
|
|
ec.distanceCheckMode = msdfgen::ErrorCorrectionConfig::CHECK_DISTANCE_AT_EDGE;
|
|
} else if (!strcmp(argv[argPos+1], "auto-fast") || !strcmp(argv[argPos+1], "fast")) {
|
|
ec.mode = msdfgen::ErrorCorrectionConfig::EDGE_PRIORITY;
|
|
ec.distanceCheckMode = msdfgen::ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
|
|
} else if (!strcmp(argv[argPos+1], "auto-full") || !strcmp(argv[argPos+1], "full")) {
|
|
ec.mode = msdfgen::ErrorCorrectionConfig::EDGE_PRIORITY;
|
|
ec.distanceCheckMode = msdfgen::ErrorCorrectionConfig::ALWAYS_CHECK_DISTANCE;
|
|
} else if (!strcmp(argv[argPos+1], "distance") || !strcmp(argv[argPos+1], "distance-fast") || !strcmp(argv[argPos+1], "indiscriminate") || !strcmp(argv[argPos+1], "indiscriminate-fast")) {
|
|
ec.mode = msdfgen::ErrorCorrectionConfig::INDISCRIMINATE;
|
|
ec.distanceCheckMode = msdfgen::ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
|
|
} else if (!strcmp(argv[argPos+1], "distance-full") || !strcmp(argv[argPos+1], "indiscriminate-full")) {
|
|
ec.mode = msdfgen::ErrorCorrectionConfig::INDISCRIMINATE;
|
|
ec.distanceCheckMode = msdfgen::ErrorCorrectionConfig::ALWAYS_CHECK_DISTANCE;
|
|
} else if (!strcmp(argv[argPos+1], "edge-fast")) {
|
|
ec.mode = msdfgen::ErrorCorrectionConfig::EDGE_ONLY;
|
|
ec.distanceCheckMode = msdfgen::ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
|
|
} else if (!strcmp(argv[argPos+1], "edge") || !strcmp(argv[argPos+1], "edge-full")) {
|
|
ec.mode = msdfgen::ErrorCorrectionConfig::EDGE_ONLY;
|
|
ec.distanceCheckMode = msdfgen::ErrorCorrectionConfig::ALWAYS_CHECK_DISTANCE;
|
|
} else if (!strcmp(argv[argPos+1], "help")) {
|
|
puts(errorCorrectionHelpText);
|
|
return 0;
|
|
} else
|
|
ABORT("Unknown error correction mode. Use -errorcorrection help for more information.");
|
|
explicitErrorCorrectionMode = true;
|
|
argPos += 2;
|
|
continue;
|
|
}
|
|
ARG_CASE("-errordeviationratio", 1) {
|
|
double edr;
|
|
if (!(parseDouble(edr, argv[argPos+1]) && edr > 0))
|
|
ABORT("Invalid error deviation ratio. Use -errordeviationratio <ratio> with a positive real number.");
|
|
config.generatorAttributes.config.errorCorrection.minDeviationRatio = edr;
|
|
argPos += 2;
|
|
continue;
|
|
}
|
|
ARG_CASE("-errorimproveratio", 1) {
|
|
double eir;
|
|
if (!(parseDouble(eir, argv[argPos+1]) && eir > 0))
|
|
ABORT("Invalid error improvement ratio. Use -errorimproveratio <ratio> with a positive real number.");
|
|
config.generatorAttributes.config.errorCorrection.minImproveRatio = eir;
|
|
argPos += 2;
|
|
continue;
|
|
}
|
|
ARG_CASE("-coloringstrategy", 1) {
|
|
if (!strcmp(argv[argPos+1], "simple")) config.edgeColoring = msdfgen::edgeColoringSimple, config.expensiveColoring = false;
|
|
else if (!strcmp(argv[argPos+1], "inktrap")) config.edgeColoring = msdfgen::edgeColoringInkTrap, config.expensiveColoring = false;
|
|
else if (!strcmp(argv[argPos+1], "distance")) config.edgeColoring = msdfgen::edgeColoringByDistance, config.expensiveColoring = true;
|
|
else
|
|
puts("Unknown coloring strategy specified.");
|
|
argPos += 2;
|
|
continue;
|
|
}
|
|
ARG_CASE("-miterlimit", 1) {
|
|
double m;
|
|
if (!(parseDouble(m, argv[++argPos]) && m >= 0))
|
|
ABORT("Invalid miter limit argument. Use -miterlimit <limit> with a positive real number.");
|
|
config.miterLimit = m;
|
|
++argPos;
|
|
continue;
|
|
}
|
|
ARG_CASE("-nokerning", 0) {
|
|
config.kerning = false;
|
|
++argPos;
|
|
continue;
|
|
}
|
|
ARG_CASE("-kerning", 0) {
|
|
config.kerning = true;
|
|
++argPos;
|
|
continue;
|
|
}
|
|
ARG_CASE("-nopreprocess", 0) {
|
|
config.preprocessGeometry = false;
|
|
++argPos;
|
|
continue;
|
|
}
|
|
ARG_CASE("-preprocess", 0) {
|
|
config.preprocessGeometry = true;
|
|
++argPos;
|
|
continue;
|
|
}
|
|
ARG_CASE("-nooverlap", 0) {
|
|
config.generatorAttributes.config.overlapSupport = false;
|
|
++argPos;
|
|
continue;
|
|
}
|
|
ARG_CASE("-overlap", 0) {
|
|
config.generatorAttributes.config.overlapSupport = true;
|
|
++argPos;
|
|
continue;
|
|
}
|
|
ARG_CASE("-noscanline", 0) {
|
|
config.generatorAttributes.scanlinePass = false;
|
|
++argPos;
|
|
continue;
|
|
}
|
|
ARG_CASE("-scanline", 0) {
|
|
config.generatorAttributes.scanlinePass = true;
|
|
++argPos;
|
|
continue;
|
|
}
|
|
ARG_CASE("-seed", 1) {
|
|
if (!parseUnsignedLL(config.coloringSeed, argv[argPos+1]))
|
|
ABORT("Invalid seed. Use -seed <N> with N being a non-negative integer.");
|
|
argPos += 2;
|
|
continue;
|
|
}
|
|
ARG_CASE("-threads", 1) {
|
|
unsigned tc;
|
|
if (!parseUnsigned(tc, argv[argPos+1]) || (int) tc < 0)
|
|
ABORT("Invalid thread count. Use -threads <N> with N being a non-negative integer.");
|
|
config.threadCount = (int) tc;
|
|
argPos += 2;
|
|
continue;
|
|
}
|
|
ARG_CASE("-version", 0) {
|
|
puts(versionText);
|
|
return 0;
|
|
}
|
|
ARG_CASE("-help", 0) {
|
|
puts(helpText);
|
|
return 0;
|
|
}
|
|
printf("Unknown setting or insufficient parameters: %s\n", argv[argPos]);
|
|
suggestHelp = true;
|
|
++argPos;
|
|
}
|
|
if (suggestHelp)
|
|
printf("Use -help for more information.\n");
|
|
|
|
// Nothing to do?
|
|
if (argc == 1) {
|
|
printf(
|
|
"Usage: msdf-atlas-gen"
|
|
#ifdef _WIN32
|
|
".exe"
|
|
#endif
|
|
" -font <filename.ttf/otf> -charset <charset> <output specification> <options>\n"
|
|
"Use -help for more information.\n"
|
|
);
|
|
return 0;
|
|
}
|
|
if (!fontInput.fontFilename)
|
|
ABORT("No font specified.");
|
|
if (!(config.arteryFontFilename || config.imageFilename || config.jsonFilename || config.csvFilename || config.shadronPreviewFilename)) {
|
|
puts("No output specified.");
|
|
return 0;
|
|
}
|
|
bool layoutOnly = !(config.arteryFontFilename || config.imageFilename);
|
|
|
|
// Finalize font inputs
|
|
const FontInput *nextFontInput = &fontInput;
|
|
for (std::vector<FontInput>::reverse_iterator it = fontInputs.rbegin(); it != fontInputs.rend(); ++it) {
|
|
if (!it->fontFilename && nextFontInput->fontFilename)
|
|
it->fontFilename = nextFontInput->fontFilename;
|
|
if (!it->charsetFilename && nextFontInput->charsetFilename) {
|
|
it->charsetFilename = nextFontInput->charsetFilename;
|
|
it->glyphIdentifierType = nextFontInput->glyphIdentifierType;
|
|
}
|
|
if (it->fontScale < 0 && nextFontInput->fontScale >= 0)
|
|
it->fontScale = nextFontInput->fontScale;
|
|
nextFontInput = &*it;
|
|
}
|
|
if (fontInputs.empty() || memcmp(&fontInputs.back(), &fontInput, sizeof(FontInput)))
|
|
fontInputs.push_back(fontInput);
|
|
|
|
// Fix up configuration based on related values
|
|
if (!(config.imageType == ImageType::PSDF || config.imageType == ImageType::MSDF || config.imageType == ImageType::MTSDF))
|
|
config.miterLimit = 0;
|
|
if (config.emSize > minEmSize)
|
|
minEmSize = config.emSize;
|
|
if (!(fixedWidth > 0 && fixedHeight > 0) && !(minEmSize > 0)) {
|
|
puts("Neither atlas size nor glyph size selected, using default...");
|
|
minEmSize = MSDF_ATLAS_DEFAULT_EM_SIZE;
|
|
}
|
|
if (config.imageType == ImageType::HARD_MASK || config.imageType == ImageType::SOFT_MASK) {
|
|
rangeMode = RANGE_PIXEL;
|
|
rangeValue = 1;
|
|
} else if (rangeValue <= 0) {
|
|
rangeMode = RANGE_PIXEL;
|
|
rangeValue = DEFAULT_PIXEL_RANGE;
|
|
}
|
|
if (config.kerning && !(config.arteryFontFilename || config.jsonFilename || config.shadronPreviewFilename))
|
|
config.kerning = false;
|
|
if (config.threadCount <= 0)
|
|
config.threadCount = std::max((int) std::thread::hardware_concurrency(), 1);
|
|
if (config.generatorAttributes.scanlinePass) {
|
|
if (explicitErrorCorrectionMode && config.generatorAttributes.config.errorCorrection.distanceCheckMode != msdfgen::ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE) {
|
|
const char *fallbackModeName = "unknown";
|
|
switch (config.generatorAttributes.config.errorCorrection.mode) {
|
|
case msdfgen::ErrorCorrectionConfig::DISABLED: fallbackModeName = "disabled"; break;
|
|
case msdfgen::ErrorCorrectionConfig::INDISCRIMINATE: fallbackModeName = "distance-fast"; break;
|
|
case msdfgen::ErrorCorrectionConfig::EDGE_PRIORITY: fallbackModeName = "auto-fast"; break;
|
|
case msdfgen::ErrorCorrectionConfig::EDGE_ONLY: fallbackModeName = "edge-fast"; break;
|
|
}
|
|
printf("Selected error correction mode not compatible with scanline mode, falling back to %s.\n", fallbackModeName);
|
|
}
|
|
config.generatorAttributes.config.errorCorrection.distanceCheckMode = msdfgen::ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
|
|
}
|
|
|
|
// Finalize image format
|
|
ImageFormat imageExtension = ImageFormat::UNSPECIFIED;
|
|
if (config.imageFilename) {
|
|
if (cmpExtension(config.imageFilename, ".png")) imageExtension = ImageFormat::PNG;
|
|
else if (cmpExtension(config.imageFilename, ".bmp")) imageExtension = ImageFormat::BMP;
|
|
else if (cmpExtension(config.imageFilename, ".tif") || cmpExtension(config.imageFilename, ".tiff")) imageExtension = ImageFormat::TIFF;
|
|
else if (cmpExtension(config.imageFilename, ".txt")) imageExtension = ImageFormat::TEXT;
|
|
else if (cmpExtension(config.imageFilename, ".bin")) imageExtension = ImageFormat::BINARY;
|
|
}
|
|
if (config.imageFormat == ImageFormat::UNSPECIFIED) {
|
|
config.imageFormat = ImageFormat::PNG;
|
|
imageFormatName = "png";
|
|
// If image format is not specified and -imageout is the only image output, infer format from its extension
|
|
if (imageExtension != ImageFormat::UNSPECIFIED && !config.arteryFontFilename)
|
|
config.imageFormat = imageExtension;
|
|
}
|
|
if (config.imageType == ImageType::MTSDF && config.imageFormat == ImageFormat::BMP)
|
|
ABORT("Atlas type not compatible with image format. MTSDF requires a format with alpha channel.");
|
|
#ifndef MSDF_ATLAS_NO_ARTERY_FONT
|
|
if (config.arteryFontFilename && !(config.imageFormat == ImageFormat::PNG || config.imageFormat == ImageFormat::BINARY || config.imageFormat == ImageFormat::BINARY_FLOAT)) {
|
|
config.arteryFontFilename = nullptr;
|
|
result = 1;
|
|
puts("Error: Unable to create an Artery Font file with the specified image format!");
|
|
// Recheck whether there is anything else to do
|
|
if (!(config.arteryFontFilename || config.imageFilename || config.jsonFilename || config.csvFilename || config.shadronPreviewFilename))
|
|
return result;
|
|
layoutOnly = !(config.arteryFontFilename || config.imageFilename);
|
|
}
|
|
#endif
|
|
if (imageExtension != ImageFormat::UNSPECIFIED) {
|
|
// Warn if image format mismatches -imageout extension
|
|
bool mismatch = false;
|
|
switch (config.imageFormat) {
|
|
case ImageFormat::TEXT: case ImageFormat::TEXT_FLOAT:
|
|
mismatch = imageExtension != ImageFormat::TEXT;
|
|
break;
|
|
case ImageFormat::BINARY: case ImageFormat::BINARY_FLOAT: case ImageFormat::BINARY_FLOAT_BE:
|
|
mismatch = imageExtension != ImageFormat::BINARY;
|
|
break;
|
|
default:
|
|
mismatch = imageExtension != config.imageFormat;
|
|
}
|
|
if (mismatch)
|
|
printf("Warning: Output image file extension does not match the image's actual format (%s)!\n", imageFormatName);
|
|
}
|
|
imageFormatName = nullptr; // No longer consistent with imageFormat
|
|
bool floatingPointFormat = (
|
|
config.imageFormat == ImageFormat::TIFF ||
|
|
config.imageFormat == ImageFormat::TEXT_FLOAT ||
|
|
config.imageFormat == ImageFormat::BINARY_FLOAT ||
|
|
config.imageFormat == ImageFormat::BINARY_FLOAT_BE
|
|
);
|
|
|
|
// Load fonts
|
|
std::vector<GlyphGeometry> glyphs;
|
|
std::vector<FontGeometry> fonts;
|
|
bool anyCodepointsAvailable = false;
|
|
{
|
|
class FontHolder {
|
|
msdfgen::FreetypeHandle *ft;
|
|
msdfgen::FontHandle *font;
|
|
const char *fontFilename;
|
|
public:
|
|
FontHolder() : ft(msdfgen::initializeFreetype()), font(nullptr), fontFilename(nullptr) { }
|
|
~FontHolder() {
|
|
if (ft) {
|
|
if (font)
|
|
msdfgen::destroyFont(font);
|
|
msdfgen::deinitializeFreetype(ft);
|
|
}
|
|
}
|
|
bool load(const char *fontFilename, bool isVarFont) {
|
|
if (ft && fontFilename) {
|
|
if (this->fontFilename && !strcmp(this->fontFilename, fontFilename))
|
|
return true;
|
|
if (font)
|
|
msdfgen::destroyFont(font);
|
|
if ((font = isVarFont ? loadVarFont(ft, fontFilename) : msdfgen::loadFont(ft, fontFilename))) {
|
|
this->fontFilename = fontFilename;
|
|
return true;
|
|
}
|
|
this->fontFilename = nullptr;
|
|
}
|
|
return false;
|
|
}
|
|
operator msdfgen::FontHandle *() const {
|
|
return font;
|
|
}
|
|
} font;
|
|
|
|
for (FontInput &fontInput : fontInputs) {
|
|
if (!font.load(fontInput.fontFilename, fontInput.variableFont))
|
|
ABORT("Failed to load specified font file.");
|
|
if (fontInput.fontScale <= 0)
|
|
fontInput.fontScale = 1;
|
|
|
|
// Load character set
|
|
Charset charset;
|
|
if (fontInput.charsetFilename) {
|
|
if (!charset.load(fontInput.charsetFilename, fontInput.glyphIdentifierType != GlyphIdentifierType::UNICODE_CODEPOINT))
|
|
ABORT(fontInput.glyphIdentifierType == GlyphIdentifierType::GLYPH_INDEX ? "Failed to load glyph set specification." : "Failed to load character set specification.");
|
|
} else {
|
|
charset = Charset::ASCII;
|
|
fontInput.glyphIdentifierType = GlyphIdentifierType::UNICODE_CODEPOINT;
|
|
}
|
|
|
|
// Load glyphs
|
|
FontGeometry fontGeometry(&glyphs);
|
|
int glyphsLoaded = -1;
|
|
switch (fontInput.glyphIdentifierType) {
|
|
case GlyphIdentifierType::GLYPH_INDEX:
|
|
glyphsLoaded = fontGeometry.loadGlyphset(font, fontInput.fontScale, charset, config.preprocessGeometry, config.kerning);
|
|
break;
|
|
case GlyphIdentifierType::UNICODE_CODEPOINT:
|
|
glyphsLoaded = fontGeometry.loadCharset(font, fontInput.fontScale, charset, config.preprocessGeometry, config.kerning);
|
|
anyCodepointsAvailable |= glyphsLoaded > 0;
|
|
break;
|
|
}
|
|
if (glyphsLoaded < 0)
|
|
ABORT("Failed to load glyphs from font.");
|
|
printf("Loaded geometry of %d out of %d glyphs", glyphsLoaded, (int) charset.size());
|
|
if (fontInputs.size() > 1)
|
|
printf(" from font \"%s\"", fontInput.fontFilename);
|
|
printf(".\n");
|
|
// List missing glyphs
|
|
if (glyphsLoaded < (int) charset.size()) {
|
|
printf("Missing %d %s", (int) charset.size()-glyphsLoaded, fontInput.glyphIdentifierType == GlyphIdentifierType::UNICODE_CODEPOINT ? "codepoints" : "glyphs");
|
|
bool first = true;
|
|
switch (fontInput.glyphIdentifierType) {
|
|
case GlyphIdentifierType::GLYPH_INDEX:
|
|
for (unicode_t cp : charset)
|
|
if (!fontGeometry.getGlyph(msdfgen::GlyphIndex(cp)))
|
|
printf("%c 0x%02X", first ? ((first = false), ':') : ',', cp);
|
|
break;
|
|
case GlyphIdentifierType::UNICODE_CODEPOINT:
|
|
for (unicode_t cp : charset)
|
|
if (!fontGeometry.getGlyph(cp))
|
|
printf("%c 0x%02X", first ? ((first = false), ':') : ',', cp);
|
|
break;
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
if (fontInput.fontName)
|
|
fontGeometry.setName(fontInput.fontName);
|
|
|
|
fonts.push_back((FontGeometry &&) fontGeometry);
|
|
}
|
|
}
|
|
if (glyphs.empty())
|
|
ABORT("No glyphs loaded.");
|
|
|
|
// Determine final atlas dimensions, scale and range, pack glyphs
|
|
{
|
|
double unitRange = 0, pxRange = 0;
|
|
switch (rangeMode) {
|
|
case RANGE_EM:
|
|
unitRange = rangeValue;
|
|
break;
|
|
case RANGE_PIXEL:
|
|
pxRange = rangeValue;
|
|
break;
|
|
}
|
|
bool fixedDimensions = fixedWidth >= 0 && fixedHeight >= 0;
|
|
bool fixedScale = config.emSize > 0;
|
|
TightAtlasPacker atlasPacker;
|
|
if (fixedDimensions)
|
|
atlasPacker.setDimensions(fixedWidth, fixedHeight);
|
|
else
|
|
atlasPacker.setDimensionsConstraint(atlasSizeConstraint);
|
|
atlasPacker.setPadding(config.imageType == ImageType::MSDF || config.imageType == ImageType::MTSDF ? 0 : -1);
|
|
// TODO: In this case (if padding is -1), the border pixels of each glyph are black, but still computed. For floating-point output, this may play a role.
|
|
if (fixedScale)
|
|
atlasPacker.setScale(config.emSize);
|
|
else
|
|
atlasPacker.setMinimumScale(minEmSize);
|
|
atlasPacker.setPixelRange(pxRange);
|
|
atlasPacker.setUnitRange(unitRange);
|
|
atlasPacker.setMiterLimit(config.miterLimit);
|
|
if (int remaining = atlasPacker.pack(glyphs.data(), glyphs.size())) {
|
|
if (remaining < 0) {
|
|
ABORT("Failed to pack glyphs into atlas.");
|
|
} else {
|
|
printf("Error: Could not fit %d out of %d glyphs into the atlas.\n", remaining, (int) glyphs.size());
|
|
return 1;
|
|
}
|
|
}
|
|
atlasPacker.getDimensions(config.width, config.height);
|
|
if (!(config.width > 0 && config.height > 0))
|
|
ABORT("Unable to determine atlas size.");
|
|
config.emSize = atlasPacker.getScale();
|
|
config.pxRange = atlasPacker.getPixelRange();
|
|
if (!fixedScale)
|
|
printf("Glyph size: %.9g pixels/EM\n", config.emSize);
|
|
if (!fixedDimensions)
|
|
printf("Atlas dimensions: %d x %d\n", config.width, config.height);
|
|
}
|
|
|
|
// Generate atlas bitmap
|
|
if (!layoutOnly) {
|
|
|
|
// Edge coloring
|
|
if (config.imageType == ImageType::MSDF || config.imageType == ImageType::MTSDF) {
|
|
if (config.expensiveColoring) {
|
|
Workload([&glyphs, &config](int i, int threadNo) -> bool {
|
|
unsigned long long glyphSeed = (LCG_MULTIPLIER*(config.coloringSeed^i)+LCG_INCREMENT)*!!config.coloringSeed;
|
|
glyphs[i].edgeColoring(config.edgeColoring, config.angleThreshold, glyphSeed);
|
|
return true;
|
|
}, glyphs.size()).finish(config.threadCount);
|
|
} else {
|
|
unsigned long long glyphSeed = config.coloringSeed;
|
|
for (GlyphGeometry &glyph : glyphs) {
|
|
glyphSeed *= LCG_MULTIPLIER;
|
|
glyph.edgeColoring(config.edgeColoring, config.angleThreshold, glyphSeed);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool success = false;
|
|
switch (config.imageType) {
|
|
case ImageType::HARD_MASK:
|
|
if (floatingPointFormat)
|
|
success = makeAtlas<float, float, 1, scanlineGenerator>(glyphs, fonts, config);
|
|
else
|
|
success = makeAtlas<byte, float, 1, scanlineGenerator>(glyphs, fonts, config);
|
|
break;
|
|
case ImageType::SOFT_MASK:
|
|
case ImageType::SDF:
|
|
if (floatingPointFormat)
|
|
success = makeAtlas<float, float, 1, sdfGenerator>(glyphs, fonts, config);
|
|
else
|
|
success = makeAtlas<byte, float, 1, sdfGenerator>(glyphs, fonts, config);
|
|
break;
|
|
case ImageType::PSDF:
|
|
if (floatingPointFormat)
|
|
success = makeAtlas<float, float, 1, psdfGenerator>(glyphs, fonts, config);
|
|
else
|
|
success = makeAtlas<byte, float, 1, psdfGenerator>(glyphs, fonts, config);
|
|
break;
|
|
case ImageType::MSDF:
|
|
if (floatingPointFormat)
|
|
success = makeAtlas<float, float, 3, msdfGenerator>(glyphs, fonts, config);
|
|
else
|
|
success = makeAtlas<byte, float, 3, msdfGenerator>(glyphs, fonts, config);
|
|
break;
|
|
case ImageType::MTSDF:
|
|
if (floatingPointFormat)
|
|
success = makeAtlas<float, float, 4, mtsdfGenerator>(glyphs, fonts, config);
|
|
else
|
|
success = makeAtlas<byte, float, 4, mtsdfGenerator>(glyphs, fonts, config);
|
|
break;
|
|
}
|
|
if (!success)
|
|
result = 1;
|
|
}
|
|
|
|
if (config.csvFilename) {
|
|
if (exportCSV(fonts.data(), fonts.size(), config.width, config.height, config.yDirection, config.csvFilename))
|
|
puts("Glyph layout written into CSV file.");
|
|
else {
|
|
result = 1;
|
|
puts("Failed to write CSV output file.");
|
|
}
|
|
}
|
|
if (config.jsonFilename) {
|
|
if (exportJSON(fonts.data(), fonts.size(), config.emSize, config.pxRange, config.width, config.height, config.imageType, config.yDirection, config.jsonFilename, config.kerning))
|
|
puts("Glyph layout and metadata written into JSON file.");
|
|
else {
|
|
result = 1;
|
|
puts("Failed to write JSON output file.");
|
|
}
|
|
}
|
|
|
|
if (config.shadronPreviewFilename && config.shadronPreviewText) {
|
|
if (anyCodepointsAvailable) {
|
|
std::vector<unicode_t> previewText;
|
|
utf8Decode(previewText, config.shadronPreviewText);
|
|
previewText.push_back(0);
|
|
if (generateShadronPreview(fonts.data(), fonts.size(), config.imageType, config.width, config.height, config.pxRange, previewText.data(), config.imageFilename, floatingPointFormat, config.shadronPreviewFilename))
|
|
puts("Shadron preview script generated.");
|
|
else {
|
|
result = 1;
|
|
puts("Failed to generate Shadron preview file.");
|
|
}
|
|
} else {
|
|
result = 1;
|
|
puts("Shadron preview not supported in -glyphset mode.");
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
#endif
|