Skip to content

Commit

Permalink
Loading glyphs by glyph index
Browse files Browse the repository at this point in the history
  • Loading branch information
Chlumsky committed Oct 18, 2020
1 parent c42964a commit 60789b8
Show file tree
Hide file tree
Showing 15 changed files with 201 additions and 63 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
23 changes: 17 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,17 @@

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)

## 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.
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).
- See what's new in the [changelog](CHANGELOG.md).

## 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|
|-|-|-|-|-|-|-|
Expand All @@ -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.
- *Hard effects* refers to the support of effects that use pseudo-distance, such as mitered borders or thickness adjustment.

## Getting started

This project can be used either as a library or as a standalone console program.
To start using the program immediately, there is a Windows binary available for download in the ["Releases" section](https://github.com/Chlumsky/msdf-atlas-gen/releases).
To build the project, you may use the included [Visual Studio solution](msdf-atlas-gen.sln) or the [Unix Makefile](Makefile).

## Command line arguments

Use the following command line arguments for the standalone version of the atlas generator.

### Input

- `-font <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`.
- `-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

Expand Down Expand Up @@ -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:

`@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.
2 changes: 1 addition & 1 deletion msdf-atlas-gen/Charset.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class Charset {
std::set<unicode_t>::const_iterator end() const;

/// 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:
std::set<unicode_t> codepoints;
Expand Down
4 changes: 1 addition & 3 deletions msdf-atlas-gen/GlyphBox.h
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@

#pragma once

#include "types.h"

namespace msdf_atlas {

/// The glyph box - its bounds in plane and atlas
struct GlyphBox {
unicode_t codepoint;
int index;
double advance;
struct {
double l, b, r, t;
Expand Down
40 changes: 35 additions & 5 deletions msdf-atlas-gen/GlyphGeometry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@

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) {
if (font && msdfgen::loadGlyph(shape, font, codepoint, &advance) && shape.validate()) {
this->codepoint = codepoint;
bool GlyphGeometry::load(msdfgen::FontHandle *font, msdfgen::GlyphIndex index, bool preprocessGeometry) {
if (font && msdfgen::loadGlyph(shape, font, index, &advance) && shape.validate()) {
this->index = index.getIndex();
codepoint = 0;
#ifdef MSDFGEN_USE_SKIA
if (preprocessGeometry)
msdfgen::resolveShapeGeometry(shape);
Expand All @@ -33,6 +34,17 @@ bool GlyphGeometry::load(msdfgen::FontHandle *font, unicode_t codepoint, bool pr
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) {
msdfgen::edgeColoringInkTrap(shape, angleThreshold, seed);
}
Expand Down Expand Up @@ -62,10 +74,28 @@ void GlyphGeometry::placeBox(int x, int y) {
box.rect.x = x, box.rect.y = y;
}

int GlyphGeometry::getIndex() const {
return index;
}

msdfgen::GlyphIndex GlyphGeometry::getGlyphIndex() const {
return msdfgen::GlyphIndex(index);
}

unicode_t GlyphGeometry::getCodepoint() const {
return codepoint;
}

int GlyphGeometry::getIdentifier(GlyphIdentifierType type) const {
switch (type) {
case GlyphIdentifierType::GLYPH_INDEX:
return index;
case GlyphIdentifierType::UNICODE_CODEPOINT:
return (int) codepoint;
}
return 0;
}

const msdfgen::Shape & GlyphGeometry::getShape() const {
return shape;
}
Expand Down Expand Up @@ -121,7 +151,7 @@ bool GlyphGeometry::isWhitespace() const {

GlyphGeometry::operator GlyphBox() const {
GlyphBox box;
box.codepoint = codepoint;
box.index = index;
box.advance = advance;
getQuadPlaneBounds(box.bounds.l, box.bounds.b, box.bounds.r, box.bounds.t);
box.rect.x = this->box.rect.x, box.rect.y = this->box.rect.y, box.rect.w = this->box.rect.w, box.rect.h = this->box.rect.h;
Expand Down
10 changes: 9 additions & 1 deletion msdf-atlas-gen/GlyphGeometry.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,22 @@ class GlyphGeometry {
public:
GlyphGeometry();
/// 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);
/// 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
/// Returns the glyph's index within the font
int getIndex() const;
/// Returns the glyph's index as a msdfgen::GlyphIndex
msdfgen::GlyphIndex getGlyphIndex() const;
/// Returns the Unicode codepoint represented by the glyph or 0 if unknown
unicode_t getCodepoint() const;
/// Returns the glyph's identifier specified by the supplied identifier type
int getIdentifier(GlyphIdentifierType type) const;
/// Returns the glyph's shape
const msdfgen::Shape & getShape() const;
/// Returns the glyph's advance
Expand All @@ -47,6 +54,7 @@ class GlyphGeometry {
operator GlyphBox() const;

private:
int index;
unicode_t codepoint;
msdfgen::Shape shape;
msdfgen::Shape::Bounds bounds;
Expand Down
46 changes: 28 additions & 18 deletions msdf-atlas-gen/artery-font-export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ static artery_font::ImageType convertImageType(ImageType imageType) {
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>
static bool encodeTiff(std::vector<byte> &output, const msdfgen::BitmapConstRef<T, N> &atlas) {
// TODO
Expand All @@ -43,7 +53,7 @@ artery_font::PixelFormat getPixelFormat<float>() {
}

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 = { };
arfont.metadataFormat = artery_font::METADATA_NONE;

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

}
10 changes: 9 additions & 1 deletion msdf-atlas-gen/artery-font-export.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,16 @@

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
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);

}
8 changes: 4 additions & 4 deletions msdf-atlas-gen/charset-parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ static std::string combinePath(const char *basePath, const char *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")) {

Expand Down Expand Up @@ -125,7 +125,7 @@ bool Charset::load(const char *filename) {
goto FAIL;
switch (state) {
case CLEAR:
if (cp > 0)
if (cp >= 0)
add((unicode_t) cp);
state = TIGHT;
break;
Expand All @@ -144,7 +144,7 @@ bool Charset::load(const char *filename) {
buffer.clear();
continue; // next character already read
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;
if (!readString(buffer, f, '\''))
goto FAIL;
Expand Down Expand Up @@ -173,7 +173,7 @@ bool Charset::load(const char *filename) {
buffer.clear();
break;
case '"': // string of UTF-8 characters
if (state != CLEAR)
if (state != CLEAR || disableCharLiterals)
goto FAIL;
if (!readString(buffer, f, '"'))
goto FAIL;
Expand Down
4 changes: 2 additions & 2 deletions msdf-atlas-gen/csv-export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@

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");
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());
fprintf(f, "%d,%.17g,", glyphs[i].getIdentifier(glyphIdentifierType), 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);
Expand Down
Loading

0 comments on commit 60789b8

Please sign in to comment.