diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..88acefb
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,4 @@
+
+all:
+ mkdir -p bin
+ g++ -I /usr/local/include/freetype2 -I /usr/include/freetype2 -I artery-font-format -I msdfgen/include -I msdfgen -D MSDFGEN_USE_CPP11 -D MSDF_ATLAS_STANDALONE -std=c++11 -pthread -O2 -o bin/msdf-atlas-gen msdfgen/core/*.cpp msdfgen/lib/*.cpp msdfgen/ext/*.cpp msdf-atlas-gen/*.cpp -lfreetype
diff --git a/icon.ico b/icon.ico
new file mode 100644
index 0000000..09e1a93
Binary files /dev/null and b/icon.ico differ
diff --git a/msdf-atlas-gen.rc b/msdf-atlas-gen.rc
new file mode 100644
index 0000000..dde4932
Binary files /dev/null and b/msdf-atlas-gen.rc differ
diff --git a/msdf-atlas-gen.sln b/msdf-atlas-gen.sln
new file mode 100644
index 0000000..4dcff66
--- /dev/null
+++ b/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/msdf-atlas-gen.vcxproj b/msdf-atlas-gen.vcxproj
new file mode 100644
index 0000000..92cfd35
--- /dev/null
+++ b/msdf-atlas-gen.vcxproj
@@ -0,0 +1,360 @@
+
+
+
+
+ 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;MSDF_ATLAS_STANDALONE;%(PreprocessorDefinitions)
+
+
+ Console
+ freetype.lib;msdfgen.lib;%(AdditionalDependencies)
+ msdfgen\freetype\win32;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;%(PreprocessorDefinitions)
+
+
+ Console
+ freetype.lib;msdfgen.lib;%(AdditionalDependencies)
+ ..\msdfgen\freetype\win32;$(SolutionDir)$(Configuration) Library;%(AdditionalLibraryDirectories)
+
+
+ MachineX86
+
+
+
+
+ Level3
+ Disabled
+ true
+ msdfgen\include;msdfgen\freetype\include;msdfgen;artery-font-format;%(AdditionalIncludeDirectories)
+ MultiThreadedDebug
+ _CRT_SECURE_NO_WARNINGS;MSDFGEN_USE_CPP11;MSDF_ATLAS_STANDALONE;%(PreprocessorDefinitions)
+
+
+ Console
+ freetype.lib;msdfgen.lib;%(AdditionalDependencies)
+ msdfgen\freetype\win64;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;%(PreprocessorDefinitions)
+
+
+ Console
+ freetype.lib;msdfgen.lib;%(AdditionalDependencies)
+ ..\msdfgen\freetype\win64;$(SolutionDir)$(Platform)\$(Configuration) Library;%(AdditionalLibraryDirectories)
+
+
+
+
+ Level3
+ MaxSpeed
+ true
+ true
+ true
+ msdfgen\include;msdfgen\freetype\include;msdfgen;artery-font-format;%(AdditionalIncludeDirectories)
+ MultiThreaded
+ _CRT_SECURE_NO_WARNINGS;MSDFGEN_USE_CPP11;MSDF_ATLAS_STANDALONE;%(PreprocessorDefinitions)
+
+
+ true
+ true
+ Console
+ freetype.lib;msdfgen.lib;%(AdditionalDependencies)
+ msdfgen\freetype\win32;msdfgen\bin;%(AdditionalLibraryDirectories)
+
+
+
+
+ Level3
+ MaxSpeed
+ true
+ true
+ true
+ msdfgen\include;msdfgen\freetype\include;msdfgen;artery-font-format;%(AdditionalIncludeDirectories)
+ MultiThreaded
+ _CRT_SECURE_NO_WARNINGS;MSDFGEN_USE_CPP11;%(PreprocessorDefinitions)
+
+
+ true
+ true
+ Console
+ freetype.lib;msdfgen.lib;%(AdditionalDependencies)
+ ..\msdfgen\freetype\win32;$(SolutionDir)bin;%(AdditionalLibraryDirectories)
+
+
+ MachineX86
+
+
+
+
+ Level3
+ MaxSpeed
+ true
+ true
+ true
+ msdfgen\include;msdfgen\freetype\include;msdfgen;artery-font-format;%(AdditionalIncludeDirectories)
+ MultiThreaded
+ _CRT_SECURE_NO_WARNINGS;MSDFGEN_USE_CPP11;MSDF_ATLAS_STANDALONE;%(PreprocessorDefinitions)
+
+
+ true
+ true
+ Console
+ freetype.lib;msdfgen.lib;%(AdditionalDependencies)
+ msdfgen\freetype\win64;msdfgen\$(Platform)\$(Configuration) Library;%(AdditionalLibraryDirectories)
+
+
+
+
+ Level3
+ MaxSpeed
+ true
+ true
+ true
+ msdfgen\include;msdfgen\freetype\include;msdfgen;artery-font-format;%(AdditionalIncludeDirectories)
+ MultiThreaded
+ _CRT_SECURE_NO_WARNINGS;MSDFGEN_USE_CPP11;%(PreprocessorDefinitions)
+
+
+ true
+ true
+ Console
+ freetype.lib;msdfgen.lib;%(AdditionalDependencies)
+ ..\msdfgen\freetype\win64;$(SolutionDir)$(Platform)\$(Configuration) Library;%(AdditionalLibraryDirectories)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/msdf-atlas-gen.vcxproj.filters b/msdf-atlas-gen.vcxproj.filters
new file mode 100644
index 0000000..e105908
--- /dev/null
+++ b/msdf-atlas-gen.vcxproj.filters
@@ -0,0 +1,178 @@
+
+
+
+
+ {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
+
+
+
+
+ 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
+
+
+
+
+ Resource Files
+
+
+
+
+ Resource Files
+
+
+
\ No newline at end of file
diff --git a/msdf-atlas-gen/AtlasGenerator.h b/msdf-atlas-gen/AtlasGenerator.h
new file mode 100644
index 0000000..71e5513
--- /dev/null
+++ b/msdf-atlas-gen/AtlasGenerator.h
@@ -0,0 +1,43 @@
+
+#pragma once
+
+#include
+#include "Remap.h"
+#include "GlyphGeometry.h"
+
+namespace msdf_atlas {
+
+namespace {
+
+/** Prototype of an atlas generator class.
+ * An atlas generator maintains the atlas bitmap (AtlasStorage) and its layout and facilitates
+ * generation of bitmap representation of glyphs. The layout of the atlas is given by the caller.
+ */
+class AtlasGenerator {
+
+public:
+ AtlasGenerator();
+ AtlasGenerator(int width, int height);
+ /// Generates bitmap representation for the supplied array of glyphs
+ void generate(const GlyphGeometry *glyphs, int count);
+ /// Resizes the atlas and rearranges the generated pixels according to the remapping array
+ void rearrange(int width, int height, const Remap *remapping, int count);
+ /// Resizes the atlas and keeps the generated pixels in place
+ void resize(int width, int height);
+
+};
+
+}
+
+/// Configuration of signed distance field generator
+struct GeneratorAttributes {
+ bool overlapSupport = true;
+ bool scanlinePass = true;
+ double errorCorrectionThreshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD;
+};
+
+/// A function that generates the bitmap for a single glyph
+template
+using GeneratorFunction = void (*)(const msdfgen::BitmapRef &, const GlyphGeometry &, const GeneratorAttributes &);
+
+}
diff --git a/msdf-atlas-gen/AtlasStorage.h b/msdf-atlas-gen/AtlasStorage.h
new file mode 100644
index 0000000..a51f46a
--- /dev/null
+++ b/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/msdf-atlas-gen/BitmapAtlasStorage.h b/msdf-atlas-gen/BitmapAtlasStorage.h
new file mode 100644
index 0000000..f0f7c99
--- /dev/null
+++ b/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/msdf-atlas-gen/BitmapAtlasStorage.hpp b/msdf-atlas-gen/BitmapAtlasStorage.hpp
new file mode 100644
index 0000000..602a352
--- /dev/null
+++ b/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/msdf-atlas-gen/Charset.cpp b/msdf-atlas-gen/Charset.cpp
new file mode 100644
index 0000000..69c93ee
--- /dev/null
+++ b/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/msdf-atlas-gen/Charset.h b/msdf-atlas-gen/Charset.h
new file mode 100644
index 0000000..6b107b3
--- /dev/null
+++ b/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);
+
+private:
+ std::set codepoints;
+
+};
+
+}
diff --git a/msdf-atlas-gen/DynamicAtlas.h b/msdf-atlas-gen/DynamicAtlas.h
new file mode 100644
index 0000000..e164097
--- /dev/null
+++ b/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/msdf-atlas-gen/DynamicAtlas.hpp b/msdf-atlas-gen/DynamicAtlas.hpp
new file mode 100644
index 0000000..51a712a
--- /dev/null
+++ b/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/msdf-atlas-gen/GlyphBox.h b/msdf-atlas-gen/GlyphBox.h
new file mode 100644
index 0000000..a6d1c6a
--- /dev/null
+++ b/msdf-atlas-gen/GlyphBox.h
@@ -0,0 +1,21 @@
+
+#pragma once
+
+#include "types.h"
+
+namespace msdf_atlas {
+
+/// The glyph box - its bounds in plane and atlas
+struct GlyphBox {
+ unicode_t codepoint;
+ double advance;
+ struct {
+ double l, b, r, t;
+ } bounds;
+ struct {
+ int x, y, w, h;
+ } rect;
+
+};
+
+}
diff --git a/msdf-atlas-gen/GlyphGeometry.cpp b/msdf-atlas-gen/GlyphGeometry.cpp
new file mode 100644
index 0000000..e6a8a67
--- /dev/null
+++ b/msdf-atlas-gen/GlyphGeometry.cpp
@@ -0,0 +1,133 @@
+
+#include "GlyphGeometry.h"
+
+#include
+
+namespace msdf_atlas {
+
+GlyphGeometry::GlyphGeometry() : codepoint(), bounds(), reverseWinding(), advance(), box() { }
+
+double GlyphGeometry::simpleSignedDistance(const msdfgen::Point2 &p) const {
+ double dummy;
+ msdfgen::SignedDistance minDistance;
+ for (const msdfgen::Contour &contour : shape.contours)
+ for (const msdfgen::EdgeHolder &edge : contour.edges) {
+ msdfgen::SignedDistance distance = edge->signedDistance(p, dummy);
+ if (distance < minDistance)
+ minDistance = distance;
+ }
+ return minDistance.distance;
+}
+
+bool GlyphGeometry::load(msdfgen::FontHandle *font, unicode_t codepoint) {
+ if (font && msdfgen::loadGlyph(shape, font, codepoint, &advance) && shape.validate()) {
+ this->codepoint = codepoint;
+ shape.normalize();
+ bounds = shape.getBounds();
+ msdfgen::Point2 outerPoint(bounds.l-(bounds.r-bounds.l)-1, bounds.b-(bounds.t-bounds.b)-1);
+ reverseWinding = simpleSignedDistance(outerPoint) > 0;
+ return true;
+ }
+ return false;
+}
+
+void GlyphGeometry::edgeColoring(double angleThreshold, unsigned long long seed) {
+ msdfgen::edgeColoringInkTrap(shape, angleThreshold, seed);
+}
+
+void GlyphGeometry::wrapBox(double scale, double range, double miterLimit) {
+ 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, reverseWinding ? -1 : +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;
+}
+
+unicode_t GlyphGeometry::getCodepoint() const {
+ return codepoint;
+}
+
+const msdfgen::Shape & GlyphGeometry::getShape() const {
+ return shape;
+}
+
+double GlyphGeometry::getAdvance() const {
+ return advance;
+}
+
+bool GlyphGeometry::isWindingReverse() const {
+ return reverseWinding;
+}
+
+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;
+}
+
+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) {
+ l = -box.translate.x+.5/box.scale;
+ b = -box.translate.y+.5/box.scale;
+ r = -box.translate.x+(box.rect.w-.5)/box.scale;
+ t = -box.translate.y+(box.rect.h-.5)/box.scale;
+ } 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.codepoint = codepoint;
+ 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/msdf-atlas-gen/GlyphGeometry.h b/msdf-atlas-gen/GlyphGeometry.h
new file mode 100644
index 0000000..a0f3dda
--- /dev/null
+++ b/msdf-atlas-gen/GlyphGeometry.h
@@ -0,0 +1,71 @@
+
+#pragma once
+
+#include
+#include
+#include "types.h"
+#include "GlyphBox.h"
+
+namespace msdf_atlas {
+
+/// Represent's 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, unicode_t codepoint);
+ /// Applies edge coloring to glyph shape
+ void edgeColoring(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 Unicode index
+ unicode_t getCodepoint() const;
+ /// Returns the glyph's shape
+ const msdfgen::Shape & getShape() const;
+ /// Returns the glyph's advance
+ double getAdvance() const;
+ /// Returns true if the shape has reverse winding
+ bool isWindingReverse() 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 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:
+ unicode_t codepoint;
+ msdfgen::Shape shape;
+ msdfgen::Shape::Bounds bounds;
+ bool reverseWinding;
+ double advance;
+ struct {
+ struct {
+ int x, y, w, h;
+ } rect;
+ double range;
+ double scale;
+ msdfgen::Vector2 translate;
+ } box;
+
+ /// Computes the signed distance from point p in a naive way
+ double simpleSignedDistance(const msdfgen::Point2 &p) const;
+
+};
+
+}
diff --git a/msdf-atlas-gen/ImmediateAtlasGenerator.h b/msdf-atlas-gen/ImmediateAtlasGenerator.h
new file mode 100644
index 0000000..2dc789e
--- /dev/null
+++ b/msdf-atlas-gen/ImmediateAtlasGenerator.h
@@ -0,0 +1,44 @@
+
+#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;
+ GeneratorAttributes attributes;
+ int threadCount;
+
+};
+
+}
+
+#include "ImmediateAtlasGenerator.hpp"
diff --git a/msdf-atlas-gen/ImmediateAtlasGenerator.hpp b/msdf-atlas-gen/ImmediateAtlasGenerator.hpp
new file mode 100644
index 0000000..cdfd705
--- /dev/null
+++ b/msdf-atlas-gen/ImmediateAtlasGenerator.hpp
@@ -0,0 +1,70 @@
+
+#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);
+
+ Workload([this, &glyphs, 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, attributes);
+ 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/msdf-atlas-gen/Rectangle.h b/msdf-atlas-gen/Rectangle.h
new file mode 100644
index 0000000..8221be4
--- /dev/null
+++ b/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/msdf-atlas-gen/RectanglePacker.cpp b/msdf-atlas-gen/RectanglePacker.cpp
new file mode 100644
index 0000000..b85d697
--- /dev/null
+++ b/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/msdf-atlas-gen/RectanglePacker.h b/msdf-atlas-gen/RectanglePacker.h
new file mode 100644
index 0000000..10e4c19
--- /dev/null
+++ b/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/msdf-atlas-gen/Remap.h b/msdf-atlas-gen/Remap.h
new file mode 100644
index 0000000..4a69889
--- /dev/null
+++ b/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/msdf-atlas-gen/TightAtlasPacker.cpp b/msdf-atlas-gen/TightAtlasPacker.cpp
new file mode 100644
index 0000000..fe59411
--- /dev/null
+++ b/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 && TRY_PACK(maxScale = 2*minScale))
+ minScale = maxScale;
+ } else {
+ while (minScale > 1e-32 && !TRY_PACK(minScale = .5*maxScale))
+ 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/msdf-atlas-gen/TightAtlasPacker.h b/msdf-atlas-gen/TightAtlasPacker.h
new file mode 100644
index 0000000..35bb2a9
--- /dev/null
+++ b/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/msdf-atlas-gen/Workload.cpp b/msdf-atlas-gen/Workload.cpp
new file mode 100644
index 0000000..e9cd5d4
--- /dev/null
+++ b/msdf-atlas-gen/Workload.cpp
@@ -0,0 +1,49 @@
+
+#include "Workload.h"
+
+#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(threadCount);
+ return false;
+}
+
+}
diff --git a/msdf-atlas-gen/Workload.h b/msdf-atlas-gen/Workload.h
new file mode 100644
index 0000000..cee0c9d
--- /dev/null
+++ b/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/msdf-atlas-gen/artery-font-export.cpp b/msdf-atlas-gen/artery-font-export.cpp
new file mode 100644
index 0000000..6e151ef
--- /dev/null
+++ b/msdf-atlas-gen/artery-font-export.cpp
@@ -0,0 +1,149 @@
+
+#include "artery-font-export.h"
+
+#include
+#include
+#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;
+}
+
+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(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, const msdfgen::BitmapConstRef &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename) {
+ artery_font::StdArteryFont arfont = { };
+ arfont.metadataFormat = artery_font::METADATA_NONE;
+
+ if (glyphCount > 0) {
+ msdfgen::FontMetrics fontMetrics;
+ if (!msdfgen::getFontMetrics(fontMetrics, font))
+ return false;
+ double fsScale = 1/fontMetrics.emSize;
+ artery_font::StdFontVariant fontVariant = { };
+ fontVariant.codepointType = artery_font::CP_UNICODE;
+ fontVariant.imageType = convertImageType(imageType);
+ fontVariant.metrics.fontSize = REAL(fontSize);
+ if (imageType != ImageType::HARD_MASK)
+ fontVariant.metrics.distanceRange = REAL(pxRange);
+ fontVariant.metrics.emSize = REAL(fsScale*fontMetrics.emSize);
+ fontVariant.metrics.ascender = REAL(fsScale*fontMetrics.ascenderY);
+ fontVariant.metrics.descender = REAL(fsScale*fontMetrics.descenderY);
+ fontVariant.metrics.lineHeight = REAL(fsScale*fontMetrics.lineHeight);
+ fontVariant.metrics.underlineY = REAL(fsScale*fontMetrics.underlineY);
+ fontVariant.metrics.underlineThickness = REAL(fsScale*fontMetrics.underlineThickness);
+ fontVariant.glyphs = artery_font::StdList >(glyphCount);
+ for (int i = 0; i < glyphCount; ++i) {
+ artery_font::Glyph &glyph = fontVariant.glyphs[i];
+ glyph.codepoint = glyphs[i].getCodepoint();
+ glyph.image = 0;
+ double l, b, r, t;
+ glyphs[i].getQuadPlaneBounds(l, b, r, t);
+ glyph.planeBounds.l = REAL(fsScale*l);
+ glyph.planeBounds.b = REAL(fsScale*b);
+ glyph.planeBounds.r = REAL(fsScale*r);
+ glyph.planeBounds.t = REAL(fsScale*t);
+ glyphs[i].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(fsScale*glyphs[i].getAdvance());
+ glyph.advance.v = REAL(0);
+ for (int j = 0; j < glyphCount; ++j) {
+ double kerning;
+ if (msdfgen::getKerning(kerning, font, glyphs[i].getCodepoint(), glyphs[j].getCodepoint()) && kerning) {
+ artery_font::KernPair kernPair = { };
+ kernPair.codepoint1 = glyphs[i].getCodepoint();
+ kernPair.codepoint2 = glyphs[j].getCodepoint();
+ kernPair.advance.h = REAL(fsScale*kerning);
+ fontVariant.kernPairs.vector.push_back((artery_font::KernPair &&) kernPair);
+ }
+ }
+ }
+ 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(imageType);
+ switch (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.rawBinaryFormat.orientation = artery_font::ORIENTATION_BOTTOM_UP;
+ image.data = artery_font::StdByteArray(N*sizeof(T)*atlas.width*atlas.height);
+ memcpy((byte *) image.data, atlas.pixels, N*sizeof(T)*atlas.width*atlas.height);
+ break;
+ default:
+ return false;
+ }
+ arfont.images.vector.push_back((artery_font::StdImage &&) image);
+ }
+
+ return artery_font::writeFile(arfont, filename);
+}
+
+template bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, const msdfgen::BitmapConstRef &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename);
+template bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, const msdfgen::BitmapConstRef &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename);
+template bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, const msdfgen::BitmapConstRef &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename);
+template bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, const msdfgen::BitmapConstRef &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename);
+template bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, const msdfgen::BitmapConstRef &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename);
+template bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, const msdfgen::BitmapConstRef &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename);
+
+}
diff --git a/msdf-atlas-gen/artery-font-export.h b/msdf-atlas-gen/artery-font-export.h
new file mode 100644
index 0000000..fe2abdc
--- /dev/null
+++ b/msdf-atlas-gen/artery-font-export.h
@@ -0,0 +1,15 @@
+
+#pragma once
+
+#include
+#include
+#include "types.h"
+#include "GlyphGeometry.h"
+
+namespace msdf_atlas {
+
+/// Encodes the atlas bitmap and its layout into an Artery Atlas Font file
+template
+bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, const msdfgen::BitmapConstRef &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename);
+
+}
diff --git a/msdf-atlas-gen/bitmap-blit.cpp b/msdf-atlas-gen/bitmap-blit.cpp
new file mode 100644
index 0000000..c1e6afa
--- /dev/null
+++ b/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/msdf-atlas-gen/bitmap-blit.h b/msdf-atlas-gen/bitmap-blit.h
new file mode 100644
index 0000000..b186d12
--- /dev/null
+++ b/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/msdf-atlas-gen/charset-parser.cpp b/msdf-atlas-gen/charset-parser.cpp
new file mode 100644
index 0000000..44c511b
--- /dev/null
+++ b/msdf-atlas-gen/charset-parser.cpp
@@ -0,0 +1,251 @@
+
+#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) {
+
+ 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))
+ 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();
+ state = TIGHT;
+ break;
+ case '"': // string of UTF-8 characters
+ if (state != CLEAR)
+ 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/msdf-atlas-gen/csv-export.cpp b/msdf-atlas-gen/csv-export.cpp
new file mode 100644
index 0000000..e48fbe1
--- /dev/null
+++ b/msdf-atlas-gen/csv-export.cpp
@@ -0,0 +1,27 @@
+
+#include "csv-export.h"
+
+#include
+
+namespace msdf_atlas {
+
+bool exportCSV(const GlyphGeometry *glyphs, int glyphCount, double emSize, const char *filename) {
+ FILE *f = fopen(filename, "w");
+ if (!f)
+ return false;
+
+ double fsScale = 1/emSize;
+ for (int i = 0; i < glyphCount; ++i) {
+ double l, b, r, t;
+ fprintf(f, "%u,%.17g,", glyphs[i].getCodepoint(), fsScale*glyphs[i].getAdvance());
+ glyphs[i].getQuadPlaneBounds(l, b, r, t);
+ fprintf(f, "%.17g,%.17g,%.17g,%.17g,", fsScale*l, fsScale*b, fsScale*r, fsScale*t);
+ glyphs[i].getQuadAtlasBounds(l, b, r, t);
+ fprintf(f, "%.17g,%.17g,%.17g,%.17g\n", l, b, r, t);
+ }
+
+ fclose(f);
+ return true;
+}
+
+}
diff --git a/msdf-atlas-gen/csv-export.h b/msdf-atlas-gen/csv-export.h
new file mode 100644
index 0000000..492c50f
--- /dev/null
+++ b/msdf-atlas-gen/csv-export.h
@@ -0,0 +1,14 @@
+
+#pragma once
+
+#include "GlyphGeometry.h"
+
+namespace msdf_atlas {
+
+/**
+ * Writes the positioning data and atlas layout of the glyphs into a CSV file
+ * The columns are: Unicode index, horizontal advance, plane bounds (l, b, r, t), atlas bounds (l, b, r, t)
+ */
+bool exportCSV(const GlyphGeometry *glyphs, int glyphCount, double emSize, const char *filename);
+
+}
diff --git a/msdf-atlas-gen/glyph-generators.cpp b/msdf-atlas-gen/glyph-generators.cpp
new file mode 100644
index 0000000..c09adc8
--- /dev/null
+++ b/msdf-atlas-gen/glyph-generators.cpp
@@ -0,0 +1,51 @@
+
+#include "glyph-generators.h"
+
+namespace msdf_atlas {
+
+template
+static void invertColor(const msdfgen::BitmapRef &bitmap) {
+ const float *end = bitmap.pixels+N*bitmap.width*bitmap.height;
+ for (float *p = bitmap.pixels; p < end; ++p)
+ *p = 1.f-*p;
+}
+
+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.getBoxRange(), glyph.getBoxScale(), glyph.getBoxTranslate(), attribs.overlapSupport);
+ if (glyph.isWindingReverse())
+ invertColor(output);
+ if (attribs.scanlinePass)
+ msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxScale(), glyph.getBoxTranslate(), MSDF_ATLAS_GLYPH_FILL_RULE);
+}
+
+void psdfGenerator(const msdfgen::BitmapRef &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs) {
+ msdfgen::generatePseudoSDF(output, glyph.getShape(), glyph.getBoxRange(), glyph.getBoxScale(), glyph.getBoxTranslate(), attribs.overlapSupport);
+ if (glyph.isWindingReverse())
+ invertColor(output);
+ if (attribs.scanlinePass)
+ msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxScale(), glyph.getBoxTranslate(), MSDF_ATLAS_GLYPH_FILL_RULE);
+}
+
+void msdfGenerator(const msdfgen::BitmapRef &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs) {
+ msdfgen::generateMSDF(output, glyph.getShape(), glyph.getBoxRange(), glyph.getBoxScale(), glyph.getBoxTranslate(), 0, attribs.overlapSupport);
+ if (glyph.isWindingReverse())
+ invertColor(output);
+ if (attribs.scanlinePass)
+ msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxScale(), glyph.getBoxTranslate(), MSDF_ATLAS_GLYPH_FILL_RULE);
+ msdfgen::msdfErrorCorrection(output, attribs.errorCorrectionThreshold/(glyph.getBoxScale()*glyph.getBoxRange()));
+}
+
+void mtsdfGenerator(const msdfgen::BitmapRef &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs) {
+ msdfgen::generateMTSDF(output, glyph.getShape(), glyph.getBoxRange(), glyph.getBoxScale(), glyph.getBoxTranslate(), 0, attribs.overlapSupport);
+ if (glyph.isWindingReverse())
+ invertColor(output);
+ if (attribs.scanlinePass)
+ msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxScale(), glyph.getBoxTranslate(), MSDF_ATLAS_GLYPH_FILL_RULE);
+ msdfgen::msdfErrorCorrection(output, attribs.errorCorrectionThreshold/(glyph.getBoxScale()*glyph.getBoxRange()));
+}
+
+}
diff --git a/msdf-atlas-gen/glyph-generators.h b/msdf-atlas-gen/glyph-generators.h
new file mode 100644
index 0000000..1df7c24
--- /dev/null
+++ b/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/msdf-atlas-gen/image-encode.cpp b/msdf-atlas-gen/image-encode.cpp
new file mode 100644
index 0000000..b34fc5a
--- /dev/null
+++ b/msdf-atlas-gen/image-encode.cpp
@@ -0,0 +1,63 @@
+
+#include "image-encode.h"
+
+#include
+
+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/msdf-atlas-gen/image-encode.h b/msdf-atlas-gen/image-encode.h
new file mode 100644
index 0000000..59a8fc5
--- /dev/null
+++ b/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/msdf-atlas-gen/image-save.h b/msdf-atlas-gen/image-save.h
new file mode 100644
index 0000000..0e73fa8
--- /dev/null
+++ b/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);
+
+}
+
+#include "image-save.hpp"
diff --git a/msdf-atlas-gen/image-save.hpp b/msdf-atlas-gen/image-save.hpp
new file mode 100644
index 0000000..cd3dc7e
--- /dev/null
+++ b/msdf-atlas-gen/image-save.hpp
@@ -0,0 +1,151 @@
+
+#include "image-save.h"
+
+#include
+#include
+
+namespace msdf_atlas {
+
+template
+bool saveImageBinary(const msdfgen::BitmapConstRef &bitmap, const char *filename);
+template