#include "Atlas.h" #include "GlyphGeometry.h" #include "conversion.h" #include "edge-coloring.h" #include "import-font.h" #include "ofFileUtils.h" namespace ofxMsdfgen { 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; } Atlas::Atlas(){ } Atlas::~Atlas(){ if(font){ msdfgen::destroyFont(font); } msdfgen::deinitializeFreetype(ft); } void Atlas::setup(string _fontPath){ fontPath = _fontPath; ft = msdfgen::initializeFreetype(); font = loadFont(ft, fontPath.c_str()); variationAxesAvailable.clear(); vector _variationAxesAvailable; if(msdfgen::listFontVariationAxes(_variationAxesAvailable, ft, font)){ cout << "found variation axes for " << fontPath << ":" << endl; for(const auto & _axis : _variationAxesAvailable){ FontVariationAxis axis = { _axis.name, F16DOT16_TO_FLOAT(_axis.minValue), F16DOT16_TO_FLOAT(_axis.maxValue), F16DOT16_TO_FLOAT(_axis.defaultValue) }; cout << std::fixed << axis.name << "\n" << "\tmin:" << axis.minValue << "\tmax:" << axis.maxValue << "\t:default:" << axis.defaultValue << endl; variationAxesAvailable.push_back(axis); addVariations({ {axis.name, axis.minValue}, {axis.name, axis.maxValue} //{axis.name, axis.defaultValue} }); } ofLogNotice("Atlas::setup()") << "retrieved variation axes"; }else{ ofLogNotice("Atlas::setup()") << "font does not have variation axes"; } //if(generate()){ //ofLogNotice("Atlas::setup()") << "generated Atlas"; //}else{ //ofLogError("Atlas::setup()") << "whoops, could not generate Atlas"; //} } void Atlas::setup(string _fontPath, AtlasSettings _settings){ settings = _settings; setup(_fontPath); } void Atlas::addVariations(vector 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; } } bool Atlas::generate(bool useCache, bool saveToCache){ cout << "Atlas::generate()" << endl; bool success = false; bool foundAtlasCacheImage = false; if(useCache){ ofFile atlasPathFile(getAtlasPath()); if(atlasPathFile.isFile()){ foundAtlasCacheImage = true; if(atlasImage.load(getAtlasPath())){ foundAtlasCacheImage = true; } } } if(ft){ if(font){ // Storage for glyph geometry and their coordinates in the atlas std::vector glyphs; for(const FontVariation & variation : variationSteps){ std::vector 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(); fontGeometry.loadCharset(font, 1.0, // fontScale charset, // charset false, // preprocess geometry true); // kerning //} } 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++; } glyphs.insert(glyphs.end(), variationGlyphs.begin(), variationGlyphs.end()); } // Apply MSDF edge coloring. See edge-coloring.h for other coloring strategies. for(msdf_atlas::GlyphGeometry & glyph : glyphs){ // NOTE: const_cast is potentially evil, // but like this we can use clipper for preprocessing shapes // msdfgen::Shape & shape = const_cast (glyph.getShape()); // 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: // shape.inverseYAxis = true; glyph.edgeColoring(&msdfgen::edgeColoringSimple, // strategy settings.maxCornerAngle, // angleThreshold 0); // seed } // 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 //packer.setMinimumScale(settings.minimumScale); packer.setScale(settings.scale); // setPixelRange or setUnitRange packer.setPixelRange(settings.pixelRange); packer.setMiterLimit(settings.miterLimit); // Compute atlas layout - pack glyphs packer.pack(glyphs.data(), glyphs.size()); 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 // 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 bitmap(generator.atlasStorage()); toOfImage(bitmap, atlasImage); atlasImage.update(); if(saveToCache){ ofDirectory dir(getAtlasPathDir()); dir.create(true); atlasImage.save(getAtlasPath()); } } //std::vector 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){ glyphGeometries.push_back(gg); } // Cleanup //msdfgen::destroyFont(font); success = true; // FIXME: always turns true, why do we have this } //msdfgen::deinitializeFreetype(ft); } return success; } const ofImage & Atlas::getAtlasImage(){ return atlasImage; } const vector & Atlas::getGlyphGeometries(){ return glyphGeometries; } const GlyphGeometry & Atlas::getGlyphGeometry(unsigned char character){ for(const GlyphGeometry & gg : glyphGeometries){ if(gg.getCodepoint() == static_cast (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]; } const GlyphGeometry & Atlas::getGlyphGeometry(unsigned char character, FontVariation variation){ // FIXME: error handling? 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; } const FontGeometry & Atlas::getFontGeometry(){ return fontGeometry; } const vector & Atlas::getVariationAxesAvailable(){ return variationAxesAvailable; } // private string Atlas::getAtlasPathDir(){ string fontPathNoData = fontPath; ofStringReplace(fontPathNoData, "data/", ""); string atlasPath = "data/atlascache/" + fontPathNoData + "/msdfgen"; 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"; return atlasPath; } }