From 690ae5fb16c39f147f698479c2d2881b9b68d971 Mon Sep 17 00:00:00 2001 From: Chlumsky Date: Sat, 13 Mar 2021 01:21:44 +0100 Subject: [PATCH] Support for multiple fonts in one atlas --- README.md | 2 + msdf-atlas-gen.vcxproj | 2 + msdf-atlas-gen.vcxproj.filters | 6 + msdf-atlas-gen/FontGeometry.cpp | 172 +++++++++++ msdf-atlas-gen/FontGeometry.h | 80 +++++ msdf-atlas-gen/GlyphGeometry.cpp | 21 +- msdf-atlas-gen/GlyphGeometry.h | 7 +- msdf-atlas-gen/artery-font-export.cpp | 91 +++--- msdf-atlas-gen/artery-font-export.h | 5 +- msdf-atlas-gen/csv-export.cpp | 22 +- msdf-atlas-gen/csv-export.h | 7 +- msdf-atlas-gen/json-export.cpp | 129 ++++---- msdf-atlas-gen/json-export.h | 4 +- msdf-atlas-gen/main.cpp | 295 ++++++++++++------- msdf-atlas-gen/msdf-atlas-gen.h | 1 + msdf-atlas-gen/shadron-preview-generator.cpp | 35 ++- msdf-atlas-gen/shadron-preview-generator.h | 4 +- 17 files changed, 643 insertions(+), 240 deletions(-) create mode 100644 msdf-atlas-gen/FontGeometry.cpp create mode 100644 msdf-atlas-gen/FontGeometry.h diff --git a/README.md b/README.md index 26a2fb0..9f7f8ac 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,8 @@ Use the following command line arguments for the standalone version of the atlas - `-font ` – sets the input font file. - `-charset ` – sets the character set. The ASCII charset will be used if not specified. See [the syntax specification](#character-set-specification-syntax) of `charset.txt`. - `-glyphset ` – sets the set of input glyphs using their indices within the font file. See [the syntax specification](#glyph-set-specification). +- `-fontscale ` – applies a scaling transformation to the font's glyphs. Mainly to be used to generate multiple sizes in a single atlas, otherwise use [`-size`](#glyph-configuration). +- `-and` – separates multiple inputs to be combined into a single atlas. ### Bitmap atlas type diff --git a/msdf-atlas-gen.vcxproj b/msdf-atlas-gen.vcxproj index bb7c6a5..4764001 100644 --- a/msdf-atlas-gen.vcxproj +++ b/msdf-atlas-gen.vcxproj @@ -314,6 +314,7 @@ + @@ -337,6 +338,7 @@ + diff --git a/msdf-atlas-gen.vcxproj.filters b/msdf-atlas-gen.vcxproj.filters index e105908..dda43f0 100644 --- a/msdf-atlas-gen.vcxproj.filters +++ b/msdf-atlas-gen.vcxproj.filters @@ -66,6 +66,9 @@ Source Files + + Source Files + @@ -164,6 +167,9 @@ Header Files + + Header Files + diff --git a/msdf-atlas-gen/FontGeometry.cpp b/msdf-atlas-gen/FontGeometry.cpp new file mode 100644 index 0000000..4a75a22 --- /dev/null +++ b/msdf-atlas-gen/FontGeometry.cpp @@ -0,0 +1,172 @@ + +#include "FontGeometry.h" + +namespace msdf_atlas { + +FontGeometry::GlyphRange::GlyphRange() : glyphs(), rangeStart(), rangeEnd() { } + +FontGeometry::GlyphRange::GlyphRange(const std::vector *glyphs, size_t rangeStart, size_t rangeEnd) : glyphs(glyphs), rangeStart(rangeStart), rangeEnd(rangeEnd) { } + +size_t FontGeometry::GlyphRange::size() const { + return glyphs->size(); +} + +bool FontGeometry::GlyphRange::empty() const { + return glyphs->empty(); +} + +const GlyphGeometry * FontGeometry::GlyphRange::begin() const { + return glyphs->data()+rangeStart; +} + +const GlyphGeometry * FontGeometry::GlyphRange::end() const { + return glyphs->data()+rangeEnd; +} + +FontGeometry::FontGeometry() : geometryScale(1), metrics(), preferredIdentifierType(GlyphIdentifierType::UNICODE_CODEPOINT), glyphs(&ownGlyphs), rangeStart(glyphs->size()), rangeEnd(glyphs->size()) { } + +FontGeometry::FontGeometry(std::vector *glyphStorage) : geometryScale(1), metrics(), preferredIdentifierType(GlyphIdentifierType::UNICODE_CODEPOINT), glyphs(glyphStorage), rangeStart(glyphs->size()), rangeEnd(glyphs->size()) { } + +int FontGeometry::loadGlyphset(msdfgen::FontHandle *font, double fontScale, const Charset &glyphset, bool preprocessGeometry, bool enableKerning) { + if (!(glyphs->size() == rangeEnd && loadMetrics(font, fontScale))) + return -1; + glyphs->reserve(glyphs->size()+glyphset.size()); + int loaded = 0; + for (unicode_t index : glyphset) { + GlyphGeometry glyph; + if (glyph.load(font, geometryScale, msdfgen::GlyphIndex(index), preprocessGeometry)) { + addGlyph((GlyphGeometry &&) glyph); + ++loaded; + } + } + if (enableKerning) + loadKerning(font); + preferredIdentifierType = GlyphIdentifierType::GLYPH_INDEX; + return loaded; +} + +int FontGeometry::loadCharset(msdfgen::FontHandle *font, double fontScale, const Charset &charset, bool preprocessGeometry, bool enableKerning) { + if (!(glyphs->size() == rangeEnd && loadMetrics(font, fontScale))) + return -1; + glyphs->reserve(glyphs->size()+charset.size()); + int loaded = 0; + for (unicode_t cp : charset) { + GlyphGeometry glyph; + if (glyph.load(font, geometryScale, cp, preprocessGeometry)) { + addGlyph((GlyphGeometry &&) glyph); + ++loaded; + } + } + if (enableKerning) + loadKerning(font); + preferredIdentifierType = GlyphIdentifierType::UNICODE_CODEPOINT; + return loaded; +} + +bool FontGeometry::loadMetrics(msdfgen::FontHandle *font, double fontScale) { + if (!msdfgen::getFontMetrics(metrics, font)) + return false; + if (metrics.emSize <= 0) + metrics.emSize = MSDF_ATLAS_DEFAULT_EM_SIZE; + geometryScale = fontScale/metrics.emSize; + metrics.emSize *= geometryScale; + metrics.ascenderY *= geometryScale; + metrics.descenderY *= geometryScale; + metrics.lineHeight *= geometryScale; + metrics.underlineY *= geometryScale; + metrics.underlineThickness *= geometryScale; + return true; +} + +bool FontGeometry::addGlyph(const GlyphGeometry &glyph) { + if (glyphs->size() != rangeEnd) + return false; + glyphsByIndex.insert(std::make_pair(glyph.getIndex(), rangeEnd)); + if (glyph.getCodepoint()) + glyphsByCodepoint.insert(std::make_pair(glyph.getCodepoint(), rangeEnd)); + glyphs->push_back(glyph); + ++rangeEnd; + return true; +} + +bool FontGeometry::addGlyph(GlyphGeometry &&glyph) { + if (glyphs->size() != rangeEnd) + return false; + glyphsByIndex.insert(std::make_pair(glyph.getIndex(), rangeEnd)); + if (glyph.getCodepoint()) + glyphsByCodepoint.insert(std::make_pair(glyph.getCodepoint(), rangeEnd)); + glyphs->push_back((GlyphGeometry &&) glyph); + ++rangeEnd; + return true; +} + +int FontGeometry::loadKerning(msdfgen::FontHandle *font) { + int loaded = 0; + for (size_t i = rangeStart; i < rangeEnd; ++i) + for (size_t j = rangeStart; j < rangeEnd; ++j) { + double advance; + if (msdfgen::getKerning(advance, font, (*glyphs)[i].getGlyphIndex(), (*glyphs)[j].getGlyphIndex()) && advance) { + kerning[std::make_pair((*glyphs)[i].getIndex(), (*glyphs)[j].getIndex())] = geometryScale*advance; + ++loaded; + } + } + return loaded; +} + +double FontGeometry::getGeometryScale() const { + return geometryScale; +} + +const msdfgen::FontMetrics & FontGeometry::getMetrics() const { + return metrics; +} + +GlyphIdentifierType FontGeometry::getPreferredIdentifierType() const { + return preferredIdentifierType; +} + +FontGeometry::GlyphRange FontGeometry::getGlyphs() const { + return GlyphRange(glyphs, rangeStart, rangeEnd); +} + +const GlyphGeometry * FontGeometry::getGlyph(msdfgen::GlyphIndex index) const { + std::map::const_iterator it = glyphsByIndex.find(index.getIndex()); + if (it != glyphsByIndex.end()) + return &(*glyphs)[it->second]; + return nullptr; +} + +const GlyphGeometry * FontGeometry::getGlyph(unicode_t codepoint) const { + std::map::const_iterator it = glyphsByCodepoint.find(codepoint); + if (it != glyphsByCodepoint.end()) + return &(*glyphs)[it->second]; + return nullptr; +} + +bool FontGeometry::getAdvance(double &advance, msdfgen::GlyphIndex index1, msdfgen::GlyphIndex index2) const { + const GlyphGeometry *glyph1 = getGlyph(index1); + if (!glyph1) + return false; + advance = glyph1->getAdvance(); + std::map, double>::const_iterator it = kerning.find(std::make_pair(index1.getIndex(), index2.getIndex())); + if (it != kerning.end()) + advance += it->second; + return true; +} + +bool FontGeometry::getAdvance(double &advance, unicode_t codepoint1, unicode_t codepoint2) const { + const GlyphGeometry *glyph1, *glyph2; + if (!((glyph1 = getGlyph(codepoint1)) && (glyph2 = getGlyph(codepoint2)))) + return false; + advance = glyph1->getAdvance(); + std::map, double>::const_iterator it = kerning.find(std::make_pair(glyph1->getIndex(), glyph2->getIndex())); + if (it != kerning.end()) + advance += it->second; + return true; +} + +const std::map, double> & FontGeometry::getKerning() const { + return kerning; +} + +} diff --git a/msdf-atlas-gen/FontGeometry.h b/msdf-atlas-gen/FontGeometry.h new file mode 100644 index 0000000..c95185c --- /dev/null +++ b/msdf-atlas-gen/FontGeometry.h @@ -0,0 +1,80 @@ + +#pragma once + +#include +#include +#include +#include +#include +#include "types.h" +#include "GlyphGeometry.h" +#include "Charset.h" + +#define MSDF_ATLAS_DEFAULT_EM_SIZE 32.0 + +namespace msdf_atlas { + +/// Represents the geometry of all glyphs of a given font or font variant +class FontGeometry { + +public: + class GlyphRange { + public: + GlyphRange(); + GlyphRange(const std::vector *glyphs, size_t rangeStart, size_t rangeEnd); + size_t size() const; + bool empty() const; + const GlyphGeometry * begin() const; + const GlyphGeometry * end() const; + private: + const std::vector *glyphs; + size_t rangeStart, rangeEnd; + }; + + FontGeometry(); + explicit FontGeometry(std::vector *glyphStorage); + + /// Loads all glyphs in a glyphset (Charset elements are glyph indices), returns the number of successfully loaded glyphs + int loadGlyphset(msdfgen::FontHandle *font, double fontScale, const Charset &glyphset, bool preprocessGeometry = true, bool enableKerning = true); + /// Loads all glyphs in a charset (Charset elements are Unicode codepoints), returns the number of successfully loaded glyphs + int loadCharset(msdfgen::FontHandle *font, double fontScale, const Charset &charset, bool preprocessGeometry = true, bool enableKerning = true); + + /// Only loads font metrics and geometry scale from font + bool loadMetrics(msdfgen::FontHandle *font, double fontScale); + /// Adds a loaded glyph + bool addGlyph(const GlyphGeometry &glyph); + bool addGlyph(GlyphGeometry &&glyph); + /// Loads kerning pairs for all glyphs that are currently present, returns the number of loaded kerning pairs + int loadKerning(msdfgen::FontHandle *font); + + /// Returns the geometry scale to be used when loading glyphs + double getGeometryScale() const; + /// Returns the processed font metrics + const msdfgen::FontMetrics & getMetrics() const; + /// Returns the type of identifier that was used to load glyphs + GlyphIdentifierType getPreferredIdentifierType() const; + /// Returns the list of all glyphs + GlyphRange getGlyphs() const; + /// Finds a glyph by glyph index or Unicode codepoint, returns null if not found + const GlyphGeometry * getGlyph(msdfgen::GlyphIndex index) const; + const GlyphGeometry * getGlyph(unicode_t codepoint) const; + /// Outputs the advance between two glyphs with kerning taken into consideration, returns false on failure + bool getAdvance(double &advance, msdfgen::GlyphIndex index1, msdfgen::GlyphIndex index2) const; + bool getAdvance(double &advance, unicode_t codepoint1, unicode_t codepoint2) const; + /// Returns the complete mapping of kerning pairs (by glyph indices) and their respective advance values + const std::map, double> & getKerning() const; + +private: + double geometryScale; + msdfgen::FontMetrics metrics; + GlyphIdentifierType preferredIdentifierType; + std::vector *glyphs; + size_t rangeStart, rangeEnd; + std::map glyphsByIndex; + std::map glyphsByCodepoint; + std::map, double> kerning; + std::vector ownGlyphs; + +}; + +} diff --git a/msdf-atlas-gen/GlyphGeometry.cpp b/msdf-atlas-gen/GlyphGeometry.cpp index 0ac3817..11b6c04 100644 --- a/msdf-atlas-gen/GlyphGeometry.cpp +++ b/msdf-atlas-gen/GlyphGeometry.cpp @@ -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; } diff --git a/msdf-atlas-gen/GlyphGeometry.h b/msdf-atlas-gen/GlyphGeometry.h index 556e114..44724ef 100644 --- a/msdf-atlas-gen/GlyphGeometry.h +++ b/msdf-atlas-gen/GlyphGeometry.h @@ -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; diff --git a/msdf-atlas-gen/artery-font-export.cpp b/msdf-atlas-gen/artery-font-export.cpp index 75a9e3c..31db21d 100644 --- a/msdf-atlas-gen/artery-font-export.cpp +++ b/msdf-atlas-gen/artery-font-export.cpp @@ -3,6 +3,7 @@ #include #include +#include "GlyphGeometry.h" #include "image-encode.h" namespace msdf_atlas { @@ -53,55 +54,69 @@ artery_font::PixelFormat getPixelFormat() { } template -bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties) { +bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties) { artery_font::StdArteryFont arfont = { }; arfont.metadataFormat = artery_font::METADATA_NONE; - 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 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 >(glyphCount); - for (int i = 0; i < glyphCount; ++i) { - artery_font::Glyph &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 >(font.getGlyphs().size()); + int j = 0; + for (const GlyphGeometry &glyphGeom : font.getGlyphs()) { + artery_font::Glyph &glyph = fontVariant.glyphs[j++]; + glyph.codepoint = glyphGeom.getIdentifier(identifierType); glyph.image = 0; double l, b, r, t; - 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, double> &elem : font.getKerning()) { artery_font::KernPair 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 &&) kernPair); } - } + break; + case GlyphIdentifierType::UNICODE_CODEPOINT: + for (const std::pair, double> &elem : font.getKerning()) { + const GlyphGeometry *glyph1 = font.getGlyph(msdfgen::GlyphIndex(elem.first.first)); + const GlyphGeometry *glyph2 = font.getGlyph(msdfgen::GlyphIndex(elem.first.second)); + if (glyph1 && glyph2 && glyph1->getCodepoint() && glyph2->getCodepoint()) { + artery_font::KernPair kernPair = { }; + kernPair.codepoint1 = glyph1->getCodepoint(); + kernPair.codepoint2 = glyph2->getCodepoint(); + kernPair.advance.h = REAL(elem.second); + fontVariant.kernPairs.vector.push_back((artery_font::KernPair &&) kernPair); + } + } + break; } arfont.variants.vector.push_back((artery_font::StdFontVariant &&) fontVariant); } @@ -149,11 +164,11 @@ bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, in return artery_font::writeFile(arfont, filename); } -template bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties); -template bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties); -template bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties); -template bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties); -template bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties); -template bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties); +template bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties); +template bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties); +template bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties); +template bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties); +template bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties); +template bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties); } diff --git a/msdf-atlas-gen/artery-font-export.h b/msdf-atlas-gen/artery-font-export.h index 4d907a7..74a2b22 100644 --- a/msdf-atlas-gen/artery-font-export.h +++ b/msdf-atlas-gen/artery-font-export.h @@ -4,12 +4,11 @@ #include #include #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 -bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties); +bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties); } diff --git a/msdf-atlas-gen/csv-export.cpp b/msdf-atlas-gen/csv-export.cpp index 6dc5a91..28e46ff 100644 --- a/msdf-atlas-gen/csv-export.cpp +++ b/msdf-atlas-gen/csv-export.cpp @@ -2,22 +2,26 @@ #include "csv-export.h" #include +#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); diff --git a/msdf-atlas-gen/csv-export.h b/msdf-atlas-gen/csv-export.h index 4ea8dfc..49184e7 100644 --- a/msdf-atlas-gen/csv-export.h +++ b/msdf-atlas-gen/csv-export.h @@ -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); } diff --git a/msdf-atlas-gen/json-export.cpp b/msdf-atlas-gen/json-export.cpp index a7048ee..745f21c 100644 --- a/msdf-atlas-gen/json-export.cpp +++ b/msdf-atlas-gen/json-export.cpp @@ -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, double> &kernPair : font.getKerning()) { + fputs(firstPair ? "{" : ",{", f); + fprintf(f, "\"index1\":%d,", kernPair.first.first); + fprintf(f, "\"index2\":%d,", kernPair.first.second); + fprintf(f, "\"advance\":%.17g", kernPair.second); + fputs("}", f); + firstPair = false; + } + break; + case GlyphIdentifierType::UNICODE_CODEPOINT: + for (const std::pair, double> &kernPair : font.getKerning()) { + const GlyphGeometry *glyph1 = font.getGlyph(msdfgen::GlyphIndex(kernPair.first.first)); + const GlyphGeometry *glyph2 = font.getGlyph(msdfgen::GlyphIndex(kernPair.first.second)); + if (glyph1 && glyph2 && glyph1->getCodepoint() && glyph2->getCodepoint()) { + fputs(firstPair ? "{" : ",{", f); + fprintf(f, "\"unicode1\":%u,", glyph1->getCodepoint()); + fprintf(f, "\"unicode2\":%u,", glyph2->getCodepoint()); + fprintf(f, "\"advance\":%.17g", kernPair.second); + fputs("}", f); + firstPair = false; + } + } + break; + } fputs("]", f); } + + if (fontCount > 1) + fputs("}", f); } - fputs("]", f); + if (fontCount > 1) + fputs("]", f); fputs("}\n", f); fclose(f); diff --git a/msdf-atlas-gen/json-export.h b/msdf-atlas-gen/json-export.h index 7256e16..e068ac7 100644 --- a/msdf-atlas-gen/json-export.h +++ b/msdf-atlas-gen/json-export.h @@ -4,11 +4,11 @@ #include #include #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); } diff --git a/msdf-atlas-gen/main.cpp b/msdf-atlas-gen/main.cpp index 56e3395..29cc9d8 100644 --- a/msdf-atlas-gen/main.cpp +++ b/msdf-atlas-gen/main.cpp @@ -11,7 +11,9 @@ #define _USE_MATH_DEFINES #include #include +#include #include +#include #include #include @@ -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 Specifies the set of input glyphs as glyph indices within the font file. + -fontscale + Specifies the scale to be applied to the glyph geometry of the font. + -and + Separates multiple inputs to be combined into a single atlas. ATLAS CONFIGURATION -type @@ -79,6 +84,8 @@ GLYPH CONFIGURATION Specifies the SDF distance range in EM's. -pxrange Specifies the SDF distance range in output pixels. The default value is 2. + -nokerning + Disables inclusion of kerning pair table in output files. DISTANCE FIELD GENERATOR SETTINGS -angle @@ -147,32 +154,14 @@ static bool cmpExtension(const char *path, const char *ext) { return true; } -static void loadGlyphsByIndex(std::vector &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 &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 GEN_FN> -static bool makeAtlas(const std::vector &glyphs, msdfgen::FontHandle *font, const Configuration &config) { +static bool makeAtlas(const std::vector &glyphs, const std::vector &fonts, const Configuration &config) { ImmediateAtlasGenerator > generator(config.width, config.height); generator.setAttributes(config.generatorAttributes); generator.setThreadCount(config.threadCount); @@ -213,12 +203,11 @@ static bool makeAtlas(const std::vector &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(font, glyphs.data(), glyphs.size(), bitmap, config.arteryFontFilename, arfontProps)) + if (exportArteryFont(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 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 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::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 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 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(glyphs, font, config); + success = makeAtlas(glyphs, fonts, config); else - success = makeAtlas(glyphs, font, config); + success = makeAtlas(glyphs, fonts, config); break; case ImageType::SOFT_MASK: case ImageType::SDF: if (floatingPointFormat) - success = makeAtlas(glyphs, font, config); + success = makeAtlas(glyphs, fonts, config); else - success = makeAtlas(glyphs, font, config); + success = makeAtlas(glyphs, fonts, config); break; case ImageType::PSDF: if (floatingPointFormat) - success = makeAtlas(glyphs, font, config); + success = makeAtlas(glyphs, fonts, config); else - success = makeAtlas(glyphs, font, config); + success = makeAtlas(glyphs, fonts, config); break; case ImageType::MSDF: if (floatingPointFormat) - success = makeAtlas(glyphs, font, config); + success = makeAtlas(glyphs, fonts, config); else - success = makeAtlas(glyphs, font, config); + success = makeAtlas(glyphs, fonts, config); break; case ImageType::MTSDF: if (floatingPointFormat) - success = makeAtlas(glyphs, font, config); + success = makeAtlas(glyphs, fonts, config); else - success = makeAtlas(glyphs, font, config); + success = makeAtlas(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 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; diff --git a/msdf-atlas-gen/msdf-atlas-gen.h b/msdf-atlas-gen/msdf-atlas-gen.h index 244c9b9..b3482ac 100644 --- a/msdf-atlas-gen/msdf-atlas-gen.h +++ b/msdf-atlas-gen/msdf-atlas-gen.h @@ -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" diff --git a/msdf-atlas-gen/shadron-preview-generator.cpp b/msdf-atlas-gen/shadron-preview-generator.cpp index 7d5ee0f..479e773 100644 --- a/msdf-atlas-gen/shadron-preview-generator.cpp +++ b/msdf-atlas-gen/shadron-preview-generator.cpp @@ -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; } } diff --git a/msdf-atlas-gen/shadron-preview-generator.h b/msdf-atlas-gen/shadron-preview-generator.h index 61db5e9..75da3eb 100644 --- a/msdf-atlas-gen/shadron-preview-generator.h +++ b/msdf-atlas-gen/shadron-preview-generator.h @@ -4,11 +4,11 @@ #include #include #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); }