2023-02-21 18:41:53 +01:00
|
|
|
#include "Atlas.h"
|
2023-04-08 17:15:20 +02:00
|
|
|
#include "GlyphGeometry.h"
|
2023-02-27 16:24:14 +01:00
|
|
|
#include "conversion.h"
|
2023-03-30 16:19:13 +02:00
|
|
|
#include "edge-coloring.h"
|
2023-02-27 16:24:14 +01:00
|
|
|
#include "import-font.h"
|
2023-04-08 17:15:20 +02:00
|
|
|
#include "ofFileUtils.h"
|
2023-02-22 18:28:56 +01:00
|
|
|
|
2023-03-26 21:18:33 +02:00
|
|
|
namespace ofxMsdfgen {
|
2023-03-29 20:45:39 +02:00
|
|
|
|
|
|
|
bool compareFontVariations(const FontVariation & a, const FontVariation & b){
|
|
|
|
if(a.name < b.name){
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if(a.name > b.name){
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if(a.value < b.value){
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
// now always false
|
|
|
|
//if(a.value > b.value){
|
|
|
|
//return false;
|
|
|
|
//}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-03-26 21:18:33 +02:00
|
|
|
Atlas::Atlas(){
|
2023-02-22 18:28:56 +01:00
|
|
|
}
|
2023-03-26 21:18:33 +02:00
|
|
|
Atlas::~Atlas(){
|
2023-03-29 20:45:39 +02:00
|
|
|
if(font){
|
|
|
|
msdfgen::destroyFont(font);
|
|
|
|
}
|
2023-03-28 17:50:38 +02:00
|
|
|
msdfgen::deinitializeFreetype(ft);
|
2023-02-22 18:28:56 +01:00
|
|
|
}
|
2023-02-21 18:41:53 +01:00
|
|
|
|
2023-03-26 21:18:33 +02:00
|
|
|
void Atlas::setup(string _fontPath){
|
2023-02-21 18:41:53 +01:00
|
|
|
fontPath = _fontPath;
|
2023-03-29 20:45:39 +02:00
|
|
|
ft = msdfgen::initializeFreetype();
|
|
|
|
font = loadFont(ft, fontPath.c_str());
|
|
|
|
|
|
|
|
variationAxesAvailable.clear();
|
|
|
|
vector <msdfgen::FontVariationAxis> _variationAxesAvailable;
|
|
|
|
if(msdfgen::listFontVariationAxes(_variationAxesAvailable,
|
|
|
|
ft,
|
|
|
|
font)){
|
2023-05-25 12:45:26 +02:00
|
|
|
//cout << "found variation axes for " << fontPath << ":" << endl;
|
2023-03-29 20:45:39 +02:00
|
|
|
for(const auto & _axis : _variationAxesAvailable){
|
|
|
|
FontVariationAxis axis = {
|
|
|
|
_axis.name,
|
|
|
|
F16DOT16_TO_FLOAT(_axis.minValue),
|
|
|
|
F16DOT16_TO_FLOAT(_axis.maxValue),
|
|
|
|
F16DOT16_TO_FLOAT(_axis.defaultValue)
|
|
|
|
};
|
2023-05-25 12:45:26 +02:00
|
|
|
//cout << std::fixed << axis.name << "\n"
|
|
|
|
//<< "\tmin:" << axis.minValue
|
|
|
|
//<< "\tmax:" << axis.maxValue
|
|
|
|
//<< "\t:default:" << axis.defaultValue << endl;
|
2023-03-29 20:45:39 +02:00
|
|
|
variationAxesAvailable.push_back(axis);
|
|
|
|
addVariations({
|
|
|
|
{axis.name, axis.minValue},
|
|
|
|
{axis.name, axis.maxValue}
|
|
|
|
//{axis.name, axis.defaultValue}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
ofLogNotice("Atlas::setup()") << "retrieved variation axes";
|
2023-02-21 18:41:53 +01:00
|
|
|
}else{
|
2023-03-29 20:45:39 +02:00
|
|
|
ofLogNotice("Atlas::setup()") << "font does not have variation axes";
|
2023-02-21 18:41:53 +01:00
|
|
|
}
|
2023-03-29 20:45:39 +02:00
|
|
|
//if(generate()){
|
|
|
|
//ofLogNotice("Atlas::setup()") << "generated Atlas";
|
|
|
|
//}else{
|
|
|
|
//ofLogError("Atlas::setup()") << "whoops, could not generate Atlas";
|
|
|
|
//}
|
2023-02-21 18:41:53 +01:00
|
|
|
}
|
|
|
|
|
2023-03-26 21:18:33 +02:00
|
|
|
void Atlas::setup(string _fontPath,
|
|
|
|
AtlasSettings _settings){
|
2023-02-21 18:41:53 +01:00
|
|
|
settings = _settings;
|
|
|
|
setup(_fontPath);
|
|
|
|
}
|
|
|
|
|
2023-03-29 20:45:39 +02:00
|
|
|
void Atlas::addVariations(vector <FontVariation> variations){
|
|
|
|
// add to extremes
|
|
|
|
for(const auto & variation : variations){
|
|
|
|
auto & v = variationExtremes;
|
|
|
|
|
|
|
|
bool found = false;
|
|
|
|
for(const auto & ve : variationExtremes){
|
|
|
|
if(ve.name == variation.name && ve.value == variation.value){
|
|
|
|
found = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//if(std::find(v.begin(), v.end(), variation) != v.end()){
|
|
|
|
if(!found){
|
|
|
|
variationExtremes.push_back(variation);
|
|
|
|
cout << "variation " << variation.name << " v:" << variation.value << endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// calculate steps
|
|
|
|
variationSteps.clear();
|
|
|
|
// first, sort the extremes
|
|
|
|
sort(variationExtremes.begin(),
|
|
|
|
variationExtremes.end(),
|
|
|
|
compareFontVariations);
|
|
|
|
|
|
|
|
for(int i = 0; i < variationExtremes.size() - 1; i++){
|
|
|
|
const FontVariation & a = variationExtremes[i];
|
|
|
|
const FontVariation & b = variationExtremes[i + 1];
|
|
|
|
float diff = b.value - a.value;
|
|
|
|
int stepAmount = ceil(diff / settings.maxInterpolationStepSize);
|
|
|
|
float step = diff / stepAmount;
|
|
|
|
float value = a.value + step;
|
|
|
|
variationSteps.push_back(a);
|
|
|
|
int stop = 0;
|
|
|
|
while(value < b.value && stop < 100){
|
|
|
|
variationSteps.push_back({a.name, value});
|
|
|
|
value += step;
|
|
|
|
stop++;
|
|
|
|
}
|
|
|
|
variationSteps.push_back(b);
|
|
|
|
}
|
|
|
|
if(variationExtremes.size() == 1){
|
|
|
|
variationSteps = variationExtremes;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-08 17:15:20 +02:00
|
|
|
bool Atlas::generate(bool useCache,
|
|
|
|
bool saveToCache){
|
2023-04-06 11:16:21 +02:00
|
|
|
cout << "Atlas::generate()" << endl;
|
2023-02-21 18:41:53 +01:00
|
|
|
bool success = false;
|
2023-04-08 17:15:20 +02:00
|
|
|
bool foundAtlasCacheImage = false;
|
|
|
|
if(useCache){
|
|
|
|
ofFile atlasPathFile(getAtlasPath());
|
|
|
|
if(atlasPathFile.isFile()){
|
|
|
|
foundAtlasCacheImage = true;
|
|
|
|
if(atlasImage.load(getAtlasPath())){
|
|
|
|
foundAtlasCacheImage = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-02-22 18:28:56 +01:00
|
|
|
if(ft){
|
|
|
|
if(font){
|
2023-02-21 18:41:53 +01:00
|
|
|
// Storage for glyph geometry and their coordinates in the atlas
|
|
|
|
std::vector <msdf_atlas::GlyphGeometry> glyphs;
|
2023-03-29 20:45:39 +02:00
|
|
|
|
|
|
|
for(const FontVariation & variation : variationSteps){
|
|
|
|
std::vector <msdf_atlas::GlyphGeometry> variationGlyphs;
|
|
|
|
msdf_atlas::FontGeometry _fontGeometry(&variationGlyphs);
|
|
|
|
fontGeometry = _fontGeometry;
|
|
|
|
|
|
|
|
msdfgen::setFontVariationAxis(ft,
|
|
|
|
font,
|
|
|
|
variation.name.c_str(),
|
|
|
|
variation.value);
|
|
|
|
// Load a set of character glyphs:
|
|
|
|
// The second argument can be ignored unless you mix different font sizes in one atlas.
|
|
|
|
// In the last argument, you can specify a charset other than ASCII.
|
|
|
|
// To load specific glyph indices, use loadGlyphs instead.
|
|
|
|
if(settings.characters == "charset::ascii"
|
|
|
|
|| settings.characters == ""){
|
|
|
|
fontGeometry.loadCharset(font, 1.0, msdf_atlas::Charset::ASCII);
|
|
|
|
}else{
|
|
|
|
msdf_atlas::Charset charset;
|
|
|
|
for(const unsigned char c : settings.characters){
|
|
|
|
msdf_atlas::unicode_t ut(c);
|
|
|
|
charset.add(ut);
|
|
|
|
}
|
|
|
|
//fontGeometry.loadCharset(font, 1.0, charset);
|
|
|
|
//if(glyphs.size() != settings.characters.length()){
|
|
|
|
//fontGeometry.getGlyphs().empty();
|
2023-03-19 12:00:39 +01:00
|
|
|
fontGeometry.loadCharset(font,
|
2023-03-29 20:45:39 +02:00
|
|
|
1.0, // fontScale
|
|
|
|
charset, // charset
|
|
|
|
false, // preprocess geometry
|
|
|
|
true); // kerning
|
|
|
|
//}
|
2023-02-27 16:24:14 +01:00
|
|
|
}
|
2023-03-29 20:45:39 +02:00
|
|
|
|
2023-03-30 16:19:13 +02:00
|
|
|
int vgi = 0;
|
|
|
|
for(const auto & vg : variationGlyphs){
|
|
|
|
const unsigned char cp = vg.getCodepoint();
|
|
|
|
GlyphVariationKey key;
|
|
|
|
key.character = cp;
|
|
|
|
key.variation = variation;
|
|
|
|
glyphGeometryMap[key] = glyphs.size() + vgi;
|
|
|
|
vgi++;
|
|
|
|
}
|
|
|
|
|
2023-03-29 20:45:39 +02:00
|
|
|
glyphs.insert(glyphs.end(),
|
|
|
|
variationGlyphs.begin(),
|
|
|
|
variationGlyphs.end());
|
2023-02-27 16:24:14 +01:00
|
|
|
}
|
2023-02-21 18:41:53 +01:00
|
|
|
// Apply MSDF edge coloring. See edge-coloring.h for other coloring strategies.
|
|
|
|
for(msdf_atlas::GlyphGeometry & glyph : glyphs){
|
2023-03-30 16:19:13 +02:00
|
|
|
// NOTE: const_cast is potentially evil,
|
|
|
|
// but like this we can use clipper for preprocessing shapes
|
2023-03-19 12:01:06 +01:00
|
|
|
// msdfgen::Shape & shape = const_cast <msdfgen::Shape &>(glyph.getShape());
|
2023-03-30 16:19:13 +02:00
|
|
|
// then convert Shape to ofPolyline and use getResampledBySpacing
|
|
|
|
// to convert it into straight lines
|
|
|
|
// then use clipper2 to union the shapes
|
|
|
|
// convert back into ofPolyline and then Shape
|
|
|
|
// or directly into Shape
|
|
|
|
//
|
|
|
|
// don't do this:
|
2023-03-19 12:01:06 +01:00
|
|
|
// shape.inverseYAxis = true;
|
2023-03-30 16:19:13 +02:00
|
|
|
glyph.edgeColoring(&msdfgen::edgeColoringSimple, // strategy
|
2023-03-19 12:01:06 +01:00
|
|
|
settings.maxCornerAngle, // angleThreshold
|
|
|
|
0); // seed
|
2023-02-21 18:41:53 +01:00
|
|
|
}
|
|
|
|
// TightAtlasPacker class computes the layout of the atlas.
|
|
|
|
msdf_atlas::TightAtlasPacker packer;
|
|
|
|
// Set atlas parameters:
|
|
|
|
// setDimensions or setDimensionsConstraint to find the best value
|
|
|
|
packer.setDimensionsConstraint(msdf_atlas::TightAtlasPacker::DimensionsConstraint::SQUARE);
|
|
|
|
// setScale for a fixed size or setMinimumScale to use the largest that fits
|
2023-02-27 16:24:14 +01:00
|
|
|
//packer.setMinimumScale(settings.minimumScale);
|
|
|
|
packer.setScale(settings.scale);
|
2023-02-21 18:41:53 +01:00
|
|
|
// setPixelRange or setUnitRange
|
|
|
|
packer.setPixelRange(settings.pixelRange);
|
|
|
|
packer.setMiterLimit(settings.miterLimit);
|
|
|
|
// Compute atlas layout - pack glyphs
|
|
|
|
packer.pack(glyphs.data(), glyphs.size());
|
2023-04-08 17:15:20 +02:00
|
|
|
if(!useCache || !foundAtlasCacheImage){
|
|
|
|
// Get final atlas dimensions
|
|
|
|
int width = 0, height = 0;
|
|
|
|
packer.getDimensions(width, height);
|
|
|
|
// The ImmediateAtlasGenerator class facilitates the generation of the atlas bitmap.
|
|
|
|
msdf_atlas::ImmediateAtlasGenerator <
|
|
|
|
float, // pixel type of buffer for individual glyphs depends on generator function
|
|
|
|
3, // number of atlas color channels
|
|
|
|
& msdf_atlas::msdfGenerator, // function to generate bitmaps for individual glyphs
|
|
|
|
msdf_atlas::BitmapAtlasStorage <float, 3> // class that stores the atlas bitmap
|
|
|
|
// For example, a custom atlas storage class that stores it in VRAM can be used.
|
|
|
|
> generator(width, height);
|
|
|
|
// GeneratorAttributes can be modified to change the generator's default settings.
|
|
|
|
msdf_atlas::GeneratorAttributes attributes;
|
|
|
|
generator.setAttributes(attributes);
|
|
|
|
generator.setThreadCount(settings.threadCount);
|
|
|
|
// Generate atlas bitmap
|
|
|
|
generator.generate(glyphs.data(), glyphs.size());
|
|
|
|
// The atlas bitmap can now be retrieved via atlasStorage as a BitmapConstRef.
|
|
|
|
// The glyphs array (or fontGeometry) contains positioning data for typesetting text.
|
|
|
|
msdfgen::Bitmap <float, 3> bitmap(generator.atlasStorage());
|
|
|
|
toOfImage(bitmap, atlasImage);
|
|
|
|
atlasImage.update();
|
|
|
|
if(saveToCache){
|
|
|
|
ofDirectory dir(getAtlasPathDir());
|
2023-05-25 12:45:10 +02:00
|
|
|
dir.create(true);
|
2023-04-08 17:15:20 +02:00
|
|
|
atlasImage.save(getAtlasPath());
|
|
|
|
}
|
|
|
|
}
|
2023-02-21 18:41:53 +01:00
|
|
|
|
2023-04-08 17:15:20 +02:00
|
|
|
//std::vector <msdf_atlas::GlyphBox> layout = generator.getLayout();
|
|
|
|
//int i = 0;
|
|
|
|
//for(const msdf_atlas::GlyphBox & gb : layout){
|
|
|
|
//msdf_atlas::GlyphGeometry gg = glyphs[i];
|
|
|
|
//i++;
|
|
|
|
//glyphGeometries.push_back(gg);
|
|
|
|
//}
|
|
|
|
for(const msdf_atlas::GlyphGeometry & gg : glyphs){
|
2023-02-27 16:24:14 +01:00
|
|
|
glyphGeometries.push_back(gg);
|
2023-02-22 18:28:56 +01:00
|
|
|
}
|
2023-02-21 18:41:53 +01:00
|
|
|
// Cleanup
|
2023-03-28 17:50:38 +02:00
|
|
|
//msdfgen::destroyFont(font);
|
2023-02-21 18:41:53 +01:00
|
|
|
|
2023-02-22 18:28:56 +01:00
|
|
|
success = true; // FIXME: always turns true, why do we have this
|
2023-02-21 18:41:53 +01:00
|
|
|
}
|
2023-03-28 17:50:38 +02:00
|
|
|
//msdfgen::deinitializeFreetype(ft);
|
2023-02-21 18:41:53 +01:00
|
|
|
}
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
2023-03-26 21:18:33 +02:00
|
|
|
const ofImage & Atlas::getAtlasImage(){
|
2023-02-21 18:41:53 +01:00
|
|
|
return atlasImage;
|
|
|
|
}
|
2023-02-27 16:24:14 +01:00
|
|
|
|
2023-03-26 21:18:33 +02:00
|
|
|
const vector <GlyphGeometry> & Atlas::getGlyphGeometries(){
|
2023-02-27 16:24:14 +01:00
|
|
|
return glyphGeometries;
|
|
|
|
}
|
2023-03-26 21:18:33 +02:00
|
|
|
|
|
|
|
const GlyphGeometry & Atlas::getGlyphGeometry(unsigned char character){
|
|
|
|
for(const GlyphGeometry & gg : glyphGeometries){
|
|
|
|
if(gg.getCodepoint() == static_cast <msdf_atlas::unicode_t>(character)){
|
|
|
|
return gg;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// not found!
|
|
|
|
ofLogError("Atlas::getGlyphGeometry")
|
|
|
|
<< "glyph(" << character << ") not found!" << endl
|
|
|
|
<< "returning first glyph. this is bad." << endl;
|
|
|
|
// FIXME: proper error handling
|
|
|
|
return glyphGeometries[0];
|
|
|
|
}
|
2023-03-28 17:50:38 +02:00
|
|
|
|
2023-03-29 20:45:39 +02:00
|
|
|
const GlyphGeometry & Atlas::getGlyphGeometry(unsigned char character,
|
|
|
|
FontVariation variation){
|
|
|
|
// FIXME: error handling?
|
2023-03-30 16:19:13 +02:00
|
|
|
float smallestDistance = FLT_MAX;
|
|
|
|
int closestIndex = 0;
|
|
|
|
int i = 0;
|
|
|
|
for(const auto & v : variationSteps){
|
|
|
|
float distance = abs(v.value - variation.value);
|
|
|
|
if(distance < smallestDistance){
|
|
|
|
smallestDistance = distance;
|
|
|
|
closestIndex = i;
|
|
|
|
}
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
const auto & closestVariation = variationSteps[closestIndex];
|
|
|
|
GlyphVariationKey key = {character, closestVariation};
|
|
|
|
int index = glyphGeometryMap[key];
|
|
|
|
return glyphGeometries[index];
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Atlas::getGlyphGeometryPair(const unsigned char character,
|
|
|
|
const FontVariation variation,
|
|
|
|
GlyphGeometry & a,
|
|
|
|
GlyphGeometry & b,
|
|
|
|
float & mix){
|
|
|
|
float smallestDistanceDown = -1 * FLT_MAX;
|
|
|
|
float smallestDistanceUp = FLT_MAX;
|
|
|
|
int closestIndexDown = 0;
|
|
|
|
int closestIndexUp = 0;
|
|
|
|
int i = 0;
|
|
|
|
for(const auto & v : variationSteps){
|
|
|
|
float distance = v.value - variation.value;
|
|
|
|
if(distance <= 0){
|
|
|
|
if(distance > smallestDistanceDown){
|
|
|
|
closestIndexDown = i;
|
|
|
|
smallestDistanceDown = distance;
|
|
|
|
}
|
|
|
|
}else{
|
|
|
|
if(distance < smallestDistanceUp){
|
|
|
|
closestIndexUp = i;
|
|
|
|
smallestDistanceUp = distance;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
float total = abs(smallestDistanceUp) + abs(smallestDistanceDown);
|
|
|
|
//mix = ofMap(abs(smallestDistanceDown), 0, total, 0, 1);
|
|
|
|
mix = total == 0 || smallestDistanceDown == 0 ? 0 : abs(smallestDistanceDown) / total;
|
|
|
|
}
|
|
|
|
if(mix < 0 || mix > 1){
|
|
|
|
ofLogError("Atlas::getGlyphGeometryPair")
|
|
|
|
<< "mix out of range.\n"
|
|
|
|
<< "sorry for the shitty error message, just check the code";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const auto & closestVariationUp = variationSteps[closestIndexUp];
|
|
|
|
const auto & closestVariationDown = variationSteps[closestIndexDown];
|
|
|
|
GlyphVariationKey glyphKeyUp = {character, closestVariationUp};
|
|
|
|
GlyphVariationKey glyphKeyDown = {character, closestVariationDown};
|
|
|
|
int index_a = glyphGeometryMap[glyphKeyDown];
|
|
|
|
int index_b = glyphGeometryMap[glyphKeyUp];
|
|
|
|
a = glyphGeometries[index_a];
|
|
|
|
b = glyphGeometries[index_b];
|
|
|
|
|
|
|
|
return true;
|
2023-03-29 20:45:39 +02:00
|
|
|
}
|
|
|
|
|
2023-03-28 17:50:38 +02:00
|
|
|
const FontGeometry & Atlas::getFontGeometry(){
|
|
|
|
return fontGeometry;
|
|
|
|
}
|
2023-03-30 16:19:13 +02:00
|
|
|
const vector <ofxMsdfgen::FontVariationAxis> & Atlas::getVariationAxesAvailable(){
|
|
|
|
return variationAxesAvailable;
|
|
|
|
}
|
2023-04-08 17:15:20 +02:00
|
|
|
|
|
|
|
// private
|
|
|
|
string Atlas::getAtlasPathDir(){
|
|
|
|
string fontPathNoData = fontPath;
|
|
|
|
ofStringReplace(fontPathNoData, "data/", "");
|
2023-04-11 10:05:06 +02:00
|
|
|
string atlasPath = "data/atlascache/" + fontPathNoData + "/msdfgen";
|
2023-04-08 17:15:20 +02:00
|
|
|
return atlasPath;
|
|
|
|
}
|
|
|
|
string Atlas::getAtlasPath(){
|
|
|
|
string atlasPath = getAtlasPathDir() + "/"
|
|
|
|
+ ofToString(settings.scale) + "_"
|
|
|
|
+ ofToString(settings.minimumScale) + "_"
|
|
|
|
+ ofToString(settings.maxInterpolationStepSize);
|
|
|
|
for(auto & ve : variationExtremes){
|
|
|
|
atlasPath += ve.name + "_" + ofToString(ve.value) + "_";
|
|
|
|
}
|
|
|
|
atlasPath += ".png";
|
2023-05-25 12:45:26 +02:00
|
|
|
cout << "Atlas::getAtlasPath() - atlas path: " << atlasPath << endl;
|
2023-04-08 17:15:20 +02:00
|
|
|
return atlasPath;
|
|
|
|
}
|
2023-03-26 21:18:33 +02:00
|
|
|
}
|