#include "iwa/io/font.hpp" #include #define STB_RECT_PACK_IMPLEMENTATION #define STB_TRUETYPE_IMPLEMENTATION #include #include #undef STB_RECT_PACK_IMPLEMENTATION #undef STB_TRUETYPE_IMPLEMENTATION namespace iwa { namespace { std::vector getRenderCodePoints() { std::vector result; for (int chr = 32; chr <= 255; ++chr) { result.push_back(chr); } return result; } } ObjectPtr loadFont(const FontLoadOptions& options) { mijin::TypelessBuffer fontData; if (const mijin::StreamError error = options.stream->readRest(fontData); error != mijin::StreamError::SUCCESS) { throw std::runtime_error("IO error reading font from stream."); } // init font data stbtt_fontinfo fontInfo; const int fontOffset = stbtt_GetFontOffsetForIndex(static_cast(fontData.data()), 0); stbtt_InitFont(&fontInfo, static_cast(fontData.data()), fontOffset); // prepare data for packing std::vector codepoints = getRenderCodePoints(); std::vector chardata; std::vector rects; chardata.resize(codepoints.size()); rects.resize(codepoints.size()); stbtt_pack_range range = { .font_size = options.size, .first_unicode_codepoint_in_range = 0, .array_of_unicode_codepoints = codepoints.data(), .num_chars = static_cast(codepoints.size()), .chardata_for_range = chardata.data() }; // TODO: this is just a guess, maybe there is a better way to detect this int oversample = 1; if (options.size < 30) { oversample = 4; } else if (options.size < 35) { oversample = 3; } else if (options.size < 40) { oversample = 4; } int textureSize = 64; mijin::TypelessBuffer pixels; while (textureSize <= 4096) { pixels.resize(textureSize * textureSize * sizeof(unsigned char)); // NOLINT // reset char data for (stbtt_packedchar& packedChar : chardata) { packedChar.x0 = packedChar.y0 = packedChar.x1 = packedChar.y1 = 0; } // try packing with this size stbtt_pack_context packContext; stbtt_PackBegin( /* spc = */ &packContext, /* pixels = */ static_cast(pixels.data()), /* width = */ textureSize, /* height = */ textureSize, /* stride_in_bytes = */ 0, /* padding = */ 1, /* alloc_context = */ nullptr ); stbtt_PackSetOversampling(&packContext, oversample, oversample); // TODO: make adjustable for better font quality const int nRects = stbtt_PackFontRangesGatherRects(&packContext, &fontInfo, &range, 1, rects.data()); stbtt_PackFontRangesPackRects(&packContext, rects.data(), nRects); bool allPacked = true; for (int rectIdx = 0; rectIdx < nRects; ++rectIdx) { if (!rects[rectIdx].was_packed) { allPacked = false; break; } } if (!allPacked) { textureSize *= 2; continue; } stbtt_PackFontRangesRenderIntoRects( /* spc = */ &packContext, /* info = */ &fontInfo, /* ranges = */ &range, /* num_ranges = */ 1, /* rects = */ rects.data() ); stbtt_PackEnd(&packContext); break; } // now create a bitmap from pixels ObjectPtr bitmap = Bitmap::create(BitmapCreationArgs{ .format = vk::Format::eR8Unorm, .size = { .width = static_cast(textureSize), .height = static_cast(textureSize) }, .initialData = std::move(pixels) }); std::unordered_map glyphMap; glyphMap.reserve(chardata.size()); for (auto [codepoint, chrdata] : mijin::zip(codepoints, chardata)) { glyphMap.emplace(static_cast(codepoint), GlyphInfo{ .uvPos0 = { static_cast(chrdata.x0) / static_cast(textureSize), static_cast(chrdata.y0) / static_cast(textureSize) }, .uvPos1 = { static_cast(chrdata.x1) / static_cast(textureSize), static_cast(chrdata.y1) / static_cast(textureSize) }, .xOffsetBefore = chrdata.xoff, .xOffsetAfter = chrdata.xoff2, .yOffsetBefore = chrdata.yoff, .yOffsetAfter = chrdata.yoff2, .xAdvance = chrdata.xadvance }); } const float scale = options.size > 0 ? stbtt_ScaleForPixelHeight(&fontInfo, options.size) : stbtt_ScaleForMappingEmToPixels(&fontInfo, -options.size); int ascent = 0, descent = 0, lineGap = 0; stbtt_GetFontVMetrics(&fontInfo, &ascent, &descent, &lineGap); return Font::create(FontCreationArgs{ .bitmap = std::move(bitmap), .glyphMap = std::move(glyphMap), .metrics = { .ascent = scale * static_cast(ascent), .descent = scale * static_cast(descent), .lineGap = scale * static_cast(lineGap), .sizeFactor = 1.f / static_cast(oversample) } }); } } // namespace iwa