Loading glyphs by glyph index

This commit is contained in:
Chlumsky 2020-10-18 14:00:24 +02:00
parent c42964a9ec
commit 60789b8cf3
15 changed files with 201 additions and 63 deletions

18
CHANGELOG.md Normal file
View File

@ -0,0 +1,18 @@
## 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.

View File

@ -3,19 +3,17 @@
This is a utility for generating compact font atlases using [MSDFgen](https://github.com/Chlumsky/msdfgen). This is a utility for generating compact font atlases using [MSDFgen](https://github.com/Chlumsky/msdfgen).
It can read TTF and OTF font files, select a subset of characters, generate distance fields or bitmaps for each, and tightly pack them into an atlas bitmap, which can be e.g. used as a texture in video games. The atlas can be exported as an image file or an [Artery Font](https://github.com/Chlumsky/artery-font-format) file, and its layout written into a CSV or structured JSON file. 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) ![Atlas example](https://user-images.githubusercontent.com/18639794/76163889-811f2e80-614a-11ea-9b28-1eed54dbb899.png)
## Getting started A font atlas is typically stored in texture memory and used to draw text in real-time rendering contexts such as video games.
This project can be used either as a library or as a console program. - See what's new in the [changelog](CHANGELOG.md).
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).
## Atlas types ## Atlas types
The utility can generate the atlas bitmap in the following six ways: The atlas generator can generate the following six types of atlases.
| |Hard mask|Soft mask|SDF|PSDF|MSDF|MTSDF| | |Hard mask|Soft mask|SDF|PSDF|MSDF|MTSDF|
|-|-|-|-|-|-|-| |-|-|-|-|-|-|-|
@ -32,12 +30,21 @@ Notes:
- *Soft effects* refers to the support of effects that use true distance, such as glows, rounded borders, or simplified shadows. - *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. - *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 ## Command line arguments
Use the following command line arguments for the standalone version of the atlas generator.
### Input ### Input
- `-font <fontfile.ttf/otf>` &ndash; sets the input font file. - `-font <fontfile.ttf/otf>` &ndash; sets the input font file.
- `-charset <charset.txt>` &ndash; sets the character set. The ASCII charset will be used if not specified. See [the syntax specification](#character-set-specification-syntax) of `charset.txt`. - `-charset <charset.txt>` &ndash; 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 <glyphset.txt>` &ndash; sets the set of input glyphs using their indices within the font file. See [the syntax specification](#glyph-set-specification).
### Bitmap atlas type ### Bitmap atlas type
@ -123,3 +130,7 @@ Additionally, the include directive can be used to include other charset files a
It must be written on a separate line: It must be written on a separate line:
`@include "base-charset.txt"` `@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.

View File

@ -25,7 +25,7 @@ public:
std::set<unicode_t>::const_iterator end() const; std::set<unicode_t>::const_iterator end() const;
/// Load character set from a text file with the correct syntax /// Load character set from a text file with the correct syntax
bool load(const char *filename); bool load(const char *filename, bool disableCharLiterals = false);
private: private:
std::set<unicode_t> codepoints; std::set<unicode_t> codepoints;

View File

@ -1,13 +1,11 @@
#pragma once #pragma once
#include "types.h"
namespace msdf_atlas { namespace msdf_atlas {
/// The glyph box - its bounds in plane and atlas /// The glyph box - its bounds in plane and atlas
struct GlyphBox { struct GlyphBox {
unicode_t codepoint; int index;
double advance; double advance;
struct { struct {
double l, b, r, t; double l, b, r, t;

View File

@ -6,11 +6,12 @@
namespace msdf_atlas { namespace msdf_atlas {
GlyphGeometry::GlyphGeometry() : codepoint(), bounds(), advance(), box() { } GlyphGeometry::GlyphGeometry() : index(), codepoint(), bounds(), advance(), box() { }
bool GlyphGeometry::load(msdfgen::FontHandle *font, unicode_t codepoint, bool preprocessGeometry) { bool GlyphGeometry::load(msdfgen::FontHandle *font, msdfgen::GlyphIndex index, bool preprocessGeometry) {
if (font && msdfgen::loadGlyph(shape, font, codepoint, &advance) && shape.validate()) { if (font && msdfgen::loadGlyph(shape, font, index, &advance) && shape.validate()) {
this->codepoint = codepoint; this->index = index.getIndex();
codepoint = 0;
#ifdef MSDFGEN_USE_SKIA #ifdef MSDFGEN_USE_SKIA
if (preprocessGeometry) if (preprocessGeometry)
msdfgen::resolveShapeGeometry(shape); msdfgen::resolveShapeGeometry(shape);
@ -33,6 +34,17 @@ bool GlyphGeometry::load(msdfgen::FontHandle *font, unicode_t codepoint, bool pr
return false; return false;
} }
bool GlyphGeometry::load(msdfgen::FontHandle *font, unicode_t codepoint, bool preprocessGeometry) {
msdfgen::GlyphIndex index;
if (msdfgen::getGlyphIndex(index, font, codepoint)) {
if (load(font, index, preprocessGeometry)) {
this->codepoint = codepoint;
return true;
}
}
return false;
}
void GlyphGeometry::edgeColoring(double angleThreshold, unsigned long long seed) { void GlyphGeometry::edgeColoring(double angleThreshold, unsigned long long seed) {
msdfgen::edgeColoringInkTrap(shape, angleThreshold, seed); msdfgen::edgeColoringInkTrap(shape, angleThreshold, seed);
} }
@ -62,10 +74,28 @@ void GlyphGeometry::placeBox(int x, int y) {
box.rect.x = x, box.rect.y = 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 { unicode_t GlyphGeometry::getCodepoint() const {
return codepoint; 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 { const msdfgen::Shape & GlyphGeometry::getShape() const {
return shape; return shape;
} }
@ -121,7 +151,7 @@ bool GlyphGeometry::isWhitespace() const {
GlyphGeometry::operator GlyphBox() const { GlyphGeometry::operator GlyphBox() const {
GlyphBox box; GlyphBox box;
box.codepoint = codepoint; box.index = index;
box.advance = advance; box.advance = advance;
getQuadPlaneBounds(box.bounds.l, box.bounds.b, box.bounds.r, box.bounds.t); 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; 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;

View File

@ -14,6 +14,7 @@ class GlyphGeometry {
public: public:
GlyphGeometry(); GlyphGeometry();
/// Loads glyph geometry from font /// Loads glyph geometry from font
bool load(msdfgen::FontHandle *font, msdfgen::GlyphIndex index, bool preprocessGeometry = true);
bool load(msdfgen::FontHandle *font, unicode_t codepoint, bool preprocessGeometry = true); bool load(msdfgen::FontHandle *font, unicode_t codepoint, bool preprocessGeometry = true);
/// Applies edge coloring to glyph shape /// Applies edge coloring to glyph shape
void edgeColoring(double angleThreshold, unsigned long long seed); void edgeColoring(double angleThreshold, unsigned long long seed);
@ -21,8 +22,14 @@ public:
void wrapBox(double scale, double range, double miterLimit); void wrapBox(double scale, double range, double miterLimit);
/// Sets the glyph's box's position in the atlas /// Sets the glyph's box's position in the atlas
void placeBox(int x, int y); void placeBox(int x, int y);
/// Returns the glyph's Unicode index /// 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; 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 /// Returns the glyph's shape
const msdfgen::Shape & getShape() const; const msdfgen::Shape & getShape() const;
/// Returns the glyph's advance /// Returns the glyph's advance
@ -47,6 +54,7 @@ public:
operator GlyphBox() const; operator GlyphBox() const;
private: private:
int index;
unicode_t codepoint; unicode_t codepoint;
msdfgen::Shape shape; msdfgen::Shape shape;
msdfgen::Shape::Bounds bounds; msdfgen::Shape::Bounds bounds;

View File

@ -24,6 +24,16 @@ static artery_font::ImageType convertImageType(ImageType imageType) {
return artery_font::IMAGE_NONE; 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 <typename T, int N> template <typename T, int N>
static bool encodeTiff(std::vector<byte> &output, const msdfgen::BitmapConstRef<T, N> &atlas) { static bool encodeTiff(std::vector<byte> &output, const msdfgen::BitmapConstRef<T, N> &atlas) {
// TODO // TODO
@ -43,7 +53,7 @@ artery_font::PixelFormat getPixelFormat<float>() {
} }
template <typename REAL, typename T, int N> template <typename REAL, typename T, int N>
bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, const msdfgen::BitmapConstRef<T, N> &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename) { bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, const msdfgen::BitmapConstRef<T, N> &atlas, const char *filename, const ArteryFontExportProperties &properties) {
artery_font::StdArteryFont<REAL> arfont = { }; artery_font::StdArteryFont<REAL> arfont = { };
arfont.metadataFormat = artery_font::METADATA_NONE; arfont.metadataFormat = artery_font::METADATA_NONE;
@ -53,11 +63,11 @@ bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, in
return false; return false;
double fsScale = 1/fontMetrics.emSize; double fsScale = 1/fontMetrics.emSize;
artery_font::StdFontVariant<REAL> fontVariant = { }; artery_font::StdFontVariant<REAL> fontVariant = { };
fontVariant.codepointType = artery_font::CP_UNICODE; fontVariant.codepointType = convertCodepointType(properties.glyphIdentifierType);
fontVariant.imageType = convertImageType(imageType); fontVariant.imageType = convertImageType(properties.imageType);
fontVariant.metrics.fontSize = REAL(fontSize); fontVariant.metrics.fontSize = REAL(properties.fontSize);
if (imageType != ImageType::HARD_MASK) if (properties.imageType != ImageType::HARD_MASK)
fontVariant.metrics.distanceRange = REAL(pxRange); fontVariant.metrics.distanceRange = REAL(properties.pxRange);
fontVariant.metrics.emSize = REAL(fsScale*fontMetrics.emSize); fontVariant.metrics.emSize = REAL(fsScale*fontMetrics.emSize);
fontVariant.metrics.ascender = REAL(fsScale*fontMetrics.ascenderY); fontVariant.metrics.ascender = REAL(fsScale*fontMetrics.ascenderY);
fontVariant.metrics.descender = REAL(fsScale*fontMetrics.descenderY); fontVariant.metrics.descender = REAL(fsScale*fontMetrics.descenderY);
@ -67,7 +77,7 @@ bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, in
fontVariant.glyphs = artery_font::StdList<artery_font::Glyph<REAL> >(glyphCount); fontVariant.glyphs = artery_font::StdList<artery_font::Glyph<REAL> >(glyphCount);
for (int i = 0; i < glyphCount; ++i) { for (int i = 0; i < glyphCount; ++i) {
artery_font::Glyph<REAL> &glyph = fontVariant.glyphs[i]; artery_font::Glyph<REAL> &glyph = fontVariant.glyphs[i];
glyph.codepoint = glyphs[i].getCodepoint(); glyph.codepoint = glyphs[i].getIdentifier(properties.glyphIdentifierType);
glyph.image = 0; glyph.image = 0;
double l, b, r, t; double l, b, r, t;
glyphs[i].getQuadPlaneBounds(l, b, r, t); glyphs[i].getQuadPlaneBounds(l, b, r, t);
@ -84,10 +94,10 @@ bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, in
glyph.advance.v = REAL(0); glyph.advance.v = REAL(0);
for (int j = 0; j < glyphCount; ++j) { for (int j = 0; j < glyphCount; ++j) {
double kerning; double kerning;
if (msdfgen::getKerning(kerning, font, glyphs[i].getCodepoint(), glyphs[j].getCodepoint()) && kerning) { if (msdfgen::getKerning(kerning, font, glyphs[i].getGlyphIndex(), glyphs[j].getGlyphIndex()) && kerning) {
artery_font::KernPair<REAL> kernPair = { }; artery_font::KernPair<REAL> kernPair = { };
kernPair.codepoint1 = glyphs[i].getCodepoint(); kernPair.codepoint1 = glyphs[i].getIdentifier(properties.glyphIdentifierType);
kernPair.codepoint2 = glyphs[j].getCodepoint(); kernPair.codepoint2 = glyphs[j].getIdentifier(properties.glyphIdentifierType);
kernPair.advance.h = REAL(fsScale*kerning); kernPair.advance.h = REAL(fsScale*kerning);
fontVariant.kernPairs.vector.push_back((artery_font::KernPair<REAL> &&) kernPair); fontVariant.kernPairs.vector.push_back((artery_font::KernPair<REAL> &&) kernPair);
} }
@ -101,8 +111,8 @@ bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, in
image.width = atlas.width; image.width = atlas.width;
image.height = atlas.height; image.height = atlas.height;
image.channels = N; image.channels = N;
image.imageType = convertImageType(imageType); image.imageType = convertImageType(properties.imageType);
switch (imageFormat) { switch (properties.imageFormat) {
case ImageFormat::PNG: case ImageFormat::PNG:
image.encoding = artery_font::IMAGE_PNG; image.encoding = artery_font::IMAGE_PNG;
image.pixelFormat = artery_font::PIXEL_UNSIGNED8; image.pixelFormat = artery_font::PIXEL_UNSIGNED8;
@ -139,11 +149,11 @@ bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, in
return artery_font::writeFile(arfont, filename); return artery_font::writeFile(arfont, filename);
} }
template bool exportArteryFont<float>(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, const msdfgen::BitmapConstRef<byte, 1> &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename); template bool exportArteryFont<float>(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, const msdfgen::BitmapConstRef<byte, 1> &atlas, const char *filename, const ArteryFontExportProperties &properties);
template bool exportArteryFont<float>(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, const msdfgen::BitmapConstRef<byte, 3> &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename); template bool exportArteryFont<float>(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, const msdfgen::BitmapConstRef<byte, 3> &atlas, const char *filename, const ArteryFontExportProperties &properties);
template bool exportArteryFont<float>(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, const msdfgen::BitmapConstRef<byte, 4> &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename); template bool exportArteryFont<float>(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, const msdfgen::BitmapConstRef<byte, 4> &atlas, const char *filename, const ArteryFontExportProperties &properties);
template bool exportArteryFont<float>(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, const msdfgen::BitmapConstRef<float, 1> &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename); template bool exportArteryFont<float>(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, const msdfgen::BitmapConstRef<float, 1> &atlas, const char *filename, const ArteryFontExportProperties &properties);
template bool exportArteryFont<float>(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, const msdfgen::BitmapConstRef<float, 3> &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename); template bool exportArteryFont<float>(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, const msdfgen::BitmapConstRef<float, 3> &atlas, const char *filename, const ArteryFontExportProperties &properties);
template bool exportArteryFont<float>(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, const msdfgen::BitmapConstRef<float, 4> &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename); template bool exportArteryFont<float>(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, const msdfgen::BitmapConstRef<float, 4> &atlas, const char *filename, const ArteryFontExportProperties &properties);
} }

View File

@ -8,8 +8,16 @@
namespace msdf_atlas { namespace msdf_atlas {
struct ArteryFontExportProperties {
GlyphIdentifierType glyphIdentifierType;
double fontSize;
double pxRange;
ImageType imageType;
ImageFormat imageFormat;
};
/// Encodes the atlas bitmap and its layout into an Artery Atlas Font file /// Encodes the atlas bitmap and its layout into an Artery Atlas Font file
template <typename REAL, typename T, int N> template <typename REAL, typename T, int N>
bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, const msdfgen::BitmapConstRef<T, N> &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename); bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, const msdfgen::BitmapConstRef<T, N> &atlas, const char *filename, const ArteryFontExportProperties &properties);
} }

View File

@ -96,7 +96,7 @@ static std::string combinePath(const char *basePath, const char *relPath) {
return std::string(basePath, lastSlash+1)+relPath; return std::string(basePath, lastSlash+1)+relPath;
} }
bool Charset::load(const char *filename) { bool Charset::load(const char *filename, bool disableCharLiterals) {
if (FILE *f = fopen(filename, "rb")) { if (FILE *f = fopen(filename, "rb")) {
@ -125,7 +125,7 @@ bool Charset::load(const char *filename) {
goto FAIL; goto FAIL;
switch (state) { switch (state) {
case CLEAR: case CLEAR:
if (cp > 0) if (cp >= 0)
add((unicode_t) cp); add((unicode_t) cp);
state = TIGHT; state = TIGHT;
break; break;
@ -144,7 +144,7 @@ bool Charset::load(const char *filename) {
buffer.clear(); buffer.clear();
continue; // next character already read continue; // next character already read
case '\'': // single UTF-8 character case '\'': // single UTF-8 character
if (!(state == CLEAR || state == RANGE_BRACKET || state == RANGE_SEPARATOR)) if (!(state == CLEAR || state == RANGE_BRACKET || state == RANGE_SEPARATOR) || disableCharLiterals)
goto FAIL; goto FAIL;
if (!readString(buffer, f, '\'')) if (!readString(buffer, f, '\''))
goto FAIL; goto FAIL;
@ -173,7 +173,7 @@ bool Charset::load(const char *filename) {
buffer.clear(); buffer.clear();
break; break;
case '"': // string of UTF-8 characters case '"': // string of UTF-8 characters
if (state != CLEAR) if (state != CLEAR || disableCharLiterals)
goto FAIL; goto FAIL;
if (!readString(buffer, f, '"')) if (!readString(buffer, f, '"'))
goto FAIL; goto FAIL;

View File

@ -5,7 +5,7 @@
namespace msdf_atlas { namespace msdf_atlas {
bool exportCSV(const GlyphGeometry *glyphs, int glyphCount, double emSize, const char *filename) { bool exportCSV(const GlyphGeometry *glyphs, int glyphCount, GlyphIdentifierType glyphIdentifierType, double emSize, const char *filename) {
FILE *f = fopen(filename, "w"); FILE *f = fopen(filename, "w");
if (!f) if (!f)
return false; return false;
@ -13,7 +13,7 @@ bool exportCSV(const GlyphGeometry *glyphs, int glyphCount, double emSize, const
double fsScale = 1/emSize; double fsScale = 1/emSize;
for (int i = 0; i < glyphCount; ++i) { for (int i = 0; i < glyphCount; ++i) {
double l, b, r, t; double l, b, r, t;
fprintf(f, "%u,%.17g,", glyphs[i].getCodepoint(), fsScale*glyphs[i].getAdvance()); fprintf(f, "%d,%.17g,", glyphs[i].getIdentifier(glyphIdentifierType), fsScale*glyphs[i].getAdvance());
glyphs[i].getQuadPlaneBounds(l, b, r, t); glyphs[i].getQuadPlaneBounds(l, b, r, t);
fprintf(f, "%.17g,%.17g,%.17g,%.17g,", fsScale*l, fsScale*b, fsScale*r, fsScale*t); fprintf(f, "%.17g,%.17g,%.17g,%.17g,", fsScale*l, fsScale*b, fsScale*r, fsScale*t);
glyphs[i].getQuadAtlasBounds(l, b, r, t); glyphs[i].getQuadAtlasBounds(l, b, r, t);

View File

@ -1,14 +1,15 @@
#pragma once #pragma once
#include "types.h"
#include "GlyphGeometry.h" #include "GlyphGeometry.h"
namespace msdf_atlas { namespace msdf_atlas {
/** /**
* Writes the positioning data and atlas layout of the glyphs into a CSV file * 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) * The columns are: glyph identifier (index or Unicode), 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); bool exportCSV(const GlyphGeometry *glyphs, int glyphCount, GlyphIdentifierType glyphIdentifierType, double emSize, const char *filename);
} }

View File

@ -21,7 +21,7 @@ static const char * imageTypeString(ImageType type) {
return nullptr; return nullptr;
} }
bool exportJSON(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, int atlasWidth, int atlasHeight, ImageType imageType, const char *filename) { bool exportJSON(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, GlyphIdentifierType glyphIdentifierType, double fontSize, double pxRange, int atlasWidth, int atlasHeight, ImageType imageType, const char *filename) {
msdfgen::FontMetrics fontMetrics; msdfgen::FontMetrics fontMetrics;
if (!msdfgen::getFontMetrics(fontMetrics, font)) if (!msdfgen::getFontMetrics(fontMetrics, font))
return false; return false;
@ -56,7 +56,14 @@ bool exportJSON(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyp
fputs("\"glyphs\":[", f); fputs("\"glyphs\":[", f);
for (int i = 0; i < glyphCount; ++i) { for (int i = 0; i < glyphCount; ++i) {
fputs(i == 0 ? "{" : ",{", f); fputs(i == 0 ? "{" : ",{", f);
switch (glyphIdentifierType) {
case GlyphIdentifierType::GLYPH_INDEX:
fprintf(f, "\"index\":%d,", glyphs[i].getIndex());
break;
case GlyphIdentifierType::UNICODE_CODEPOINT:
fprintf(f, "\"unicode\":%u,", glyphs[i].getCodepoint()); fprintf(f, "\"unicode\":%u,", glyphs[i].getCodepoint());
break;
}
fprintf(f, "\"advance\":%.17g", fsScale*glyphs[i].getAdvance()); fprintf(f, "\"advance\":%.17g", fsScale*glyphs[i].getAdvance());
double l, b, r, t; double l, b, r, t;
glyphs[i].getQuadPlaneBounds(l, b, r, t); glyphs[i].getQuadPlaneBounds(l, b, r, t);

View File

@ -9,6 +9,6 @@
namespace msdf_atlas { namespace msdf_atlas {
/// Writes the font and glyph metrics and atlas layout data into a comprehensive JSON file /// Writes the font and glyph metrics and atlas layout data into a comprehensive JSON file
bool exportJSON(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, int atlasWidth, int atlasHeight, ImageType imageType, const char *filename); bool exportJSON(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, GlyphIdentifierType glyphIdentifierType, double fontSize, double pxRange, int atlasWidth, int atlasHeight, ImageType imageType, const char *filename);
} }

View File

@ -44,6 +44,8 @@ INPUT SPECIFICATION
Specifies the input TrueType / OpenType font file. This is required. Specifies the input TrueType / OpenType font file. This is required.
-charset <filename> -charset <filename>
Specifies the input character set. Refer to the documentation for format of charset specification. Defaults to ASCII. Specifies the input character set. Refer to the documentation for format of charset specification. Defaults to ASCII.
-glyphset <filename>
Specifies the set of input glyphs as glyph indices within the font file.
ATLAS CONFIGURATION ATLAS CONFIGURATION
-type <hardmask / softmask / sdf / psdf / msdf / mtsdf> -type <hardmask / softmask / sdf / psdf / msdf / mtsdf>
@ -145,7 +147,19 @@ static bool cmpExtension(const char *path, const char *ext) {
return true; return true;
} }
static void loadGlyphs(std::vector<GlyphGeometry> &glyphs, msdfgen::FontHandle *font, const Charset &charset, bool preprocessGeometry) { static void loadGlyphsByIndex(std::vector<GlyphGeometry> &glyphs, msdfgen::FontHandle *font, const Charset &charset, bool preprocessGeometry) {
glyphs.clear();
glyphs.reserve(charset.size());
for (unicode_t cp : charset) {
GlyphGeometry glyph;
if (glyph.load(font, msdfgen::GlyphIndex(cp), preprocessGeometry))
glyphs.push_back((GlyphGeometry &&) glyph);
else
printf("Glyph # 0x%X missing\n", cp);
}
}
static void loadGlyphsByUnicode(std::vector<GlyphGeometry> &glyphs, msdfgen::FontHandle *font, const Charset &charset, bool preprocessGeometry) {
glyphs.clear(); glyphs.clear();
glyphs.reserve(charset.size()); glyphs.reserve(charset.size());
for (unicode_t cp : charset) { for (unicode_t cp : charset) {
@ -158,6 +172,7 @@ static void loadGlyphs(std::vector<GlyphGeometry> &glyphs, msdfgen::FontHandle *
} }
struct Configuration { struct Configuration {
GlyphIdentifierType glyphIdentifierType;
ImageType imageType; ImageType imageType;
ImageFormat imageFormat; ImageFormat imageFormat;
int width, height; int width, height;
@ -197,7 +212,13 @@ static bool makeAtlas(const std::vector<GlyphGeometry> &glyphs, msdfgen::FontHan
} }
if (config.arteryFontFilename) { if (config.arteryFontFilename) {
if (exportArteryFont<float>(font, glyphs.data(), glyphs.size(), config.emSize, config.pxRange, bitmap, config.imageType, config.imageFormat, config.arteryFontFilename)) ArteryFontExportProperties arfontProps;
arfontProps.glyphIdentifierType = config.glyphIdentifierType;
arfontProps.fontSize = config.emSize;
arfontProps.pxRange = config.pxRange;
arfontProps.imageType = config.imageType;
arfontProps.imageFormat = config.imageFormat;
if (exportArteryFont<float>(font, glyphs.data(), glyphs.size(), bitmap, config.arteryFontFilename, arfontProps))
puts("Artery Font file generated."); puts("Artery Font file generated.");
else { else {
success = false; success = false;
@ -215,6 +236,7 @@ int main(int argc, const char * const *argv) {
Configuration config = { }; Configuration config = { };
const char *fontFilename = nullptr; const char *fontFilename = nullptr;
const char *charsetFilename = nullptr; const char *charsetFilename = nullptr;
config.glyphIdentifierType = GlyphIdentifierType::UNICODE_CODEPOINT;
config.imageType = ImageType::MSDF; config.imageType = ImageType::MSDF;
config.imageFormat = ImageFormat::UNSPECIFIED; config.imageFormat = ImageFormat::UNSPECIFIED;
const char *imageFormatName = nullptr; const char *imageFormatName = nullptr;
@ -299,6 +321,13 @@ int main(int argc, const char * const *argv) {
} }
ARG_CASE("-charset", 1) { ARG_CASE("-charset", 1) {
charsetFilename = argv[++argPos]; charsetFilename = argv[++argPos];
config.glyphIdentifierType = GlyphIdentifierType::UNICODE_CODEPOINT;
++argPos;
continue;
}
ARG_CASE("-glyphset", 1) {
charsetFilename = argv[++argPos];
config.glyphIdentifierType = GlyphIdentifierType::GLYPH_INDEX;
++argPos; ++argPos;
continue; continue;
} }
@ -593,17 +622,24 @@ int main(int argc, const char * const *argv) {
// Load character set // Load character set
Charset charset; Charset charset;
if (charsetFilename) { if (charsetFilename) {
if (!charset.load(charsetFilename)) if (!charset.load(charsetFilename, config.glyphIdentifierType != GlyphIdentifierType::UNICODE_CODEPOINT))
ABORT("Failed to load character set specification."); ABORT(config.glyphIdentifierType == GlyphIdentifierType::GLYPH_INDEX ? "Failed to load glyph set specification." : "Failed to load character set specification.");
} else } else
charset = Charset::ASCII; charset = Charset::ASCII;
if (charset.empty())
ABORT("No character set loaded.");
// Load glyphs // Load glyphs
std::vector<GlyphGeometry> glyphs; std::vector<GlyphGeometry> glyphs;
loadGlyphs(glyphs, font, charset, config.preprocessGeometry); switch (config.glyphIdentifierType) {
printf("Loaded geometry of %d out of %d characters.\n", (int) glyphs.size(), (int) charset.size()); case GlyphIdentifierType::GLYPH_INDEX:
loadGlyphsByIndex(glyphs, font, charset, config.preprocessGeometry);
break;
case GlyphIdentifierType::UNICODE_CODEPOINT:
loadGlyphsByUnicode(glyphs, font, charset, config.preprocessGeometry);
break;
}
if (glyphs.empty())
ABORT("No glyphs loaded.");
printf("Loaded geometry of %d out of %d %s.\n", (int) glyphs.size(), (int) charset.size(), config.glyphIdentifierType == GlyphIdentifierType::GLYPH_INDEX ? "glyphs" : "characters");
// Determine final atlas dimensions, scale and range, pack glyphs // Determine final atlas dimensions, scale and range, pack glyphs
{ {
@ -709,7 +745,7 @@ int main(int argc, const char * const *argv) {
} }
if (config.csvFilename) { if (config.csvFilename) {
if (exportCSV(glyphs.data(), glyphs.size(), fontMetrics.emSize, config.csvFilename)) if (exportCSV(glyphs.data(), glyphs.size(), config.glyphIdentifierType, fontMetrics.emSize, config.csvFilename))
puts("Glyph layout written into CSV file."); puts("Glyph layout written into CSV file.");
else { else {
result = 1; result = 1;
@ -717,7 +753,7 @@ int main(int argc, const char * const *argv) {
} }
} }
if (config.jsonFilename) { if (config.jsonFilename) {
if (exportJSON(font, glyphs.data(), glyphs.size(), config.emSize, config.pxRange, config.width, config.height, config.imageType, config.jsonFilename)) if (exportJSON(font, glyphs.data(), glyphs.size(), config.glyphIdentifierType, config.emSize, config.pxRange, config.width, config.height, config.imageType, config.jsonFilename))
puts("Glyph layout and metadata written into JSON file."); puts("Glyph layout and metadata written into JSON file.");
else { else {
result = 1; result = 1;
@ -726,6 +762,7 @@ int main(int argc, const char * const *argv) {
} }
if (config.shadronPreviewFilename && config.shadronPreviewText) { if (config.shadronPreviewFilename && config.shadronPreviewText) {
if (config.glyphIdentifierType == GlyphIdentifierType::UNICODE_CODEPOINT) {
std::vector<unicode_t> previewText; std::vector<unicode_t> previewText;
utf8Decode(previewText, config.shadronPreviewText); utf8Decode(previewText, config.shadronPreviewText);
previewText.push_back(0); previewText.push_back(0);
@ -735,6 +772,10 @@ int main(int argc, const char * const *argv) {
result = 1; result = 1;
puts("Failed to generate Shadron preview file."); puts("Failed to generate Shadron preview file.");
} }
} else {
result = 1;
puts("Shadron preview not supported in -glyphset mode.");
}
} }
return result; return result;

View File

@ -37,4 +37,10 @@ enum class ImageFormat {
BINARY_FLOAT_BE BINARY_FLOAT_BE
}; };
/// Glyph identification
enum class GlyphIdentifierType {
GLYPH_INDEX,
UNICODE_CODEPOINT
};
} }