Support for multiple fonts in one atlas

This commit is contained in:
Chlumsky 2021-03-13 01:21:44 +01:00 committed by Viktor Chlumský
parent e44853e47e
commit 690ae5fb16
17 changed files with 643 additions and 240 deletions

View File

@ -45,6 +45,8 @@ Use the following command line arguments for the standalone version of the atlas
- `-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).
- `-fontscale <scale>` &ndash; applies a scaling transformation to the font's glyphs. Mainly to be used to generate multiple sizes in a single atlas, otherwise use [`-size`](#glyph-configuration).
- `-and` &ndash; separates multiple inputs to be combined into a single atlas.
### Bitmap atlas type

View File

@ -314,6 +314,7 @@
<ClCompile Include="msdf-atlas-gen\charset-parser.cpp" />
<ClCompile Include="msdf-atlas-gen\Charset.cpp" />
<ClCompile Include="msdf-atlas-gen\csv-export.cpp" />
<ClCompile Include="msdf-atlas-gen\FontGeometry.cpp" />
<ClCompile Include="msdf-atlas-gen\glyph-generators.cpp" />
<ClCompile Include="msdf-atlas-gen\GlyphGeometry.cpp" />
<ClCompile Include="msdf-atlas-gen\image-encode.cpp" />
@ -337,6 +338,7 @@
<ClInclude Include="msdf-atlas-gen\csv-export.h" />
<ClInclude Include="msdf-atlas-gen\DynamicAtlas.h" />
<ClInclude Include="msdf-atlas-gen\DynamicAtlas.hpp" />
<ClInclude Include="msdf-atlas-gen\FontGeometry.h" />
<ClInclude Include="msdf-atlas-gen\glyph-generators.h" />
<ClInclude Include="msdf-atlas-gen\image-encode.h" />
<ClInclude Include="msdf-atlas-gen\Charset.h" />

View File

@ -66,6 +66,9 @@
<ClCompile Include="msdf-atlas-gen\Workload.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="msdf-atlas-gen\FontGeometry.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="resource.h">
@ -164,6 +167,9 @@
<ClInclude Include="msdf-atlas-gen\Workload.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\FontGeometry.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="msdf-atlas-gen.rc">

View File

@ -0,0 +1,172 @@
#include "FontGeometry.h"
namespace msdf_atlas {
FontGeometry::GlyphRange::GlyphRange() : glyphs(), rangeStart(), rangeEnd() { }
FontGeometry::GlyphRange::GlyphRange(const std::vector<GlyphGeometry> *glyphs, size_t rangeStart, size_t rangeEnd) : glyphs(glyphs), rangeStart(rangeStart), rangeEnd(rangeEnd) { }
size_t FontGeometry::GlyphRange::size() const {
return glyphs->size();
}
bool FontGeometry::GlyphRange::empty() const {
return glyphs->empty();
}
const GlyphGeometry * FontGeometry::GlyphRange::begin() const {
return glyphs->data()+rangeStart;
}
const GlyphGeometry * FontGeometry::GlyphRange::end() const {
return glyphs->data()+rangeEnd;
}
FontGeometry::FontGeometry() : geometryScale(1), metrics(), preferredIdentifierType(GlyphIdentifierType::UNICODE_CODEPOINT), glyphs(&ownGlyphs), rangeStart(glyphs->size()), rangeEnd(glyphs->size()) { }
FontGeometry::FontGeometry(std::vector<GlyphGeometry> *glyphStorage) : geometryScale(1), metrics(), preferredIdentifierType(GlyphIdentifierType::UNICODE_CODEPOINT), glyphs(glyphStorage), rangeStart(glyphs->size()), rangeEnd(glyphs->size()) { }
int FontGeometry::loadGlyphset(msdfgen::FontHandle *font, double fontScale, const Charset &glyphset, bool preprocessGeometry, bool enableKerning) {
if (!(glyphs->size() == rangeEnd && loadMetrics(font, fontScale)))
return -1;
glyphs->reserve(glyphs->size()+glyphset.size());
int loaded = 0;
for (unicode_t index : glyphset) {
GlyphGeometry glyph;
if (glyph.load(font, geometryScale, msdfgen::GlyphIndex(index), preprocessGeometry)) {
addGlyph((GlyphGeometry &&) glyph);
++loaded;
}
}
if (enableKerning)
loadKerning(font);
preferredIdentifierType = GlyphIdentifierType::GLYPH_INDEX;
return loaded;
}
int FontGeometry::loadCharset(msdfgen::FontHandle *font, double fontScale, const Charset &charset, bool preprocessGeometry, bool enableKerning) {
if (!(glyphs->size() == rangeEnd && loadMetrics(font, fontScale)))
return -1;
glyphs->reserve(glyphs->size()+charset.size());
int loaded = 0;
for (unicode_t cp : charset) {
GlyphGeometry glyph;
if (glyph.load(font, geometryScale, cp, preprocessGeometry)) {
addGlyph((GlyphGeometry &&) glyph);
++loaded;
}
}
if (enableKerning)
loadKerning(font);
preferredIdentifierType = GlyphIdentifierType::UNICODE_CODEPOINT;
return loaded;
}
bool FontGeometry::loadMetrics(msdfgen::FontHandle *font, double fontScale) {
if (!msdfgen::getFontMetrics(metrics, font))
return false;
if (metrics.emSize <= 0)
metrics.emSize = MSDF_ATLAS_DEFAULT_EM_SIZE;
geometryScale = fontScale/metrics.emSize;
metrics.emSize *= geometryScale;
metrics.ascenderY *= geometryScale;
metrics.descenderY *= geometryScale;
metrics.lineHeight *= geometryScale;
metrics.underlineY *= geometryScale;
metrics.underlineThickness *= geometryScale;
return true;
}
bool FontGeometry::addGlyph(const GlyphGeometry &glyph) {
if (glyphs->size() != rangeEnd)
return false;
glyphsByIndex.insert(std::make_pair(glyph.getIndex(), rangeEnd));
if (glyph.getCodepoint())
glyphsByCodepoint.insert(std::make_pair(glyph.getCodepoint(), rangeEnd));
glyphs->push_back(glyph);
++rangeEnd;
return true;
}
bool FontGeometry::addGlyph(GlyphGeometry &&glyph) {
if (glyphs->size() != rangeEnd)
return false;
glyphsByIndex.insert(std::make_pair(glyph.getIndex(), rangeEnd));
if (glyph.getCodepoint())
glyphsByCodepoint.insert(std::make_pair(glyph.getCodepoint(), rangeEnd));
glyphs->push_back((GlyphGeometry &&) glyph);
++rangeEnd;
return true;
}
int FontGeometry::loadKerning(msdfgen::FontHandle *font) {
int loaded = 0;
for (size_t i = rangeStart; i < rangeEnd; ++i)
for (size_t j = rangeStart; j < rangeEnd; ++j) {
double advance;
if (msdfgen::getKerning(advance, font, (*glyphs)[i].getGlyphIndex(), (*glyphs)[j].getGlyphIndex()) && advance) {
kerning[std::make_pair<int, int>((*glyphs)[i].getIndex(), (*glyphs)[j].getIndex())] = geometryScale*advance;
++loaded;
}
}
return loaded;
}
double FontGeometry::getGeometryScale() const {
return geometryScale;
}
const msdfgen::FontMetrics & FontGeometry::getMetrics() const {
return metrics;
}
GlyphIdentifierType FontGeometry::getPreferredIdentifierType() const {
return preferredIdentifierType;
}
FontGeometry::GlyphRange FontGeometry::getGlyphs() const {
return GlyphRange(glyphs, rangeStart, rangeEnd);
}
const GlyphGeometry * FontGeometry::getGlyph(msdfgen::GlyphIndex index) const {
std::map<int, size_t>::const_iterator it = glyphsByIndex.find(index.getIndex());
if (it != glyphsByIndex.end())
return &(*glyphs)[it->second];
return nullptr;
}
const GlyphGeometry * FontGeometry::getGlyph(unicode_t codepoint) const {
std::map<unicode_t, size_t>::const_iterator it = glyphsByCodepoint.find(codepoint);
if (it != glyphsByCodepoint.end())
return &(*glyphs)[it->second];
return nullptr;
}
bool FontGeometry::getAdvance(double &advance, msdfgen::GlyphIndex index1, msdfgen::GlyphIndex index2) const {
const GlyphGeometry *glyph1 = getGlyph(index1);
if (!glyph1)
return false;
advance = glyph1->getAdvance();
std::map<std::pair<int, int>, double>::const_iterator it = kerning.find(std::make_pair<int, int>(index1.getIndex(), index2.getIndex()));
if (it != kerning.end())
advance += it->second;
return true;
}
bool FontGeometry::getAdvance(double &advance, unicode_t codepoint1, unicode_t codepoint2) const {
const GlyphGeometry *glyph1, *glyph2;
if (!((glyph1 = getGlyph(codepoint1)) && (glyph2 = getGlyph(codepoint2))))
return false;
advance = glyph1->getAdvance();
std::map<std::pair<int, int>, double>::const_iterator it = kerning.find(std::make_pair<int, int>(glyph1->getIndex(), glyph2->getIndex()));
if (it != kerning.end())
advance += it->second;
return true;
}
const std::map<std::pair<int, int>, double> & FontGeometry::getKerning() const {
return kerning;
}
}

View File

@ -0,0 +1,80 @@
#pragma once
#include <utility>
#include <vector>
#include <map>
#include <msdfgen.h>
#include <msdfgen-ext.h>
#include "types.h"
#include "GlyphGeometry.h"
#include "Charset.h"
#define MSDF_ATLAS_DEFAULT_EM_SIZE 32.0
namespace msdf_atlas {
/// Represents the geometry of all glyphs of a given font or font variant
class FontGeometry {
public:
class GlyphRange {
public:
GlyphRange();
GlyphRange(const std::vector<GlyphGeometry> *glyphs, size_t rangeStart, size_t rangeEnd);
size_t size() const;
bool empty() const;
const GlyphGeometry * begin() const;
const GlyphGeometry * end() const;
private:
const std::vector<GlyphGeometry> *glyphs;
size_t rangeStart, rangeEnd;
};
FontGeometry();
explicit FontGeometry(std::vector<GlyphGeometry> *glyphStorage);
/// Loads all glyphs in a glyphset (Charset elements are glyph indices), returns the number of successfully loaded glyphs
int loadGlyphset(msdfgen::FontHandle *font, double fontScale, const Charset &glyphset, bool preprocessGeometry = true, bool enableKerning = true);
/// Loads all glyphs in a charset (Charset elements are Unicode codepoints), returns the number of successfully loaded glyphs
int loadCharset(msdfgen::FontHandle *font, double fontScale, const Charset &charset, bool preprocessGeometry = true, bool enableKerning = true);
/// Only loads font metrics and geometry scale from font
bool loadMetrics(msdfgen::FontHandle *font, double fontScale);
/// Adds a loaded glyph
bool addGlyph(const GlyphGeometry &glyph);
bool addGlyph(GlyphGeometry &&glyph);
/// Loads kerning pairs for all glyphs that are currently present, returns the number of loaded kerning pairs
int loadKerning(msdfgen::FontHandle *font);
/// Returns the geometry scale to be used when loading glyphs
double getGeometryScale() const;
/// Returns the processed font metrics
const msdfgen::FontMetrics & getMetrics() const;
/// Returns the type of identifier that was used to load glyphs
GlyphIdentifierType getPreferredIdentifierType() const;
/// Returns the list of all glyphs
GlyphRange getGlyphs() const;
/// Finds a glyph by glyph index or Unicode codepoint, returns null if not found
const GlyphGeometry * getGlyph(msdfgen::GlyphIndex index) const;
const GlyphGeometry * getGlyph(unicode_t codepoint) const;
/// Outputs the advance between two glyphs with kerning taken into consideration, returns false on failure
bool getAdvance(double &advance, msdfgen::GlyphIndex index1, msdfgen::GlyphIndex index2) const;
bool getAdvance(double &advance, unicode_t codepoint1, unicode_t codepoint2) const;
/// Returns the complete mapping of kerning pairs (by glyph indices) and their respective advance values
const std::map<std::pair<int, int>, double> & getKerning() const;
private:
double geometryScale;
msdfgen::FontMetrics metrics;
GlyphIdentifierType preferredIdentifierType;
std::vector<GlyphGeometry> *glyphs;
size_t rangeStart, rangeEnd;
std::map<int, size_t> glyphsByIndex;
std::map<unicode_t, size_t> glyphsByCodepoint;
std::map<std::pair<int, int>, double> kerning;
std::vector<GlyphGeometry> ownGlyphs;
};
}

View File

@ -6,12 +6,14 @@
namespace msdf_atlas {
GlyphGeometry::GlyphGeometry() : index(), codepoint(), bounds(), advance(), box() { }
GlyphGeometry::GlyphGeometry() : index(), codepoint(), geometryScale(), bounds(), advance(), box() { }
bool GlyphGeometry::load(msdfgen::FontHandle *font, msdfgen::GlyphIndex index, bool preprocessGeometry) {
bool GlyphGeometry::load(msdfgen::FontHandle *font, double geometryScale, msdfgen::GlyphIndex index, bool preprocessGeometry) {
if (font && msdfgen::loadGlyph(shape, font, index, &advance) && shape.validate()) {
this->index = index.getIndex();
this->geometryScale = geometryScale;
codepoint = 0;
advance *= geometryScale;
#ifdef MSDFGEN_USE_SKIA
if (preprocessGeometry)
msdfgen::resolveShapeGeometry(shape);
@ -34,10 +36,10 @@ bool GlyphGeometry::load(msdfgen::FontHandle *font, msdfgen::GlyphIndex index, b
return false;
}
bool GlyphGeometry::load(msdfgen::FontHandle *font, unicode_t codepoint, bool preprocessGeometry) {
bool GlyphGeometry::load(msdfgen::FontHandle *font, double geometryScale, unicode_t codepoint, bool preprocessGeometry) {
msdfgen::GlyphIndex index;
if (msdfgen::getGlyphIndex(index, font, codepoint)) {
if (load(font, index, preprocessGeometry)) {
if (load(font, geometryScale, index, preprocessGeometry)) {
this->codepoint = codepoint;
return true;
}
@ -50,6 +52,8 @@ void GlyphGeometry::edgeColoring(double angleThreshold, unsigned long long seed)
}
void GlyphGeometry::wrapBox(double scale, double range, double miterLimit) {
scale *= geometryScale;
range /= geometryScale;
box.range = range;
box.scale = scale;
if (bounds.l < bounds.r && bounds.b < bounds.t) {
@ -127,10 +131,11 @@ msdfgen::Vector2 GlyphGeometry::getBoxTranslate() const {
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;
double invBoxScale = 1/box.scale;
l = geometryScale*(-box.translate.x+.5*invBoxScale);
b = geometryScale*(-box.translate.y+.5*invBoxScale);
r = geometryScale*(-box.translate.x+(box.rect.w-.5)*invBoxScale);
t = geometryScale*(-box.translate.y+(box.rect.h-.5)*invBoxScale);
} else
l = 0, b = 0, r = 0, t = 0;
}

View File

@ -8,14 +8,14 @@
namespace msdf_atlas {
/// Represent's the shape geometry of a single glyph as well as its configuration
/// Represents the shape geometry of a single glyph as well as its configuration
class GlyphGeometry {
public:
GlyphGeometry();
/// Loads glyph geometry from font
bool load(msdfgen::FontHandle *font, msdfgen::GlyphIndex index, bool preprocessGeometry = true);
bool load(msdfgen::FontHandle *font, unicode_t codepoint, bool preprocessGeometry = true);
bool load(msdfgen::FontHandle *font, double geometryScale, msdfgen::GlyphIndex index, bool preprocessGeometry = true);
bool load(msdfgen::FontHandle *font, double geometryScale, unicode_t codepoint, bool preprocessGeometry = true);
/// Applies edge coloring to glyph shape
void edgeColoring(double angleThreshold, unsigned long long seed);
/// Computes the dimensions of the glyph's box as well as the transformation for the generator function
@ -56,6 +56,7 @@ public:
private:
int index;
unicode_t codepoint;
double geometryScale;
msdfgen::Shape shape;
msdfgen::Shape::Bounds bounds;
double advance;

View File

@ -3,6 +3,7 @@
#include <artery-font/std-artery-font.h>
#include <artery-font/stdio-serialization.h>
#include "GlyphGeometry.h"
#include "image-encode.h"
namespace msdf_atlas {
@ -53,55 +54,69 @@ artery_font::PixelFormat getPixelFormat<float>() {
}
template <typename REAL, typename T, int N>
bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, const msdfgen::BitmapConstRef<T, N> &atlas, const char *filename, const ArteryFontExportProperties &properties) {
bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef<T, N> &atlas, const char *filename, const ArteryFontExportProperties &properties) {
artery_font::StdArteryFont<REAL> 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;
for (int i = 0; i < fontCount; ++i) {
const FontGeometry &font = fonts[i];
GlyphIdentifierType identifierType = font.getPreferredIdentifierType();
const msdfgen::FontMetrics &fontMetrics = font.getMetrics();
artery_font::StdFontVariant<REAL> fontVariant = { };
fontVariant.codepointType = convertCodepointType(properties.glyphIdentifierType);
fontVariant.codepointType = convertCodepointType(identifierType);
fontVariant.imageType = convertImageType(properties.imageType);
fontVariant.metrics.fontSize = REAL(properties.fontSize);
fontVariant.metrics.fontSize = REAL(properties.fontSize*fontMetrics.emSize);
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);
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<artery_font::Glyph<REAL> >(glyphCount);
for (int i = 0; i < glyphCount; ++i) {
artery_font::Glyph<REAL> &glyph = fontVariant.glyphs[i];
glyph.codepoint = glyphs[i].getIdentifier(properties.glyphIdentifierType);
fontVariant.metrics.emSize = REAL(fontMetrics.emSize);
fontVariant.metrics.ascender = REAL(fontMetrics.ascenderY);
fontVariant.metrics.descender = REAL(fontMetrics.descenderY);
fontVariant.metrics.lineHeight = REAL(fontMetrics.lineHeight);
fontVariant.metrics.underlineY = REAL(fontMetrics.underlineY);
fontVariant.metrics.underlineThickness = REAL(fontMetrics.underlineThickness);
fontVariant.glyphs = artery_font::StdList<artery_font::Glyph<REAL> >(font.getGlyphs().size());
int j = 0;
for (const GlyphGeometry &glyphGeom : font.getGlyphs()) {
artery_font::Glyph<REAL> &glyph = fontVariant.glyphs[j++];
glyph.codepoint = glyphGeom.getIdentifier(identifierType);
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);
glyphGeom.getQuadPlaneBounds(l, b, r, t);
glyph.planeBounds.l = REAL(l);
glyph.planeBounds.b = REAL(b);
glyph.planeBounds.r = REAL(r);
glyph.planeBounds.t = REAL(t);
glyphGeom.getQuadAtlasBounds(l, b, r, t);
glyph.imageBounds.l = REAL(l);
glyph.imageBounds.b = REAL(b);
glyph.imageBounds.r = REAL(r);
glyph.imageBounds.t = REAL(t);
glyph.advance.h = REAL(fsScale*glyphs[i].getAdvance());
glyph.advance.h = REAL(glyphGeom.getAdvance());
glyph.advance.v = REAL(0);
for (int j = 0; j < glyphCount; ++j) {
double kerning;
if (msdfgen::getKerning(kerning, font, glyphs[i].getGlyphIndex(), glyphs[j].getGlyphIndex()) && kerning) {
}
switch (identifierType) {
case GlyphIdentifierType::GLYPH_INDEX:
for (const std::pair<std::pair<int, int>, double> &elem : font.getKerning()) {
artery_font::KernPair<REAL> kernPair = { };
kernPair.codepoint1 = glyphs[i].getIdentifier(properties.glyphIdentifierType);
kernPair.codepoint2 = glyphs[j].getIdentifier(properties.glyphIdentifierType);
kernPair.advance.h = REAL(fsScale*kerning);
kernPair.codepoint1 = elem.first.first;
kernPair.codepoint2 = elem.first.second;
kernPair.advance.h = REAL(elem.second);
fontVariant.kernPairs.vector.push_back((artery_font::KernPair<REAL> &&) kernPair);
}
}
break;
case GlyphIdentifierType::UNICODE_CODEPOINT:
for (const std::pair<std::pair<int, int>, double> &elem : font.getKerning()) {
const GlyphGeometry *glyph1 = font.getGlyph(msdfgen::GlyphIndex(elem.first.first));
const GlyphGeometry *glyph2 = font.getGlyph(msdfgen::GlyphIndex(elem.first.second));
if (glyph1 && glyph2 && glyph1->getCodepoint() && glyph2->getCodepoint()) {
artery_font::KernPair<REAL> kernPair = { };
kernPair.codepoint1 = glyph1->getCodepoint();
kernPair.codepoint2 = glyph2->getCodepoint();
kernPair.advance.h = REAL(elem.second);
fontVariant.kernPairs.vector.push_back((artery_font::KernPair<REAL> &&) kernPair);
}
}
break;
}
arfont.variants.vector.push_back((artery_font::StdFontVariant<REAL> &&) fontVariant);
}
@ -149,11 +164,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, 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);
template bool exportArteryFont<float>(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef<byte, 1> &atlas, const char *filename, const ArteryFontExportProperties &properties);
template bool exportArteryFont<float>(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef<byte, 3> &atlas, const char *filename, const ArteryFontExportProperties &properties);
template bool exportArteryFont<float>(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef<byte, 4> &atlas, const char *filename, const ArteryFontExportProperties &properties);
template bool exportArteryFont<float>(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef<float, 1> &atlas, const char *filename, const ArteryFontExportProperties &properties);
template bool exportArteryFont<float>(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef<float, 3> &atlas, const char *filename, const ArteryFontExportProperties &properties);
template bool exportArteryFont<float>(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef<float, 4> &atlas, const char *filename, const ArteryFontExportProperties &properties);
}

View File

@ -4,12 +4,11 @@
#include <msdfgen.h>
#include <msdfgen-ext.h>
#include "types.h"
#include "GlyphGeometry.h"
#include "FontGeometry.h"
namespace msdf_atlas {
struct ArteryFontExportProperties {
GlyphIdentifierType glyphIdentifierType;
double fontSize;
double pxRange;
ImageType imageType;
@ -18,6 +17,6 @@ struct ArteryFontExportProperties {
/// 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, const msdfgen::BitmapConstRef<T, N> &atlas, const char *filename, const ArteryFontExportProperties &properties);
bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef<T, N> &atlas, const char *filename, const ArteryFontExportProperties &properties);
}

View File

@ -2,22 +2,26 @@
#include "csv-export.h"
#include <cstdio>
#include "GlyphGeometry.h"
namespace msdf_atlas {
bool exportCSV(const GlyphGeometry *glyphs, int glyphCount, GlyphIdentifierType glyphIdentifierType, double emSize, const char *filename) {
bool exportCSV(const FontGeometry *fonts, int fontCount, 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, "%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);
fprintf(f, "%.17g,%.17g,%.17g,%.17g\n", l, b, r, t);
for (int i = 0; i < fontCount; ++i) {
for (const GlyphGeometry &glyph : fonts[i].getGlyphs()) {
double l, b, r, t;
if (fontCount > 1)
fprintf(f, "%d,", i);
fprintf(f, "%d,%.17g,", glyph.getIdentifier(fonts[i].getPreferredIdentifierType()), glyph.getAdvance());
glyph.getQuadPlaneBounds(l, b, r, t);
fprintf(f, "%.17g,%.17g,%.17g,%.17g,", l, b, r, t);
glyph.getQuadAtlasBounds(l, b, r, t);
fprintf(f, "%.17g,%.17g,%.17g,%.17g\n", l, b, r, t);
}
}
fclose(f);

View File

@ -1,15 +1,14 @@
#pragma once
#include "types.h"
#include "GlyphGeometry.h"
#include "FontGeometry.h"
namespace msdf_atlas {
/**
* Writes the positioning data and atlas layout of the glyphs into a CSV file
* The columns are: glyph identifier (index or Unicode), horizontal advance, plane bounds (l, b, r, t), atlas bounds (l, b, r, t)
* The columns are: font variant index (if fontCount > 1), glyph identifier (index or Unicode), horizontal advance, plane bounds (l, b, r, t), atlas bounds (l, b, r, t)
*/
bool exportCSV(const GlyphGeometry *glyphs, int glyphCount, GlyphIdentifierType glyphIdentifierType, double emSize, const char *filename);
bool exportCSV(const FontGeometry *fonts, int fontCount, const char *filename);
}

View File

@ -1,6 +1,8 @@
#include "json-export.h"
#include "GlyphGeometry.h"
namespace msdf_atlas {
static const char * imageTypeString(ImageType type) {
@ -21,12 +23,7 @@ static const char * imageTypeString(ImageType type) {
return nullptr;
}
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;
if (!msdfgen::getFontMetrics(fontMetrics, font))
return false;
double fsScale = 1/fontMetrics.emSize;
bool exportJSON(const FontGeometry *fonts, int fontCount, double fontSize, double pxRange, int atlasWidth, int atlasHeight, ImageType imageType, const char *filename, bool kerning) {
FILE *f = fopen(filename, "w");
if (!f)
return false;
@ -43,56 +40,86 @@ bool exportJSON(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyp
fputs("\"yOrigin\":\"bottom\"", f);
} fputs("},", f);
// Font metrics
fputs("\"metrics\":{", f); {
fprintf(f, "\"lineHeight\":%.17g,", fsScale*fontMetrics.lineHeight);
fprintf(f, "\"ascender\":%.17g,", fsScale*fontMetrics.ascenderY);
fprintf(f, "\"descender\":%.17g,", fsScale*fontMetrics.descenderY);
fprintf(f, "\"underlineY\":%.17g,", fsScale*fontMetrics.underlineY);
fprintf(f, "\"underlineThickness\":%.17g", fsScale*fontMetrics.underlineThickness);
} fputs("},", f);
if (fontCount > 1)
fputs("\"variants\":[", f);
for (int i = 0; i < fontCount; ++i) {
const FontGeometry &font = fonts[i];
if (fontCount > 1)
fputs(i == 0 ? "{" : ",{", f);
// Glyph mapping
fputs("\"glyphs\":[", f);
for (int i = 0; i < glyphCount; ++i) {
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());
break;
}
fprintf(f, "\"advance\":%.17g", fsScale*glyphs[i].getAdvance());
double l, b, r, t;
glyphs[i].getQuadPlaneBounds(l, b, r, t);
if (l || b || r || t)
fprintf(f, ",\"planeBounds\":{\"left\":%.17g,\"bottom\":%.17g,\"right\":%.17g,\"top\":%.17g}", fsScale*l, fsScale*b, fsScale*r, fsScale*t);
glyphs[i].getQuadAtlasBounds(l, b, r, t);
if (l || b || r || t)
fprintf(f, ",\"atlasBounds\":{\"left\":%.17g,\"bottom\":%.17g,\"right\":%.17g,\"top\":%.17g}", l, b, r, t);
fputs("}", f);
}
fputs("],", f);
// Font metrics
fputs("\"metrics\":{", f); {
const msdfgen::FontMetrics &metrics = font.getMetrics();
fprintf(f, "\"emSize\":%.17g,", metrics.emSize);
fprintf(f, "\"lineHeight\":%.17g,", metrics.lineHeight);
fprintf(f, "\"ascender\":%.17g,", metrics.ascenderY);
fprintf(f, "\"descender\":%.17g,", metrics.descenderY);
fprintf(f, "\"underlineY\":%.17g,", metrics.underlineY);
fprintf(f, "\"underlineThickness\":%.17g", metrics.underlineThickness);
} fputs("},", f);
// Kerning pairs
fputs("\"kerning\":[", f);
bool firstPair = true;
for (int i = 0; i < glyphCount; ++i) {
for (int j = 0; j < glyphCount; ++j) {
double kerning;
if (msdfgen::getKerning(kerning, font, glyphs[i].getCodepoint(), glyphs[j].getCodepoint()) && kerning) {
fputs(firstPair ? "{" : ",{", f);
fprintf(f, "\"unicode1\":%u,", glyphs[i].getCodepoint());
fprintf(f, "\"unicode2\":%u,", glyphs[j].getCodepoint());
fprintf(f, "\"advance\":%.17g", fsScale*kerning);
fputs("}", f);
firstPair = false;
// Glyph mapping
fputs("\"glyphs\":[", f);
bool firstGlyph = true;
for (const GlyphGeometry &glyph : font.getGlyphs()) {
fputs(firstGlyph ? "{" : ",{", f);
switch (font.getPreferredIdentifierType()) {
case GlyphIdentifierType::GLYPH_INDEX:
fprintf(f, "\"index\":%d,", glyph.getIndex());
break;
case GlyphIdentifierType::UNICODE_CODEPOINT:
fprintf(f, "\"unicode\":%u,", glyph.getCodepoint());
break;
}
fprintf(f, "\"advance\":%.17g", glyph.getAdvance());
double l, b, r, t;
glyph.getQuadPlaneBounds(l, b, r, t);
if (l || b || r || t)
fprintf(f, ",\"planeBounds\":{\"left\":%.17g,\"bottom\":%.17g,\"right\":%.17g,\"top\":%.17g}", l, b, r, t);
glyph.getQuadAtlasBounds(l, b, r, t);
if (l || b || r || t)
fprintf(f, ",\"atlasBounds\":{\"left\":%.17g,\"bottom\":%.17g,\"right\":%.17g,\"top\":%.17g}", l, b, r, t);
fputs("}", f);
firstGlyph = false;
} fputs("]", f);
// Kerning pairs
if (kerning) {
fputs(",\"kerning\":[", f);
bool firstPair = true;
switch (font.getPreferredIdentifierType()) {
case GlyphIdentifierType::GLYPH_INDEX:
for (const std::pair<std::pair<int, int>, double> &kernPair : font.getKerning()) {
fputs(firstPair ? "{" : ",{", f);
fprintf(f, "\"index1\":%d,", kernPair.first.first);
fprintf(f, "\"index2\":%d,", kernPair.first.second);
fprintf(f, "\"advance\":%.17g", kernPair.second);
fputs("}", f);
firstPair = false;
}
break;
case GlyphIdentifierType::UNICODE_CODEPOINT:
for (const std::pair<std::pair<int, int>, double> &kernPair : font.getKerning()) {
const GlyphGeometry *glyph1 = font.getGlyph(msdfgen::GlyphIndex(kernPair.first.first));
const GlyphGeometry *glyph2 = font.getGlyph(msdfgen::GlyphIndex(kernPair.first.second));
if (glyph1 && glyph2 && glyph1->getCodepoint() && glyph2->getCodepoint()) {
fputs(firstPair ? "{" : ",{", f);
fprintf(f, "\"unicode1\":%u,", glyph1->getCodepoint());
fprintf(f, "\"unicode2\":%u,", glyph2->getCodepoint());
fprintf(f, "\"advance\":%.17g", kernPair.second);
fputs("}", f);
firstPair = false;
}
}
break;
} fputs("]", f);
}
if (fontCount > 1)
fputs("}", f);
}
fputs("]", f);
if (fontCount > 1)
fputs("]", f);
fputs("}\n", f);
fclose(f);

View File

@ -4,11 +4,11 @@
#include <msdfgen.h>
#include <msdfgen-ext.h>
#include "types.h"
#include "GlyphGeometry.h"
#include "FontGeometry.h"
namespace msdf_atlas {
/// 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, GlyphIdentifierType glyphIdentifierType, double fontSize, double pxRange, int atlasWidth, int atlasHeight, ImageType imageType, const char *filename);
bool exportJSON(const FontGeometry *fonts, int fontCount, double fontSize, double pxRange, int atlasWidth, int atlasHeight, ImageType imageType, const char *filename, bool kerning);
}

View File

@ -11,7 +11,9 @@
#define _USE_MATH_DEFINES
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cassert>
#include <vector>
#include <algorithm>
#include <thread>
@ -21,7 +23,6 @@ using namespace msdf_atlas;
#define DEFAULT_ANGLE_THRESHOLD 3.0
#define DEFAULT_MITER_LIMIT 1.0
#define DEFAULT_EM_SIZE 32.0
#define DEFAULT_PIXEL_RANGE 2.0
#define SDF_ERROR_ESTIMATE_PRECISION 19
#define GLYPH_FILL_RULE msdfgen::FILL_NONZERO
@ -46,6 +47,10 @@ INPUT SPECIFICATION
Specifies the input character set. Refer to the documentation for format of charset specification. Defaults to ASCII.
-glyphset <filename>
Specifies the set of input glyphs as glyph indices within the font file.
-fontscale <scale>
Specifies the scale to be applied to the glyph geometry of the font.
-and
Separates multiple inputs to be combined into a single atlas.
ATLAS CONFIGURATION
-type <hardmask / softmask / sdf / psdf / msdf / mtsdf>
@ -79,6 +84,8 @@ GLYPH CONFIGURATION
Specifies the SDF distance range in EM's.
-pxrange <pixel range>
Specifies the SDF distance range in output pixels. The default value is 2.
-nokerning
Disables inclusion of kerning pair table in output files.
DISTANCE FIELD GENERATOR SETTINGS
-angle <angle>
@ -147,32 +154,14 @@ static bool cmpExtension(const char *path, const char *ext) {
return true;
}
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.reserve(charset.size());
for (unicode_t cp : charset) {
GlyphGeometry glyph;
if (glyph.load(font, cp, preprocessGeometry))
glyphs.push_back((GlyphGeometry &&) glyph);
else
printf("Glyph for codepoint 0x%X missing\n", cp);
}
}
struct FontInput {
const char *fontFilename;
GlyphIdentifierType glyphIdentifierType;
const char *charsetFilename;
double fontScale;
};
struct Configuration {
GlyphIdentifierType glyphIdentifierType;
ImageType imageType;
ImageFormat imageFormat;
int width, height;
@ -183,6 +172,7 @@ struct Configuration {
unsigned long long coloringSeed;
GeneratorAttributes generatorAttributes;
bool preprocessGeometry;
bool kerning;
int threadCount;
const char *arteryFontFilename;
const char *imageFilename;
@ -193,7 +183,7 @@ struct Configuration {
};
template <typename T, typename S, int N, GeneratorFunction<S, N> GEN_FN>
static bool makeAtlas(const std::vector<GlyphGeometry> &glyphs, msdfgen::FontHandle *font, const Configuration &config) {
static bool makeAtlas(const std::vector<GlyphGeometry> &glyphs, const std::vector<FontGeometry> &fonts, const Configuration &config) {
ImmediateAtlasGenerator<S, N, GEN_FN, BitmapAtlasStorage<T, N> > generator(config.width, config.height);
generator.setAttributes(config.generatorAttributes);
generator.setThreadCount(config.threadCount);
@ -213,12 +203,11 @@ static bool makeAtlas(const std::vector<GlyphGeometry> &glyphs, msdfgen::FontHan
if (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))
if (exportArteryFont<float>(fonts.data(), fonts.size(), bitmap, config.arteryFontFilename, arfontProps))
puts("Artery Font file generated.");
else {
success = false;
@ -233,10 +222,12 @@ int main(int argc, const char * const *argv) {
#define ABORT(msg) { puts(msg); return 1; }
int result = 0;
std::vector<FontInput> fontInputs;
FontInput fontInput = { };
Configuration config = { };
const char *fontFilename = nullptr;
const char *charsetFilename = nullptr;
config.glyphIdentifierType = GlyphIdentifierType::UNICODE_CODEPOINT;
fontInput.glyphIdentifierType = GlyphIdentifierType::UNICODE_CODEPOINT;
fontInput.fontScale = -1;
config.kerning = true;
config.imageType = ImageType::MSDF;
config.imageFormat = ImageFormat::UNSPECIFIED;
const char *imageFormatName = nullptr;
@ -315,19 +306,36 @@ int main(int argc, const char * const *argv) {
continue;
}
ARG_CASE("-font", 1) {
fontFilename = argv[++argPos];
fontInput.fontFilename = argv[++argPos];
++argPos;
continue;
}
ARG_CASE("-charset", 1) {
charsetFilename = argv[++argPos];
config.glyphIdentifierType = GlyphIdentifierType::UNICODE_CODEPOINT;
fontInput.charsetFilename = argv[++argPos];
fontInput.glyphIdentifierType = GlyphIdentifierType::UNICODE_CODEPOINT;
++argPos;
continue;
}
ARG_CASE("-glyphset", 1) {
charsetFilename = argv[++argPos];
config.glyphIdentifierType = GlyphIdentifierType::GLYPH_INDEX;
fontInput.charsetFilename = argv[++argPos];
fontInput.glyphIdentifierType = GlyphIdentifierType::GLYPH_INDEX;
++argPos;
continue;
}
ARG_CASE("-fontscale", 1) {
double fs;
if (!(parseDouble(fs, argv[++argPos]) && fs > 0))
ABORT("Invalid font scale argument. Use -fontscale <font scale> with a positive real number.");
fontInput.fontScale = fs;
++argPos;
continue;
}
ARG_CASE("-and", 0) {
if (!fontInput.fontFilename && !fontInput.charsetFilename && fontInput.fontScale < 0)
ABORT("No font, character set, or font scale specified before -and separator.");
if (!fontInputs.empty() && !memcmp(&fontInputs.back(), &fontInput, sizeof(FontInput)))
ABORT("No changes between subsequent inputs. A different font, character set, or font scale must be set inbetween -and separators.");
fontInputs.push_back(fontInput);
++argPos;
continue;
}
@ -453,34 +461,44 @@ int main(int argc, const char * const *argv) {
++argPos;
continue;
}
ARG_CASE("-nokerning", 0) {
config.kerning = false;
++argPos;
continue;
}
ARG_CASE("-kerning", 0) {
config.kerning = true;
++argPos;
continue;
}
ARG_CASE("-nopreprocess", 0) {
config.preprocessGeometry = false;
argPos += 1;
++argPos;
continue;
}
ARG_CASE("-preprocess", 0) {
config.preprocessGeometry = true;
argPos += 1;
++argPos;
continue;
}
ARG_CASE("-nooverlap", 0) {
config.generatorAttributes.overlapSupport = false;
argPos += 1;
++argPos;
continue;
}
ARG_CASE("-overlap", 0) {
config.generatorAttributes.overlapSupport = true;
argPos += 1;
++argPos;
continue;
}
ARG_CASE("-noscanline", 0) {
config.generatorAttributes.scanlinePass = false;
argPos += 1;
++argPos;
continue;
}
ARG_CASE("-scanline", 0) {
config.generatorAttributes.scanlinePass = true;
argPos += 1;
++argPos;
continue;
}
ARG_CASE("-seed", 1) {
@ -520,7 +538,7 @@ int main(int argc, const char * const *argv) {
);
return 0;
}
if (!fontFilename)
if (!fontInput.fontFilename)
ABORT("No font specified.");
if (!(config.arteryFontFilename || config.imageFilename || config.jsonFilename || config.csvFilename || config.shadronPreviewFilename)) {
puts("No output specified.");
@ -528,6 +546,22 @@ int main(int argc, const char * const *argv) {
}
bool layoutOnly = !(config.arteryFontFilename || config.imageFilename);
// Finalize font inputs
const FontInput *nextFontInput = &fontInput;
for (std::vector<FontInput>::reverse_iterator it = fontInputs.rbegin(); it != fontInputs.rend(); ++it) {
if (!it->fontFilename && nextFontInput->fontFilename)
it->fontFilename = nextFontInput->fontFilename;
if (!it->charsetFilename && nextFontInput->charsetFilename) {
it->charsetFilename = nextFontInput->charsetFilename;
it->glyphIdentifierType = nextFontInput->glyphIdentifierType;
}
if (it->fontScale < 0 && nextFontInput->fontScale >= 0)
it->fontScale = nextFontInput->fontScale;
nextFontInput = &*it;
}
if (fontInputs.empty() || memcmp(&fontInputs.back(), &fontInput, sizeof(FontInput)))
fontInputs.push_back(fontInput);
// Fix up configuration based on related values
if (!(config.imageType == ImageType::PSDF || config.imageType == ImageType::MSDF || config.imageType == ImageType::MTSDF))
config.miterLimit = 0;
@ -535,7 +569,7 @@ int main(int argc, const char * const *argv) {
minEmSize = config.emSize;
if (!(fixedWidth > 0 && fixedHeight > 0) && !(minEmSize > 0)) {
puts("Neither atlas size nor glyph size selected, using default...");
minEmSize = DEFAULT_EM_SIZE;
minEmSize = MSDF_ATLAS_DEFAULT_EM_SIZE;
}
if (!(config.imageType == ImageType::SDF || config.imageType == ImageType::PSDF || config.imageType == ImageType::MSDF || config.imageType == ImageType::MTSDF)) {
rangeMode = RANGE_PIXEL;
@ -544,6 +578,8 @@ int main(int argc, const char * const *argv) {
rangeMode = RANGE_PIXEL;
rangeValue = DEFAULT_PIXEL_RANGE;
}
if (config.kerning && !(config.arteryFontFilename || config.jsonFilename || config.shadronPreviewFilename))
config.kerning = false;
if (config.threadCount <= 0)
config.threadCount = std::max((int) std::thread::hardware_concurrency(), 1);
@ -598,61 +634,108 @@ int main(int argc, const char * const *argv) {
config.imageFormat == ImageFormat::BINARY_FLOAT_BE
);
// Load font
class FontHolder {
msdfgen::FreetypeHandle *ft;
msdfgen::FontHandle *font;
public:
explicit FontHolder(const char *fontFilename) : ft(nullptr), font(nullptr) {
if ((ft = msdfgen::initializeFreetype()))
font = msdfgen::loadFont(ft, fontFilename);
}
~FontHolder() {
if (ft) {
if (font)
msdfgen::destroyFont(font);
msdfgen::deinitializeFreetype(ft);
}
}
operator msdfgen::FontHandle *() const {
return font;
}
} font(fontFilename);
if (!font)
ABORT("Failed to load specified font file.");
msdfgen::FontMetrics fontMetrics = { };
msdfgen::getFontMetrics(fontMetrics, font);
if (fontMetrics.emSize <= 0)
fontMetrics.emSize = DEFAULT_EM_SIZE;
// Load character set
Charset charset;
if (charsetFilename) {
if (!charset.load(charsetFilename, config.glyphIdentifierType != GlyphIdentifierType::UNICODE_CODEPOINT))
ABORT(config.glyphIdentifierType == GlyphIdentifierType::GLYPH_INDEX ? "Failed to load glyph set specification." : "Failed to load character set specification.");
} else
charset = Charset::ASCII;
// Load glyphs
// Load fonts
std::vector<GlyphGeometry> glyphs;
switch (config.glyphIdentifierType) {
case GlyphIdentifierType::GLYPH_INDEX:
loadGlyphsByIndex(glyphs, font, charset, config.preprocessGeometry);
break;
case GlyphIdentifierType::UNICODE_CODEPOINT:
loadGlyphsByUnicode(glyphs, font, charset, config.preprocessGeometry);
break;
std::vector<FontGeometry> fonts;
bool anyCodepointsAvailable = false;
{
class FontHolder {
msdfgen::FreetypeHandle *ft;
msdfgen::FontHandle *font;
const char *fontFilename;
public:
FontHolder() : ft(msdfgen::initializeFreetype()), font(nullptr), fontFilename(nullptr) { }
~FontHolder() {
if (ft) {
if (font)
msdfgen::destroyFont(font);
msdfgen::deinitializeFreetype(ft);
}
}
bool load(const char *fontFilename) {
if (ft && fontFilename) {
if (this->fontFilename && !strcmp(this->fontFilename, fontFilename))
return true;
if (font)
msdfgen::destroyFont(font);
if ((font = msdfgen::loadFont(ft, fontFilename))) {
this->fontFilename = fontFilename;
return true;
}
this->fontFilename = nullptr;
}
return false;
}
operator msdfgen::FontHandle *() const {
return font;
}
} font;
for (FontInput &fontInput : fontInputs) {
if (!font.load(fontInput.fontFilename))
ABORT("Failed to load specified font file.");
if (fontInput.fontScale <= 0)
fontInput.fontScale = 1;
// Load character set
Charset charset;
if (fontInput.charsetFilename) {
if (!charset.load(fontInput.charsetFilename, fontInput.glyphIdentifierType != GlyphIdentifierType::UNICODE_CODEPOINT))
ABORT(fontInput.glyphIdentifierType == GlyphIdentifierType::GLYPH_INDEX ? "Failed to load glyph set specification." : "Failed to load character set specification.");
} else {
charset = Charset::ASCII;
fontInput.glyphIdentifierType = GlyphIdentifierType::UNICODE_CODEPOINT;
}
// Load glyphs
FontGeometry fontGeometry(&glyphs);
int glyphsLoaded = -1;
switch (fontInput.glyphIdentifierType) {
case GlyphIdentifierType::GLYPH_INDEX:
glyphsLoaded = fontGeometry.loadGlyphset(font, fontInput.fontScale, charset, config.preprocessGeometry, config.kerning);
break;
case GlyphIdentifierType::UNICODE_CODEPOINT:
glyphsLoaded = fontGeometry.loadCharset(font, fontInput.fontScale, charset, config.preprocessGeometry, config.kerning);
anyCodepointsAvailable |= glyphsLoaded > 0;
break;
}
if (glyphsLoaded < 0)
ABORT("Failed to load glyphs from font.");
printf("Loaded geometry of %d out of %d glyphs", glyphsLoaded, (int) charset.size());
if (fontInputs.size() > 1)
printf(" from font \"%s\"", fontInput.fontFilename);
printf(".\n");
// List missing glyphs
if (glyphsLoaded < (int) charset.size()) {
printf("Missing %d %s", (int) charset.size()-glyphsLoaded, fontInput.glyphIdentifierType == GlyphIdentifierType::UNICODE_CODEPOINT ? "codepoints" : "glyphs");
bool first = true;
switch (fontInput.glyphIdentifierType) {
case GlyphIdentifierType::GLYPH_INDEX:
for (unicode_t cp : charset)
if (!fontGeometry.getGlyph(msdfgen::GlyphIndex(cp)))
printf("%c 0x%02X", first ? ((first = false), ':') : ',', cp);
break;
case GlyphIdentifierType::UNICODE_CODEPOINT:
for (unicode_t cp : charset)
if (!fontGeometry.getGlyph(cp))
printf("%c 0x%02X", first ? ((first = false), ':') : ',', cp);
break;
}
printf("\n");
}
fonts.push_back((FontGeometry &&) fontGeometry);
}
}
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
{
double unitRange = 0, pxRange = 0;
switch (rangeMode) {
case RANGE_EM:
unitRange = rangeValue*fontMetrics.emSize;
unitRange = rangeValue;
break;
case RANGE_PIXEL:
pxRange = rangeValue;
@ -668,9 +751,9 @@ int main(int argc, const char * const *argv) {
atlasPacker.setPadding(config.imageType == ImageType::MSDF || config.imageType == ImageType::MTSDF ? 0 : -1);
// TODO: In this case (if padding is -1), the border pixels of each glyph are black, but still computed. For floating-point output, this may play a role.
if (fixedScale)
atlasPacker.setScale(config.emSize/fontMetrics.emSize);
atlasPacker.setScale(config.emSize);
else
atlasPacker.setMinimumScale(minEmSize/fontMetrics.emSize);
atlasPacker.setMinimumScale(minEmSize);
atlasPacker.setPixelRange(pxRange);
atlasPacker.setUnitRange(unitRange);
atlasPacker.setMiterLimit(config.miterLimit);
@ -685,7 +768,7 @@ int main(int argc, const char * const *argv) {
atlasPacker.getDimensions(config.width, config.height);
if (!(config.width > 0 && config.height > 0))
ABORT("Unable to determine atlas size.");
config.emSize = atlasPacker.getScale()*fontMetrics.emSize;
config.emSize = atlasPacker.getScale();
config.pxRange = atlasPacker.getPixelRange();
if (!fixedScale)
printf("Glyph size: %.9g pixels/EM\n", config.emSize);
@ -709,34 +792,34 @@ int main(int argc, const char * const *argv) {
switch (config.imageType) {
case ImageType::HARD_MASK:
if (floatingPointFormat)
success = makeAtlas<float, float, 1, scanlineGenerator>(glyphs, font, config);
success = makeAtlas<float, float, 1, scanlineGenerator>(glyphs, fonts, config);
else
success = makeAtlas<byte, float, 1, scanlineGenerator>(glyphs, font, config);
success = makeAtlas<byte, float, 1, scanlineGenerator>(glyphs, fonts, config);
break;
case ImageType::SOFT_MASK:
case ImageType::SDF:
if (floatingPointFormat)
success = makeAtlas<float, float, 1, sdfGenerator>(glyphs, font, config);
success = makeAtlas<float, float, 1, sdfGenerator>(glyphs, fonts, config);
else
success = makeAtlas<byte, float, 1, sdfGenerator>(glyphs, font, config);
success = makeAtlas<byte, float, 1, sdfGenerator>(glyphs, fonts, config);
break;
case ImageType::PSDF:
if (floatingPointFormat)
success = makeAtlas<float, float, 1, psdfGenerator>(glyphs, font, config);
success = makeAtlas<float, float, 1, psdfGenerator>(glyphs, fonts, config);
else
success = makeAtlas<byte, float, 1, psdfGenerator>(glyphs, font, config);
success = makeAtlas<byte, float, 1, psdfGenerator>(glyphs, fonts, config);
break;
case ImageType::MSDF:
if (floatingPointFormat)
success = makeAtlas<float, float, 3, msdfGenerator>(glyphs, font, config);
success = makeAtlas<float, float, 3, msdfGenerator>(glyphs, fonts, config);
else
success = makeAtlas<byte, float, 3, msdfGenerator>(glyphs, font, config);
success = makeAtlas<byte, float, 3, msdfGenerator>(glyphs, fonts, config);
break;
case ImageType::MTSDF:
if (floatingPointFormat)
success = makeAtlas<float, float, 4, mtsdfGenerator>(glyphs, font, config);
success = makeAtlas<float, float, 4, mtsdfGenerator>(glyphs, fonts, config);
else
success = makeAtlas<byte, float, 4, mtsdfGenerator>(glyphs, font, config);
success = makeAtlas<byte, float, 4, mtsdfGenerator>(glyphs, fonts, config);
break;
}
if (!success)
@ -744,7 +827,7 @@ int main(int argc, const char * const *argv) {
}
if (config.csvFilename) {
if (exportCSV(glyphs.data(), glyphs.size(), config.glyphIdentifierType, fontMetrics.emSize, config.csvFilename))
if (exportCSV(fonts.data(), fonts.size(), config.csvFilename))
puts("Glyph layout written into CSV file.");
else {
result = 1;
@ -752,7 +835,7 @@ int main(int argc, const char * const *argv) {
}
}
if (config.jsonFilename) {
if (exportJSON(font, glyphs.data(), glyphs.size(), config.glyphIdentifierType, config.emSize, config.pxRange, config.width, config.height, config.imageType, config.jsonFilename))
if (exportJSON(fonts.data(), fonts.size(), config.emSize, config.pxRange, config.width, config.height, config.imageType, config.jsonFilename, config.kerning))
puts("Glyph layout and metadata written into JSON file.");
else {
result = 1;
@ -761,11 +844,11 @@ int main(int argc, const char * const *argv) {
}
if (config.shadronPreviewFilename && config.shadronPreviewText) {
if (config.glyphIdentifierType == GlyphIdentifierType::UNICODE_CODEPOINT) {
if (anyCodepointsAvailable) {
std::vector<unicode_t> previewText;
utf8Decode(previewText, config.shadronPreviewText);
previewText.push_back(0);
if (generateShadronPreview(font, glyphs.data(), glyphs.size(), config.imageType, config.width, config.height, config.pxRange, previewText.data(), config.imageFilename, floatingPointFormat, config.shadronPreviewFilename))
if (generateShadronPreview(fonts.data(), fonts.size(), config.imageType, config.width, config.height, config.pxRange, previewText.data(), config.imageFilename, floatingPointFormat, config.shadronPreviewFilename))
puts("Shadron preview script generated.");
else {
result = 1;

View File

@ -19,6 +19,7 @@
#include "Charset.h"
#include "GlyphBox.h"
#include "GlyphGeometry.h"
#include "FontGeometry.h"
#include "RectanglePacker.h"
#include "rectangle-packing.h"
#include "Workload.h"

View File

@ -77,9 +77,12 @@ static std::string relativizePath(const char *base, const char *target) {
return output;
}
bool generateShadronPreview(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, ImageType atlasType, int atlasWidth, int atlasHeight, double pxRange, const unicode_t *text, const char *imageFilename, bool fullRange, const char *outputFilename) {
bool generateShadronPreview(const FontGeometry *fonts, int fontCount, ImageType atlasType, int atlasWidth, int atlasHeight, double pxRange, const unicode_t *text, const char *imageFilename, bool fullRange, const char *outputFilename) {
if (fontCount <= 0)
return false;
double texelWidth = 1./atlasWidth;
double texelHeight = 1./atlasHeight;
bool anyGlyphs = false;
FILE *file = fopen(outputFilename, "w");
if (!file)
return false;
@ -91,9 +94,12 @@ bool generateShadronPreview(msdfgen::FontHandle *font, const GlyphGeometry *glyp
fprintf(file, " : %sfilter(%s), map(repeat);\n", fullRange ? "full_range(true), " : "", atlasType == ImageType::HARD_MASK ? "nearest" : "linear");
fprintf(file, "const vec2 txRange = vec2(%.9g, %.9g);\n\n", pxRange*texelWidth, pxRange*texelHeight);
{
msdfgen::FontMetrics fontMetrics;
if (!msdfgen::getFontMetrics(fontMetrics, font))
return false;
msdfgen::FontMetrics fontMetrics = fonts->getMetrics();
for (int i = 1; i < fontCount; ++i) {
fontMetrics.lineHeight = std::max(fontMetrics.lineHeight, fonts[i].getMetrics().lineHeight);
fontMetrics.ascenderY = std::max(fontMetrics.ascenderY, fonts[i].getMetrics().ascenderY);
fontMetrics.descenderY = std::min(fontMetrics.descenderY, fonts[i].getMetrics().descenderY);
}
double fsScale = 1/(fontMetrics.ascenderY-fontMetrics.descenderY);
fputs("vertex_list GlyphVertex textQuadVertices = {\n", file);
double x = 0, y = -fsScale*fontMetrics.ascenderY;
@ -107,13 +113,14 @@ bool generateShadronPreview(msdfgen::FontHandle *font, const GlyphGeometry *glyp
y -= fsScale*fontMetrics.lineHeight;
continue;
}
for (int i = 0; i < glyphCount; ++i) {
if (glyphs[i].getCodepoint() == *cp) {
if (!glyphs[i].isWhitespace()) {
for (int i = 0; i < fontCount; ++i) {
const GlyphGeometry *glyph = fonts[i].getGlyph(*cp);
if (glyph) {
if (!glyph->isWhitespace()) {
double pl, pb, pr, pt;
double il, ib, ir, it;
glyphs[i].getQuadPlaneBounds(pl, pb, pr, pt);
glyphs[i].getQuadAtlasBounds(il, ib, ir, it);
glyph->getQuadPlaneBounds(pl, pb, pr, pt);
glyph->getQuadAtlasBounds(il, ib, ir, it);
pl *= fsScale, pb *= fsScale, pr *= fsScale, pt *= fsScale;
pl += x, pb += y, pr += x, pt += y;
il *= texelWidth, ib *= texelHeight, ir *= texelWidth, it *= texelHeight;
@ -126,10 +133,10 @@ bool generateShadronPreview(msdfgen::FontHandle *font, const GlyphGeometry *glyp
pr, pb, ir, ib
);
}
x += fsScale*glyphs[i].getAdvance();
double kerning;
if (msdfgen::getKerning(kerning, font, cp[0], cp[1]))
x += fsScale*kerning;
double advance = glyph->getAdvance();
fonts[i].getAdvance(advance, cp[0], cp[1]);
x += fsScale*advance;
anyGlyphs = true;
break;
}
}
@ -142,7 +149,7 @@ bool generateShadronPreview(msdfgen::FontHandle *font, const GlyphGeometry *glyp
fputs("PREVIEW_IMAGE(Preview, Atlas, txRange, vec3(1.0), textQuadVertices, textSize, ivec2(1200, 400));\n", file);
fputs("export png(Preview, \"preview.png\");\n", file);
fclose(file);
return true;
return anyGlyphs;
}
}

View File

@ -4,11 +4,11 @@
#include <msdfgen.h>
#include <msdfgen-ext.h>
#include "types.h"
#include "GlyphGeometry.h"
#include "FontGeometry.h"
namespace msdf_atlas {
/// Generates a Shadron script that displays a string using the generated atlas
bool generateShadronPreview(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, ImageType atlasType, int atlasWidth, int atlasHeight, double pxRange, const unicode_t *text, const char *imageFilename, bool fullRange, const char *outputFilename);
bool generateShadronPreview(const FontGeometry *fonts, int fontCount, ImageType atlasType, int atlasWidth, int atlasHeight, double pxRange, const unicode_t *text, const char *imageFilename, bool fullRange, const char *outputFilename);
}