Files
UnleashedRecomp-hedge-dev/UnleashedRecomp/gpu/imgui/imgui_font_builder.cpp
Skyth (Asilkan) cdcacff53b Implement SDF fonts. (#24)
* Initial SDF font generation work.

* Text now correctly displaying with proper spacing.

* Fix untextured draws, implement custom rectangles.

* Fix regular image display.

* Slightly refactor ImGui rendering.

* Implement outlines.

* Implement bevel.

* Create host device after loading the module if the installer wasn't run.

* Move ImGui files to its own folder.

* Fix outline sizes.

* Fix default ImGui font and font scales.

* Update font atlas files.
2024-12-11 23:30:19 +03:00

275 lines
11 KiB
C++

#include "imgui_font_builder.h"
#include <msdf-atlas-gen/msdf-atlas-gen.h>
// Taken directly from msdf-atlas-gen, modified to support custom rectangles.
struct TightAtlasPacker {
TightAtlasPacker() :
width(-1), height(-1),
spacing(0),
dimensionsConstraint(msdf_atlas::DimensionsConstraint::POWER_OF_TWO_SQUARE),
scale(-1),
minScale(1),
unitRange(0),
pxRange(0),
miterLimit(0),
pxAlignOriginX(false), pxAlignOriginY(false),
scaleMaximizationTolerance(.001)
{
}
int pack(msdf_atlas::GlyphGeometry* glyphs, int count, msdf_atlas::Rectangle* customRects, int customRectCount) {
double initialScale = scale > 0 ? scale : minScale;
if (initialScale > 0) {
if (int remaining = tryPack(glyphs, count, customRects, customRectCount, dimensionsConstraint, width, height, initialScale))
return remaining;
}
else if (width < 0 || height < 0)
return -1;
if (scale <= 0)
scale = packAndScale(glyphs, count, customRects, customRectCount);
if (scale <= 0)
return -1;
return 0;
}
int width, height;
int spacing;
msdf_atlas::DimensionsConstraint dimensionsConstraint;
double scale;
double minScale;
msdfgen::Range unitRange;
msdfgen::Range pxRange;
double miterLimit;
bool pxAlignOriginX, pxAlignOriginY;
msdf_atlas::Padding innerUnitPadding, outerUnitPadding;
msdf_atlas::Padding innerPxPadding, outerPxPadding;
double scaleMaximizationTolerance;
int tryPack(msdf_atlas::GlyphGeometry* glyphs, int count, msdf_atlas::Rectangle* customRects, int customRectCount, msdf_atlas::DimensionsConstraint dimensionsConstraint, int& width, int& height, double scale) const {
// Wrap glyphs into boxes
std::vector<msdf_atlas::Rectangle> rectangles;
std::vector<msdf_atlas::GlyphGeometry*> rectangleGlyphs;
rectangles.reserve(count + customRectCount);
rectangleGlyphs.reserve(count);
msdf_atlas::GlyphGeometry::GlyphAttributes attribs = { };
attribs.scale = scale;
attribs.range = { unitRange.lower + pxRange.lower / scale, unitRange.upper + pxRange.upper / scale };
attribs.innerPadding = innerUnitPadding + innerPxPadding / scale;
attribs.outerPadding = outerUnitPadding + outerPxPadding / scale;
attribs.miterLimit = miterLimit;
attribs.pxAlignOriginX = pxAlignOriginX;
attribs.pxAlignOriginY = pxAlignOriginY;
for (msdf_atlas::GlyphGeometry* glyph = glyphs, *end = glyphs + count; glyph < end; ++glyph) {
if (!glyph->isWhitespace()) {
msdf_atlas::Rectangle rect = { };
glyph->wrapBox(attribs);
glyph->getBoxSize(rect.w, rect.h);
if (rect.w > 0 && rect.h > 0) {
rectangles.push_back(rect);
rectangleGlyphs.push_back(glyph);
}
}
}
rectangles.insert(rectangles.end(), customRects, customRects + customRectCount);
// No non-zero size boxes?
if (rectangles.empty()) {
if (width < 0 || height < 0)
width = 0, height = 0;
return 0;
}
// Box rectangle packing
if (width < 0 || height < 0) {
std::pair<int, int> dimensions = std::make_pair(width, height);
switch (dimensionsConstraint) {
case msdf_atlas::DimensionsConstraint::POWER_OF_TWO_SQUARE:
dimensions = msdf_atlas::packRectangles<msdf_atlas::SquarePowerOfTwoSizeSelector>(rectangles.data(), rectangles.size(), spacing);
break;
case msdf_atlas::DimensionsConstraint::POWER_OF_TWO_RECTANGLE:
dimensions = msdf_atlas::packRectangles<msdf_atlas::PowerOfTwoSizeSelector>(rectangles.data(), rectangles.size(), spacing);
break;
case msdf_atlas::DimensionsConstraint::MULTIPLE_OF_FOUR_SQUARE:
dimensions = msdf_atlas::packRectangles<msdf_atlas::SquareSizeSelector<4> >(rectangles.data(), rectangles.size(), spacing);
break;
case msdf_atlas::DimensionsConstraint::EVEN_SQUARE:
dimensions = msdf_atlas::packRectangles<msdf_atlas::SquareSizeSelector<2> >(rectangles.data(), rectangles.size(), spacing);
break;
case msdf_atlas::DimensionsConstraint::SQUARE:
default:
dimensions = msdf_atlas::packRectangles<msdf_atlas::SquareSizeSelector<> >(rectangles.data(), rectangles.size(), spacing);
break;
}
if (!(dimensions.first > 0 && dimensions.second > 0))
return -1;
width = dimensions.first, height = dimensions.second;
}
else {
if (int result = packRectangles(rectangles.data(), rectangles.size(), width, height, spacing))
return result;
}
// Set glyph box placement
for (size_t i = 0; i < rectangleGlyphs.size(); ++i)
rectangleGlyphs[i]->placeBox(rectangles[i].x, height - (rectangles[i].y + rectangles[i].h));
for (int i = 0; i < customRectCount; ++i) {
customRects[i].x = rectangles[rectangleGlyphs.size() + i].x;
customRects[i].y = height - (rectangles[rectangleGlyphs.size() + i].y + rectangles[rectangleGlyphs.size() + i].h);
}
return 0;
}
double packAndScale(msdf_atlas::GlyphGeometry* glyphs, int count, msdf_atlas::Rectangle* customRects, int customRectCount) const {
bool lastResult = false;
int w = width, h = height;
#define TRY_PACK(scale) (lastResult = !tryPack(glyphs, count, customRects, customRectCount, msdf_atlas::DimensionsConstraint(), w, h, (scale)))
double minScale = 1, maxScale = 1;
if (TRY_PACK(1)) {
while (maxScale < 1e+32 && ((maxScale = 2 * minScale), TRY_PACK(maxScale)))
minScale = maxScale;
}
else {
while (minScale > 1e-32 && ((minScale = .5 * maxScale), !TRY_PACK(minScale)))
maxScale = minScale;
}
if (minScale == maxScale)
return 0;
while (minScale / maxScale < 1 - scaleMaximizationTolerance) {
double midScale = .5 * (minScale + maxScale);
if (TRY_PACK(midScale))
minScale = midScale;
else
maxScale = midScale;
}
if (!lastResult)
TRY_PACK(minScale);
return minScale;
}
};
extern void ImFontAtlasBuildInit(ImFontAtlas* atlas);
extern void ImFontAtlasBuildFinish(ImFontAtlas* atlas);
static bool FontBuilder_Build(ImFontAtlas* atlas)
{
ImFontAtlasBuildInit(atlas);
auto freeType = msdfgen::initializeFreetype();
std::vector<msdf_atlas::GlyphGeometry> glyphs;
std::vector<std::pair<size_t, size_t>> ranges;
std::vector<msdf_atlas::Rectangle> customRects;
for (auto& config : atlas->ConfigData)
{
msdf_atlas::Charset charset;
const ImWchar* glyphRanges = config.GlyphRanges;
while (*glyphRanges != NULL)
{
for (ImWchar i = glyphRanges[0]; i <= glyphRanges[1]; i++)
charset.add(i);
glyphRanges += 2;
}
size_t index = glyphs.size();
auto font = msdfgen::loadFontData(freeType, reinterpret_cast<const msdfgen::byte*>(config.FontData), config.FontDataSize);
msdf_atlas::FontGeometry fontGeometry(&glyphs);
fontGeometry.loadCharset(font, config.SizePixels, charset);
auto& metrics = fontGeometry.getMetrics();
config.DstFont->FontSize = config.SizePixels;
config.DstFont->ConfigData = &config;
config.DstFont->ConfigDataCount = 1;
config.DstFont->ContainerAtlas = atlas;
config.DstFont->Ascent = metrics.ascenderY;
config.DstFont->Descent = metrics.descenderY;
msdfgen::destroyFont(font);
ranges.emplace_back(index, glyphs.size() - index);
}
for (auto& glyph : glyphs)
glyph.edgeColoring(&msdfgen::edgeColoringByDistance, 3.0, 0);
for (auto& customRect : atlas->CustomRects)
customRects.emplace_back(0, 0, int(customRect.Width), int(customRect.Height));
TightAtlasPacker packer;
packer.spacing = 1;
packer.dimensionsConstraint = msdf_atlas::DimensionsConstraint::POWER_OF_TWO_RECTANGLE;
packer.miterLimit = 1.0;
packer.pxRange = 8.0;
packer.pack(glyphs.data(), glyphs.size(), customRects.data(), customRects.size());
for (size_t i = 0; i < customRects.size(); i++)
{
auto& srcRect = customRects[i];
auto& dstRect = atlas->CustomRects[i];
dstRect.X = srcRect.x;
dstRect.Y = srcRect.y;
}
msdf_atlas::ImmediateAtlasGenerator<float, 3, &msdf_atlas::msdfGenerator, msdf_atlas::BitmapAtlasStorage<uint8_t, 3>> generator(packer.width, packer.height);
generator.generate(glyphs.data(), glyphs.size());
for (size_t i = 0; i < atlas->ConfigData.size(); i++)
{
auto& config = atlas->ConfigData[i];
auto& [index, count] = ranges[i];
for (size_t j = 0; j < count; j++)
{
auto& glyph = glyphs[index + j];
double x0, y0, x1, y1, u0, v0, u1, v1;
glyph.getQuadPlaneBounds(x0, y0, x1, y1);
glyph.getQuadAtlasBounds(u0, v0, u1, v1);
config.DstFont->AddGlyph(
&config,
glyph.getCodepoint(),
x0,
-y1 + config.DstFont->Ascent,
x1,
-y0 + config.DstFont->Ascent,
u0 / packer.width,
v1 / packer.height,
u1 / packer.width,
v0 / packer.height,
glyph.getAdvance());
}
config.DstFont->BuildLookupTable();
}
atlas->TexPixelsRGBA32 = (unsigned int*)IM_ALLOC(packer.width * packer.height * 4);
atlas->TexWidth = packer.width;
atlas->TexHeight = packer.height;
atlas->TexUvScale = { 1.0f / packer.width, 1.0f / packer.height };
auto bitmapRef = (msdfgen::BitmapConstRef<uint8_t, 3>)generator.atlasStorage();
for (int y = 0; y < packer.height; y++)
{
for (int x = 0; x < packer.width; x++)
{
auto* srcPixels = bitmapRef(x, y);
auto* dstPixels = (uint8_t*)&atlas->TexPixelsRGBA32[y * packer.width + x];
dstPixels[0] = srcPixels[0];
dstPixels[1] = srcPixels[1];
dstPixels[2] = srcPixels[2];
dstPixels[3] = 0xFF;
}
}
msdfgen::deinitializeFreetype(freeType);
ImFontAtlasBuildFinish(atlas);
return true;
}
ImFontBuilderIO g_fontBuilderIO = { FontBuilder_Build };