From 6e721724c9d8529a154973449c7f6f45352c5a25 Mon Sep 17 00:00:00 2001 From: Antoine Pilote Date: Sat, 12 Jun 2021 14:06:12 -0400 Subject: [PATCH] added msdf-atlas-gen --- Editor/resources/Fonts/RobotoMono-Regular.ttf | Bin 0 -> 86908 bytes Nuake/src/Vendors/msdf-atlas-gen/.gitignore | 20 + Nuake/src/Vendors/msdf-atlas-gen/.gitmodules | 6 + .../Vendors/msdf-atlas-gen/AtlasGenerator.h | 42 + .../src/Vendors/msdf-atlas-gen/AtlasStorage.h | 37 + .../msdf-atlas-gen/BitmapAtlasStorage.h | 33 + .../msdf-atlas-gen/BitmapAtlasStorage.hpp | 65 ++ Nuake/src/Vendors/msdf-atlas-gen/CHANGELOG.md | 26 + Nuake/src/Vendors/msdf-atlas-gen/Charset.cpp | 39 + Nuake/src/Vendors/msdf-atlas-gen/Charset.h | 35 + .../src/Vendors/msdf-atlas-gen/DynamicAtlas.h | 43 + .../Vendors/msdf-atlas-gen/DynamicAtlas.hpp | 69 ++ .../Vendors/msdf-atlas-gen/FontGeometry.cpp | 185 ++++ .../src/Vendors/msdf-atlas-gen/FontGeometry.h | 86 ++ Nuake/src/Vendors/msdf-atlas-gen/GlyphBox.h | 19 + .../Vendors/msdf-atlas-gen/GlyphGeometry.cpp | 170 +++ .../Vendors/msdf-atlas-gen/GlyphGeometry.h | 76 ++ .../msdf-atlas-gen/ImmediateAtlasGenerator.h | 45 + .../ImmediateAtlasGenerator.hpp | 77 ++ Nuake/src/Vendors/msdf-atlas-gen/LICENSE.txt | 21 + Nuake/src/Vendors/msdf-atlas-gen/README.md | 142 +++ Nuake/src/Vendors/msdf-atlas-gen/Rectangle.h | 14 + .../msdf-atlas-gen/RectanglePacker.cpp | 143 +++ .../Vendors/msdf-atlas-gen/RectanglePacker.h | 28 + Nuake/src/Vendors/msdf-atlas-gen/Remap.h | 15 + .../msdf-atlas-gen/TightAtlasPacker.cpp | 168 +++ .../Vendors/msdf-atlas-gen/TightAtlasPacker.h | 71 ++ Nuake/src/Vendors/msdf-atlas-gen/Workload.cpp | 50 + Nuake/src/Vendors/msdf-atlas-gen/Workload.h | 32 + .../msdf-atlas-gen/artery-font-export.cpp | 191 ++++ .../msdf-atlas-gen/artery-font-export.h | 23 + .../Vendors/msdf-atlas-gen/bitmap-blit.cpp | 58 + .../src/Vendors/msdf-atlas-gen/bitmap-blit.h | 26 + .../Vendors/msdf-atlas-gen/charset-parser.cpp | 250 +++++ .../src/Vendors/msdf-atlas-gen/csv-export.cpp | 45 + Nuake/src/Vendors/msdf-atlas-gen/csv-export.h | 14 + .../msdf-atlas-gen/glyph-generators.cpp | 52 + .../Vendors/msdf-atlas-gen/glyph-generators.h | 25 + Nuake/src/Vendors/msdf-atlas-gen/icon.ico | Bin 0 -> 32038 bytes .../Vendors/msdf-atlas-gen/image-encode.cpp | 63 ++ .../src/Vendors/msdf-atlas-gen/image-encode.h | 20 + Nuake/src/Vendors/msdf-atlas-gen/image-save.h | 15 + .../src/Vendors/msdf-atlas-gen/image-save.hpp | 172 +++ .../Vendors/msdf-atlas-gen/json-export.cpp | 186 ++++ .../src/Vendors/msdf-atlas-gen/json-export.h | 14 + Nuake/src/Vendors/msdf-atlas-gen/main.cpp | 999 ++++++++++++++++++ .../Vendors/msdf-atlas-gen/msdf-atlas-gen.h | 42 + .../Vendors/msdf-atlas-gen/msdf-atlas-gen.rc | Bin 0 -> 5274 bytes .../Vendors/msdf-atlas-gen/msdf-atlas-gen.sln | 61 ++ .../msdf-atlas-gen/msdf-atlas-gen.vcxproj | 374 +++++++ .../msdf-atlas-gen.vcxproj.filters | 184 ++++ .../msdf-atlas-gen/rectangle-packing.h | 19 + .../msdf-atlas-gen/rectangle-packing.hpp | 61 ++ Nuake/src/Vendors/msdf-atlas-gen/resource.h | Bin 0 -> 916 bytes .../shadron-preview-generator.cpp | 155 +++ .../shadron-preview-generator.h | 14 + .../Vendors/msdf-atlas-gen/size-selectors.cpp | 90 ++ .../Vendors/msdf-atlas-gen/size-selectors.h | 54 + Nuake/src/Vendors/msdf-atlas-gen/types.h | 52 + Nuake/src/Vendors/msdf-atlas-gen/utf8.cpp | 38 + Nuake/src/Vendors/msdf-atlas-gen/utf8.h | 12 + 61 files changed, 5066 insertions(+) create mode 100644 Editor/resources/Fonts/RobotoMono-Regular.ttf create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/.gitignore create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/.gitmodules create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/AtlasGenerator.h create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/AtlasStorage.h create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/BitmapAtlasStorage.h create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/BitmapAtlasStorage.hpp create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/CHANGELOG.md create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/Charset.cpp create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/Charset.h create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/DynamicAtlas.h create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/DynamicAtlas.hpp create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/FontGeometry.cpp create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/FontGeometry.h create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/GlyphBox.h create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/GlyphGeometry.cpp create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/GlyphGeometry.h create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/ImmediateAtlasGenerator.h create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/ImmediateAtlasGenerator.hpp create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/LICENSE.txt create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/README.md create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/Rectangle.h create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/RectanglePacker.cpp create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/RectanglePacker.h create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/Remap.h create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/TightAtlasPacker.cpp create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/TightAtlasPacker.h create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/Workload.cpp create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/Workload.h create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/artery-font-export.cpp create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/artery-font-export.h create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/bitmap-blit.cpp create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/bitmap-blit.h create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/charset-parser.cpp create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/csv-export.cpp create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/csv-export.h create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/glyph-generators.cpp create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/glyph-generators.h create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/icon.ico create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/image-encode.cpp create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/image-encode.h create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/image-save.h create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/image-save.hpp create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/json-export.cpp create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/json-export.h create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/main.cpp create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/msdf-atlas-gen.h create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/msdf-atlas-gen.rc create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/msdf-atlas-gen.sln create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/msdf-atlas-gen.vcxproj create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/msdf-atlas-gen.vcxproj.filters create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/rectangle-packing.h create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/rectangle-packing.hpp create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/resource.h create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/shadron-preview-generator.cpp create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/shadron-preview-generator.h create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/size-selectors.cpp create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/size-selectors.h create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/types.h create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/utf8.cpp create mode 100644 Nuake/src/Vendors/msdf-atlas-gen/utf8.h diff --git a/Editor/resources/Fonts/RobotoMono-Regular.ttf b/Editor/resources/Fonts/RobotoMono-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..7c4ce36a442d14eeb12444ad707c2afa19422fd8 GIT binary patch literal 86908 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 +using GeneratorFunction = void (*)(const msdfgen::BitmapRef &, const GlyphGeometry &, const GeneratorAttributes &); + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/AtlasStorage.h b/Nuake/src/Vendors/msdf-atlas-gen/AtlasStorage.h new file mode 100644 index 00000000..a51f46a4 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/AtlasStorage.h @@ -0,0 +1,37 @@ + +#pragma once + +#include +#include "Remap.h" + +namespace msdf_atlas { + +namespace { + +/** Prototype of an atlas storage class. + * An atlas storage physically holds the pixels of the atlas + * and allows to read and write subsections represented as bitmaps. + * Can be implemented using a simple bitmap (BitmapAtlasStorage), + * as texture memory, or any other way. + */ +class AtlasStorage { + +public: + AtlasStorage(); + AtlasStorage(int width, int height); + /// Creates a copy with different dimensions + AtlasStorage(const AtlasStorage &orig, int width, int height); + /// Creates a copy with different dimensions and rearranges the pixels according to the remapping array + AtlasStorage(const AtlasStorage &orig, int width, int height, const Remap *remapping, int count); + /// Stores a subsection at x, y into the atlas storage. May be implemented for only some T, N + template + void put(int x, int y, const msdfgen::BitmapConstRef &subBitmap); + /// Retrieves a subsection at x, y from the atlas storage. May be implemented for only some T, N + template + void get(int x, int y, const msdfgen::BitmapRef &subBitmap) const; + +}; + +} + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/BitmapAtlasStorage.h b/Nuake/src/Vendors/msdf-atlas-gen/BitmapAtlasStorage.h new file mode 100644 index 00000000..f0f7c99b --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/BitmapAtlasStorage.h @@ -0,0 +1,33 @@ + +#pragma once + +#include "AtlasStorage.h" + +namespace msdf_atlas { + +/// An implementation of AtlasStorage represented by a bitmap in memory (msdfgen::Bitmap) +template +class BitmapAtlasStorage { + +public: + BitmapAtlasStorage(); + BitmapAtlasStorage(int width, int height); + explicit BitmapAtlasStorage(const msdfgen::BitmapConstRef &bitmap); + explicit BitmapAtlasStorage(msdfgen::Bitmap &&bitmap); + BitmapAtlasStorage(const BitmapAtlasStorage &orig, int width, int height); + BitmapAtlasStorage(const BitmapAtlasStorage &orig, int width, int height, const Remap *remapping, int count); + operator msdfgen::BitmapConstRef() const; + operator msdfgen::BitmapRef(); + operator msdfgen::Bitmap() &&; + template + void put(int x, int y, const msdfgen::BitmapConstRef &subBitmap); + void get(int x, int y, const msdfgen::BitmapRef &subBitmap) const; + +private: + msdfgen::Bitmap bitmap; + +}; + +} + +#include "BitmapAtlasStorage.hpp" diff --git a/Nuake/src/Vendors/msdf-atlas-gen/BitmapAtlasStorage.hpp b/Nuake/src/Vendors/msdf-atlas-gen/BitmapAtlasStorage.hpp new file mode 100644 index 00000000..fbfdc9b1 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/BitmapAtlasStorage.hpp @@ -0,0 +1,65 @@ + +#include "BitmapAtlasStorage.h" + +#include +#include +#include "bitmap-blit.h" + +namespace msdf_atlas { + +template +BitmapAtlasStorage::BitmapAtlasStorage() { } + +template +BitmapAtlasStorage::BitmapAtlasStorage(int width, int height) : bitmap(width, height) { + memset((T *) bitmap, 0, sizeof(T)*N*width*height); +} + +template +BitmapAtlasStorage::BitmapAtlasStorage(const msdfgen::BitmapConstRef &bitmap) : bitmap(bitmap) { } + +template +BitmapAtlasStorage::BitmapAtlasStorage(msdfgen::Bitmap &&bitmap) : bitmap((msdfgen::Bitmap &&) bitmap) { } + +template +BitmapAtlasStorage::BitmapAtlasStorage(const BitmapAtlasStorage &orig, int width, int height) : bitmap(width, height) { + memset((T *) bitmap, 0, sizeof(T)*N*width*height); + blit(bitmap, orig.bitmap, 0, 0, 0, 0, std::min(width, orig.bitmap.width()), std::min(height, orig.bitmap.height())); +} + +template +BitmapAtlasStorage::BitmapAtlasStorage(const BitmapAtlasStorage &orig, int width, int height, const Remap *remapping, int count) : bitmap(width, height) { + memset((T *) bitmap, 0, sizeof(T)*N*width*height); + for (int i = 0; i < count; ++i) { + const Remap &remap = remapping[i]; + blit(bitmap, orig.bitmap, remap.target.x, remap.target.y, remap.source.x, remap.source.y, remap.width, remap.height); + } +} + +template +BitmapAtlasStorage::operator msdfgen::BitmapConstRef() const { + return bitmap; +} + +template +BitmapAtlasStorage::operator msdfgen::BitmapRef() { + return bitmap; +} + +template +BitmapAtlasStorage::operator msdfgen::Bitmap() && { + return (msdfgen::Bitmap &&) bitmap; +} + +template +template +void BitmapAtlasStorage::put(int x, int y, const msdfgen::BitmapConstRef &subBitmap) { + blit(bitmap, subBitmap, x, y, 0, 0, subBitmap.width, subBitmap.height); +} + +template +void BitmapAtlasStorage::get(int x, int y, const msdfgen::BitmapRef &subBitmap) const { + blit(subBitmap, bitmap, 0, 0, x, y, subBitmap.width, subBitmap.height); +} + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/CHANGELOG.md b/Nuake/src/Vendors/msdf-atlas-gen/CHANGELOG.md new file mode 100644 index 00000000..d7d4480a --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/CHANGELOG.md @@ -0,0 +1,26 @@ + +## Version 1.2 (2021-05-29) + +- Updated to MSDFgen 1.9. +- Multiple fonts or font sizes can now be compiled into a single atlas. +- Added `-yorigin` option to choose if Y-coordinates increase from bottom to top or from top to bottom. +- Added `-coloringstrategy` option to select MSDF edge coloring heuristic. +- Shadron preview now properly loads floating-point image outputs in full range mode. + +## Version 1.1 (2020-10-18) + +- Updated to MSDFgen 1.8. +- Glyph geometry is now preprocessed by Skia to resolve irregularities which were previously unsupported and caused artifacts. + - The scanline pass and overlapping contour mode is made obsolete by this step and has been disabled by default. The preprocess step can be disabled by the new `-nopreprocess` switch and the former enabled by `-scanline` and `-overlap` respectively. + - The project can be built without the Skia library, forgoing the geometry preprocessing feature. This is controlled by the macro definition `MSDFGEN_USE_SKIA`. +- Glyphs can now also be loaded by glyph index rather than Unicode values. In the standalone version, a set of glyphs can be passed by `-glyphset` in place of `-charset`. +- Glyphs not present in the font should now be correctly skipped instead of producing a placeholder symbol. +- Added `-threads` argument to set the number of concurrent threads used during distance field generation. + +### Version 1.0.1 (2020-03-09) + +- Updated to MSDFgen 1.7.1. + +## Version 1.0 (2020-03-08) + +- Initial release. diff --git a/Nuake/src/Vendors/msdf-atlas-gen/Charset.cpp b/Nuake/src/Vendors/msdf-atlas-gen/Charset.cpp new file mode 100644 index 00000000..69c93ee7 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/Charset.cpp @@ -0,0 +1,39 @@ + +#include "Charset.h" + +namespace msdf_atlas { + +static Charset createAsciiCharset() { + Charset ascii; + for (unicode_t cp = 0x20; cp < 0x7f; ++cp) + ascii.add(cp); + return ascii; +} + +const Charset Charset::ASCII = createAsciiCharset(); + +void Charset::add(unicode_t cp) { + codepoints.insert(cp); +} + +void Charset::remove(unicode_t cp) { + codepoints.erase(cp); +} + +size_t Charset::size() const { + return codepoints.size(); +} + +bool Charset::empty() const { + return codepoints.empty(); +} + +std::set::const_iterator Charset::begin() const { + return codepoints.begin(); +} + +std::set::const_iterator Charset::end() const { + return codepoints.end(); +} + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/Charset.h b/Nuake/src/Vendors/msdf-atlas-gen/Charset.h new file mode 100644 index 00000000..d0cc7191 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/Charset.h @@ -0,0 +1,35 @@ + +#pragma once + +#include +#include +#include "types.h" + +namespace msdf_atlas { + +/// Represents a set of Unicode codepoints (characters) +class Charset { + +public: + /// The set of the 95 printable ASCII characters + static const Charset ASCII; + + /// Adds a codepoint + void add(unicode_t cp); + /// Removes a codepoint + void remove(unicode_t cp); + + size_t size() const; + bool empty() const; + std::set::const_iterator begin() const; + std::set::const_iterator end() const; + + /// Load character set from a text file with the correct syntax + bool load(const char *filename, bool disableCharLiterals = false); + +private: + std::set codepoints; + +}; + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/DynamicAtlas.h b/Nuake/src/Vendors/msdf-atlas-gen/DynamicAtlas.h new file mode 100644 index 00000000..e1640977 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/DynamicAtlas.h @@ -0,0 +1,43 @@ + +#pragma once + +#include +#include "RectanglePacker.h" +#include "AtlasGenerator.h" + +namespace msdf_atlas { + +/** + * This class can be used to produce a dynamic atlas to which more glyphs are added over time. + * It takes care of laying out and enlarging the atlas as necessary and delegates the actual work + * to the specified AtlasGenerator, which may e.g. do the work asynchronously. + */ +template +class DynamicAtlas { + +public: + DynamicAtlas(); + /// Creates with a configured generator. The generator must not contain any prior glyphs! + explicit DynamicAtlas(AtlasGenerator &&generator); + /// Adds a batch of glyphs. Adding more than one glyph at a time may improve packing efficiency + void add(GlyphGeometry *glyphs, int count); + /// Allows access to generator. Do not add glyphs to the generator directly! + AtlasGenerator & atlasGenerator(); + const AtlasGenerator & atlasGenerator() const; + +private: + AtlasGenerator generator; + RectanglePacker packer; + int glyphCount; + int side; + std::vector rectangles; + std::vector remapBuffer; + int totalArea; + GeneratorAttributes genAttribs; + int padding; + +}; + +} + +#include "DynamicAtlas.hpp" diff --git a/Nuake/src/Vendors/msdf-atlas-gen/DynamicAtlas.hpp b/Nuake/src/Vendors/msdf-atlas-gen/DynamicAtlas.hpp new file mode 100644 index 00000000..51a712a8 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/DynamicAtlas.hpp @@ -0,0 +1,69 @@ + +#include "DynamicAtlas.h" + +namespace msdf_atlas { + +template +DynamicAtlas::DynamicAtlas() : glyphCount(0), side(0), totalArea(0), padding(0) { } + +template +DynamicAtlas::DynamicAtlas(AtlasGenerator &&generator) : generator((AtlasGenerator &&) generator), glyphCount(0), side(0), totalArea(0), padding(0) { } + +template +void DynamicAtlas::add(GlyphGeometry *glyphs, int count) { + int start = rectangles.size(); + for (int i = 0; i < count; ++i) { + if (!glyphs[i].isWhitespace()) { + int w, h; + glyphs[i].getBoxSize(w, h); + Rectangle rect = { 0, 0, w+padding, h+padding }; + rectangles.push_back(rect); + Remap remapEntry = { }; + remapEntry.index = glyphCount+i; + remapEntry.width = w; + remapEntry.height = h; + remapBuffer.push_back(remapEntry); + totalArea += (w+padding)*(h+padding); + } + } + if ((int) rectangles.size() > start) { + int oldSide = side; + int packerStart = start; + while (packer.pack(rectangles.data()+packerStart, rectangles.size()-packerStart) > 0) { + side = side+!side<<1; + while (side*side < totalArea) + side <<= 1; + packer = RectanglePacker(side+padding, side+padding); + packerStart = 0; + } + if (packerStart < start) { + for (int i = 0; i < start; ++i) { + Remap &remap = remapBuffer[i]; + remap.source = remap.target; + remap.target.x = rectangles[i].x; + remap.target.y = rectangles[i].y; + } + generator.rearrange(side, side, remapBuffer.data(), start); + } else if (side != oldSide) + generator.resize(side, side); + for (int i = start; i < (int) rectangles.size(); ++i) { + remapBuffer[i].target.x = rectangles[i].x; + remapBuffer[i].target.y = rectangles[i].y; + glyphs[remapBuffer[i].index-glyphCount].placeBox(rectangles[i].x, rectangles[i].y); + } + } + generator.generate(glyphs, count, genAttribs); + glyphCount += count; +} + +template +AtlasGenerator & DynamicAtlas::atlasGenerator() { + return generator; +} + +template +const AtlasGenerator & DynamicAtlas::atlasGenerator() const { + return generator; +} + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/FontGeometry.cpp b/Nuake/src/Vendors/msdf-atlas-gen/FontGeometry.cpp new file mode 100644 index 00000000..ad89d45f --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/FontGeometry.cpp @@ -0,0 +1,185 @@ + +#include "FontGeometry.h" + +namespace msdf_atlas { + +FontGeometry::GlyphRange::GlyphRange() : glyphs(), rangeStart(), rangeEnd() { } + +FontGeometry::GlyphRange::GlyphRange(const std::vector *glyphs, size_t rangeStart, size_t rangeEnd) : glyphs(glyphs), rangeStart(rangeStart), rangeEnd(rangeEnd) { } + +size_t FontGeometry::GlyphRange::size() const { + return glyphs->size(); +} + +bool FontGeometry::GlyphRange::empty() const { + return glyphs->empty(); +} + +const GlyphGeometry * FontGeometry::GlyphRange::begin() const { + return glyphs->data()+rangeStart; +} + +const GlyphGeometry * FontGeometry::GlyphRange::end() const { + return glyphs->data()+rangeEnd; +} + +FontGeometry::FontGeometry() : geometryScale(1), metrics(), preferredIdentifierType(GlyphIdentifierType::UNICODE_CODEPOINT), glyphs(&ownGlyphs), rangeStart(glyphs->size()), rangeEnd(glyphs->size()) { } + +FontGeometry::FontGeometry(std::vector *glyphStorage) : geometryScale(1), metrics(), preferredIdentifierType(GlyphIdentifierType::UNICODE_CODEPOINT), glyphs(glyphStorage), rangeStart(glyphs->size()), rangeEnd(glyphs->size()) { } + +int FontGeometry::loadGlyphset(msdfgen::FontHandle *font, double fontScale, const Charset &glyphset, bool preprocessGeometry, bool enableKerning) { + if (!(glyphs->size() == rangeEnd && loadMetrics(font, fontScale))) + return -1; + glyphs->reserve(glyphs->size()+glyphset.size()); + int loaded = 0; + for (unicode_t index : glyphset) { + GlyphGeometry glyph; + if (glyph.load(font, geometryScale, msdfgen::GlyphIndex(index), preprocessGeometry)) { + addGlyph((GlyphGeometry &&) glyph); + ++loaded; + } + } + if (enableKerning) + loadKerning(font); + preferredIdentifierType = GlyphIdentifierType::GLYPH_INDEX; + return loaded; +} + +int FontGeometry::loadCharset(msdfgen::FontHandle *font, double fontScale, const Charset &charset, bool preprocessGeometry, bool enableKerning) { + if (!(glyphs->size() == rangeEnd && loadMetrics(font, fontScale))) + return -1; + glyphs->reserve(glyphs->size()+charset.size()); + int loaded = 0; + for (unicode_t cp : charset) { + GlyphGeometry glyph; + if (glyph.load(font, geometryScale, cp, preprocessGeometry)) { + addGlyph((GlyphGeometry &&) glyph); + ++loaded; + } + } + if (enableKerning) + loadKerning(font); + preferredIdentifierType = GlyphIdentifierType::UNICODE_CODEPOINT; + return loaded; +} + +bool FontGeometry::loadMetrics(msdfgen::FontHandle *font, double fontScale) { + if (!msdfgen::getFontMetrics(metrics, font)) + return false; + if (metrics.emSize <= 0) + metrics.emSize = MSDF_ATLAS_DEFAULT_EM_SIZE; + geometryScale = fontScale/metrics.emSize; + metrics.emSize *= geometryScale; + metrics.ascenderY *= geometryScale; + metrics.descenderY *= geometryScale; + metrics.lineHeight *= geometryScale; + metrics.underlineY *= geometryScale; + metrics.underlineThickness *= geometryScale; + return true; +} + +bool FontGeometry::addGlyph(const GlyphGeometry &glyph) { + if (glyphs->size() != rangeEnd) + return false; + glyphsByIndex.insert(std::make_pair(glyph.getIndex(), rangeEnd)); + if (glyph.getCodepoint()) + glyphsByCodepoint.insert(std::make_pair(glyph.getCodepoint(), rangeEnd)); + glyphs->push_back(glyph); + ++rangeEnd; + return true; +} + +bool FontGeometry::addGlyph(GlyphGeometry &&glyph) { + if (glyphs->size() != rangeEnd) + return false; + glyphsByIndex.insert(std::make_pair(glyph.getIndex(), rangeEnd)); + if (glyph.getCodepoint()) + glyphsByCodepoint.insert(std::make_pair(glyph.getCodepoint(), rangeEnd)); + glyphs->push_back((GlyphGeometry &&) glyph); + ++rangeEnd; + return true; +} + +int FontGeometry::loadKerning(msdfgen::FontHandle *font) { + int loaded = 0; + for (size_t i = rangeStart; i < rangeEnd; ++i) + for (size_t j = rangeStart; j < rangeEnd; ++j) { + double advance; + if (msdfgen::getKerning(advance, font, (*glyphs)[i].getGlyphIndex(), (*glyphs)[j].getGlyphIndex()) && advance) { + kerning[std::make_pair((*glyphs)[i].getIndex(), (*glyphs)[j].getIndex())] = geometryScale*advance; + ++loaded; + } + } + return loaded; +} + +void FontGeometry::setName(const char *name) { + if (name) + this->name = name; + else + this->name.clear(); +} + +double FontGeometry::getGeometryScale() const { + return geometryScale; +} + +const msdfgen::FontMetrics & FontGeometry::getMetrics() const { + return metrics; +} + +GlyphIdentifierType FontGeometry::getPreferredIdentifierType() const { + return preferredIdentifierType; +} + +FontGeometry::GlyphRange FontGeometry::getGlyphs() const { + return GlyphRange(glyphs, rangeStart, rangeEnd); +} + +const GlyphGeometry * FontGeometry::getGlyph(msdfgen::GlyphIndex index) const { + std::map::const_iterator it = glyphsByIndex.find(index.getIndex()); + if (it != glyphsByIndex.end()) + return &(*glyphs)[it->second]; + return nullptr; +} + +const GlyphGeometry * FontGeometry::getGlyph(unicode_t codepoint) const { + std::map::const_iterator it = glyphsByCodepoint.find(codepoint); + if (it != glyphsByCodepoint.end()) + return &(*glyphs)[it->second]; + return nullptr; +} + +bool FontGeometry::getAdvance(double &advance, msdfgen::GlyphIndex index1, msdfgen::GlyphIndex index2) const { + const GlyphGeometry *glyph1 = getGlyph(index1); + if (!glyph1) + return false; + advance = glyph1->getAdvance(); + std::map, double>::const_iterator it = kerning.find(std::make_pair(index1.getIndex(), index2.getIndex())); + if (it != kerning.end()) + advance += it->second; + return true; +} + +bool FontGeometry::getAdvance(double &advance, unicode_t codepoint1, unicode_t codepoint2) const { + const GlyphGeometry *glyph1, *glyph2; + if (!((glyph1 = getGlyph(codepoint1)) && (glyph2 = getGlyph(codepoint2)))) + return false; + advance = glyph1->getAdvance(); + std::map, double>::const_iterator it = kerning.find(std::make_pair(glyph1->getIndex(), glyph2->getIndex())); + if (it != kerning.end()) + advance += it->second; + return true; +} + +const std::map, double> & FontGeometry::getKerning() const { + return kerning; +} + +const char * FontGeometry::getName() const { + if (name.empty()) + return nullptr; + return name.c_str(); +} + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/FontGeometry.h b/Nuake/src/Vendors/msdf-atlas-gen/FontGeometry.h new file mode 100644 index 00000000..4ac230e9 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/FontGeometry.h @@ -0,0 +1,86 @@ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include "types.h" +#include "GlyphGeometry.h" +#include "Charset.h" + +#define MSDF_ATLAS_DEFAULT_EM_SIZE 32.0 + +namespace msdf_atlas { + +/// Represents the geometry of all glyphs of a given font or font variant +class FontGeometry { + +public: + class GlyphRange { + public: + GlyphRange(); + GlyphRange(const std::vector *glyphs, size_t rangeStart, size_t rangeEnd); + size_t size() const; + bool empty() const; + const GlyphGeometry * begin() const; + const GlyphGeometry * end() const; + private: + const std::vector *glyphs; + size_t rangeStart, rangeEnd; + }; + + FontGeometry(); + explicit FontGeometry(std::vector *glyphStorage); + + /// Loads all glyphs in a glyphset (Charset elements are glyph indices), returns the number of successfully loaded glyphs + int loadGlyphset(msdfgen::FontHandle *font, double fontScale, const Charset &glyphset, bool preprocessGeometry = true, bool enableKerning = true); + /// Loads all glyphs in a charset (Charset elements are Unicode codepoints), returns the number of successfully loaded glyphs + int loadCharset(msdfgen::FontHandle *font, double fontScale, const Charset &charset, bool preprocessGeometry = true, bool enableKerning = true); + + /// Only loads font metrics and geometry scale from font + bool loadMetrics(msdfgen::FontHandle *font, double fontScale); + /// Adds a loaded glyph + bool addGlyph(const GlyphGeometry &glyph); + bool addGlyph(GlyphGeometry &&glyph); + /// Loads kerning pairs for all glyphs that are currently present, returns the number of loaded kerning pairs + int loadKerning(msdfgen::FontHandle *font); + /// Sets a name to be associated with the font + void setName(const char *name); + + /// Returns the geometry scale to be used when loading glyphs + double getGeometryScale() const; + /// Returns the processed font metrics + const msdfgen::FontMetrics & getMetrics() const; + /// Returns the type of identifier that was used to load glyphs + GlyphIdentifierType getPreferredIdentifierType() const; + /// Returns the list of all glyphs + GlyphRange getGlyphs() const; + /// Finds a glyph by glyph index or Unicode codepoint, returns null if not found + const GlyphGeometry * getGlyph(msdfgen::GlyphIndex index) const; + const GlyphGeometry * getGlyph(unicode_t codepoint) const; + /// Outputs the advance between two glyphs with kerning taken into consideration, returns false on failure + bool getAdvance(double &advance, msdfgen::GlyphIndex index1, msdfgen::GlyphIndex index2) const; + bool getAdvance(double &advance, unicode_t codepoint1, unicode_t codepoint2) const; + /// Returns the complete mapping of kerning pairs (by glyph indices) and their respective advance values + const std::map, double> & getKerning() const; + /// Returns the name associated with the font or null if not set + const char * getName() const; + +private: + double geometryScale; + msdfgen::FontMetrics metrics; + GlyphIdentifierType preferredIdentifierType; + std::vector *glyphs; + size_t rangeStart, rangeEnd; + std::map glyphsByIndex; + std::map glyphsByCodepoint; + std::map, double> kerning; + std::vector ownGlyphs; + std::string name; + +}; + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/GlyphBox.h b/Nuake/src/Vendors/msdf-atlas-gen/GlyphBox.h new file mode 100644 index 00000000..83b8842c --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/GlyphBox.h @@ -0,0 +1,19 @@ + +#pragma once + +namespace msdf_atlas { + +/// The glyph box - its bounds in plane and atlas +struct GlyphBox { + int index; + double advance; + struct { + double l, b, r, t; + } bounds; + struct { + int x, y, w, h; + } rect; + +}; + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/GlyphGeometry.cpp b/Nuake/src/Vendors/msdf-atlas-gen/GlyphGeometry.cpp new file mode 100644 index 00000000..5ee557a9 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/GlyphGeometry.cpp @@ -0,0 +1,170 @@ + +#include "GlyphGeometry.h" + +#include +#include + +namespace msdf_atlas { + +GlyphGeometry::GlyphGeometry() : index(), codepoint(), geometryScale(), bounds(), advance(), box() { } + +bool GlyphGeometry::load(msdfgen::FontHandle *font, double geometryScale, msdfgen::GlyphIndex index, bool preprocessGeometry) { + if (font && msdfgen::loadGlyph(shape, font, index, &advance) && shape.validate()) { + this->index = index.getIndex(); + this->geometryScale = geometryScale; + codepoint = 0; + advance *= geometryScale; + #ifdef MSDFGEN_USE_SKIA + if (preprocessGeometry) + msdfgen::resolveShapeGeometry(shape); + #endif + shape.normalize(); + bounds = shape.getBounds(); + #ifdef MSDFGEN_USE_SKIA + if (!preprocessGeometry) + #endif + { + // Determine if shape is winded incorrectly and reverse it in that case + msdfgen::Point2 outerPoint(bounds.l-(bounds.r-bounds.l)-1, bounds.b-(bounds.t-bounds.b)-1); + if (msdfgen::SimpleTrueShapeDistanceFinder::oneShotDistance(shape, outerPoint) > 0) { + for (msdfgen::Contour &contour : shape.contours) + contour.reverse(); + } + } + return true; + } + return false; +} + +bool GlyphGeometry::load(msdfgen::FontHandle *font, double geometryScale, unicode_t codepoint, bool preprocessGeometry) { + msdfgen::GlyphIndex index; + if (msdfgen::getGlyphIndex(index, font, codepoint)) { + if (load(font, geometryScale, index, preprocessGeometry)) { + this->codepoint = codepoint; + return true; + } + } + return false; +} + +void GlyphGeometry::edgeColoring(void (*fn)(msdfgen::Shape &, double, unsigned long long), double angleThreshold, unsigned long long seed) { + fn(shape, angleThreshold, seed); +} + +void GlyphGeometry::wrapBox(double scale, double range, double miterLimit) { + scale *= geometryScale; + range /= geometryScale; + box.range = range; + box.scale = scale; + if (bounds.l < bounds.r && bounds.b < bounds.t) { + double l = bounds.l, b = bounds.b, r = bounds.r, t = bounds.t; + l -= .5*range, b -= .5*range; + r += .5*range, t += .5*range; + if (miterLimit > 0) + shape.boundMiters(l, b, r, t, .5*range, miterLimit, 1); + double w = scale*(r-l); + double h = scale*(t-b); + box.rect.w = (int) ceil(w)+1; + box.rect.h = (int) ceil(h)+1; + box.translate.x = -l+.5*(box.rect.w-w)/scale; + box.translate.y = -b+.5*(box.rect.h-h)/scale; + } else { + box.rect.w = 0, box.rect.h = 0; + box.translate = msdfgen::Vector2(); + } +} + +void GlyphGeometry::placeBox(int x, int y) { + box.rect.x = x, box.rect.y = y; +} + +int GlyphGeometry::getIndex() const { + return index; +} + +msdfgen::GlyphIndex GlyphGeometry::getGlyphIndex() const { + return msdfgen::GlyphIndex(index); +} + +unicode_t GlyphGeometry::getCodepoint() const { + return codepoint; +} + +int GlyphGeometry::getIdentifier(GlyphIdentifierType type) const { + switch (type) { + case GlyphIdentifierType::GLYPH_INDEX: + return index; + case GlyphIdentifierType::UNICODE_CODEPOINT: + return (int) codepoint; + } + return 0; +} + +const msdfgen::Shape & GlyphGeometry::getShape() const { + return shape; +} + +double GlyphGeometry::getAdvance() const { + return advance; +} + +void GlyphGeometry::getBoxRect(int &x, int &y, int &w, int &h) const { + x = box.rect.x, y = box.rect.y; + w = box.rect.w, h = box.rect.h; +} + +void GlyphGeometry::getBoxSize(int &w, int &h) const { + w = box.rect.w, h = box.rect.h; +} + +double GlyphGeometry::getBoxRange() const { + return box.range; +} + +msdfgen::Projection GlyphGeometry::getBoxProjection() const { + return msdfgen::Projection(msdfgen::Vector2(box.scale), box.translate); +} + +double GlyphGeometry::getBoxScale() const { + return box.scale; +} + +msdfgen::Vector2 GlyphGeometry::getBoxTranslate() const { + return box.translate; +} + +void GlyphGeometry::getQuadPlaneBounds(double &l, double &b, double &r, double &t) const { + if (box.rect.w > 0 && box.rect.h > 0) { + double invBoxScale = 1/box.scale; + l = geometryScale*(-box.translate.x+.5*invBoxScale); + b = geometryScale*(-box.translate.y+.5*invBoxScale); + r = geometryScale*(-box.translate.x+(box.rect.w-.5)*invBoxScale); + t = geometryScale*(-box.translate.y+(box.rect.h-.5)*invBoxScale); + } else + l = 0, b = 0, r = 0, t = 0; +} + +void GlyphGeometry::getQuadAtlasBounds(double &l, double &b, double &r, double &t) const { + if (box.rect.w > 0 && box.rect.h > 0) { + l = box.rect.x+.5; + b = box.rect.y+.5; + r = box.rect.x+box.rect.w-.5; + t = box.rect.y+box.rect.h-.5; + } else + l = 0, b = 0, r = 0, t = 0; +} + +bool GlyphGeometry::isWhitespace() const { + return shape.contours.empty(); +} + +GlyphGeometry::operator GlyphBox() const { + GlyphBox box; + box.index = index; + box.advance = advance; + getQuadPlaneBounds(box.bounds.l, box.bounds.b, box.bounds.r, box.bounds.t); + box.rect.x = this->box.rect.x, box.rect.y = this->box.rect.y, box.rect.w = this->box.rect.w, box.rect.h = this->box.rect.h; + return box; +} + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/GlyphGeometry.h b/Nuake/src/Vendors/msdf-atlas-gen/GlyphGeometry.h new file mode 100644 index 00000000..748baed4 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/GlyphGeometry.h @@ -0,0 +1,76 @@ + +#pragma once + +#include +#include +#include "types.h" +#include "GlyphBox.h" + +namespace msdf_atlas { + +/// Represents the shape geometry of a single glyph as well as its configuration +class GlyphGeometry { + +public: + GlyphGeometry(); + /// Loads glyph geometry from font + bool load(msdfgen::FontHandle *font, double geometryScale, msdfgen::GlyphIndex index, bool preprocessGeometry = true); + bool load(msdfgen::FontHandle *font, double geometryScale, unicode_t codepoint, bool preprocessGeometry = true); + /// Applies edge coloring to glyph shape + void edgeColoring(void (*fn)(msdfgen::Shape &, double, unsigned long long), double angleThreshold, unsigned long long seed); + /// Computes the dimensions of the glyph's box as well as the transformation for the generator function + void wrapBox(double scale, double range, double miterLimit); + /// Sets the glyph's box's position in the atlas + void placeBox(int x, int y); + /// Returns the glyph's index within the font + int getIndex() const; + /// Returns the glyph's index as a msdfgen::GlyphIndex + msdfgen::GlyphIndex getGlyphIndex() const; + /// Returns the Unicode codepoint represented by the glyph or 0 if unknown + unicode_t getCodepoint() const; + /// Returns the glyph's identifier specified by the supplied identifier type + int getIdentifier(GlyphIdentifierType type) const; + /// Returns the glyph's shape + const msdfgen::Shape & getShape() const; + /// Returns the glyph's advance + double getAdvance() const; + /// Outputs the position and dimensions of the glyph's box in the atlas + void getBoxRect(int &x, int &y, int &w, int &h) const; + /// Outputs the dimensions of the glyph's box in the atlas + void getBoxSize(int &w, int &h) const; + /// Returns the range needed to generate the glyph's SDF + double getBoxRange() const; + /// Returns the projection needed to generate the glyph's bitmap + msdfgen::Projection getBoxProjection() const; + /// Returns the scale needed to generate the glyph's bitmap + double getBoxScale() const; + /// Returns the translation vector needed to generate the glyph's bitmap + msdfgen::Vector2 getBoxTranslate() const; + /// Outputs the bounding box of the glyph as it should be placed on the baseline + void getQuadPlaneBounds(double &l, double &b, double &r, double &t) const; + /// Outputs the bounding box of the glyph in the atlas + void getQuadAtlasBounds(double &l, double &b, double &r, double &t) const; + /// Returns true if the glyph is a whitespace and has no geometry + bool isWhitespace() const; + /// Simplifies to GlyphBox + operator GlyphBox() const; + +private: + int index; + unicode_t codepoint; + double geometryScale; + msdfgen::Shape shape; + msdfgen::Shape::Bounds bounds; + double advance; + struct { + struct { + int x, y, w, h; + } rect; + double range; + double scale; + msdfgen::Vector2 translate; + } box; + +}; + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/ImmediateAtlasGenerator.h b/Nuake/src/Vendors/msdf-atlas-gen/ImmediateAtlasGenerator.h new file mode 100644 index 00000000..c627ab63 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/ImmediateAtlasGenerator.h @@ -0,0 +1,45 @@ + +#pragma once + +#include +#include "GlyphBox.h" +#include "Workload.h" +#include "AtlasGenerator.h" + +namespace msdf_atlas { + +/** + * An implementation of AtlasGenerator that uses the specified generator function + * and AtlasStorage class and generates glyph bitmaps immediately + * (does not return until all submitted work is finished), + * but may use multiple threads (setThreadCount). + */ +template GEN_FN, class AtlasStorage> +class ImmediateAtlasGenerator { + +public: + ImmediateAtlasGenerator(); + ImmediateAtlasGenerator(int width, int height); + void generate(const GlyphGeometry *glyphs, int count); + void rearrange(int width, int height, const Remap *remapping, int count); + void resize(int width, int height); + /// Sets attributes for the generator function + void setAttributes(const GeneratorAttributes &attributes); + /// Sets the number of threads to be run by generate + void setThreadCount(int threadCount); + /// Allows access to the underlying AtlasStorage + const AtlasStorage & atlasStorage() const; + +private: + AtlasStorage storage; + std::vector layout; + std::vector glyphBuffer; + std::vector errorCorrectionBuffer; + GeneratorAttributes attributes; + int threadCount; + +}; + +} + +#include "ImmediateAtlasGenerator.hpp" diff --git a/Nuake/src/Vendors/msdf-atlas-gen/ImmediateAtlasGenerator.hpp b/Nuake/src/Vendors/msdf-atlas-gen/ImmediateAtlasGenerator.hpp new file mode 100644 index 00000000..67351de9 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/ImmediateAtlasGenerator.hpp @@ -0,0 +1,77 @@ + +#include "ImmediateAtlasGenerator.h" + +#include + +namespace msdf_atlas { + +template GEN_FN, class AtlasStorage> +ImmediateAtlasGenerator::ImmediateAtlasGenerator() : threadCount(1) { } + +template GEN_FN, class AtlasStorage> +ImmediateAtlasGenerator::ImmediateAtlasGenerator(int width, int height) : storage(width, height), threadCount(1) { } + +template GEN_FN, class AtlasStorage> +void ImmediateAtlasGenerator::generate(const GlyphGeometry *glyphs, int count) { + int maxBoxArea = 0; + for (int i = 0; i < count; ++i) { + GlyphBox box = glyphs[i]; + maxBoxArea = std::max(maxBoxArea, box.rect.w*box.rect.h); + layout.push_back((GlyphBox &&) box); + } + int threadBufferSize = N*maxBoxArea; + if (threadCount*threadBufferSize > (int) glyphBuffer.size()) + glyphBuffer.resize(threadCount*threadBufferSize); + if (threadCount*maxBoxArea > (int) errorCorrectionBuffer.size()) + errorCorrectionBuffer.resize(threadCount*maxBoxArea); + std::vector threadAttributes(threadCount); + for (int i = 0; i < threadCount; ++i) { + threadAttributes[i] = attributes; + threadAttributes[i].config.errorCorrection.buffer = errorCorrectionBuffer.data()+i*maxBoxArea; + } + + Workload([this, glyphs, &threadAttributes, threadBufferSize](int i, int threadNo) -> bool { + const GlyphGeometry &glyph = glyphs[i]; + if (!glyph.isWhitespace()) { + int l, b, w, h; + glyph.getBoxRect(l, b, w, h); + msdfgen::BitmapRef glyphBitmap(glyphBuffer.data()+threadNo*threadBufferSize, w, h); + GEN_FN(glyphBitmap, glyph, threadAttributes[threadNo]); + storage.put(l, b, msdfgen::BitmapConstRef(glyphBitmap)); + } + return true; + }, count).finish(threadCount); +} + +template GEN_FN, class AtlasStorage> +void ImmediateAtlasGenerator::rearrange(int width, int height, const Remap *remapping, int count) { + for (int i = 0; i < count; ++i) { + layout[remapping[i].index].rect.x = remapping[i].target.x; + layout[remapping[i].index].rect.y = remapping[i].target.y; + } + AtlasStorage newStorage((AtlasStorage &&) storage, width, height, remapping, count); + storage = (AtlasStorage &&) newStorage; +} + +template GEN_FN, class AtlasStorage> +void ImmediateAtlasGenerator::resize(int width, int height) { + AtlasStorage newStorage((AtlasStorage &&) storage, width, height); + storage = (AtlasStorage &&) newStorage; +} + +template GEN_FN, class AtlasStorage> +void ImmediateAtlasGenerator::setAttributes(const GeneratorAttributes &attributes) { + this->attributes = attributes; +} + +template GEN_FN, class AtlasStorage> +void ImmediateAtlasGenerator::setThreadCount(int threadCount) { + this->threadCount = threadCount; +} + +template GEN_FN, class AtlasStorage> +const AtlasStorage & ImmediateAtlasGenerator::atlasStorage() const { + return storage; +} + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/LICENSE.txt b/Nuake/src/Vendors/msdf-atlas-gen/LICENSE.txt new file mode 100644 index 00000000..83edc597 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 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 +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Nuake/src/Vendors/msdf-atlas-gen/README.md b/Nuake/src/Vendors/msdf-atlas-gen/README.md new file mode 100644 index 00000000..c8c54ef6 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/README.md @@ -0,0 +1,142 @@ + +# Multi-channel signed distance field atlas generator + +This is a utility for generating compact font atlases using [MSDFgen](https://github.com/Chlumsky/msdfgen). + +The atlas generator loads a subset of glyphs from a TTF or OTF font file, generates a distance field for each of them, and tightly packs them into an atlas bitmap (example below). The finished atlas and/or its layout metadata can be exported as an [Artery Font](https://github.com/Chlumsky/artery-font-format) file, a plain image file, a CSV sheet or a structured JSON file. + +![Atlas example](https://user-images.githubusercontent.com/18639794/76163889-811f2e80-614a-11ea-9b28-1eed54dbb899.png) + +A font atlas is typically stored in texture memory and used to draw text in real-time rendering contexts such as video games. + +- See what's new in the [changelog](CHANGELOG.md). + +## Atlas types + +The atlas generator can generate the following six types of atlases. + +| |Hard mask|Soft mask|SDF|PSDF|MSDF|MTSDF| +|-|-|-|-|-|-|-| +| |![Hard mask](https://user-images.githubusercontent.com/18639794/76163903-9eec9380-614a-11ea-92cb-d49485bbad31.png)|![Soft mask](https://user-images.githubusercontent.com/18639794/76163904-a1e78400-614a-11ea-912a-b220fed081cb.png)|![SDF](https://user-images.githubusercontent.com/18639794/76163905-a4e27480-614a-11ea-93eb-c80819a44e6e.png)|![PSDF](https://user-images.githubusercontent.com/18639794/76163907-a6ac3800-614a-11ea-8d97-dafc1db6711d.png)|![MSDF](https://user-images.githubusercontent.com/18639794/76163909-a9a72880-614a-11ea-9726-e825ee0dde94.png)|![MTSDF](https://user-images.githubusercontent.com/18639794/76163910-ac098280-614a-11ea-8b6b-811d864cd584.png)| +|Channels:|1 (1-bit)|1|1|1|3|4| +|Anti-aliasing:|-|Yes|Yes|Yes|Yes|Yes| +|Scalability:|-|-|Yes|Yes|Yes|Yes| +|Sharp corners:|-|-|-|-|Yes|Yes| +|Soft effects:|-|-|Yes|-|-|Yes| +|Hard effects:|-|-|-|Yes|Yes|Yes| + +Notes: +- *Sharp corners* refers to preservation of corner sharpness when upscaled. +- *Soft effects* refers to the support of effects that use true distance, such as glows, rounded borders, or simplified shadows. +- *Hard effects* refers to the support of effects that use pseudo-distance, such as mitered borders or thickness adjustment. + +## Getting started + +This project can be used either as a library or as a standalone console program. +To start using the program immediately, there is a Windows binary available for download in the ["Releases" section](https://github.com/Chlumsky/msdf-atlas-gen/releases). +To build the project, you may use the included [Visual Studio solution](msdf-atlas-gen.sln) or the [Unix Makefile](Makefile). + +## Command line arguments + +Use the following command line arguments for the standalone version of the atlas generator. + +### Input + +- `-font ` (required) – sets the input font file. +- `-charset ` – sets the character set. The ASCII charset will be used if not specified. See [the syntax specification](#character-set-specification-syntax) of `charset.txt`. +- `-glyphset ` – sets the set of input glyphs using their indices within the font file. See [the syntax specification](#glyph-set-specification). +- `-fontscale ` – applies a scaling transformation to the font's glyphs. Mainly to be used to generate multiple sizes in a single atlas, otherwise use [`-size`](#glyph-configuration). +- `-fontname ` – sets a name for the font that will be stored in certain output files as metadata. +- `-and` – separates multiple inputs to be combined into a single atlas. + +### Bitmap atlas type + +`-type ` – see [Atlas types](#atlas-types) + +`` can be one of: + +- `hardmask` – a non-anti-aliased binary image +- `softmask` – an anti-aliased image +- `sdf` – a true signed distance field (SDF) +- `psdf` – a pseudo-distance field +- `msdf` (default) – a multi-channel signed distance field (MSDF) +- `mtsdf` – a combination of MSDF and true SDF in the alpha channel + +### Atlas image format + +`-format ` + +`` can be one of: + +- `png` – a compressed PNG image +- `bmp` – an uncompressed BMP image +- `tiff` – an uncompressed floating-point TIFF image +- `text` – a sequence of pixel values in plain text +- `textfloat` – a sequence of floating-point pixel values in plain text +- `bin` – a sequence of pixel values encoded as raw bytes of data +- `binfloat` – a sequence of pixel values encoded as raw 32-bit floating-point values + +### Atlas dimensions + +`-dimensions ` – sets fixed atlas dimensions + +Alternativelly, the minimum possible dimensions may be selected automatically if a dimensions constraint is set instead: + +- `-pots` – a power-of-two square +- `-potr` – a power-of-two square or rectangle (2:1) +- `-square` – any square dimensions +- `-square2` – square with even side length +- `-square4` (default) – square with side length divisible by four + +### Outputs + +Any non-empty subset of the following may be specified: + +- `-imageout ` – saves the atlas bitmap as a plain image file. Format matches `-format` +- `-json ` – writes the atlas's layout data as well as other metrics into a structured JSON file +- `-csv ` – writes the glyph layout data into a simple CSV file +- `-arfont ` – saves the atlas and its layout data as an [Artery Font](https://github.com/Chlumsky/artery-font-format) file +- `-shadronpreview ` – generates a [Shadron script](https://www.arteryengine.com/shadron/) that uses the generated atlas to draw a sample text as a preview + +### Glyph configuration + +- `-size ` – sets the size of the glyphs in the atlas in pixels per EM +- `-minsize ` – sets the minimum size. The largest possible size that fits the same atlas dimensions will be used +- `-emrange ` – sets the distance field range in EM's +- `-pxrange ` (default = 2) – sets the distance field range in output pixels + +### Distance field generator settings + +- `-angle ` – sets the minimum angle between adjacent edges to be considered a corner. Append D for degrees (`msdf` / `mtsdf` only) +- `-coloringstrategy ` – selects the edge coloring heuristic (`msdf` / `mtsdf` only) +- `-errorcorrection ` – selects the error correction algorithm. Use `help` as mode for more information (`msdf` / `mtsdf` only) +- `-miterlimit ` – sets the miter limit that limits the extension of each glyph's bounding box due to very sharp corners (`psdf` / `msdf` / `mtsdf` only) +- `-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 +- `-seed ` – sets the initial seed for the edge coloring heuristic +- `-threads ` – sets the number of threads for the parallel computation (0 = auto) + +Use `-help` for an exhaustive list of options. + +## Character set specification syntax + +The character set file is a text file with UTF-8 or ASCII encoding. +The characters can be denoted in the following ways: + +- Single character: `'A'` (UTF-8 encoded), `65` (decimal Unicode), `0x41` (hexadecimal Unicode) +- Range of characters: `['A', 'Z']`, `[65, 90]`, `[0x41, 0x5a]` +- String of characters: `"ABCDEFGHIJKLMNOPQRSTUVWXYZ"` (UTF-8 encoded) + +The entries should be separated by commas or whitespace. +In between quotation marks, backslash is used as the escape character (e.g. `'\''`, `'\\'`, `"!\"#"`). +The order in which characters appear is not taken into consideration. + +Additionally, the include directive can be used to include other charset files and combine character sets in a hierarchical way. +It must be written on a separate line: + +`@include "base-charset.txt"` + +### Glyph set specification + +The syntax of the glyph set specification is mostly the same as that of a character set, but only numeric values (decimal and hexadecimal) are allowed. diff --git a/Nuake/src/Vendors/msdf-atlas-gen/Rectangle.h b/Nuake/src/Vendors/msdf-atlas-gen/Rectangle.h new file mode 100644 index 00000000..8221be42 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/Rectangle.h @@ -0,0 +1,14 @@ + +#pragma once + +namespace msdf_atlas { + +struct Rectangle { + int x, y, w, h; +}; + +struct OrientedRectangle : Rectangle { + bool rotated; +}; + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/RectanglePacker.cpp b/Nuake/src/Vendors/msdf-atlas-gen/RectanglePacker.cpp new file mode 100644 index 00000000..b85d6975 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/RectanglePacker.cpp @@ -0,0 +1,143 @@ + +#include "RectanglePacker.h" + +#include + +namespace msdf_atlas { + +#define WORST_FIT 0x7fffffff + +template +static void removeFromUnorderedVector(std::vector &vector, size_t index) { + if (index != vector.size()-1) + std::swap(vector[index], vector.back()); + vector.pop_back(); +} + +int RectanglePacker::rateFit(int w, int h, int sw, int sh) { + return std::min(sw-w, sh-h); +} + +RectanglePacker::RectanglePacker() : RectanglePacker(0, 0) { } + +RectanglePacker::RectanglePacker(int width, int height) { + if (width > 0 && height > 0) + spaces.push_back(Rectangle { 0, 0, width, height }); +} + +void RectanglePacker::splitSpace(int index, int w, int h) { + Rectangle space = spaces[index]; + removeFromUnorderedVector(spaces, index); + Rectangle a = { space.x, space.y+h, w, space.h-h }; + Rectangle b = { space.x+w, space.y, space.w-w, h }; + if (w*(space.h-h) <= h*(space.w-w)) + a.w = space.w; + else + b.h = space.h; + if (a.w > 0 && a.h > 0) + spaces.push_back(a); + if (b.w > 0 && b.h > 0) + spaces.push_back(b); +} + +int RectanglePacker::pack(Rectangle *rectangles, int count) { + std::vector remainingRects(count); + for (int i = 0; i < count; ++i) + remainingRects[i] = i; + while (!remainingRects.empty()) { + int bestFit = WORST_FIT; + int bestSpace = -1; + int bestRect = -1; + for (size_t i = 0; i < spaces.size(); ++i) { + const Rectangle &space = spaces[i]; + for (size_t j = 0; j < remainingRects.size(); ++j) { + const Rectangle &rect = rectangles[remainingRects[j]]; + if (rect.w == space.w && rect.h == space.h) { + bestSpace = i; + bestRect = j; + goto BEST_FIT_FOUND; + } + if (rect.w <= space.w && rect.h <= space.h) { + int fit = rateFit(rect.w, rect.h, space.w, space.h); + if (fit < bestFit) { + bestSpace = i; + bestRect = j; + bestFit = fit; + } + } + } + } + if (bestSpace < 0 || bestRect < 0) + break; + BEST_FIT_FOUND: + Rectangle &rect = rectangles[remainingRects[bestRect]]; + rect.x = spaces[bestSpace].x; + rect.y = spaces[bestSpace].y; + splitSpace(bestSpace, rect.w, rect.h); + removeFromUnorderedVector(remainingRects, bestRect); + } + return (int) remainingRects.size(); +} + +int RectanglePacker::pack(OrientedRectangle *rectangles, int count) { + std::vector remainingRects(count); + for (int i = 0; i < count; ++i) + remainingRects[i] = i; + while (!remainingRects.empty()) { + int bestFit = WORST_FIT; + int bestSpace = -1; + int bestRect = -1; + bool bestRotated = false; + for (size_t i = 0; i < spaces.size(); ++i) { + const Rectangle &space = spaces[i]; + for (size_t j = 0; j < remainingRects.size(); ++j) { + const OrientedRectangle &rect = rectangles[remainingRects[j]]; + if (rect.w == space.w && rect.h == space.h) { + bestSpace = i; + bestRect = j; + bestRotated = false; + goto BEST_FIT_FOUND; + } + if (rect.h == space.w && rect.w == space.h) { + bestSpace = i; + bestRect = j; + bestRotated = true; + goto BEST_FIT_FOUND; + } + if (rect.w <= space.w && rect.h <= space.h) { + int fit = rateFit(rect.w, rect.h, space.w, space.h); + if (fit < bestFit) { + bestSpace = i; + bestRect = j; + bestRotated = false; + bestFit = fit; + } + } + if (rect.h <= space.w && rect.w <= space.h) { + int fit = rateFit(rect.h, rect.w, space.w, space.h); + if (fit < bestFit) { + bestSpace = i; + bestRect = j; + bestRotated = true; + bestFit = fit; + } + } + } + } + if (bestSpace < 0 || bestRect < 0) + break; + BEST_FIT_FOUND: + OrientedRectangle &rect = rectangles[remainingRects[bestRect]]; + rect.x = spaces[bestSpace].x; + rect.y = spaces[bestSpace].y; + rect.rotated = bestRotated; + if (bestRotated) + splitSpace(bestSpace, rect.h, rect.w); + else + splitSpace(bestSpace, rect.w, rect.h); + removeFromUnorderedVector(remainingRects, bestRect); + } + return (int) remainingRects.size(); +} + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/RectanglePacker.h b/Nuake/src/Vendors/msdf-atlas-gen/RectanglePacker.h new file mode 100644 index 00000000..10e4c194 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/RectanglePacker.h @@ -0,0 +1,28 @@ + +#pragma once + +#include +#include "Rectangle.h" + +namespace msdf_atlas { + +/// Guillotine 2D single bin packer +class RectanglePacker { + +public: + RectanglePacker(); + RectanglePacker(int width, int height); + /// Packs the rectangle array, returns how many didn't fit (0 on success) + int pack(Rectangle *rectangles, int count); + int pack(OrientedRectangle *rectangles, int count); + +private: + std::vector spaces; + + static int rateFit(int w, int h, int sw, int sh); + + void splitSpace(int index, int w, int h); + +}; + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/Remap.h b/Nuake/src/Vendors/msdf-atlas-gen/Remap.h new file mode 100644 index 00000000..4a698896 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/Remap.h @@ -0,0 +1,15 @@ + +#pragma once + +namespace msdf_atlas { + +/// Represents the repositioning of a subsection of the atlas +struct Remap { + int index; + struct { + int x, y; + } source, target; + int width, height; +}; + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/TightAtlasPacker.cpp b/Nuake/src/Vendors/msdf-atlas-gen/TightAtlasPacker.cpp new file mode 100644 index 00000000..5c623eac --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/TightAtlasPacker.cpp @@ -0,0 +1,168 @@ + +#include "TightAtlasPacker.h" + +#include +#include "Rectangle.h" +#include "rectangle-packing.h" +#include "size-selectors.h" + +namespace msdf_atlas { + +int TightAtlasPacker::tryPack(GlyphGeometry *glyphs, int count, DimensionsConstraint dimensionsConstraint, int &width, int &height, int padding, double scale, double range, double miterLimit) { + // Wrap glyphs into boxes + std::vector rectangles; + std::vector rectangleGlyphs; + rectangles.reserve(count); + rectangleGlyphs.reserve(count); + for (GlyphGeometry *glyph = glyphs, *end = glyphs+count; glyph < end; ++glyph) { + if (!glyph->isWhitespace()) { + Rectangle rect = { }; + glyph->wrapBox(scale, range, miterLimit); + glyph->getBoxSize(rect.w, rect.h); + if (rect.w > 0 && rect.h > 0) { + rectangles.push_back(rect); + rectangleGlyphs.push_back(glyph); + } + } + } + // No non-zero size boxes? + if (rectangles.empty()) { + if (width < 0 || height < 0) + width = 0, height = 0; + return 0; + } + // Box rectangle packing + if (width < 0 || height < 0) { + std::pair dimensions = std::make_pair(width, height); + switch (dimensionsConstraint) { + case DimensionsConstraint::POWER_OF_TWO_SQUARE: + dimensions = packRectangles(rectangles.data(), rectangles.size(), padding); + break; + case DimensionsConstraint::POWER_OF_TWO_RECTANGLE: + dimensions = packRectangles(rectangles.data(), rectangles.size(), padding); + break; + case DimensionsConstraint::MULTIPLE_OF_FOUR_SQUARE: + dimensions = packRectangles >(rectangles.data(), rectangles.size(), padding); + break; + case DimensionsConstraint::EVEN_SQUARE: + dimensions = packRectangles >(rectangles.data(), rectangles.size(), padding); + break; + case DimensionsConstraint::SQUARE: + dimensions = packRectangles >(rectangles.data(), rectangles.size(), padding); + break; + } + if (!(dimensions.first > 0 && dimensions.second > 0)) + return -1; + width = dimensions.first, height = dimensions.second; + } else { + if (int result = packRectangles(rectangles.data(), rectangles.size(), width, height, padding)) + return result; + } + // Set glyph box placement + for (size_t i = 0; i < rectangles.size(); ++i) + rectangleGlyphs[i]->placeBox(rectangles[i].x, height-(rectangles[i].y+rectangles[i].h)); + return 0; +} + +double TightAtlasPacker::packAndScale(GlyphGeometry *glyphs, int count, int width, int height, int padding, double unitRange, double pxRange, double miterLimit, double tolerance) { + bool lastResult = false; + #define TRY_PACK(scale) (lastResult = !tryPack(glyphs, count, DimensionsConstraint(), width, height, padding, (scale), unitRange+pxRange/(scale), miterLimit)) + double minScale = 1, maxScale = 1; + if (TRY_PACK(1)) { + while (maxScale < 1e+32 && ((maxScale = 2*minScale), TRY_PACK(maxScale))) + minScale = maxScale; + } else { + while (minScale > 1e-32 && ((minScale = .5*maxScale), !TRY_PACK(minScale))) + maxScale = minScale; + } + if (minScale == maxScale) + return 0; + while (minScale/maxScale < 1-tolerance) { + double midScale = .5*(minScale+maxScale); + if (TRY_PACK(midScale)) + minScale = midScale; + else + maxScale = midScale; + } + if (!lastResult) + TRY_PACK(minScale); + return minScale; +} + +TightAtlasPacker::TightAtlasPacker() : + width(-1), height(-1), + padding(0), + dimensionsConstraint(DimensionsConstraint::POWER_OF_TWO_SQUARE), + scale(-1), + minScale(1), + unitRange(0), + pxRange(0), + miterLimit(0), + scaleMaximizationTolerance(.001) +{ } + +int TightAtlasPacker::pack(GlyphGeometry *glyphs, int count) { + double initialScale = scale > 0 ? scale : minScale; + if (initialScale > 0) { + if (int remaining = tryPack(glyphs, count, dimensionsConstraint, width, height, padding, initialScale, unitRange+pxRange/initialScale, miterLimit)) + return remaining; + } else if (width < 0 || height < 0) + return -1; + if (scale <= 0) + scale = packAndScale(glyphs, count, width, height, padding, unitRange, pxRange, miterLimit, scaleMaximizationTolerance); + if (scale <= 0) + return -1; + pxRange += scale*unitRange; + unitRange = 0; + return 0; +} + +void TightAtlasPacker::setDimensions(int width, int height) { + this->width = width, this->height = height; +} + +void TightAtlasPacker::unsetDimensions() { + width = -1, height = -1; +} + +void TightAtlasPacker::setDimensionsConstraint(DimensionsConstraint dimensionsConstraint) { + this->dimensionsConstraint = dimensionsConstraint; +} + +void TightAtlasPacker::setPadding(int padding) { + this->padding = padding; +} + +void TightAtlasPacker::setScale(double scale) { + this->scale = scale; +} + +void TightAtlasPacker::setMinimumScale(double minScale) { + this->minScale = minScale; +} + +void TightAtlasPacker::setUnitRange(double unitRange) { + this->unitRange = unitRange; +} + +void TightAtlasPacker::setPixelRange(double pxRange) { + this->pxRange = pxRange; +} + +void TightAtlasPacker::setMiterLimit(double miterLimit) { + this->miterLimit = miterLimit; +} + +void TightAtlasPacker::getDimensions(int &width, int &height) const { + width = this->width, height = this->height; +} + +double TightAtlasPacker::getScale() const { + return scale; +} + +double TightAtlasPacker::getPixelRange() const { + return pxRange; +} + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/TightAtlasPacker.h b/Nuake/src/Vendors/msdf-atlas-gen/TightAtlasPacker.h new file mode 100644 index 00000000..35bb2a97 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/TightAtlasPacker.h @@ -0,0 +1,71 @@ + +#pragma once + +#include "GlyphGeometry.h" + +namespace msdf_atlas { + +/** + * This class computes the layout of a static atlas and may optionally + * also find the minimum required dimensions and/or the maximum glyph scale + */ +class TightAtlasPacker { + +public: + /// Constraints for the atlas's dimensions - see size selectors for more info + enum class DimensionsConstraint { + POWER_OF_TWO_SQUARE, + POWER_OF_TWO_RECTANGLE, + MULTIPLE_OF_FOUR_SQUARE, + EVEN_SQUARE, + SQUARE + }; + + TightAtlasPacker(); + + /// Computes the layout for the array of glyphs. Returns 0 on success + int pack(GlyphGeometry *glyphs, int count); + + /// Sets the atlas's dimensions to be fixed + void setDimensions(int width, int height); + /// Sets the atlas's dimensions to be determined during pack + void unsetDimensions(); + /// Sets the constraint to be used when determining dimensions + void setDimensionsConstraint(DimensionsConstraint dimensionsConstraint); + /// Sets the padding between glyph boxes + void setPadding(int padding); + /// Sets fixed glyph scale + void setScale(double scale); + /// Sets the minimum glyph scale + void setMinimumScale(double minScale); + /// Sets the unit component of the total distance range + void setUnitRange(double unitRange); + /// Sets the pixel component of the total distance range + void setPixelRange(double pxRange); + /// Sets the miter limit for bounds computation + void setMiterLimit(double miterLimit); + + /// Outputs the atlas's final dimensions + void getDimensions(int &width, int &height) const; + /// Returns the final glyph scale + double getScale() const; + /// Returns the final combined pixel range (including converted unit range) + double getPixelRange() const; + +private: + int width, height; + int padding; + DimensionsConstraint dimensionsConstraint; + double scale; + double minScale; + double unitRange; + double pxRange; + double miterLimit; + double scaleMaximizationTolerance; + + static int tryPack(GlyphGeometry *glyphs, int count, DimensionsConstraint dimensionsConstraint, int &width, int &height, int padding, double scale, double range, double miterLimit); + static double packAndScale(GlyphGeometry *glyphs, int count, int width, int height, int padding, double unitRange, double pxRange, double miterLimit, double tolerance); + +}; + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/Workload.cpp b/Nuake/src/Vendors/msdf-atlas-gen/Workload.cpp new file mode 100644 index 00000000..6050bd0c --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/Workload.cpp @@ -0,0 +1,50 @@ + +#include "Workload.h" + +#include +#include +#include +#include + +namespace msdf_atlas { + +Workload::Workload() : chunks(0) { } + +Workload::Workload(const std::function &workerFunction, int chunks) : workerFunction(workerFunction), chunks(chunks) { } + +bool Workload::finishSequential() { + for (int i = 0; i < chunks; ++i) + if (!workerFunction(i, 0)) + return false; + return true; +} + +bool Workload::finishParallel(int threadCount) { + bool result = true; + std::atomic next(0); + std::function threadWorker = [this, &result, &next](int threadNo) { + for (int i = next++; result && i < chunks; i = next++) { + if (!workerFunction(i, threadNo)) + result = false; + } + }; + std::vector threads; + threads.reserve(threadCount); + for (int i = 0; i < threadCount; ++i) + threads.emplace_back(threadWorker, i); + for (std::thread &thread : threads) + thread.join(); + return result; +} + +bool Workload::finish(int threadCount) { + if (!chunks) + return true; + if (threadCount == 1 || chunks == 1) + return finishSequential(); + if (threadCount > 1) + return finishParallel(std::min(threadCount, chunks)); + return false; +} + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/Workload.h b/Nuake/src/Vendors/msdf-atlas-gen/Workload.h new file mode 100644 index 00000000..cee0c9d7 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/Workload.h @@ -0,0 +1,32 @@ + +#pragma once + +#include + +namespace msdf_atlas { + +/** + * This function allows to split a workload into multiple threads. + * The worker function: + * bool FN(int chunk, int threadNo); + * should process the given chunk (out of chunks) and return true. + * If false is returned, the process is interrupted. + */ +class Workload { + +public: + Workload(); + Workload(const std::function &workerFunction, int chunks); + /// Runs the process and returns true if all chunks have been processed + bool finish(int threadCount); + +private: + std::function workerFunction; + int chunks; + + bool finishSequential(); + bool finishParallel(int threadCount); + +}; + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/artery-font-export.cpp b/Nuake/src/Vendors/msdf-atlas-gen/artery-font-export.cpp new file mode 100644 index 00000000..04a3f0ae --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/artery-font-export.cpp @@ -0,0 +1,191 @@ + +#include "artery-font-export.h" + +/*#include +#include +#include "GlyphGeometry.h" +#include "image-encode.h" + +namespace msdf_atlas { + +static artery_font::ImageType convertImageType(ImageType imageType) { + switch (imageType) { + case ImageType::HARD_MASK: + case ImageType::SOFT_MASK: + return artery_font::IMAGE_LINEAR_MASK; + case ImageType::SDF: + return artery_font::IMAGE_SDF; + case ImageType::PSDF: + return artery_font::IMAGE_PSDF; + case ImageType::MSDF: + return artery_font::IMAGE_MSDF; + case ImageType::MTSDF: + return artery_font::IMAGE_MTSDF; + } + return artery_font::IMAGE_NONE; +} + +static artery_font::CodepointType convertCodepointType(GlyphIdentifierType glyphIdentifierType) { + switch (glyphIdentifierType) { + case GlyphIdentifierType::GLYPH_INDEX: + return artery_font::CP_INDEXED; + case GlyphIdentifierType::UNICODE_CODEPOINT: + return artery_font::CP_UNICODE; + } + return artery_font::CP_UNSPECIFIED; +} + +template +static bool encodeTiff(std::vector &output, const msdfgen::BitmapConstRef &atlas) { + // TODO + return false; +} + +template +static artery_font::PixelFormat getPixelFormat(); + +template <> +artery_font::PixelFormat getPixelFormat() { + return artery_font::PIXEL_UNSIGNED8; +} +template <> +artery_font::PixelFormat getPixelFormat() { + return artery_font::PIXEL_FLOAT32; +} + +template +bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties) { + artery_font::StdArteryFont arfont = { }; + arfont.metadataFormat = artery_font::METADATA_NONE; + + for (int i = 0; i < fontCount; ++i) { + const FontGeometry &font = fonts[i]; + GlyphIdentifierType identifierType = font.getPreferredIdentifierType(); + const msdfgen::FontMetrics &fontMetrics = font.getMetrics(); + artery_font::StdFontVariant fontVariant = { }; + fontVariant.codepointType = convertCodepointType(identifierType); + fontVariant.imageType = convertImageType(properties.imageType); + fontVariant.metrics.fontSize = REAL(properties.fontSize*fontMetrics.emSize); + if (properties.imageType != ImageType::HARD_MASK) + fontVariant.metrics.distanceRange = REAL(properties.pxRange); + fontVariant.metrics.emSize = REAL(fontMetrics.emSize); + fontVariant.metrics.ascender = REAL(fontMetrics.ascenderY); + fontVariant.metrics.descender = REAL(fontMetrics.descenderY); + fontVariant.metrics.lineHeight = REAL(fontMetrics.lineHeight); + fontVariant.metrics.underlineY = REAL(fontMetrics.underlineY); + fontVariant.metrics.underlineThickness = REAL(fontMetrics.underlineThickness); + const char *name = font.getName(); + if (name) + fontVariant.name.string = name; + fontVariant.glyphs = artery_font::StdList >(font.getGlyphs().size()); + int j = 0; + for (const GlyphGeometry &glyphGeom : font.getGlyphs()) { + artery_font::Glyph &glyph = fontVariant.glyphs[j++]; + glyph.codepoint = glyphGeom.getIdentifier(identifierType); + glyph.image = 0; + double l, b, r, t; + glyphGeom.getQuadPlaneBounds(l, b, r, t); + glyph.planeBounds.l = REAL(l); + glyph.planeBounds.b = REAL(b); + glyph.planeBounds.r = REAL(r); + glyph.planeBounds.t = REAL(t); + glyphGeom.getQuadAtlasBounds(l, b, r, t); + glyph.imageBounds.l = REAL(l); + glyph.imageBounds.b = REAL(b); + glyph.imageBounds.r = REAL(r); + glyph.imageBounds.t = REAL(t); + glyph.advance.h = REAL(glyphGeom.getAdvance()); + glyph.advance.v = REAL(0); + } + switch (identifierType) { + case GlyphIdentifierType::GLYPH_INDEX: + for (const std::pair, double> &elem : font.getKerning()) { + artery_font::KernPair kernPair = { }; + kernPair.codepoint1 = elem.first.first; + kernPair.codepoint2 = elem.first.second; + kernPair.advance.h = REAL(elem.second); + fontVariant.kernPairs.vector.push_back((artery_font::KernPair &&) kernPair); + } + break; + case GlyphIdentifierType::UNICODE_CODEPOINT: + for (const std::pair, double> &elem : font.getKerning()) { + const GlyphGeometry *glyph1 = font.getGlyph(msdfgen::GlyphIndex(elem.first.first)); + const GlyphGeometry *glyph2 = font.getGlyph(msdfgen::GlyphIndex(elem.first.second)); + if (glyph1 && glyph2 && glyph1->getCodepoint() && glyph2->getCodepoint()) { + artery_font::KernPair kernPair = { }; + kernPair.codepoint1 = glyph1->getCodepoint(); + kernPair.codepoint2 = glyph2->getCodepoint(); + kernPair.advance.h = REAL(elem.second); + fontVariant.kernPairs.vector.push_back((artery_font::KernPair &&) kernPair); + } + } + break; + } + arfont.variants.vector.push_back((artery_font::StdFontVariant &&) fontVariant); + } + + { + artery_font::StdImage image = { }; + image.width = atlas.width; + image.height = atlas.height; + image.channels = N; + image.imageType = convertImageType(properties.imageType); + switch (properties.imageFormat) { + case ImageFormat::PNG: + image.encoding = artery_font::IMAGE_PNG; + image.pixelFormat = artery_font::PIXEL_UNSIGNED8; + if (!encodePng(image.data.vector, atlas)) + return false; + break; + case ImageFormat::TIFF: + image.encoding = artery_font::IMAGE_TIFF; + image.pixelFormat = artery_font::PIXEL_FLOAT32; + if (!encodeTiff(image.data.vector, atlas)) + return false; + break; + case ImageFormat::BINARY: + image.pixelFormat = artery_font::PIXEL_UNSIGNED8; + goto BINARY_EITHER; + case ImageFormat::BINARY_FLOAT: + image.pixelFormat = artery_font::PIXEL_FLOAT32; + goto BINARY_EITHER; + BINARY_EITHER: + if (image.pixelFormat != getPixelFormat()) + return false; + image.encoding = artery_font::IMAGE_RAW_BINARY; + image.rawBinaryFormat.rowLength = N*sizeof(T)*atlas.width; + image.data = artery_font::StdByteArray(N*sizeof(T)*atlas.width*atlas.height); + switch (properties.yDirection) { + case YDirection::BOTTOM_UP: + image.rawBinaryFormat.orientation = artery_font::ORIENTATION_BOTTOM_UP; + memcpy((byte *) image.data, atlas.pixels, N*sizeof(T)*atlas.width*atlas.height); + break; + case YDirection::TOP_DOWN: { + image.rawBinaryFormat.orientation = artery_font::ORIENTATION_TOP_DOWN; + byte *imageData = (byte *) image.data; + for (int y = atlas.height-1; y >= 0; --y) { + memcpy(imageData, atlas.pixels+N*atlas.width*y, N*sizeof(T)*atlas.width); + imageData += N*sizeof(T)*atlas.width; + } + break; + } + } + break; + default: + return false; + } + arfont.images.vector.push_back((artery_font::StdImage &&) image); + } + + return artery_font::writeFile(arfont, filename); +} + +template bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties); +template bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties); +template bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties); +template bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties); +template bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties); +template bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties); + +} +*/ diff --git a/Nuake/src/Vendors/msdf-atlas-gen/artery-font-export.h b/Nuake/src/Vendors/msdf-atlas-gen/artery-font-export.h new file mode 100644 index 00000000..593fe5cb --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/artery-font-export.h @@ -0,0 +1,23 @@ + +#pragma once + +#include +#include +#include "types.h" +#include "FontGeometry.h" + +namespace msdf_atlas { + +struct ArteryFontExportProperties { + double fontSize; + double pxRange; + ImageType imageType; + ImageFormat imageFormat; + YDirection yDirection; +}; + +/// Encodes the atlas bitmap and its layout into an Artery Atlas Font file +template +bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties); + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/bitmap-blit.cpp b/Nuake/src/Vendors/msdf-atlas-gen/bitmap-blit.cpp new file mode 100644 index 00000000..c1e6afad --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/bitmap-blit.cpp @@ -0,0 +1,58 @@ + +#include "bitmap-blit.h" + +#include + +namespace msdf_atlas { + +template +void blitSameType(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h) { + for (int y = 0; y < h; ++y) + memcpy(dst(dx, dy+y), src(sx, sy+y), sizeof(T)*N*w); +} + +#define BLIT_SAME_TYPE_IMPL(T, N) void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h) { blitSameType(dst, src, dx, dy, sx, sy, w, h); } + +BLIT_SAME_TYPE_IMPL(byte, 1) +BLIT_SAME_TYPE_IMPL(byte, 3) +BLIT_SAME_TYPE_IMPL(byte, 4) +BLIT_SAME_TYPE_IMPL(float, 1) +BLIT_SAME_TYPE_IMPL(float, 3) +BLIT_SAME_TYPE_IMPL(float, 4) + +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h) { + for (int y = 0; y < h; ++y) { + byte *dstPixel = dst(dx, dy+y); + for (int x = 0; x < w; ++x) { + const float *srcPixel = src(sx+x, sy+y); + *dstPixel++ = msdfgen::pixelFloatToByte(*srcPixel); + } + } +} + +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h) { + for (int y = 0; y < h; ++y) { + byte *dstPixel = dst(dx, dy+y); + for (int x = 0; x < w; ++x) { + const float *srcPixel = src(sx+x, sy+y); + *dstPixel++ = msdfgen::pixelFloatToByte(srcPixel[0]); + *dstPixel++ = msdfgen::pixelFloatToByte(srcPixel[1]); + *dstPixel++ = msdfgen::pixelFloatToByte(srcPixel[2]); + } + } +} + +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h) { + for (int y = 0; y < h; ++y) { + byte *dstPixel = dst(dx, dy+y); + for (int x = 0; x < w; ++x) { + const float *srcPixel = src(sx+x, sy+y); + *dstPixel++ = msdfgen::pixelFloatToByte(srcPixel[0]); + *dstPixel++ = msdfgen::pixelFloatToByte(srcPixel[1]); + *dstPixel++ = msdfgen::pixelFloatToByte(srcPixel[2]); + *dstPixel++ = msdfgen::pixelFloatToByte(srcPixel[3]); + } + } +} + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/bitmap-blit.h b/Nuake/src/Vendors/msdf-atlas-gen/bitmap-blit.h new file mode 100644 index 00000000..b186d120 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/bitmap-blit.h @@ -0,0 +1,26 @@ + +#pragma once + +#include +#include "types.h" + +namespace msdf_atlas { + +/* + * Copies a rectangular section from source bitmap to destination bitmap. + * Width and height are not checked and must not exceed bitmap bounds! + */ + +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h); +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h); +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h); + +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h); +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h); +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h); + +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h); +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h); +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h); + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/charset-parser.cpp b/Nuake/src/Vendors/msdf-atlas-gen/charset-parser.cpp new file mode 100644 index 00000000..23c68a3d --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/charset-parser.cpp @@ -0,0 +1,250 @@ + +#include "Charset.h" + +#include +#include +#include "utf8.h" + +namespace msdf_atlas { + +static char escapedChar(char c) { + switch (c) { + case '0': + return '\0'; + case 'n': case 'N': + return '\n'; + case 'r': case 'R': + return '\r'; + case 's': case 'S': + return ' '; + case 't': case 'T': + return '\t'; + case '\\': case '"': case '\'': + default: + return c; + } +} + +static int readWord(std::string &str, FILE *f) { + while (true) { + int c = fgetc(f); + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_') + str.push_back((char) c); + else + return c; + } +} + +static bool readString(std::string &str, FILE *f, char terminator) { + bool escape = false; + while (true) { + int c = fgetc(f); + if (c < 0) + return false; + if (escape) { + str.push_back(escapedChar((char) c)); + escape = false; + } else { + if (c == terminator) + return true; + else if (c == '\\') + escape = true; + else + str.push_back((char) c); + } + } +} + +static bool parseInt(int &i, const char *str) { + i = 0; + if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) { // hex + str += 2; + for (; *str; ++str) { + if (*str >= '0' && *str <= '9') { + i <<= 4; + i += *str-'0'; + } else if (*str >= 'A' && *str <= 'F') { + i <<= 4; + i += *str-'A'+10; + } else if (*str >= 'a' && *str <= 'f') { + i <<= 4; + i += *str-'a'+10; + } else + return false; + } + } else { // dec + for (; *str; ++str) { + if (*str >= '0' && *str <= '9') { + i *= 10; + i += *str-'0'; + } else + return false; + } + } + return true; +} + +static std::string combinePath(const char *basePath, const char *relPath) { + if (relPath[0] == '/' || (relPath[0] && relPath[1] == ':')) // absolute path? + return relPath; + int lastSlash = -1; + for (int i = 0; basePath[i]; ++i) + if (basePath[i] == '/' || basePath[i] == '\\') + lastSlash = i; + if (lastSlash < 0) + return relPath; + return std::string(basePath, lastSlash+1)+relPath; +} + +bool Charset::load(const char *filename, bool disableCharLiterals) { + + if (FILE *f = fopen(filename, "rb")) { + + enum { + CLEAR, + TIGHT, + RANGE_BRACKET, + RANGE_START, + RANGE_SEPARATOR, + RANGE_END + } state = CLEAR; + + std::string buffer; + std::vector unicodeBuffer; + unicode_t rangeStart = 0; + for (int c = fgetc(f), start = true; c >= 0; start = false) { + switch (c) { + case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': // number + if (!(state == CLEAR || state == RANGE_BRACKET || state == RANGE_SEPARATOR)) + goto FAIL; + buffer.push_back((char) c); + c = readWord(buffer, f); + { + int cp; + if (!parseInt(cp, buffer.c_str())) + goto FAIL; + switch (state) { + case CLEAR: + if (cp >= 0) + add((unicode_t) cp); + state = TIGHT; + break; + case RANGE_BRACKET: + rangeStart = (unicode_t) cp; + state = RANGE_START; + break; + case RANGE_SEPARATOR: + for (unicode_t u = rangeStart; (int) u <= cp; ++u) + add(u); + state = RANGE_END; + break; + default:; + } + } + buffer.clear(); + continue; // next character already read + case '\'': // single UTF-8 character + if (!(state == CLEAR || state == RANGE_BRACKET || state == RANGE_SEPARATOR) || disableCharLiterals) + goto FAIL; + if (!readString(buffer, f, '\'')) + goto FAIL; + utf8Decode(unicodeBuffer, buffer.c_str()); + if (unicodeBuffer.size() == 1) { + switch (state) { + case CLEAR: + if (unicodeBuffer[0] > 0) + add(unicodeBuffer[0]); + state = TIGHT; + break; + case RANGE_BRACKET: + rangeStart = unicodeBuffer[0]; + state = RANGE_START; + break; + case RANGE_SEPARATOR: + for (unicode_t u = rangeStart; u <= unicodeBuffer[0]; ++u) + add(u); + state = RANGE_END; + break; + default:; + } + } else + goto FAIL; + unicodeBuffer.clear(); + buffer.clear(); + break; + case '"': // string of UTF-8 characters + if (state != CLEAR || disableCharLiterals) + goto FAIL; + if (!readString(buffer, f, '"')) + goto FAIL; + utf8Decode(unicodeBuffer, buffer.c_str()); + for (unicode_t cp : unicodeBuffer) + add(cp); + unicodeBuffer.clear(); + buffer.clear(); + state = TIGHT; + break; + case '[': // character range start + if (state != CLEAR) + goto FAIL; + state = RANGE_BRACKET; + break; + case ']': // character range end + if (state == RANGE_END) + state = TIGHT; + else + goto FAIL; + break; + case '@': // annotation + if (state != CLEAR) + goto FAIL; + c = readWord(buffer, f); + if (buffer == "include") { + while (c == ' ' || c == '\t' || c == '\n' || c == '\r') + c = fgetc(f); + if (c != '"') + goto FAIL; + buffer.clear(); + if (!readString(buffer, f, '"')) + goto FAIL; + load(combinePath(filename, buffer.c_str()).c_str()); + state = TIGHT; + } else + goto FAIL; + buffer.clear(); + break; + case ',': case ';': // separator + if (!(state == CLEAR || state == TIGHT)) { + if (state == RANGE_START) + state = RANGE_SEPARATOR; + else + goto FAIL; + } // else treat as whitespace + case ' ': case '\n': case '\r': case '\t': // whitespace + if (state == TIGHT) + state = CLEAR; + break; + case 0xef: // UTF-8 byte order mark + if (start) { + if (!(fgetc(f) == 0xbb && fgetc(f) == 0xbf)) + goto FAIL; + break; + } + default: // unexpected character + goto FAIL; + } + c = fgetc(f); + } + + fclose(f); + return state == CLEAR || state == TIGHT; + + FAIL: + fclose(f); + return false; + } + + return false; +} + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/csv-export.cpp b/Nuake/src/Vendors/msdf-atlas-gen/csv-export.cpp new file mode 100644 index 00000000..a85f9bc3 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/csv-export.cpp @@ -0,0 +1,45 @@ + +#include "csv-export.h" + +#include +#include "GlyphGeometry.h" + +namespace msdf_atlas { + +bool exportCSV(const FontGeometry *fonts, int fontCount, int atlasWidth, int atlasHeight, YDirection yDirection, const char *filename) { + FILE *f = fopen(filename, "w"); + if (!f) + return false; + + for (int i = 0; i < fontCount; ++i) { + for (const GlyphGeometry &glyph : fonts[i].getGlyphs()) { + double l, b, r, t; + if (fontCount > 1) + fprintf(f, "%d,", i); + fprintf(f, "%d,%.17g,", glyph.getIdentifier(fonts[i].getPreferredIdentifierType()), glyph.getAdvance()); + glyph.getQuadPlaneBounds(l, b, r, t); + switch (yDirection) { + case YDirection::BOTTOM_UP: + fprintf(f, "%.17g,%.17g,%.17g,%.17g,", l, b, r, t); + break; + case YDirection::TOP_DOWN: + fprintf(f, "%.17g,%.17g,%.17g,%.17g,", l, -t, r, -b); + break; + } + glyph.getQuadAtlasBounds(l, b, r, t); + switch (yDirection) { + case YDirection::BOTTOM_UP: + fprintf(f, "%.17g,%.17g,%.17g,%.17g\n", l, b, r, t); + break; + case YDirection::TOP_DOWN: + fprintf(f, "%.17g,%.17g,%.17g,%.17g\n", l, atlasHeight-t, r, atlasHeight-b); + break; + } + } + } + + fclose(f); + return true; +} + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/csv-export.h b/Nuake/src/Vendors/msdf-atlas-gen/csv-export.h new file mode 100644 index 00000000..56d5c4c9 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/csv-export.h @@ -0,0 +1,14 @@ + +#pragma once + +#include "FontGeometry.h" + +namespace msdf_atlas { + +/** + * Writes the positioning data and atlas layout of the glyphs into a CSV file + * The columns are: font variant index (if fontCount > 1), glyph identifier (index or Unicode), horizontal advance, plane bounds (l, b, r, t), atlas bounds (l, b, r, t) + */ +bool exportCSV(const FontGeometry *fonts, int fontCount, int atlasWidth, int atlasHeight, YDirection yDirection, const char *filename); + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/glyph-generators.cpp b/Nuake/src/Vendors/msdf-atlas-gen/glyph-generators.cpp new file mode 100644 index 00000000..af4a325e --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/glyph-generators.cpp @@ -0,0 +1,52 @@ + +#include "glyph-generators.h" + +namespace msdf_atlas { + +void scanlineGenerator(const msdfgen::BitmapRef &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs) { + msdfgen::rasterize(output, glyph.getShape(), glyph.getBoxScale(), glyph.getBoxTranslate(), MSDF_ATLAS_GLYPH_FILL_RULE); +} + +void sdfGenerator(const msdfgen::BitmapRef &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs) { + msdfgen::generateSDF(output, glyph.getShape(), glyph.getBoxProjection(), glyph.getBoxRange(), attribs.config); + if (attribs.scanlinePass) + msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxProjection(), MSDF_ATLAS_GLYPH_FILL_RULE); +} + +void psdfGenerator(const msdfgen::BitmapRef &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs) { + msdfgen::generatePseudoSDF(output, glyph.getShape(), glyph.getBoxProjection(), glyph.getBoxRange(), attribs.config); + if (attribs.scanlinePass) + msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxProjection(), MSDF_ATLAS_GLYPH_FILL_RULE); +} + +void msdfGenerator(const msdfgen::BitmapRef &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs) { + msdfgen::MSDFGeneratorConfig config = attribs.config; + if (attribs.scanlinePass) + config.errorCorrection.mode = msdfgen::ErrorCorrectionConfig::DISABLED; + msdfgen::generateMSDF(output, glyph.getShape(), glyph.getBoxProjection(), glyph.getBoxRange(), config); + if (attribs.scanlinePass) { + msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxProjection(), MSDF_ATLAS_GLYPH_FILL_RULE); + if (attribs.config.errorCorrection.mode != msdfgen::ErrorCorrectionConfig::DISABLED) { + config.errorCorrection.mode = attribs.config.errorCorrection.mode; + config.errorCorrection.distanceCheckMode = msdfgen::ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE; + msdfgen::msdfErrorCorrection(output, glyph.getShape(), glyph.getBoxProjection(), glyph.getBoxRange(), config); + } + } +} + +void mtsdfGenerator(const msdfgen::BitmapRef &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs) { + msdfgen::MSDFGeneratorConfig config = attribs.config; + if (attribs.scanlinePass) + config.errorCorrection.mode = msdfgen::ErrorCorrectionConfig::DISABLED; + msdfgen::generateMTSDF(output, glyph.getShape(), glyph.getBoxProjection(), glyph.getBoxRange(), config); + if (attribs.scanlinePass) { + msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxProjection(), MSDF_ATLAS_GLYPH_FILL_RULE); + if (attribs.config.errorCorrection.mode != msdfgen::ErrorCorrectionConfig::DISABLED) { + config.errorCorrection.mode = attribs.config.errorCorrection.mode; + config.errorCorrection.distanceCheckMode = msdfgen::ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE; + msdfgen::msdfErrorCorrection(output, glyph.getShape(), glyph.getBoxProjection(), glyph.getBoxRange(), config); + } + } +} + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/glyph-generators.h b/Nuake/src/Vendors/msdf-atlas-gen/glyph-generators.h new file mode 100644 index 00000000..1df7c24d --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/glyph-generators.h @@ -0,0 +1,25 @@ + +#pragma once + +#include +#include "GlyphGeometry.h" +#include "AtlasGenerator.h" + +#define MSDF_ATLAS_GLYPH_FILL_RULE msdfgen::FILL_NONZERO + +namespace msdf_atlas { + +// Glyph bitmap generator functions + +/// Generates non-anti-aliased binary image of the glyph using scanline rasterization +void scanlineGenerator(const msdfgen::BitmapRef &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs); +/// Generates a true signed distance field of the glyph +void sdfGenerator(const msdfgen::BitmapRef &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs); +/// Generates a signed pseudo-distance field of the glyph +void psdfGenerator(const msdfgen::BitmapRef &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs); +/// Generates a multi-channel signed distance field of the glyph +void msdfGenerator(const msdfgen::BitmapRef &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs); +/// Generates a multi-channel and alpha-encoded true signed distance field of the glyph +void mtsdfGenerator(const msdfgen::BitmapRef &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs); + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/icon.ico b/Nuake/src/Vendors/msdf-atlas-gen/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..09e1a936e1b5df1fa2d35af3986f7b345047d42c GIT binary patch literal 32038 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 + +namespace msdf_atlas { + +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap) { + std::vector pixels(bitmap.width*bitmap.height); + for (int y = 0; y < bitmap.height; ++y) + memcpy(&pixels[bitmap.width*y], bitmap(0, bitmap.height-y-1), bitmap.width); + return !lodepng::encode(output, pixels, bitmap.width, bitmap.height, LCT_GREY); +} + +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap) { + std::vector pixels(3*bitmap.width*bitmap.height); + for (int y = 0; y < bitmap.height; ++y) + memcpy(&pixels[3*bitmap.width*y], bitmap(0, bitmap.height-y-1), 3*bitmap.width); + return !lodepng::encode(output, pixels, bitmap.width, bitmap.height, LCT_RGB); +} + +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap) { + std::vector pixels(4*bitmap.width*bitmap.height); + for (int y = 0; y < bitmap.height; ++y) + memcpy(&pixels[4*bitmap.width*y], bitmap(0, bitmap.height-y-1), 4*bitmap.width); + return !lodepng::encode(output, pixels, bitmap.width, bitmap.height, LCT_RGBA); +} + +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap) { + std::vector pixels(bitmap.width*bitmap.height); + std::vector::iterator it = pixels.begin(); + for (int y = bitmap.height-1; y >= 0; --y) + for (int x = 0; x < bitmap.width; ++x) + *it++ = msdfgen::pixelFloatToByte(*bitmap(x, y)); + return !lodepng::encode(output, pixels, bitmap.width, bitmap.height, LCT_GREY); +} + +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap) { + std::vector pixels(3*bitmap.width*bitmap.height); + std::vector::iterator it = pixels.begin(); + for (int y = bitmap.height-1; y >= 0; --y) + for (int x = 0; x < bitmap.width; ++x) { + *it++ = msdfgen::pixelFloatToByte(bitmap(x, y)[0]); + *it++ = msdfgen::pixelFloatToByte(bitmap(x, y)[1]); + *it++ = msdfgen::pixelFloatToByte(bitmap(x, y)[2]); + } + return !lodepng::encode(output, pixels, bitmap.width, bitmap.height, LCT_RGB); +} + +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap) { + std::vector pixels(4*bitmap.width*bitmap.height); + std::vector::iterator it = pixels.begin(); + for (int y = bitmap.height-1; y >= 0; --y) + for (int x = 0; x < bitmap.width; ++x) { + *it++ = msdfgen::pixelFloatToByte(bitmap(x, y)[0]); + *it++ = msdfgen::pixelFloatToByte(bitmap(x, y)[1]); + *it++ = msdfgen::pixelFloatToByte(bitmap(x, y)[2]); + *it++ = msdfgen::pixelFloatToByte(bitmap(x, y)[3]); + } + return !lodepng::encode(output, pixels, bitmap.width, bitmap.height, LCT_RGBA); +} + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/image-encode.h b/Nuake/src/Vendors/msdf-atlas-gen/image-encode.h new file mode 100644 index 00000000..59a8fc59 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/image-encode.h @@ -0,0 +1,20 @@ + +#pragma once + +#include +#include +#include "types.h" + +namespace msdf_atlas { + +// Functions to encode an image as a sequence of bytes in memory +// Only PNG format available currently + +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap); +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap); +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap); +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap); +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap); +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap); + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/image-save.h b/Nuake/src/Vendors/msdf-atlas-gen/image-save.h new file mode 100644 index 00000000..0a0b37e8 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/image-save.h @@ -0,0 +1,15 @@ + +#pragma once + +#include +#include "types.h" + +namespace msdf_atlas { + +/// Saves the bitmap as an image file with the specified format +template +bool saveImage(const msdfgen::BitmapConstRef &bitmap, ImageFormat format, const char *filename, YDirection outputYDirection = YDirection::BOTTOM_UP); + +} + +#include "image-save.hpp" diff --git a/Nuake/src/Vendors/msdf-atlas-gen/image-save.hpp b/Nuake/src/Vendors/msdf-atlas-gen/image-save.hpp new file mode 100644 index 00000000..861f9830 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/image-save.hpp @@ -0,0 +1,172 @@ + +#include "image-save.h" + +#include +#include + +namespace msdf_atlas { + +template +bool saveImageBinary(const msdfgen::BitmapConstRef &bitmap, const char *filename, YDirection outputYDirection); +template +bool saveImageBinaryLE(const msdfgen::BitmapConstRef &bitmap, const char *filename, YDirection outputYDirection); +template +bool saveImageBinaryBE(const msdfgen::BitmapConstRef &bitmap, const char *filename, YDirection outputYDirection); + +template +bool saveImageText(const msdfgen::BitmapConstRef &bitmap, const char *filename, YDirection outputYDirection); +template +bool saveImageText(const msdfgen::BitmapConstRef &bitmap, const char *filename, YDirection outputYDirection); + +template +bool saveImage(const msdfgen::BitmapConstRef &bitmap, ImageFormat format, const char *filename, YDirection outputYDirection) { + switch (format) { + //case ImageFormat::PNG: + // return msdfgen::savePng(bitmap, filename); + case ImageFormat::BMP: + return msdfgen::saveBmp(bitmap, filename); + case ImageFormat::TIFF: + return false; + case ImageFormat::TEXT: + return saveImageText(bitmap, filename, outputYDirection); + case ImageFormat::TEXT_FLOAT: + return false; + case ImageFormat::BINARY: + return saveImageBinary(bitmap, filename, outputYDirection); + case ImageFormat::BINARY_FLOAT: + case ImageFormat::BINARY_FLOAT_BE: + return false; + default:; + } + return false; +} + +template +bool saveImage(const msdfgen::BitmapConstRef &bitmap, ImageFormat format, const char *filename, YDirection outputYDirection) { + switch (format) { + case ImageFormat::PNG: + return msdfgen::savePng(bitmap, filename); + case ImageFormat::BMP: + return msdfgen::saveBmp(bitmap, filename); + case ImageFormat::TIFF: + return msdfgen::saveTiff(bitmap, filename); + case ImageFormat::TEXT: + return false; + case ImageFormat::TEXT_FLOAT: + return saveImageText(bitmap, filename, outputYDirection); + case ImageFormat::BINARY: + return false; + case ImageFormat::BINARY_FLOAT: + return saveImageBinaryLE(bitmap, filename, outputYDirection); + case ImageFormat::BINARY_FLOAT_BE: + return saveImageBinaryBE(bitmap, filename, outputYDirection); + default:; + } + return false; +} + +template +bool saveImageBinary(const msdfgen::BitmapConstRef &bitmap, const char *filename, YDirection outputYDirection) { + bool success = false; + if (FILE *f = fopen(filename, "wb")) { + int written = 0; + switch (outputYDirection) { + case YDirection::BOTTOM_UP: + written = fwrite(bitmap.pixels, 1, N*bitmap.width*bitmap.height, f); + break; + case YDirection::TOP_DOWN: + for (int y = bitmap.height-1; y >= 0; --y) + written += fwrite(bitmap.pixels+N*bitmap.width*y, 1, N*bitmap.width, f); + break; + } + success = written == N*bitmap.width*bitmap.height; + fclose(f); + } + return success; +} + +template +bool + #ifdef __BIG_ENDIAN__ + saveImageBinaryBE + #else + saveImageBinaryLE + #endif + (const msdfgen::BitmapConstRef &bitmap, const char *filename, YDirection outputYDirection) { + bool success = false; + if (FILE *f = fopen(filename, "wb")) { + int written = 0; + switch (outputYDirection) { + case YDirection::BOTTOM_UP: + written = fwrite(bitmap.pixels, sizeof(float), N*bitmap.width*bitmap.height, f); + break; + case YDirection::TOP_DOWN: + for (int y = bitmap.height-1; y >= 0; --y) + written += fwrite(bitmap.pixels+N*bitmap.width*y, sizeof(float), N*bitmap.width, f); + break; + } + success = written == N*bitmap.width*bitmap.height; + fclose(f); + } + return success; +} + +template +bool + #ifdef __BIG_ENDIAN__ + saveImageBinaryLE + #else + saveImageBinaryBE + #endif + (const msdfgen::BitmapConstRef &bitmap, const char *filename, YDirection outputYDirection) { + bool success = false; + if (FILE *f = fopen(filename, "wb")) { + int written = 0; + for (int y = 0; y < bitmap.height; ++y) { + const float *p = bitmap.pixels+N*bitmap.width*(outputYDirection == YDirection::TOP_DOWN ? bitmap.height-y-1 : y); + for (int x = 0; x < bitmap.width; ++x) { + const unsigned char *b = reinterpret_cast(p++); + for (int i = sizeof(float)-1; i >= 0; --i) + written += fwrite(b+i, 1, 1, f); + } + } + success = written == sizeof(float)*N*bitmap.width*bitmap.height; + fclose(f); + } + return success; +} + + +template +bool saveImageText(const msdfgen::BitmapConstRef &bitmap, const char *filename, YDirection outputYDirection) { + bool success = false; + if (FILE *f = fopen(filename, "wb")) { + for (int y = 0; y < bitmap.height; ++y) { + const byte *p = bitmap.pixels+N*bitmap.width*(outputYDirection == YDirection::TOP_DOWN ? bitmap.height-y-1 : y); + for (int x = 0; x < N*bitmap.width; ++x) { + fprintf(f, x ? " %02X" : "%02X", (unsigned) *p++); + } + fprintf(f, "\n"); + } + fclose(f); + } + return success; +} + +template +bool saveImageText(const msdfgen::BitmapConstRef &bitmap, const char *filename, YDirection outputYDirection) { + bool success = false; + if (FILE *f = fopen(filename, "wb")) { + for (int y = 0; y < bitmap.height; ++y) { + const float *p = bitmap.pixels+N*bitmap.width*(outputYDirection == YDirection::TOP_DOWN ? bitmap.height-y-1 : y); + for (int x = 0; x < N*bitmap.width; ++x) { + fprintf(f, x ? " %g" : "%g", *p++); + } + fprintf(f, "\n"); + } + fclose(f); + } + return success; +} + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/json-export.cpp b/Nuake/src/Vendors/msdf-atlas-gen/json-export.cpp new file mode 100644 index 00000000..2bf24dc3 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/json-export.cpp @@ -0,0 +1,186 @@ + +#include "json-export.h" + +#include +#include "GlyphGeometry.h" + +namespace msdf_atlas { + +static std::string escapeJsonString(const char *str) { + char uval[7] = "\\u0000"; + std::string outStr; + while (*str) { + switch (*str) { + case '\\': + outStr += "\\\\"; + break; + case '"': + outStr += "\\\""; + break; + case '\n': + outStr += "\\n"; + break; + case '\r': + outStr += "\\r"; + break; + case '\t': + outStr += "\\t"; + break; + case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: /* \\t */ /* \\n */ case 0x0b: case 0x0c: /* \\r */ case 0x0e: case 0x0f: + case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f: + uval[4] = '0'+(*str >= 0x10); + uval[5] = "0123456789abcdef"[*str&0x0f]; + outStr += uval; + break; + default: + outStr.push_back(*str); + } + ++str; + } + return outStr; +} + +static const char * imageTypeString(ImageType type) { + switch (type) { + case ImageType::HARD_MASK: + return "hardmask"; + case ImageType::SOFT_MASK: + return "softmask"; + case ImageType::SDF: + return "sdf"; + case ImageType::PSDF: + return "psdf"; + case ImageType::MSDF: + return "msdf"; + case ImageType::MTSDF: + return "mtsdf"; + } + return nullptr; +} + +bool exportJSON(const FontGeometry *fonts, int fontCount, double fontSize, double pxRange, int atlasWidth, int atlasHeight, ImageType imageType, YDirection yDirection, const char *filename, bool kerning) { + FILE *f = fopen(filename, "w"); + if (!f) + return false; + fputs("{", f); + + // Atlas properties + fputs("\"atlas\":{", f); { + fprintf(f, "\"type\":\"%s\",", imageTypeString(imageType)); + if (imageType == ImageType::SDF || imageType == ImageType::PSDF || imageType == ImageType::MSDF || imageType == ImageType::MTSDF) + fprintf(f, "\"distanceRange\":%.17g,", pxRange); + fprintf(f, "\"size\":%.17g,", fontSize); + fprintf(f, "\"width\":%d,", atlasWidth); + fprintf(f, "\"height\":%d,", atlasHeight); + fprintf(f, "\"yOrigin\":\"%s\"", yDirection == YDirection::TOP_DOWN ? "top" : "bottom"); + } fputs("},", f); + + if (fontCount > 1) + fputs("\"variants\":[", f); + for (int i = 0; i < fontCount; ++i) { + const FontGeometry &font = fonts[i]; + if (fontCount > 1) + fputs(i == 0 ? "{" : ",{", f); + + // Font name + const char *name = font.getName(); + if (name) + fprintf(f, "\"name\":\"%s\",", escapeJsonString(name).c_str()); + + // Font metrics + fputs("\"metrics\":{", f); { + double yFactor = yDirection == YDirection::TOP_DOWN ? -1 : 1; + const msdfgen::FontMetrics &metrics = font.getMetrics(); + fprintf(f, "\"emSize\":%.17g,", metrics.emSize); + fprintf(f, "\"lineHeight\":%.17g,", metrics.lineHeight); + fprintf(f, "\"ascender\":%.17g,", yFactor*metrics.ascenderY); + fprintf(f, "\"descender\":%.17g,", yFactor*metrics.descenderY); + fprintf(f, "\"underlineY\":%.17g,", yFactor*metrics.underlineY); + fprintf(f, "\"underlineThickness\":%.17g", metrics.underlineThickness); + } fputs("},", f); + + // Glyph mapping + fputs("\"glyphs\":[", f); + bool firstGlyph = true; + for (const GlyphGeometry &glyph : font.getGlyphs()) { + fputs(firstGlyph ? "{" : ",{", f); + switch (font.getPreferredIdentifierType()) { + case GlyphIdentifierType::GLYPH_INDEX: + fprintf(f, "\"index\":%d,", glyph.getIndex()); + break; + case GlyphIdentifierType::UNICODE_CODEPOINT: + fprintf(f, "\"unicode\":%u,", glyph.getCodepoint()); + break; + } + fprintf(f, "\"advance\":%.17g", glyph.getAdvance()); + double l, b, r, t; + glyph.getQuadPlaneBounds(l, b, r, t); + if (l || b || r || t) { + switch (yDirection) { + case YDirection::BOTTOM_UP: + fprintf(f, ",\"planeBounds\":{\"left\":%.17g,\"bottom\":%.17g,\"right\":%.17g,\"top\":%.17g}", l, b, r, t); + break; + case YDirection::TOP_DOWN: + fprintf(f, ",\"planeBounds\":{\"left\":%.17g,\"top\":%.17g,\"right\":%.17g,\"bottom\":%.17g}", l, -t, r, -b); + break; + } + } + glyph.getQuadAtlasBounds(l, b, r, t); + if (l || b || r || t) { + switch (yDirection) { + case YDirection::BOTTOM_UP: + fprintf(f, ",\"atlasBounds\":{\"left\":%.17g,\"bottom\":%.17g,\"right\":%.17g,\"top\":%.17g}", l, b, r, t); + break; + case YDirection::TOP_DOWN: + fprintf(f, ",\"atlasBounds\":{\"left\":%.17g,\"top\":%.17g,\"right\":%.17g,\"bottom\":%.17g}", l, atlasHeight-t, r, atlasHeight-b); + break; + } + } + fputs("}", f); + firstGlyph = false; + } fputs("]", f); + + // Kerning pairs + if (kerning) { + fputs(",\"kerning\":[", f); + bool firstPair = true; + switch (font.getPreferredIdentifierType()) { + case GlyphIdentifierType::GLYPH_INDEX: + for (const std::pair, double> &kernPair : font.getKerning()) { + fputs(firstPair ? "{" : ",{", f); + fprintf(f, "\"index1\":%d,", kernPair.first.first); + fprintf(f, "\"index2\":%d,", kernPair.first.second); + fprintf(f, "\"advance\":%.17g", kernPair.second); + fputs("}", f); + firstPair = false; + } + break; + case GlyphIdentifierType::UNICODE_CODEPOINT: + for (const std::pair, double> &kernPair : font.getKerning()) { + const GlyphGeometry *glyph1 = font.getGlyph(msdfgen::GlyphIndex(kernPair.first.first)); + const GlyphGeometry *glyph2 = font.getGlyph(msdfgen::GlyphIndex(kernPair.first.second)); + if (glyph1 && glyph2 && glyph1->getCodepoint() && glyph2->getCodepoint()) { + fputs(firstPair ? "{" : ",{", f); + fprintf(f, "\"unicode1\":%u,", glyph1->getCodepoint()); + fprintf(f, "\"unicode2\":%u,", glyph2->getCodepoint()); + fprintf(f, "\"advance\":%.17g", kernPair.second); + fputs("}", f); + firstPair = false; + } + } + break; + } fputs("]", f); + } + + if (fontCount > 1) + fputs("}", f); + } + if (fontCount > 1) + fputs("]", f); + + fputs("}\n", f); + fclose(f); + return true; +} + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/json-export.h b/Nuake/src/Vendors/msdf-atlas-gen/json-export.h new file mode 100644 index 00000000..fe7bc042 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/json-export.h @@ -0,0 +1,14 @@ + +#pragma once + +#include +#include +#include "types.h" +#include "FontGeometry.h" + +namespace msdf_atlas { + +/// Writes the font and glyph metrics and atlas layout data into a comprehensive JSON file +bool exportJSON(const FontGeometry *fonts, int fontCount, double fontSize, double pxRange, int atlasWidth, int atlasHeight, ImageType imageType, YDirection yDirection, const char *filename, bool kerning); + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/main.cpp b/Nuake/src/Vendors/msdf-atlas-gen/main.cpp new file mode 100644 index 00000000..5c13f0ee --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/main.cpp @@ -0,0 +1,999 @@ + +/* +* MULTI-CHANNEL SIGNED DISTANCE FIELD ATLAS GENERATOR v1.2 (2021-05-29) - standalone console program +* -------------------------------------------------------------------------------------------------- +* A utility by Viktor Chlumsky, (c) 2020 - 2021 +* +*/ + +#ifdef MSDF_ATLAS_STANDALONE + +#define _USE_MATH_DEFINES +#include +#include +#include +#include +#include +#include +#include + +#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 + +#ifdef MSDFGEN_USE_SKIA + #define TITLE_SUFFIX " & Skia" + #define EXTRA_UNDERLINE "-------" +#else + #define TITLE_SUFFIX + #define EXTRA_UNDERLINE +#endif + +static const char * const helpText = R"( +MSDF Atlas Generator by Viktor Chlumsky v)" MSDF_ATLAS_VERSION R"( (with MSDFGEN v)" MSDFGEN_VERSION TITLE_SUFFIX R"() +----------------------------------------------------------------)" EXTRA_UNDERLINE R"( + +INPUT SPECIFICATION + -font + Specifies the input TrueType / OpenType font file. This is required. + -charset + Specifies the input character set. Refer to the documentation for format of charset specification. Defaults to ASCII. + -glyphset + Specifies the set of input glyphs as glyph indices within the font file. + -fontscale + Specifies the scale to be applied to the glyph geometry of the font. + -fontname + 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 + Selects the type of atlas to be generated. + -format + Selects the format for the atlas image output. Some image formats may be incompatible with embedded output formats. + -dimensions + 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 + Determines whether the Y-axis is oriented upwards (bottom origin, default) or downwards (top origin). + +OUTPUT SPECIFICATION - one or more can be specified + -imageout + Saves the atlas as an image file with the specified format. Layout data must be stored separately. + -json + Writes the atlas's layout data, as well as other metrics into a structured JSON file. + -csv + Writes the layout data of the glyphs into a simple CSV file. + -arfont + Stores the atlas and its layout data as an Artery Font file. Supported formats: png, bin, binfloat. + -shadronpreview + Generates a Shadron script that uses the generated atlas to draw a sample text as a preview. + +GLYPH CONFIGURATION + -size + Specifies the size of the glyphs in the atlas bitmap in pixels per EM. + -minsize + Specifies the minimum size. The largest possible size that fits the same atlas dimensions will be used. + -emrange + Specifies the SDF distance range in EM's. + -pxrange + 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 + Specifies the minimum angle between adjacent edges to be considered a corner. Append D for degrees. (msdf / mtsdf only) + -coloringstrategy + Selects the strategy of the edge coloring heuristic. + -errorcorrection + Changes the MSDF/MTSDF error correction mode. Use -errorcorrection help for a list of valid modes. + -errordeviationratio + Sets the minimum ratio between the actual and maximum expected distance delta to be considered an error. + -errorimproveratio + Sets the minimum ratio between the pre-correction distance error and the post-correction distance error. + -miterlimit + 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 + Sets the initial seed for the edge coloring heuristic. + -threads + 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; +} + +struct FontInput { + const char *fontFilename; + 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 GEN_FN> +static bool makeAtlas(const std::vector &glyphs, const std::vector &fonts, const Configuration &config) { + ImmediateAtlasGenerator > generator(config.width, config.height); + generator.setAttributes(config.generatorAttributes); + generator.setThreadCount(config.threadCount); + generator.generate(glyphs.data(), glyphs.size()); + msdfgen::BitmapConstRef bitmap = (msdfgen::BitmapConstRef) 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."); + } + } + + 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(fonts.data(), fonts.size(), bitmap, config.arteryFontFilename, arfontProps)) + puts("Artery Font file generated."); + else { + success = false; + puts("Failed to generate Artery Font file."); + } + } + + return success; +} + +int main(int argc, const char * const *argv) { + #define ABORT(msg) { puts(msg); return 1; } + + int result = 0; + std::vector 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) + + 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]; + ++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 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; + } + ARG_CASE("-arfont", 1) { + config.arteryFontFilename = argv[++argPos]; + ++argPos; + continue; + } + 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 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 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 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 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 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 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 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 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 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 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 with N being a non-negative integer."); + config.threadCount = (int) tc; + argPos += 2; + continue; + } + ARG_CASE("-help", 0) { + puts(helpText); + return 0; + } + printf("Unknown setting or insufficient parameters: %s\n", arg); + 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 -charset \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::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::SDF || config.imageType == ImageType::PSDF || config.imageType == ImageType::MSDF || config.imageType == ImageType::MTSDF)) { + rangeMode = RANGE_PIXEL; + rangeValue = (double) (config.imageType == ImageType::SOFT_MASK); + } 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."); + 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); + } + 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 glyphs; + std::vector 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) { + if (ft && fontFilename) { + if (this->fontFilename && !strcmp(this->fontFilename, fontFilename)) + return true; + if (font) + msdfgen::destroyFont(font); + if ((font = 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)) + 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(glyphs, fonts, config); + else + success = makeAtlas(glyphs, fonts, config); + break; + case ImageType::SOFT_MASK: + case ImageType::SDF: + if (floatingPointFormat) + success = makeAtlas(glyphs, fonts, config); + else + success = makeAtlas(glyphs, fonts, config); + break; + case ImageType::PSDF: + if (floatingPointFormat) + success = makeAtlas(glyphs, fonts, config); + else + success = makeAtlas(glyphs, fonts, config); + break; + case ImageType::MSDF: + if (floatingPointFormat) + success = makeAtlas(glyphs, fonts, config); + else + success = makeAtlas(glyphs, fonts, config); + break; + case ImageType::MTSDF: + if (floatingPointFormat) + success = makeAtlas(glyphs, fonts, config); + else + success = makeAtlas(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 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 diff --git a/Nuake/src/Vendors/msdf-atlas-gen/msdf-atlas-gen.h b/Nuake/src/Vendors/msdf-atlas-gen/msdf-atlas-gen.h new file mode 100644 index 00000000..cf24d542 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/msdf-atlas-gen.h @@ -0,0 +1,42 @@ + +#pragma once + +/* + * MULTI-CHANNEL SIGNED DISTANCE FIELD ATLAS GENERATOR v1.2 (2021-05-29) + * --------------------------------------------------------------------- + * A utility by Viktor Chlumsky, (c) 2020 - 2021 + * + * Generates compact bitmap font atlases using MSDFGEN. + * + */ + +#include +#include + +#include "types.h" +#include "utf8.h" +#include "Rectangle.h" +#include "Charset.h" +#include "GlyphBox.h" +#include "GlyphGeometry.h" +#include "FontGeometry.h" +#include "RectanglePacker.h" +#include "rectangle-packing.h" +#include "Workload.h" +#include "size-selectors.h" +#include "bitmap-blit.h" +#include "AtlasStorage.h" +#include "BitmapAtlasStorage.h" +#include "TightAtlasPacker.h" +#include "AtlasGenerator.h" +#include "ImmediateAtlasGenerator.h" +#include "DynamicAtlas.h" +#include "glyph-generators.h" +#include "image-encode.h" +#include "image-save.h" +#include "artery-font-export.h" +#include "csv-export.h" +#include "json-export.h" +#include "shadron-preview-generator.h" + +#define MSDF_ATLAS_VERSION "1.2" diff --git a/Nuake/src/Vendors/msdf-atlas-gen/msdf-atlas-gen.rc b/Nuake/src/Vendors/msdf-atlas-gen/msdf-atlas-gen.rc new file mode 100644 index 0000000000000000000000000000000000000000..49499c63b69f9a3818fc9c682423d15429afb875 GIT binary patch literal 5274 zcmd6rTW=ay5Xa}aQoq9%za*+-$4*}IXd6ON!B_^Pwz6a;z6F(=xTI;-ub#HQ|2Yi1 z3k!{rAJA$!yXVZDGxMM8^6x)~c4&z;tZTP6wxLb9A8g88hq;N(?1S}qJF_3T5_`0J z8*q=ApEBMtH)a&yjHg?k6W+92*GAAB*!T7wryPcHkJ$mMJ*e*jZE1!7z&=?iP>XXf z&^-h`?R{bw*0Q!;S=nmVv>Us$3bSRN&#i91GSlL>VW-U0dG_-q!|q)!osWrC!DSia z2T&SXAN{AGBOKaj{R_V}v_3;m;Zb4!n0whG|8Ijo0=|(ikN@r=<`PBkn1Pi%=>fgB z^9@Jd_uZ}0rBe+4g`T(AKd~3Nw@c*do^SSy(ROjINM&^>v_hlfLugOT*@WeoKckJOSYP0N_I`Ix+a2SzE7oGC()ZN zC3@;PdqkA-N{o~!m#kKlV^+OQ*y+yNdAFWjIv*1}<(3LFSje5#Aze*^6;*>0a?Ox8 z)qeF0HT)*A&!JzKF`JN)dc3J4P?w+zKAzJ~;YM&Gw5-3N3L-|X+MK?&3gnX8j zD^|BYH0mTKa7MW&yh{?TpE|oM|Feh`Gj%S7VaY*^7aih6>@(IJcXukJtucJ!8E_(^WsjR zsFB+%OEd}A*eCz&lbLi!+6(q?VznzQ*8V0A-~D~4DOIFvP)%A?hJ;r}C4FD}$Z3lP&kTwQ zvtQRpQGAYLQD*n-S5AxzKd%9PWpGoqm~z$!XuYqG`Kr(9l2w^eJ)8Rd1euig^gcz4 zVstjkm5)=G=ZK7V%)Jir;yq<=<%Z&?p}ZY(Z1cL=&Os@rr0 z`3Cbn=DT38?*Vy|JSbK=VWv0vmO2jR literal 0 HcmV?d00001 diff --git a/Nuake/src/Vendors/msdf-atlas-gen/msdf-atlas-gen.sln b/Nuake/src/Vendors/msdf-atlas-gen/msdf-atlas-gen.sln new file mode 100644 index 00000000..4dcff669 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/msdf-atlas-gen.sln @@ -0,0 +1,61 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Msdfgen", "msdfgen\Msdfgen.vcxproj", "{84BE2D91-F071-4151-BE12-61460464C494}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "msdf-atlas-gen", "msdf-atlas-gen.vcxproj", "{223EDB94-5B35-45F2-A584-273DE6E45F6F}" + ProjectSection(ProjectDependencies) = postProject + {84BE2D91-F071-4151-BE12-61460464C494} = {84BE2D91-F071-4151-BE12-61460464C494} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug Library|x64 = Debug Library|x64 + Debug Library|x86 = Debug Library|x86 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release Library|x64 = Release Library|x64 + Release Library|x86 = Release Library|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {84BE2D91-F071-4151-BE12-61460464C494}.Debug Library|x64.ActiveCfg = Debug Library|x64 + {84BE2D91-F071-4151-BE12-61460464C494}.Debug Library|x64.Build.0 = Debug Library|x64 + {84BE2D91-F071-4151-BE12-61460464C494}.Debug Library|x86.ActiveCfg = Debug Library|Win32 + {84BE2D91-F071-4151-BE12-61460464C494}.Debug Library|x86.Build.0 = Debug Library|Win32 + {84BE2D91-F071-4151-BE12-61460464C494}.Debug|x64.ActiveCfg = Debug Library|x64 + {84BE2D91-F071-4151-BE12-61460464C494}.Debug|x64.Build.0 = Debug Library|x64 + {84BE2D91-F071-4151-BE12-61460464C494}.Debug|x86.ActiveCfg = Debug Library|Win32 + {84BE2D91-F071-4151-BE12-61460464C494}.Debug|x86.Build.0 = Debug Library|Win32 + {84BE2D91-F071-4151-BE12-61460464C494}.Release Library|x64.ActiveCfg = Release Library|x64 + {84BE2D91-F071-4151-BE12-61460464C494}.Release Library|x64.Build.0 = Release Library|x64 + {84BE2D91-F071-4151-BE12-61460464C494}.Release Library|x86.ActiveCfg = Release Library|Win32 + {84BE2D91-F071-4151-BE12-61460464C494}.Release Library|x86.Build.0 = Release Library|Win32 + {84BE2D91-F071-4151-BE12-61460464C494}.Release|x64.ActiveCfg = Release Library|x64 + {84BE2D91-F071-4151-BE12-61460464C494}.Release|x64.Build.0 = Release Library|x64 + {84BE2D91-F071-4151-BE12-61460464C494}.Release|x86.ActiveCfg = Release Library|Win32 + {84BE2D91-F071-4151-BE12-61460464C494}.Release|x86.Build.0 = Release Library|Win32 + {223EDB94-5B35-45F2-A584-273DE6E45F6F}.Debug Library|x64.ActiveCfg = Debug Library|x64 + {223EDB94-5B35-45F2-A584-273DE6E45F6F}.Debug Library|x64.Build.0 = Debug Library|x64 + {223EDB94-5B35-45F2-A584-273DE6E45F6F}.Debug Library|x86.ActiveCfg = Debug Library|Win32 + {223EDB94-5B35-45F2-A584-273DE6E45F6F}.Debug Library|x86.Build.0 = Debug Library|Win32 + {223EDB94-5B35-45F2-A584-273DE6E45F6F}.Debug|x64.ActiveCfg = Debug|x64 + {223EDB94-5B35-45F2-A584-273DE6E45F6F}.Debug|x64.Build.0 = Debug|x64 + {223EDB94-5B35-45F2-A584-273DE6E45F6F}.Debug|x86.ActiveCfg = Debug|Win32 + {223EDB94-5B35-45F2-A584-273DE6E45F6F}.Debug|x86.Build.0 = Debug|Win32 + {223EDB94-5B35-45F2-A584-273DE6E45F6F}.Release Library|x64.ActiveCfg = Release Library|x64 + {223EDB94-5B35-45F2-A584-273DE6E45F6F}.Release Library|x64.Build.0 = Release Library|x64 + {223EDB94-5B35-45F2-A584-273DE6E45F6F}.Release Library|x86.ActiveCfg = Release Library|Win32 + {223EDB94-5B35-45F2-A584-273DE6E45F6F}.Release Library|x86.Build.0 = Release Library|Win32 + {223EDB94-5B35-45F2-A584-273DE6E45F6F}.Release|x64.ActiveCfg = Release|x64 + {223EDB94-5B35-45F2-A584-273DE6E45F6F}.Release|x64.Build.0 = Release|x64 + {223EDB94-5B35-45F2-A584-273DE6E45F6F}.Release|x86.ActiveCfg = Release|Win32 + {223EDB94-5B35-45F2-A584-273DE6E45F6F}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Nuake/src/Vendors/msdf-atlas-gen/msdf-atlas-gen.vcxproj b/Nuake/src/Vendors/msdf-atlas-gen/msdf-atlas-gen.vcxproj new file mode 100644 index 00000000..47640010 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/msdf-atlas-gen.vcxproj @@ -0,0 +1,374 @@ + + + + + Debug Library + Win32 + + + Debug Library + x64 + + + Debug + Win32 + + + Release Library + Win32 + + + Release Library + x64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {223EDB94-5B35-45F2-A584-273DE6E45F6F} + msdfatlasgen + 8.1 + + + + Application + true + v140 + MultiByte + + + StaticLibrary + true + v140 + MultiByte + + + Application + false + v140 + true + MultiByte + + + StaticLibrary + false + v140 + true + MultiByte + + + Application + true + v140 + MultiByte + + + StaticLibrary + true + v140 + MultiByte + + + Application + false + v140 + true + MultiByte + + + StaticLibrary + false + v140 + true + MultiByte + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + msdf-atlas-gen + $(Configuration)\ + + + msdf-atlas-gen + $(Configuration)\ + + + msdf-atlas-gen + bin\ + + + msdf-atlas-gen + bin\ + + + msdf-atlas-gen + $(Platform)\$(Configuration)\ + + + msdf-atlas-gen + $(Platform)\$(Configuration)\ + + + msdf-atlas-gen + $(Platform)\$(Configuration)\ + + + msdf-atlas-gen + $(Platform)\$(Configuration)\ + + + + Level3 + Disabled + true + msdfgen\include;msdfgen\freetype\include;msdfgen;artery-font-format;%(AdditionalIncludeDirectories) + MultiThreadedDebug + _CRT_SECURE_NO_WARNINGS;MSDFGEN_USE_CPP11;MSDFGEN_USE_SKIA;MSDF_ATLAS_STANDALONE;%(PreprocessorDefinitions) + + + Console + freetype.lib;skia.lib;msdfgen.lib;%(AdditionalDependencies) + msdfgen\freetype\win$(PlatformArchitecture);msdfgen\skia\win$(PlatformArchitecture)\$(Configuration);msdfgen\$(Configuration) Library;%(AdditionalLibraryDirectories) + + + + + Level3 + Disabled + true + msdfgen\include;msdfgen\freetype\include;msdfgen;artery-font-format;%(AdditionalIncludeDirectories) + MultiThreadedDebug + _CRT_SECURE_NO_WARNINGS;MSDFGEN_USE_CPP11;MSDFGEN_USE_SKIA;%(PreprocessorDefinitions) + + + Console + freetype.lib;msdfgen.lib;%(AdditionalDependencies) + ..\msdfgen\freetype\win32;$(SolutionDir)$(Configuration) Library;%(AdditionalLibraryDirectories) + + + MachineX86 + msdfgen\freetype\win$(PlatformArchitecture);msdfgen\skia\win$(PlatformArchitecture)\debug;msdfgen\$(Configuration);%(AdditionalLibraryDirectories) + + + + + Level3 + Disabled + true + msdfgen\include;msdfgen\freetype\include;msdfgen;artery-font-format;%(AdditionalIncludeDirectories) + MultiThreadedDebug + _CRT_SECURE_NO_WARNINGS;MSDFGEN_USE_CPP11;MSDFGEN_USE_SKIA;MSDF_ATLAS_STANDALONE;%(PreprocessorDefinitions) + + + Console + freetype.lib;skia.lib;msdfgen.lib;%(AdditionalDependencies) + msdfgen\freetype\win$(PlatformArchitecture);msdfgen\skia\win$(PlatformArchitecture)\$(Configuration);msdfgen\$(Platform)\$(Configuration) Library;%(AdditionalLibraryDirectories) + + + + + Level3 + Disabled + true + msdfgen\include;msdfgen\freetype\include;msdfgen;artery-font-format;%(AdditionalIncludeDirectories) + MultiThreadedDebug + _CRT_SECURE_NO_WARNINGS;MSDFGEN_USE_CPP11;MSDFGEN_USE_SKIA;%(PreprocessorDefinitions) + + + Console + freetype.lib;msdfgen.lib;%(AdditionalDependencies) + ..\msdfgen\freetype\win64;$(SolutionDir)$(Platform)\$(Configuration) Library;%(AdditionalLibraryDirectories) + + + msdfgen\freetype\win$(PlatformArchitecture);msdfgen\skia\win$(PlatformArchitecture)\debug;msdfgen\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) + + + + + Level3 + MaxSpeed + true + true + true + msdfgen\include;msdfgen\freetype\include;msdfgen;artery-font-format;%(AdditionalIncludeDirectories) + MultiThreaded + _CRT_SECURE_NO_WARNINGS;MSDFGEN_USE_CPP11;MSDFGEN_USE_SKIA;MSDF_ATLAS_STANDALONE;%(PreprocessorDefinitions) + None + + + true + true + Console + freetype.lib;skia.lib;msdfgen.lib;%(AdditionalDependencies) + msdfgen\freetype\win$(PlatformArchitecture);msdfgen\skia\win$(PlatformArchitecture)\$(Configuration);msdfgen\bin;%(AdditionalLibraryDirectories) + false + + + + + Level3 + MaxSpeed + true + true + true + msdfgen\include;msdfgen\freetype\include;msdfgen;artery-font-format;%(AdditionalIncludeDirectories) + MultiThreaded + _CRT_SECURE_NO_WARNINGS;MSDFGEN_USE_CPP11;MSDFGEN_USE_SKIA;%(PreprocessorDefinitions) + + + true + true + Console + freetype.lib;msdfgen.lib;%(AdditionalDependencies) + ..\msdfgen\freetype\win32;$(SolutionDir)bin;%(AdditionalLibraryDirectories) + + + MachineX86 + msdfgen\freetype\win$(PlatformArchitecture);msdfgen\skia\win$(PlatformArchitecture)\release;msdfgen\$(Configuration);%(AdditionalLibraryDirectories) + + + + + Level3 + MaxSpeed + true + true + true + msdfgen\include;msdfgen\freetype\include;msdfgen;artery-font-format;%(AdditionalIncludeDirectories) + MultiThreaded + _CRT_SECURE_NO_WARNINGS;MSDFGEN_USE_CPP11;MSDFGEN_USE_SKIA;MSDF_ATLAS_STANDALONE;%(PreprocessorDefinitions) + None + + + true + true + Console + freetype.lib;skia.lib;msdfgen.lib;%(AdditionalDependencies) + msdfgen\freetype\win$(PlatformArchitecture);msdfgen\skia\win$(PlatformArchitecture)\$(Configuration);msdfgen\$(Platform)\$(Configuration) Library;%(AdditionalLibraryDirectories) + false + + + + + Level3 + MaxSpeed + true + true + true + msdfgen\include;msdfgen\freetype\include;msdfgen;artery-font-format;%(AdditionalIncludeDirectories) + MultiThreaded + _CRT_SECURE_NO_WARNINGS;MSDFGEN_USE_CPP11;MSDFGEN_USE_SKIA;%(PreprocessorDefinitions) + + + true + true + Console + freetype.lib;msdfgen.lib;%(AdditionalDependencies) + ..\msdfgen\freetype\win64;$(SolutionDir)$(Platform)\$(Configuration) Library;%(AdditionalLibraryDirectories) + + + msdfgen\freetype\win$(PlatformArchitecture);msdfgen\skia\win$(PlatformArchitecture)\release;msdfgen\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Nuake/src/Vendors/msdf-atlas-gen/msdf-atlas-gen.vcxproj.filters b/Nuake/src/Vendors/msdf-atlas-gen/msdf-atlas-gen.vcxproj.filters new file mode 100644 index 00000000..dda43f07 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/msdf-atlas-gen.vcxproj.filters @@ -0,0 +1,184 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {ee785f45-c1cf-48ae-b864-f27237b077c1} + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Template Source Files + + + Header Files + + + Header Files + + + Header Files + + + Template Source Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Template Source Files + + + Header Files + + + Template Source Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Template Source Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Resource Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/Nuake/src/Vendors/msdf-atlas-gen/rectangle-packing.h b/Nuake/src/Vendors/msdf-atlas-gen/rectangle-packing.h new file mode 100644 index 00000000..4bcc6a83 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/rectangle-packing.h @@ -0,0 +1,19 @@ + +#pragma once + +#include +#include "Rectangle.h" + +namespace msdf_atlas { + +/// Packs the rectangle array into an atlas with fixed dimensions, returns how many didn't fit (0 on success) +template +int packRectangles(RectangleType *rectangles, int count, int width, int height, int padding = 0); + +/// Packs the rectangle array into an atlas of unknown size, returns the minimum required dimensions constrained by SizeSelector +template +std::pair packRectangles(RectangleType *rectangles, int count, int padding = 0); + +} + +#include "rectangle-packing.hpp" diff --git a/Nuake/src/Vendors/msdf-atlas-gen/rectangle-packing.hpp b/Nuake/src/Vendors/msdf-atlas-gen/rectangle-packing.hpp new file mode 100644 index 00000000..56c7a847 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/rectangle-packing.hpp @@ -0,0 +1,61 @@ + +#include "rectangle-packing.h" + +#include +#include "RectanglePacker.h" + +namespace msdf_atlas { + +static void copyRectanglePlacement(Rectangle &dst, const Rectangle &src) { + dst.x = src.x; + dst.y = src.y; +} + +static void copyRectanglePlacement(OrientedRectangle &dst, const OrientedRectangle &src) { + dst.x = src.x; + dst.y = src.y; + dst.rotated = src.rotated; +} + +template +int packRectangles(RectangleType *rectangles, int count, int width, int height, int padding) { + if (padding) + for (int i = 0; i < count; ++i) { + rectangles[i].w += padding; + rectangles[i].h += padding; + } + int result = RectanglePacker(width+padding, height+padding).pack(rectangles, count); + if (padding) + for (int i = 0; i < count; ++i) { + rectangles[i].w -= padding; + rectangles[i].h -= padding; + } + return result; +} + +template +std::pair packRectangles(RectangleType *rectangles, int count, int padding) { + std::vector rectanglesCopy(count); + int totalArea = 0; + for (int i = 0; i < count; ++i) { + rectanglesCopy[i].w = rectangles[i].w+padding; + rectanglesCopy[i].h = rectangles[i].h+padding; + totalArea += rectangles[i].w*rectangles[i].h; + } + std::pair dimensions; + SizeSelector sizeSelector(totalArea); + int width, height; + while (sizeSelector(width, height)) { + if (!RectanglePacker(width+padding, height+padding).pack(rectanglesCopy.data(), count)) { + dimensions.first = width; + dimensions.second = height; + for (int i = 0; i < count; ++i) + copyRectanglePlacement(rectangles[i], rectanglesCopy[i]); + --sizeSelector; + } else + ++sizeSelector; + } + return dimensions; +} + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/resource.h b/Nuake/src/Vendors/msdf-atlas-gen/resource.h new file mode 100644 index 0000000000000000000000000000000000000000..d0ca059fb29154229292d8b3bdb6f4fab890847b GIT binary patch literal 916 zcmb7?-A=+l5QWdRiSN*aOCyN!1!~I$5vsA2=v5ELhMSN?N%PESiWaL8piDLolpgrCDC1R=xU+@ +#include + +namespace msdf_atlas { + +static const char * const shadronFillGlyphMask = R"( +template +glsl vec4 fillGlyph(vec2 texCoord) { + float fill = texture((ATLAS), texCoord).r; + return vec4(vec3(COLOR), fill); +} +)"; + +static const char * const shadronFillGlyphSdf = R"( +template +glsl vec4 fillGlyph(vec2 texCoord) { + vec3 s = texture((ATLAS), texCoord).rgb; + float sd = dot(vec2(RANGE), 0.5/fwidth(texCoord))*(median(s.r, s.g, s.b)-0.5); + float fill = clamp(sd+0.5, 0.0, 1.0); + return vec4(vec3(COLOR), fill); +} +)"; + +static const char * const shadronPreviewPreamble = R"( +#include + +glsl struct GlyphVertex { + vec2 coord; + vec2 texCoord; +}; + +template +glsl vec4 projectVertex(out vec2 texCoord, in GlyphVertex vertex) { + vec2 coord = vertex.coord; + float scale = 2.0/max((TEXT_SIZE).x, shadron_Aspect*(TEXT_SIZE).y); + scale *= exp(0.0625*shadron_Mouse.z); + coord += vec2(-0.5, 0.5)*vec2(TEXT_SIZE); + coord *= scale*vec2(1.0, shadron_Aspect); + texCoord = vertex.texCoord; + return vec4(coord, 0.0, 1.0); +} +%s +#define PREVIEW_IMAGE(NAME, ATLAS, RANGE, COLOR, VERTEX_LIST, TEXT_SIZE, DIMENSIONS) model image NAME : \ + vertex_data(GlyphVertex), \ + fragment_data(vec2), \ + vertex(projectVertex, triangles, VERTEX_LIST), \ + fragment(fillGlyph), \ + depth(false), \ + blend(transparency), \ + background(vec4(vec3(COLOR), 0.0)), \ + dimensions(DIMENSIONS), \ + resizable(true) + +)"; + +static std::string relativizePath(const char *base, const char *target) { + if (target[0] == '/' || (target[0] && target[1] == ':')) // absolute path? + return target; + int commonPrefix = 0; + for (int i = 0; base[i] && target[i] && base[i] == target[i]; ++i) { + if (base[i] == '/' || base[i] == '\\') + commonPrefix = i+1; + } + base += commonPrefix; + target += commonPrefix; + int baseNesting = 0; + for (int i = 0; base[i]; ++i) + if (base[i] == '/' || base[i] == '\\') + ++baseNesting; + std::string output; + for (int i = 0; i < baseNesting; ++i) + output += "../"; + output += target; + return output; +} + +bool generateShadronPreview(const FontGeometry *fonts, int fontCount, ImageType atlasType, int atlasWidth, int atlasHeight, double pxRange, const unicode_t *text, const char *imageFilename, bool fullRange, const char *outputFilename) { + if (fontCount <= 0) + return false; + double texelWidth = 1./atlasWidth; + double texelHeight = 1./atlasHeight; + bool anyGlyphs = false; + FILE *file = fopen(outputFilename, "w"); + if (!file) + return false; + fprintf(file, shadronPreviewPreamble, atlasType == ImageType::HARD_MASK || atlasType == ImageType::SOFT_MASK ? shadronFillGlyphMask : shadronFillGlyphSdf); + if (imageFilename) + fprintf(file, "image Atlas = file(\"%s\")", relativizePath(outputFilename, imageFilename).c_str()); + else + fprintf(file, "image Atlas = file()"); + fprintf(file, " : %sfilter(%s), map(repeat);\n", fullRange ? "full_range(true), " : "", atlasType == ImageType::HARD_MASK ? "nearest" : "linear"); + fprintf(file, "const vec2 txRange = vec2(%.9g, %.9g);\n\n", pxRange*texelWidth, pxRange*texelHeight); + { + msdfgen::FontMetrics fontMetrics = fonts->getMetrics(); + for (int i = 1; i < fontCount; ++i) { + fontMetrics.lineHeight = std::max(fontMetrics.lineHeight, fonts[i].getMetrics().lineHeight); + fontMetrics.ascenderY = std::max(fontMetrics.ascenderY, fonts[i].getMetrics().ascenderY); + fontMetrics.descenderY = std::min(fontMetrics.descenderY, fonts[i].getMetrics().descenderY); + } + double fsScale = 1/(fontMetrics.ascenderY-fontMetrics.descenderY); + fputs("vertex_list GlyphVertex textQuadVertices = {\n", file); + double x = 0, y = -fsScale*fontMetrics.ascenderY; + double textWidth = 0; + for (const unicode_t *cp = text; *cp; ++cp) { + if (*cp == '\r') + continue; + if (*cp == '\n') { + textWidth = std::max(textWidth, x); + x = 0; + y -= fsScale*fontMetrics.lineHeight; + continue; + } + for (int i = 0; i < fontCount; ++i) { + const GlyphGeometry *glyph = fonts[i].getGlyph(*cp); + if (glyph) { + if (!glyph->isWhitespace()) { + double pl, pb, pr, pt; + double il, ib, ir, it; + glyph->getQuadPlaneBounds(pl, pb, pr, pt); + glyph->getQuadAtlasBounds(il, ib, ir, it); + pl *= fsScale, pb *= fsScale, pr *= fsScale, pt *= fsScale; + pl += x, pb += y, pr += x, pt += y; + il *= texelWidth, ib *= texelHeight, ir *= texelWidth, it *= texelHeight; + fprintf(file, " %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g,\n", + pl, pb, il, ib, + pr, pb, ir, ib, + pl, pt, il, it, + pr, pt, ir, it, + pl, pt, il, it, + pr, pb, ir, ib + ); + } + double advance = glyph->getAdvance(); + fonts[i].getAdvance(advance, cp[0], cp[1]); + x += fsScale*advance; + anyGlyphs = true; + break; + } + } + } + textWidth = std::max(textWidth, x); + y += fsScale*fontMetrics.descenderY; + fputs("};\n", file); + fprintf(file, "const vec2 textSize = vec2(%.9g, %.9g);\n\n", textWidth, -y); + } + fputs("PREVIEW_IMAGE(Preview, Atlas, txRange, vec3(1.0), textQuadVertices, textSize, ivec2(1200, 400));\n", file); + fputs("export png(Preview, \"preview.png\");\n", file); + fclose(file); + return anyGlyphs; +} + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/shadron-preview-generator.h b/Nuake/src/Vendors/msdf-atlas-gen/shadron-preview-generator.h new file mode 100644 index 00000000..75da3ebc --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/shadron-preview-generator.h @@ -0,0 +1,14 @@ + +#pragma once + +#include +#include +#include "types.h" +#include "FontGeometry.h" + +namespace msdf_atlas { + +/// Generates a Shadron script that displays a string using the generated atlas +bool generateShadronPreview(const FontGeometry *fonts, int fontCount, ImageType atlasType, int atlasWidth, int atlasHeight, double pxRange, const unicode_t *text, const char *imageFilename, bool fullRange, const char *outputFilename); + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/size-selectors.cpp b/Nuake/src/Vendors/msdf-atlas-gen/size-selectors.cpp new file mode 100644 index 00000000..ef2f21b8 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/size-selectors.cpp @@ -0,0 +1,90 @@ + +#include "size-selectors.h" + +#include + +namespace msdf_atlas { + +template +SquareSizeSelector::SquareSizeSelector(int minArea) : lowerBound(0), upperBound(-1) { + if (minArea > 0) + lowerBound = int(sqrt(minArea-1))/MULTIPLE+1; + updateCurrent(); +} + +template +void SquareSizeSelector::updateCurrent() { + if (upperBound < 0) + current = 5*lowerBound/4+16/MULTIPLE; + else + current = lowerBound+(upperBound-lowerBound)/2; +} + +template +bool SquareSizeSelector::operator()(int &width, int &height) const { + width = MULTIPLE*current, height = MULTIPLE*current; + return lowerBound < upperBound || upperBound < 0; +} + +template +SquareSizeSelector & SquareSizeSelector::operator++() { + lowerBound = current+1; + updateCurrent(); + return *this; +} + +template +SquareSizeSelector & SquareSizeSelector::operator--() { + upperBound = current; + updateCurrent(); + return *this; +} + +template class SquareSizeSelector<1>; +template class SquareSizeSelector<2>; +template class SquareSizeSelector<4>; + +SquarePowerOfTwoSizeSelector::SquarePowerOfTwoSizeSelector(int minArea) : side(1) { + while (side*side < minArea) + side <<= 1; +} + +bool SquarePowerOfTwoSizeSelector::operator()(int &width, int &height) const { + width = side, height = side; + return side > 0; +} + +SquarePowerOfTwoSizeSelector & SquarePowerOfTwoSizeSelector::operator++() { + side <<= 1; + return *this; +} + +SquarePowerOfTwoSizeSelector & SquarePowerOfTwoSizeSelector::operator--() { + side = 0; + return *this; +} + +PowerOfTwoSizeSelector::PowerOfTwoSizeSelector(int minArea) : w(1), h(1) { + while (w*h < minArea) + ++*this; +} + +bool PowerOfTwoSizeSelector::operator()(int &width, int &height) const { + width = w, height = h; + return w > 0 && h > 0; +} + +PowerOfTwoSizeSelector & PowerOfTwoSizeSelector::operator++() { + if (w == h) + w <<= 1; + else + h = w; + return *this; +} + +PowerOfTwoSizeSelector & PowerOfTwoSizeSelector::operator--() { + w = 0, h = 0; + return *this; +} + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/size-selectors.h b/Nuake/src/Vendors/msdf-atlas-gen/size-selectors.h new file mode 100644 index 00000000..b75fc3a2 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/size-selectors.h @@ -0,0 +1,54 @@ + +#pragma once + +namespace msdf_atlas { + +// The size selector classes are used to select the minimum dimensions of the atlas fitting a given constraint. + +/// Selects square dimensions which are also a multiple of MULTIPLE +template +class SquareSizeSelector { + +public: + explicit SquareSizeSelector(int minArea = 0); + bool operator()(int &width, int &height) const; + SquareSizeSelector & operator++(); + SquareSizeSelector & operator--(); + +private: + int lowerBound, upperBound; + int current; + + void updateCurrent(); + +}; + +/// Selects square power-of-two dimensions +class SquarePowerOfTwoSizeSelector { + +public: + explicit SquarePowerOfTwoSizeSelector(int minArea = 0); + bool operator()(int &width, int &height) const; + SquarePowerOfTwoSizeSelector & operator++(); + SquarePowerOfTwoSizeSelector & operator--(); + +private: + int side; + +}; + +/// Selects square or rectangular (2:1) power-of-two dimensions +class PowerOfTwoSizeSelector { + +public: + explicit PowerOfTwoSizeSelector(int minArea = 0); + bool operator()(int &width, int &height) const; + PowerOfTwoSizeSelector & operator++(); + PowerOfTwoSizeSelector & operator--(); + +private: + int w, h; + +}; + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/types.h b/Nuake/src/Vendors/msdf-atlas-gen/types.h new file mode 100644 index 00000000..27311e79 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/types.h @@ -0,0 +1,52 @@ + +#pragma once + +#include + +namespace msdf_atlas { + +typedef unsigned char byte; +typedef uint32_t unicode_t; + +/// Type of atlas image contents +enum class ImageType { + /// Rendered glyphs without anti-aliasing (two colors only) + HARD_MASK, + /// Rendered glyphs with anti-aliasing + SOFT_MASK, + /// Signed (true) distance field + SDF, + /// Signed pseudo-distance field + PSDF, + /// Multi-channel signed distance field + MSDF, + /// Multi-channel & true signed distance field + MTSDF +}; + +/// Atlas image encoding +enum class ImageFormat { + UNSPECIFIED, + PNG, + BMP, + TIFF, + TEXT, + TEXT_FLOAT, + BINARY, + BINARY_FLOAT, + BINARY_FLOAT_BE +}; + +/// Glyph identification +enum class GlyphIdentifierType { + GLYPH_INDEX, + UNICODE_CODEPOINT +}; + +/// Direction of the Y-axis +enum class YDirection { + BOTTOM_UP, + TOP_DOWN +}; + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/utf8.cpp b/Nuake/src/Vendors/msdf-atlas-gen/utf8.cpp new file mode 100644 index 00000000..393c5f2b --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/utf8.cpp @@ -0,0 +1,38 @@ + +#include "utf8.h" + +namespace msdf_atlas { + +void utf8Decode(std::vector &codepoints, const char *utf8String) { + bool start = true; + int rBytes = 0; + unicode_t cp = 0; + + for (const char *c = utf8String; *c; ++c) { + if (rBytes > 0) { + --rBytes; + if ((*c&0xc0) == 0x80) + cp |= (*c&0x3f)<<(6*rBytes); + // else error + } else if (!(*c&0x80)) { + cp = *c; + rBytes = 0; + } else if (*c&0x40) { + int block; + for (block = 0; ((unsigned char) *c<>block))<<(6*block); + rBytes = block; + } else + continue; // error + } else + continue; // error + if (!rBytes) { + if (!(start && cp == 0xfeff)) // BOM + codepoints.push_back(cp); + start = false; + } + } +} + +} diff --git a/Nuake/src/Vendors/msdf-atlas-gen/utf8.h b/Nuake/src/Vendors/msdf-atlas-gen/utf8.h new file mode 100644 index 00000000..740c2fb0 --- /dev/null +++ b/Nuake/src/Vendors/msdf-atlas-gen/utf8.h @@ -0,0 +1,12 @@ + +#pragma once + +#include +#include "types.h" + +namespace msdf_atlas { + +/// Decodes the UTF-8 string into an array of Unicode codepoints +void utf8Decode(std::vector &codepoints, const char *utf8String); + +}