diff options
Diffstat (limited to 'source/30-game')
-rw-r--r-- | source/30-game/Font.hpp | 131 | ||||
-rw-r--r-- | source/30-game/FontManager.cpp | 94 | ||||
-rw-r--r-- | source/30-game/FontManager.hpp | 21 | ||||
-rw-r--r-- | source/30-game/Font_Base.cpp | 448 | ||||
-rw-r--r-- | source/30-game/Font_GlyphRanges.cpp | 253 | ||||
-rw-r--r-- | source/30-game/Texture.cpp | 34 | ||||
-rw-r--r-- | source/30-game/Texture.hpp | 19 |
7 files changed, 978 insertions, 22 deletions
diff --git a/source/30-game/Font.hpp b/source/30-game/Font.hpp new file mode 100644 index 0000000..cc64c0c --- /dev/null +++ b/source/30-game/Font.hpp @@ -0,0 +1,131 @@ +#pragma once + +#include "Color.hpp" +#include "CommonVertexIndex.hpp" +#include "RcPtr.hpp" + +#include <glad/glad.h> +#include <cstddef> +#include <cstdint> +#include <glm/glm.hpp> +#include <memory> +#include <span> +#include <string_view> +#include <vector> + +enum class FontType { + Regular, + Italic, + Bold, + BoldItalic, +}; + +struct GlyphVariant { + int horizontalAdvance; + float x0, y0; + float x1, y1; + float u0, v0; + float u1, v1; +}; + +// TODO optimize for memory when most glyphs don't have a non-regular variant +// such as CJK characters rarely use bold or italics +struct GlyphInfo { + char32_t codepoint; + GlyphVariant regular; + GlyphVariant italic; + GlyphVariant bold; + GlyphVariant boldItalic; +}; + +class Font : public RefCounted { +public: + static const char32_t* GetGlyphRangesDefault(); + static const char32_t* GetGlyphRangesKorean(); + static const char32_t* GetGlyphRangesJapanese(); + static const char32_t* GetGlyphRangesChineseFull(); + static const char32_t* GetGlyphRangesChineseSimplifiedCommon(); + static const char32_t* GetGlyphRangesCyrillic(); + static const char32_t* GetGlyphRangesThai(); + static const char32_t* GetGlyphRangesVietnamese(); + +private: + std::vector<GlyphInfo> mGlyphs; + std::vector<int> mGlyphLookup; + RcPtr<Texture> mAtlas; + float mFontHeight; + int mFallbackGlyphIdx; + +public: + struct LoadingCandidate { + // No std::string_view here because iostreams only support null termianted string + const char* ttfPath; + const char32_t* glyphRange; + FontType type = FontType::Regular; + }; + + Font() = default; + Font(const Font&) = delete; + Font& operator=(const Font&) = delete; + Font(Font&&) = default; + Font& operator=(Font&&) = default; + + enum ErrorCode { + EC_Success, + EC_FileIOFailed, + EC_FontLoadingFailed, + }; + struct InitResult { + ErrorCode errorCode; + int failedItemIdx; + }; + + /// Initialize this font object from a list of ttf files. Each ttf covers a range of unicode characters, + /// and a specific font type, which are combined together into an atlas texture. + /// + /// \see FontType + /// \see Font::LoadingCandidate + InitResult Init(std::span<LoadingCandidate> candidates, float fontHeight, char32_t fallbackCodepoint = '?', int oversampleH = 1, int oversampleV = 1); + + float GetFontHeight() const; + int HorizontalAdvance(char32_t c, FontType type = FontType::Regular) const; + int HorizontalAdvance(std::string_view str, FontType type = FontType::Regular) const; + + int GetGlyphCount() const; + const GlyphInfo& GetFallbackGlyph() const; + + /// Find the glyph corresponding to the given codepoint. If none is present, return the fallback glyph + /// specified in Init(). + const GlyphInfo& FindGlyphFallback(char32_t codepoint) const; + /// Find the glyph corresponding to the given codepoint. If none is present, `nullptr` is returned. + const GlyphInfo* FindGlyph(char32_t codepoint) const; + + struct DrawTargetPointer { + // Draw target info + Vertex_PTC* vertices; + uint32_t* indices; + uint32_t initialVertexIdx; + + // Text info + glm::vec3 pos; + FontType type = FontType::Regular; + RgbaColor color = RgbaColor{}; + + // Output info, written to by DrawTo() + int glyphsRendered; + float horizontalAdvance; + }; + + /// Populate the target with quads, with first character's origin at `pos` and every vertex colored `color`. + /// User must allocate enough space for all characers beforehand (one way is to reserve the # of bytes). + /// Suitable for raw array allocations or something like PodVector<T> that doesn't do zero-initialization on + /// resize. + /// + /// \see Font::DrawTargetPointer + void DrawTo(std::string_view text, DrawTargetPointer& t) const; + void DrawTo(std::u32string_view text, DrawTargetPointer& t) const; + + /// OpenGL texture containing the font atlas. Format is in GL_RED because we don't need the other channels, due to the + /// font atlas being black and white (in other words, opacity only). + const Texture& GetGlyphAtlas() const; +}; diff --git a/source/30-game/FontManager.cpp b/source/30-game/FontManager.cpp new file mode 100644 index 0000000..11a3a5b --- /dev/null +++ b/source/30-game/FontManager.cpp @@ -0,0 +1,94 @@ +#include "FontManager.hpp" + +RcPtr<Font> FontManager::sans{ new Font() }; +RcPtr<Font> FontManager::serif{ new Font() }; +RcPtr<Font> FontManager::monospace{ new Font() }; + +const RcPtr<Font>& FontManager::GetDefaultFont() { + return sans; +} + +const Font* FontManager::ResolveFallback(const Font* font) { + if (font == nullptr) { + return FontManager::sans.Get(); + } else { + return font; + } +} + +const RcPtr<Font>& FontManager::ResolveFallback(const RcPtr<Font>& font) { + if (font == nullptr) { + return FontManager::sans; + } else { + return font; + } +} + +void FontManager::Init() { + Font::LoadingCandidate sansFiles[] = { + { + .ttfPath = "fonts/NotoSans-Regular.ttf", + .glyphRange = Font::GetGlyphRangesDefault(), + .type = FontType::Regular, + }, + { + .ttfPath = "fonts/NotoSans-Italic.ttf", + .glyphRange = Font::GetGlyphRangesDefault(), + .type = FontType::Italic, + }, + { + .ttfPath = "fonts/NotoSans-Bold.ttf", + .glyphRange = Font::GetGlyphRangesDefault(), + .type = FontType::Bold, + }, + { + .ttfPath = "fonts/NotoSans-BoldItalic.ttf", + .glyphRange = Font::GetGlyphRangesDefault(), + .type = FontType::BoldItalic, + }, + { + .ttfPath = "fonts/NotoSansSC-Regular.otf", + .glyphRange = Font::GetGlyphRangesChineseSimplifiedCommon(), + .type = FontType::Regular, + }, + }; + + sans->Init(sansFiles, 18.0f); + + Font::LoadingCandidate serifFiles[] = { + { + .ttfPath = "fonts/NotoSerif-Regular.ttf", + .glyphRange = Font::GetGlyphRangesDefault(), + .type = FontType::Regular, + }, + { + .ttfPath = "fonts/NotoSerif-Italic.ttf", + .glyphRange = Font::GetGlyphRangesDefault(), + .type = FontType::Italic, + }, + { + .ttfPath = "fonts/NotoSerif-Bold.ttf", + .glyphRange = Font::GetGlyphRangesDefault(), + .type = FontType::Bold, + }, + { + .ttfPath = "fonts/NotoSerif-BoldItalic.ttf", + .glyphRange = Font::GetGlyphRangesDefault(), + .type = FontType::BoldItalic, + }, + }; + + serif->Init(sansFiles, 18.0f); + + Font::LoadingCandidate monospacedFiles[] = { + { + .ttfPath = "fonts/NotoMono-Regular.ttf", + .glyphRange = Font::GetGlyphRangesDefault(), + .type = FontType::Regular, + }, + }; + monospace->Init(monospacedFiles, 18.0f); +} + +void FontManager::Shutdown() { +} diff --git a/source/30-game/FontManager.hpp b/source/30-game/FontManager.hpp new file mode 100644 index 0000000..ef87f0c --- /dev/null +++ b/source/30-game/FontManager.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "Font.hpp" +#include "RcPtr.hpp" + +class FontManager { +public: + // Pointers are valid staring from program entry, but object will not be initialized until FontManager::Init() is called. + static RcPtr<Font> sans; + static RcPtr<Font> serif; + static RcPtr<Font> monospace; + +public: + static const RcPtr<Font>& GetDefaultFont(); + + static const Font* ResolveFallback(const Font* font); + static const RcPtr<Font>& ResolveFallback(const RcPtr<Font>& font); + + static void Init(); + static void Shutdown(); +}; diff --git a/source/30-game/Font_Base.cpp b/source/30-game/Font_Base.cpp new file mode 100644 index 0000000..36dc4d6 --- /dev/null +++ b/source/30-game/Font_Base.cpp @@ -0,0 +1,448 @@ +// Font loading code adapted from https://github.com/ocornut/imgui +#include "Font.hpp" + +#include "CommonVertexIndex.hpp" +#include "Image.hpp" +#include "Macros.hpp" +#include "String.hpp" +#include "Texture.hpp" +#include "Utils.hpp" +#include "VertexIndex.hpp" + +#include <stb_rect_pack.h> +#include <stb_truetype.h> +#include <bit> +#include <cmath> +#include <cstddef> +#include <cstdint> +#include <cstring> +#include <fstream> +#include <iostream> +#include <memory> +#include <string> +#include <string_view> + +namespace ProjectBrussel_UNITY_ID { +Font::ErrorCode LoadFile(const char* path, std::unique_ptr<uint8_t[]>& data, stbtt_fontinfo& fontInfo) { + std::ifstream ifs(path, std::ios::binary | std::ios::ate); + if (ifs) { + auto size = static_cast<size_t>(ifs.tellg()); + data = std::make_unique<uint8_t[]>(size); + + ifs.seekg(0, std::ios::beg); + ifs.read(reinterpret_cast<char*>(data.get()), size); + + auto ptr = static_cast<unsigned char*>(data.get()); + if (!stbtt_InitFont(&fontInfo, ptr, stbtt_GetFontOffsetForIndex(ptr, 0))) { + return Font::EC_FileIOFailed; + } + + return Font::EC_Success; + } else { + return Font::EC_FontLoadingFailed; + } +} + +const GlyphVariant& GetVariantFor(const GlyphInfo& info, FontType type) { + switch (type) { + case FontType::Regular: return info.regular; + case FontType::Italic: return info.italic; + case FontType::Bold: return info.bold; + case FontType::BoldItalic: return info.boldItalic; + default: UNREACHABLE; + } +} + +GlyphVariant& GetVariantFor(GlyphInfo& info, FontType type) { + return const_cast<GlyphVariant&>(GetVariantFor(const_cast<const GlyphInfo&>(info), type)); +} +} // namespace ProjectBrussel_UNITY_ID + +Font::InitResult Font::Init(std::span<LoadingCandidate> candidates, float fontHeight, char32_t fallbackCodepoint, int oversampleH, int oversampleV) { + using namespace ProjectBrussel_UNITY_ID; + + mFontHeight = fontHeight; + + struct SrcInfo { + std::vector<int> glyphList; // Need to use vector<int> because stb_true_type use int* + stbtt_fontinfo fontInfo; + stbtt_pack_range packRange; + stbrp_rect* rects; // glyphList.size() long + stbtt_packedchar* packedChars; // glyphList.size() long + const char32_t* glyphRange; + std::unique_ptr<uint8_t[]> data; + size_t dataSize; + int glyphHighest = 0; // Highest codepoint in this glyph range + FontType type; + }; + + // 1. Initialize font loading structure, load ttf from disk + std::vector<SrcInfo> srcVec(candidates.size()); + int glyphHighest = 0; // Highest codepoint across all given glyph ranges + for (size_t i = 0; i < candidates.size(); ++i) { + auto& cand = candidates[i]; + auto& src = srcVec[i]; + + src.glyphRange = cand.glyphRange; + src.type = cand.type; + auto result = LoadFile(cand.ttfPath, src.data, src.fontInfo); + if (result != EC_Success) { + return InitResult{ + .errorCode = result, + .failedItemIdx = (int)i, + }; + } + + for (auto range = src.glyphRange; range[0] && range[1]; range += 2) { + src.glyphHighest = std::max(src.glyphHighest, static_cast<int>(range[1])); + } + glyphHighest = std::max(glyphHighest, src.glyphHighest); + } + + // 2. For every requested codepoint, check for their presence in the font data, and handle redundancy or overlaps between source fonts to avoid unused glyphs + std::vector<bool> glyphSet(glyphHighest + 1); + int glyphCount = 0; + for (auto& src : srcVec) { + if (src.type != FontType::Regular) { + continue; + } + + for (auto range = src.glyphRange; range[0] && range[1]; range += 2) { + for (char32_t codepoint = range[0]; codepoint <= range[1]; ++codepoint) { + // Remove duplicates + if (glyphSet[codepoint]) { + continue; + } + // Font file doesn't have the requested codepoint + if (!stbtt_FindGlyphIndex(&src.fontInfo, codepoint)) { + continue; + } + + src.glyphList.push_back(codepoint); + glyphSet[codepoint] = true; + glyphCount++; + } + } + } + for (auto& src : srcVec) { + if (src.type == FontType::Regular) { + continue; + } + + for (auto range = src.glyphRange; range[0] && range[1]; range += 2) { + for (char32_t codepoint = range[0]; codepoint <= range[1]; ++codepoint) { + // Ignore any formatted (italic, bold, bolditalic) glyph that doesn't have a regular variant + if (!glyphSet[codepoint]) { + continue; + } + // Font file doesn't have the requested codepoint + if (!stbtt_FindGlyphIndex(&src.fontInfo, codepoint)) { + continue; + } + + src.glyphList.push_back(codepoint); + glyphCount++; + } + } + } + glyphSet.clear(); + + // 3. Reserve memory for packing + mGlyphs.reserve(glyphCount); + std::vector<stbrp_rect> rects(glyphCount); // This type them as non-packed (zero initialization) + std::vector<stbtt_packedchar> packedChars(glyphCount); + + // 4. Gather glyphs sizes so we can pack them in our virtual canvas + constexpr int kTexGlyphPadding = 1; // Prevent texture sampler from accidentally getting neighbor glyph's pixel data + float totalSurface = 0; + int rectCount = 0; + int packedCharCount = 0; + for (auto& src : srcVec) { + int glyphCount = static_cast<int>(src.glyphList.size()); + + src.rects = &rects[rectCount]; + src.packedChars = &packedChars[packedCharCount]; + rectCount += glyphCount; + packedCharCount += glyphCount; + + src.packRange.font_size = mFontHeight; + src.packRange.first_unicode_codepoint_in_range = 0; + src.packRange.array_of_unicode_codepoints = src.glyphList.data(); + src.packRange.num_chars = glyphCount; + src.packRange.chardata_for_range = src.packedChars; + src.packRange.h_oversample = oversampleH; + src.packRange.v_oversample = oversampleV; + + float scale = stbtt_ScaleForPixelHeight(&src.fontInfo, mFontHeight); + for (int i = 0; i < glyphCount; ++i) { + int glyphIndex = stbtt_FindGlyphIndex(&src.fontInfo, src.glyphList[i]); + int x0, y0, x1, y1; + stbtt_GetGlyphBitmapBoxSubpixel(&src.fontInfo, glyphIndex, scale * oversampleH, scale * oversampleV, 0, 0, &x0, &y0, &x1, &y1); + src.rects[i].w = (stbrp_coord)(x1 - x0 + kTexGlyphPadding + oversampleH - 1); + src.rects[i].h = (stbrp_coord)(y1 - y0 + kTexGlyphPadding + oversampleV - 1); + totalSurface += static_cast<float>(src.rects[i].w * src.rects[i].h); + } + } + + // We need a width for the skyline algorithm, any width + // The exact width doesn't really matter much, but some API/GPU have texture size limitations and increasing width can decrease height + auto surfaceSqrt = static_cast<int>(std::sqrt(totalSurface)) + 1; + int atlasWidth = + (surfaceSqrt >= 4096 * 0.7f) ? 4096 + : (surfaceSqrt >= 2048 * 0.7f) ? 2048 + : (surfaceSqrt >= 1024 * 0.7f) ? 1024 + : 512; + int atlasHeight = 0; // Calculate later in 6 + + // 5. Start packing + constexpr int kMaxAtlasHeight = 1024 * 32; + stbtt_pack_context spc; + stbtt_PackBegin(&spc, nullptr, atlasWidth, kMaxAtlasHeight, 0, kTexGlyphPadding, nullptr); + + // 6. Pack each source font. No rendering yet, we are working with rectangles in an infinitely tall texture at this point + auto context = reinterpret_cast<stbrp_context*>(spc.pack_info); + for (auto& src : srcVec) { + int glyphCount = static_cast<int>(src.glyphList.size()); + stbrp_pack_rects(context, src.rects, glyphCount); + + for (int i = 0; i < glyphCount; i++) { + if (src.rects[i].was_packed) { + atlasHeight = std::max(atlasHeight, src.rects[i].y + src.rects[i].h); + } + } + } + + // 7. Allocate bitmap + atlasHeight = std::bit_ceil<unsigned int>(atlasHeight); + auto bitmap = std::make_unique<uint8_t[]>(atlasWidth * atlasHeight); + std::memset(bitmap.get(), 0, atlasWidth * atlasHeight * sizeof(uint8_t)); + spc.pixels = bitmap.get(); + spc.height = atlasHeight; + + // 8. Render/rasterize font characters into the texture + for (auto& src : srcVec) { + stbtt_PackFontRangesRenderIntoRects(&spc, &src.fontInfo, &src.packRange, 1, src.rects); + src.rects = nullptr; + } + + // 9. End packing + stbtt_PackEnd(&spc); + rects.clear(); + + // Lambda for code reuse for step 10 and 12 + auto SetupGlyphInfoVariant = [&](SrcInfo& src) -> void { + float scale = stbtt_ScaleForPixelHeight(&src.fontInfo, mFontHeight); + int unscaledAscent, unscaledDescent, unscaledLineGap; + stbtt_GetFontVMetrics(&src.fontInfo, &unscaledAscent, &unscaledDescent, &unscaledLineGap); + + float ascent = std::floor(unscaledAscent * scale + ((unscaledAscent > 0.0f) ? +1 : -1)); + float descent = std::floor(unscaledDescent * scale + ((unscaledDescent > 0.0f) ? +1 : -1)); + float fontOffsetX = 0; + float fontOffsetY = std::round(ascent); + + UNUSED(descent); + + int glyphCount = static_cast<int>(src.glyphList.size()); + for (int i = 0; i < glyphCount; ++i) { + int codepoint = src.glyphList[i]; + auto& packedChar = src.packedChars[i]; + + float x = 0; + float y = 0; + UNUSED(x); + UNUSED(y); + + stbtt_aligned_quad quad; + stbtt_GetPackedQuad(src.packedChars, atlasWidth, atlasHeight, i, &x, &y, &quad, 0); + + GlyphInfo* info; + if (src.type == FontType::Regular) { + mGlyphs.emplace_back(); + info = &mGlyphs.back(); + info->codepoint = codepoint; + } else { + info = &mGlyphs[mGlyphLookup[codepoint]]; + } + + auto& variant = ProjectBrussel_UNITY_ID::GetVariantFor(*info, src.type); + variant.x0 = quad.x0 + fontOffsetX; + variant.y0 = quad.y0 + fontOffsetY; + variant.x1 = quad.x1 + fontOffsetX; + variant.y1 = quad.y1 + fontOffsetY; + variant.u0 = quad.s0; + variant.v0 = quad.t0; + variant.u1 = quad.s1; + variant.v1 = quad.t1; + variant.horizontalAdvance = std::lrint(packedChar.xadvance); + + // For step 12 to override + // If some glyph doesn't have a particular variant, it will remain as Regular in the final product + if (src.type == FontType::Regular) { + info->italic = info->regular; + info->bold = info->regular; + info->boldItalic = info->regular; + } + } + }; + + // 10. Setup glyph infos + for (auto& src : srcVec) { + if (src.type == FontType::Regular) { + SetupGlyphInfoVariant(src); + } + } + + // 11. Setup glyph lookup table + mGlyphLookup.resize(glyphHighest + 1, -1); + for (int i = 0, size = (int)mGlyphs.size(); i < size; ++i) { + auto& info = mGlyphs[i]; + mGlyphLookup[info.codepoint] = i; + } + + // Provided fallback codepoint doesn't exist, use the first glyph present + if (fallbackCodepoint >= static_cast<char32_t>(glyphHighest) || + mGlyphLookup[fallbackCodepoint] == -1) + { + mFallbackGlyphIdx = 0; + } else { + mFallbackGlyphIdx = mGlyphLookup[fallbackCodepoint]; + } + for (size_t i = 0; i < mGlyphLookup.size(); ++i) { + if (mGlyphLookup[i] == -1) { + mGlyphLookup[i] = mFallbackGlyphIdx; + } + } + + // 12. Setup glyph info for non-regular variants + // This step won't cause any new glyph infos to be added, so it's fine (and we need the lookup table here) to do it after building lookup table + for (auto& src : srcVec) { + if (src.type != FontType::Regular) { + SetupGlyphInfoVariant(src); + } + } + + // 13. Upload our rasterized bitmap into a Texture + Image image; + image.InitFromPixels(std::move(bitmap), glm::ivec2(atlasWidth, atlasHeight), 1); + + // Leave the texture upside down, we flip the UV in the text shader + mAtlas.Attach(new Texture()); + mAtlas->InitFromImage( + image, + TextureProperties{ + // When down sampling, 'linear' help reduce flicker when scale changes + .minifyingFilter = Tags::TF_Linear, + // When up sampling, 'nearest' make the text look sharp (though pixelated) + .magnifyingFilter = Tags::TF_Nearest, + }); + + return InitResult{ + .errorCode = EC_Success, + .failedItemIdx = 0, + }; +} + +float Font::GetFontHeight() const { + return mFontHeight; +} + +int Font::HorizontalAdvance(char32_t c, FontType type) const { + auto info = FindGlyphFallback(c); + return ProjectBrussel_UNITY_ID::GetVariantFor(info, type).horizontalAdvance; +} + +int Font::HorizontalAdvance(std::string_view str, FontType type) const { + int width = 0; + for (auto c : Utf8IterableString(str)) { + width += HorizontalAdvance(c, type); + } + return width; +} + +int Font::GetGlyphCount() const { + return mGlyphs.size(); +} + +const GlyphInfo& Font::GetFallbackGlyph() const { + return mGlyphs[mFallbackGlyphIdx]; +} + +const GlyphInfo& Font::FindGlyphFallback(char32_t codepoint) const { + auto codepointInt = static_cast<uint32_t>(codepoint); + if (codepointInt < mGlyphLookup.size()) { + return mGlyphs[mGlyphLookup[codepointInt]]; + } else { + return mGlyphs[mFallbackGlyphIdx]; + } +} + +const GlyphInfo* Font::FindGlyph(char32_t codepoint) const { + auto codepointInt = static_cast<uint32_t>(codepoint); + if (codepointInt < mGlyphLookup.size()) { + return &mGlyphs[mGlyphLookup[codepointInt]]; + } else { + return nullptr; + } +} + +namespace ProjectBrussel_UNITY_ID { +void GenQuad(Vertex_PTC* vertices, glm::vec3 pos, const GlyphVariant& variant) { + // Top left + vertices[0].x = pos.x + variant.x0; + vertices[0].y = pos.y + variant.y0; + vertices[0].u = variant.u0; + vertices[0].v = variant.v0; + // Top right + vertices[1].x = pos.x + variant.x1; + vertices[1].y = pos.y + variant.y0; + vertices[1].u = variant.u1; + vertices[1].v = variant.v0; + // Bottom right + vertices[2].x = pos.x + variant.x1; + vertices[2].y = pos.y + variant.y1; + vertices[2].u = variant.u1; + vertices[2].v = variant.v1; + // Bottom left + vertices[3].x = pos.x + variant.x0; + vertices[3].y = pos.y + variant.y1; + vertices[3].u = variant.u0; + vertices[3].v = variant.v1; +} + +template <class TStringIterable> +void DrawString(const Font& font, TStringIterable text, Font::DrawTargetPointer& t) { + auto pos = t.pos; + auto vertices = t.vertices; + auto indices = t.indices; + auto initialVertexIdx = t.initialVertexIdx; + + for (char32_t codepoint : text) { + auto& info = font.FindGlyphFallback(codepoint); + auto& variant = ProjectBrussel_UNITY_ID::GetVariantFor(info, t.type); + GenQuad(vertices, pos, variant); + Vertex_PTC::Assign(vertices, pos.z); + Vertex_PTC::Assign(vertices, t.color); + Index_U32::Assign(indices, initialVertexIdx); + + pos.x += variant.horizontalAdvance; + t.glyphsRendered++; + t.horizontalAdvance += variant.horizontalAdvance; + + vertices += 4; + indices += 6; + initialVertexIdx += 4; + } +} +} // namespace ProjectBrussel_UNITY_ID + +void Font::DrawTo(std::string_view text, DrawTargetPointer& t) const { + ProjectBrussel_UNITY_ID::DrawString(*this, Utf8IterableString(text), t); +} + +void Font::DrawTo(std::u32string_view text, DrawTargetPointer& t) const { + ProjectBrussel_UNITY_ID::DrawString(*this, text, t); +} + +const Texture& Font::GetGlyphAtlas() const { + return *mAtlas; +} diff --git a/source/30-game/Font_GlyphRanges.cpp b/source/30-game/Font_GlyphRanges.cpp new file mode 100644 index 0000000..dd48594 --- /dev/null +++ b/source/30-game/Font_GlyphRanges.cpp @@ -0,0 +1,253 @@ +// Adapted from https://github.com/ocornut/imgui +// specifically imgui_draw.cpp as of time of writing + +#include "Font.hpp" + +#include <cstddef> +#include <cstring> + +static void UnpackAccumulativeOffsetsIntoRanges(int baseCodepoint, const short* accumulativeOffsets, size_t accumulativeOffsetsCount, char32_t* outRanges) { + for (int n = 0; n < accumulativeOffsetsCount; n++, outRanges += 2) { + outRanges[0] = outRanges[1] = (char32_t)(baseCodepoint + accumulativeOffsets[n]); + baseCodepoint += accumulativeOffsets[n]; + } + outRanges[0] = 0; +} + +// clang-format off +static const char32_t kDefaultRanges[] = { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0, +}; +// clang-format on +const char32_t* Font::GetGlyphRangesDefault() { + return &kDefaultRanges[0]; +} + +// clang-format off +static const char32_t kKoreanRanges[] = { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x3131, 0x3163, // Korean alphabets + 0xAC00, 0xD7A3, // Korean characters + 0, +}; +// clang-format on +const char32_t* Font::GetGlyphRangesKorean() { + return &kKoreanRanges[0]; +} + +// 2999 ideograms code points for Japanese +// - 2136 Joyo (meaning "for regular use" or "for common use") Kanji code points +// - 863 Jinmeiyo (meaning "for personal name") Kanji code points +// - Sourced from the character information database of the Information-technology Promotion Agency, Japan +// - https://mojikiban.ipa.go.jp/mji/ +// - Available under the terms of the Creative Commons Attribution-ShareAlike 2.1 Japan (CC BY-SA 2.1 JP). +// - https://creativecommons.org/licenses/by-sa/2.1/jp/deed.en +// - https://creativecommons.org/licenses/by-sa/2.1/jp/legalcode +// - You can generate this code by the script at: +// - https://github.com/vaiorabbit/everyday_use_kanji +// - References: +// - List of Joyo Kanji +// - (Official list by the Agency for Cultural Affairs) https://www.bunka.go.jp/kokugo_nihongo/sisaku/joho/joho/kakuki/14/tosin02/index.html +// - (Wikipedia) https://en.wikipedia.org/wiki/List_of_j%C5%8Dy%C5%8D_kanji +// - List of Jinmeiyo Kanji +// - (Official list by the Ministry of Justice) http://www.moj.go.jp/MINJI/minji86.html +// - (Wikipedia) https://en.wikipedia.org/wiki/Jinmeiy%C5%8D_kanji +// - Missing 1 Joyo Kanji: U+20B9F (Kun'yomi: Shikaru, On'yomi: Shitsu,shichi), see https://github.com/ocornut/imgUI/pull/3627 for details. +// (Stored as accumulative offsets from the initial unicode codepoint 0x4E00. This encoding is designed to helps us compact the source code size.) +// clang-format off +static const short kJapaneseAccumulativeOffsetsFrom0x4E00[] = +{ + 0,1,2,4,1,1,1,1,2,1,3,3,2,2,1,5,3,5,7,5,6,1,2,1,7,2,6,3,1,8,1,1,4,1,1,18,2,11,2,6,2,1,2,1,5,1,2,1,3,1,2,1,2,3,3,1,1,2,3,1,1,1,12,7,9,1,4,5,1, + 1,2,1,10,1,1,9,2,2,4,5,6,9,3,1,1,1,1,9,3,18,5,2,2,2,2,1,6,3,7,1,1,1,1,2,2,4,2,1,23,2,10,4,3,5,2,4,10,2,4,13,1,6,1,9,3,1,1,6,6,7,6,3,1,2,11,3, + 2,2,3,2,15,2,2,5,4,3,6,4,1,2,5,2,12,16,6,13,9,13,2,1,1,7,16,4,7,1,19,1,5,1,2,2,7,7,8,2,6,5,4,9,18,7,4,5,9,13,11,8,15,2,1,1,1,2,1,2,2,1,2,2,8, + 2,9,3,3,1,1,4,4,1,1,1,4,9,1,4,3,5,5,2,7,5,3,4,8,2,1,13,2,3,3,1,14,1,1,4,5,1,3,6,1,5,2,1,1,3,3,3,3,1,1,2,7,6,6,7,1,4,7,6,1,1,1,1,1,12,3,3,9,5, + 2,6,1,5,6,1,2,3,18,2,4,14,4,1,3,6,1,1,6,3,5,5,3,2,2,2,2,12,3,1,4,2,3,2,3,11,1,7,4,1,2,1,3,17,1,9,1,24,1,1,4,2,2,4,1,2,7,1,1,1,3,1,2,2,4,15,1, + 1,2,1,1,2,1,5,2,5,20,2,5,9,1,10,8,7,6,1,1,1,1,1,1,6,2,1,2,8,1,1,1,1,5,1,1,3,1,1,1,1,3,1,1,12,4,1,3,1,1,1,1,1,10,3,1,7,5,13,1,2,3,4,6,1,1,30, + 2,9,9,1,15,38,11,3,1,8,24,7,1,9,8,10,2,1,9,31,2,13,6,2,9,4,49,5,2,15,2,1,10,2,1,1,1,2,2,6,15,30,35,3,14,18,8,1,16,10,28,12,19,45,38,1,3,2,3, + 13,2,1,7,3,6,5,3,4,3,1,5,7,8,1,5,3,18,5,3,6,1,21,4,24,9,24,40,3,14,3,21,3,2,1,2,4,2,3,1,15,15,6,5,1,1,3,1,5,6,1,9,7,3,3,2,1,4,3,8,21,5,16,4, + 5,2,10,11,11,3,6,3,2,9,3,6,13,1,2,1,1,1,1,11,12,6,6,1,4,2,6,5,2,1,1,3,3,6,13,3,1,1,5,1,2,3,3,14,2,1,2,2,2,5,1,9,5,1,1,6,12,3,12,3,4,13,2,14, + 2,8,1,17,5,1,16,4,2,2,21,8,9,6,23,20,12,25,19,9,38,8,3,21,40,25,33,13,4,3,1,4,1,2,4,1,2,5,26,2,1,1,2,1,3,6,2,1,1,1,1,1,1,2,3,1,1,1,9,2,3,1,1, + 1,3,6,3,2,1,1,6,6,1,8,2,2,2,1,4,1,2,3,2,7,3,2,4,1,2,1,2,2,1,1,1,1,1,3,1,2,5,4,10,9,4,9,1,1,1,1,1,1,5,3,2,1,6,4,9,6,1,10,2,31,17,8,3,7,5,40,1, + 7,7,1,6,5,2,10,7,8,4,15,39,25,6,28,47,18,10,7,1,3,1,1,2,1,1,1,3,3,3,1,1,1,3,4,2,1,4,1,3,6,10,7,8,6,2,2,1,3,3,2,5,8,7,9,12,2,15,1,1,4,1,2,1,1, + 1,3,2,1,3,3,5,6,2,3,2,10,1,4,2,8,1,1,1,11,6,1,21,4,16,3,1,3,1,4,2,3,6,5,1,3,1,1,3,3,4,6,1,1,10,4,2,7,10,4,7,4,2,9,4,3,1,1,1,4,1,8,3,4,1,3,1, + 6,1,4,2,1,4,7,2,1,8,1,4,5,1,1,2,2,4,6,2,7,1,10,1,1,3,4,11,10,8,21,4,6,1,3,5,2,1,2,28,5,5,2,3,13,1,2,3,1,4,2,1,5,20,3,8,11,1,3,3,3,1,8,10,9,2, + 10,9,2,3,1,1,2,4,1,8,3,6,1,7,8,6,11,1,4,29,8,4,3,1,2,7,13,1,4,1,6,2,6,12,12,2,20,3,2,3,6,4,8,9,2,7,34,5,1,18,6,1,1,4,4,5,7,9,1,2,2,4,3,4,1,7, + 2,2,2,6,2,3,25,5,3,6,1,4,6,7,4,2,1,4,2,13,6,4,4,3,1,5,3,4,4,3,2,1,1,4,1,2,1,1,3,1,11,1,6,3,1,7,3,6,2,8,8,6,9,3,4,11,3,2,10,12,2,5,11,1,6,4,5, + 3,1,8,5,4,6,6,3,5,1,1,3,2,1,2,2,6,17,12,1,10,1,6,12,1,6,6,19,9,6,16,1,13,4,4,15,7,17,6,11,9,15,12,6,7,2,1,2,2,15,9,3,21,4,6,49,18,7,3,2,3,1, + 6,8,2,2,6,2,9,1,3,6,4,4,1,2,16,2,5,2,1,6,2,3,5,3,1,2,5,1,2,1,9,3,1,8,6,4,8,11,3,1,1,1,1,3,1,13,8,4,1,3,2,2,1,4,1,11,1,5,2,1,5,2,5,8,6,1,1,7, + 4,3,8,3,2,7,2,1,5,1,5,2,4,7,6,2,8,5,1,11,4,5,3,6,18,1,2,13,3,3,1,21,1,1,4,1,4,1,1,1,8,1,2,2,7,1,2,4,2,2,9,2,1,1,1,4,3,6,3,12,5,1,1,1,5,6,3,2, + 4,8,2,2,4,2,7,1,8,9,5,2,3,2,1,3,2,13,7,14,6,5,1,1,2,1,4,2,23,2,1,1,6,3,1,4,1,15,3,1,7,3,9,14,1,3,1,4,1,1,5,8,1,3,8,3,8,15,11,4,14,4,4,2,5,5, + 1,7,1,6,14,7,7,8,5,15,4,8,6,5,6,2,1,13,1,20,15,11,9,2,5,6,2,11,2,6,2,5,1,5,8,4,13,19,25,4,1,1,11,1,34,2,5,9,14,6,2,2,6,1,1,14,1,3,14,13,1,6, + 12,21,14,14,6,32,17,8,32,9,28,1,2,4,11,8,3,1,14,2,5,15,1,1,1,1,3,6,4,1,3,4,11,3,1,1,11,30,1,5,1,4,1,5,8,1,1,3,2,4,3,17,35,2,6,12,17,3,1,6,2, + 1,1,12,2,7,3,3,2,1,16,2,8,3,6,5,4,7,3,3,8,1,9,8,5,1,2,1,3,2,8,1,2,9,12,1,1,2,3,8,3,24,12,4,3,7,5,8,3,3,3,3,3,3,1,23,10,3,1,2,2,6,3,1,16,1,16, + 22,3,10,4,11,6,9,7,7,3,6,2,2,2,4,10,2,1,1,2,8,7,1,6,4,1,3,3,3,5,10,12,12,2,3,12,8,15,1,1,16,6,6,1,5,9,11,4,11,4,2,6,12,1,17,5,13,1,4,9,5,1,11, + 2,1,8,1,5,7,28,8,3,5,10,2,17,3,38,22,1,2,18,12,10,4,38,18,1,4,44,19,4,1,8,4,1,12,1,4,31,12,1,14,7,75,7,5,10,6,6,13,3,2,11,11,3,2,5,28,15,6,18, + 18,5,6,4,3,16,1,7,18,7,36,3,5,3,1,7,1,9,1,10,7,2,4,2,6,2,9,7,4,3,32,12,3,7,10,2,23,16,3,1,12,3,31,4,11,1,3,8,9,5,1,30,15,6,12,3,2,2,11,19,9, + 14,2,6,2,3,19,13,17,5,3,3,25,3,14,1,1,1,36,1,3,2,19,3,13,36,9,13,31,6,4,16,34,2,5,4,2,3,3,5,1,1,1,4,3,1,17,3,2,3,5,3,1,3,2,3,5,6,3,12,11,1,3, + 1,2,26,7,12,7,2,14,3,3,7,7,11,25,25,28,16,4,36,1,2,1,6,2,1,9,3,27,17,4,3,4,13,4,1,3,2,2,1,10,4,2,4,6,3,8,2,1,18,1,1,24,2,2,4,33,2,3,63,7,1,6, + 40,7,3,4,4,2,4,15,18,1,16,1,1,11,2,41,14,1,3,18,13,3,2,4,16,2,17,7,15,24,7,18,13,44,2,2,3,6,1,1,7,5,1,7,1,4,3,3,5,10,8,2,3,1,8,1,1,27,4,2,1, + 12,1,2,1,10,6,1,6,7,5,2,3,7,11,5,11,3,6,6,2,3,15,4,9,1,1,2,1,2,11,2,8,12,8,5,4,2,3,1,5,2,2,1,14,1,12,11,4,1,11,17,17,4,3,2,5,5,7,3,1,5,9,9,8, + 2,5,6,6,13,13,2,1,2,6,1,2,2,49,4,9,1,2,10,16,7,8,4,3,2,23,4,58,3,29,1,14,19,19,11,11,2,7,5,1,3,4,6,2,18,5,12,12,17,17,3,3,2,4,1,6,2,3,4,3,1, + 1,1,1,5,1,1,9,1,3,1,3,6,1,8,1,1,2,6,4,14,3,1,4,11,4,1,3,32,1,2,4,13,4,1,2,4,2,1,3,1,11,1,4,2,1,4,4,6,3,5,1,6,5,7,6,3,23,3,5,3,5,3,3,13,3,9,10, + 1,12,10,2,3,18,13,7,160,52,4,2,2,3,2,14,5,4,12,4,6,4,1,20,4,11,6,2,12,27,1,4,1,2,2,7,4,5,2,28,3,7,25,8,3,19,3,6,10,2,2,1,10,2,5,4,1,3,4,1,5, + 3,2,6,9,3,6,2,16,3,3,16,4,5,5,3,2,1,2,16,15,8,2,6,21,2,4,1,22,5,8,1,1,21,11,2,1,11,11,19,13,12,4,2,3,2,3,6,1,8,11,1,4,2,9,5,2,1,11,2,9,1,1,2, + 14,31,9,3,4,21,14,4,8,1,7,2,2,2,5,1,4,20,3,3,4,10,1,11,9,8,2,1,4,5,14,12,14,2,17,9,6,31,4,14,1,20,13,26,5,2,7,3,6,13,2,4,2,19,6,2,2,18,9,3,5, + 12,12,14,4,6,2,3,6,9,5,22,4,5,25,6,4,8,5,2,6,27,2,35,2,16,3,7,8,8,6,6,5,9,17,2,20,6,19,2,13,3,1,1,1,4,17,12,2,14,7,1,4,18,12,38,33,2,10,1,1, + 2,13,14,17,11,50,6,33,20,26,74,16,23,45,50,13,38,33,6,6,7,4,4,2,1,3,2,5,8,7,8,9,3,11,21,9,13,1,3,10,6,7,1,2,2,18,5,5,1,9,9,2,68,9,19,13,2,5, + 1,4,4,7,4,13,3,9,10,21,17,3,26,2,1,5,2,4,5,4,1,7,4,7,3,4,2,1,6,1,1,20,4,1,9,2,2,1,3,3,2,3,2,1,1,1,20,2,3,1,6,2,3,6,2,4,8,1,3,2,10,3,5,3,4,4, + 3,4,16,1,6,1,10,2,4,2,1,1,2,10,11,2,2,3,1,24,31,4,10,10,2,5,12,16,164,15,4,16,7,9,15,19,17,1,2,1,1,5,1,1,1,1,1,3,1,4,3,1,3,1,3,1,2,1,1,3,3,7, + 2,8,1,2,2,2,1,3,4,3,7,8,12,92,2,10,3,1,3,14,5,25,16,42,4,7,7,4,2,21,5,27,26,27,21,25,30,31,2,1,5,13,3,22,5,6,6,11,9,12,1,5,9,7,5,5,22,60,3,5, + 13,1,1,8,1,1,3,3,2,1,9,3,3,18,4,1,2,3,7,6,3,1,2,3,9,1,3,1,3,2,1,3,1,1,1,2,1,11,3,1,6,9,1,3,2,3,1,2,1,5,1,1,4,3,4,1,2,2,4,4,1,7,2,1,2,2,3,5,13, + 18,3,4,14,9,9,4,16,3,7,5,8,2,6,48,28,3,1,1,4,2,14,8,2,9,2,1,15,2,4,3,2,10,16,12,8,7,1,1,3,1,1,1,2,7,4,1,6,4,38,39,16,23,7,15,15,3,2,12,7,21, + 37,27,6,5,4,8,2,10,8,8,6,5,1,2,1,3,24,1,16,17,9,23,10,17,6,1,51,55,44,13,294,9,3,6,2,4,2,2,15,1,1,1,13,21,17,68,14,8,9,4,1,4,9,3,11,7,1,1,1, + 5,6,3,2,1,1,1,2,3,8,1,2,2,4,1,5,5,2,1,4,3,7,13,4,1,4,1,3,1,1,1,5,5,10,1,6,1,5,2,1,5,2,4,1,4,5,7,3,18,2,9,11,32,4,3,3,2,4,7,11,16,9,11,8,13,38, + 32,8,4,2,1,1,2,1,2,4,4,1,1,1,4,1,21,3,11,1,16,1,1,6,1,3,2,4,9,8,57,7,44,1,3,3,13,3,10,1,1,7,5,2,7,21,47,63,3,15,4,7,1,16,1,1,2,8,2,3,42,15,4, + 1,29,7,22,10,3,78,16,12,20,18,4,67,11,5,1,3,15,6,21,31,32,27,18,13,71,35,5,142,4,10,1,2,50,19,33,16,35,37,16,19,27,7,1,133,19,1,4,8,7,20,1,4, + 4,1,10,3,1,6,1,2,51,5,40,15,24,43,22928,11,1,13,154,70,3,1,1,7,4,10,1,2,1,1,2,1,2,1,2,2,1,1,2,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1, + 3,2,1,1,1,1,2,1,1, +}; +// clang-format on + +// clang-format off +static char32_t japaneseBaseRanges[] = { + // not zero-terminated + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x3000, 0x30FF, // CJK Symbols and Punctuations, Hiragana, Katakana + 0x31F0, 0x31FF, // Katakana Phonetic Extensions + 0xFF00, 0xFFEF // Half-width characters +}; +// clang-format on +static char32_t japaneseFullRanges[std::size(japaneseBaseRanges) + std::size(kJapaneseAccumulativeOffsetsFrom0x4E00) * 2 + 1] = { 0 }; +const char32_t* Font::GetGlyphRangesJapanese() { + if (!japaneseFullRanges[0]) { + memcpy(japaneseFullRanges, japaneseBaseRanges, sizeof(japaneseBaseRanges)); + UnpackAccumulativeOffsetsIntoRanges(0x4E00, kJapaneseAccumulativeOffsetsFrom0x4E00, std::size(kJapaneseAccumulativeOffsetsFrom0x4E00), japaneseFullRanges + std::size(japaneseBaseRanges)); + } + return &japaneseFullRanges[0]; +} + +// clang-format off +static const char32_t kChineseFullRanges[] = { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x2000, 0x206F, // General Punctuation + 0x3000, 0x30FF, // CJK Symbols and Punctuations, Hiragana, Katakana + 0x31F0, 0x31FF, // Katakana Phonetic Extensions + 0xFF00, 0xFFEF, // Half-width characters + 0x4e00, 0x9FAF, // CJK Ideograms + 0, +}; +// clang-format on +const char32_t* Font::GetGlyphRangesChineseFull() { + return &kChineseFullRanges[0]; +} + +// Store 2500 regularly used characters for Simplified Chinese. +// Sourced from https://zh.wiktionary.org/wiki/%E9%99%84%E5%BD%95:%E7%8E%B0%E4%BB%A3%E6%B1%89%E8%AF%AD%E5%B8%B8%E7%94%A8%E5%AD%97%E8%A1%A8 +// This table covers 97.97% of all characters used during the month in July, 1987. +// (Stored as accumulative offsets from the initial unicode codepoint 0x4E00. This encoding is designed to helps us compact the source code size.) +// clang-format off +static const short kChineseAccumulativeOffsetsFrom0x4E00[] = { + 0,1,2,4,1,1,1,1,2,1,3,2,1,2,2,1,1,1,1,1,5,2,1,2,3,3,3,2,2,4,1,1,1,2,1,5,2,3,1,2,1,2,1,1,2,1,1,2,2,1,4,1,1,1,1,5,10,1,2,19,2,1,2,1,2,1,2,1,2, + 1,5,1,6,3,2,1,2,2,1,1,1,4,8,5,1,1,4,1,1,3,1,2,1,5,1,2,1,1,1,10,1,1,5,2,4,6,1,4,2,2,2,12,2,1,1,6,1,1,1,4,1,1,4,6,5,1,4,2,2,4,10,7,1,1,4,2,4, + 2,1,4,3,6,10,12,5,7,2,14,2,9,1,1,6,7,10,4,7,13,1,5,4,8,4,1,1,2,28,5,6,1,1,5,2,5,20,2,2,9,8,11,2,9,17,1,8,6,8,27,4,6,9,20,11,27,6,68,2,2,1,1, + 1,2,1,2,2,7,6,11,3,3,1,1,3,1,2,1,1,1,1,1,3,1,1,8,3,4,1,5,7,2,1,4,4,8,4,2,1,2,1,1,4,5,6,3,6,2,12,3,1,3,9,2,4,3,4,1,5,3,3,1,3,7,1,5,1,1,1,1,2, + 3,4,5,2,3,2,6,1,1,2,1,7,1,7,3,4,5,15,2,2,1,5,3,22,19,2,1,1,1,1,2,5,1,1,1,6,1,1,12,8,2,9,18,22,4,1,1,5,1,16,1,2,7,10,15,1,1,6,2,4,1,2,4,1,6, + 1,1,3,2,4,1,6,4,5,1,2,1,1,2,1,10,3,1,3,2,1,9,3,2,5,7,2,19,4,3,6,1,1,1,1,1,4,3,2,1,1,1,2,5,3,1,1,1,2,2,1,1,2,1,1,2,1,3,1,1,1,3,7,1,4,1,1,2,1, + 1,2,1,2,4,4,3,8,1,1,1,2,1,3,5,1,3,1,3,4,6,2,2,14,4,6,6,11,9,1,15,3,1,28,5,2,5,5,3,1,3,4,5,4,6,14,3,2,3,5,21,2,7,20,10,1,2,19,2,4,28,28,2,3, + 2,1,14,4,1,26,28,42,12,40,3,52,79,5,14,17,3,2,2,11,3,4,6,3,1,8,2,23,4,5,8,10,4,2,7,3,5,1,1,6,3,1,2,2,2,5,28,1,1,7,7,20,5,3,29,3,17,26,1,8,4, + 27,3,6,11,23,5,3,4,6,13,24,16,6,5,10,25,35,7,3,2,3,3,14,3,6,2,6,1,4,2,3,8,2,1,1,3,3,3,4,1,1,13,2,2,4,5,2,1,14,14,1,2,2,1,4,5,2,3,1,14,3,12, + 3,17,2,16,5,1,2,1,8,9,3,19,4,2,2,4,17,25,21,20,28,75,1,10,29,103,4,1,2,1,1,4,2,4,1,2,3,24,2,2,2,1,1,2,1,3,8,1,1,1,2,1,1,3,1,1,1,6,1,5,3,1,1, + 1,3,4,1,1,5,2,1,5,6,13,9,16,1,1,1,1,3,2,3,2,4,5,2,5,2,2,3,7,13,7,2,2,1,1,1,1,2,3,3,2,1,6,4,9,2,1,14,2,14,2,1,18,3,4,14,4,11,41,15,23,15,23, + 176,1,3,4,1,1,1,1,5,3,1,2,3,7,3,1,1,2,1,2,4,4,6,2,4,1,9,7,1,10,5,8,16,29,1,1,2,2,3,1,3,5,2,4,5,4,1,1,2,2,3,3,7,1,6,10,1,17,1,44,4,6,2,1,1,6, + 5,4,2,10,1,6,9,2,8,1,24,1,2,13,7,8,8,2,1,4,1,3,1,3,3,5,2,5,10,9,4,9,12,2,1,6,1,10,1,1,7,7,4,10,8,3,1,13,4,3,1,6,1,3,5,2,1,2,17,16,5,2,16,6, + 1,4,2,1,3,3,6,8,5,11,11,1,3,3,2,4,6,10,9,5,7,4,7,4,7,1,1,4,2,1,3,6,8,7,1,6,11,5,5,3,24,9,4,2,7,13,5,1,8,82,16,61,1,1,1,4,2,2,16,10,3,8,1,1, + 6,4,2,1,3,1,1,1,4,3,8,4,2,2,1,1,1,1,1,6,3,5,1,1,4,6,9,2,1,1,1,2,1,7,2,1,6,1,5,4,4,3,1,8,1,3,3,1,3,2,2,2,2,3,1,6,1,2,1,2,1,3,7,1,8,2,1,2,1,5, + 2,5,3,5,10,1,2,1,1,3,2,5,11,3,9,3,5,1,1,5,9,1,2,1,5,7,9,9,8,1,3,3,3,6,8,2,3,2,1,1,32,6,1,2,15,9,3,7,13,1,3,10,13,2,14,1,13,10,2,1,3,10,4,15, + 2,15,15,10,1,3,9,6,9,32,25,26,47,7,3,2,3,1,6,3,4,3,2,8,5,4,1,9,4,2,2,19,10,6,2,3,8,1,2,2,4,2,1,9,4,4,4,6,4,8,9,2,3,1,1,1,1,3,5,5,1,3,8,4,6, + 2,1,4,12,1,5,3,7,13,2,5,8,1,6,1,2,5,14,6,1,5,2,4,8,15,5,1,23,6,62,2,10,1,1,8,1,2,2,10,4,2,2,9,2,1,1,3,2,3,1,5,3,3,2,1,3,8,1,1,1,11,3,1,1,4, + 3,7,1,14,1,2,3,12,5,2,5,1,6,7,5,7,14,11,1,3,1,8,9,12,2,1,11,8,4,4,2,6,10,9,13,1,1,3,1,5,1,3,2,4,4,1,18,2,3,14,11,4,29,4,2,7,1,3,13,9,2,2,5, + 3,5,20,7,16,8,5,72,34,6,4,22,12,12,28,45,36,9,7,39,9,191,1,1,1,4,11,8,4,9,2,3,22,1,1,1,1,4,17,1,7,7,1,11,31,10,2,4,8,2,3,2,1,4,2,16,4,32,2, + 3,19,13,4,9,1,5,2,14,8,1,1,3,6,19,6,5,1,16,6,2,10,8,5,1,2,3,1,5,5,1,11,6,6,1,3,3,2,6,3,8,1,1,4,10,7,5,7,7,5,8,9,2,1,3,4,1,1,3,1,3,3,2,6,16, + 1,4,6,3,1,10,6,1,3,15,2,9,2,10,25,13,9,16,6,2,2,10,11,4,3,9,1,2,6,6,5,4,30,40,1,10,7,12,14,33,6,3,6,7,3,1,3,1,11,14,4,9,5,12,11,49,18,51,31, + 140,31,2,2,1,5,1,8,1,10,1,4,4,3,24,1,10,1,3,6,6,16,3,4,5,2,1,4,2,57,10,6,22,2,22,3,7,22,6,10,11,36,18,16,33,36,2,5,5,1,1,1,4,10,1,4,13,2,7, + 5,2,9,3,4,1,7,43,3,7,3,9,14,7,9,1,11,1,1,3,7,4,18,13,1,14,1,3,6,10,73,2,2,30,6,1,11,18,19,13,22,3,46,42,37,89,7,3,16,34,2,2,3,9,1,7,1,1,1,2, + 2,4,10,7,3,10,3,9,5,28,9,2,6,13,7,3,1,3,10,2,7,2,11,3,6,21,54,85,2,1,4,2,2,1,39,3,21,2,2,5,1,1,1,4,1,1,3,4,15,1,3,2,4,4,2,3,8,2,20,1,8,7,13, + 4,1,26,6,2,9,34,4,21,52,10,4,4,1,5,12,2,11,1,7,2,30,12,44,2,30,1,1,3,6,16,9,17,39,82,2,2,24,7,1,7,3,16,9,14,44,2,1,2,1,2,3,5,2,4,1,6,7,5,3, + 2,6,1,11,5,11,2,1,18,19,8,1,3,24,29,2,1,3,5,2,2,1,13,6,5,1,46,11,3,5,1,1,5,8,2,10,6,12,6,3,7,11,2,4,16,13,2,5,1,1,2,2,5,2,28,5,2,23,10,8,4, + 4,22,39,95,38,8,14,9,5,1,13,5,4,3,13,12,11,1,9,1,27,37,2,5,4,4,63,211,95,2,2,2,1,3,5,2,1,1,2,2,1,1,1,3,2,4,1,2,1,1,5,2,2,1,1,2,3,1,3,1,1,1, + 3,1,4,2,1,3,6,1,1,3,7,15,5,3,2,5,3,9,11,4,2,22,1,6,3,8,7,1,4,28,4,16,3,3,25,4,4,27,27,1,4,1,2,2,7,1,3,5,2,28,8,2,14,1,8,6,16,25,3,3,3,14,3, + 3,1,1,2,1,4,6,3,8,4,1,1,1,2,3,6,10,6,2,3,18,3,2,5,5,4,3,1,5,2,5,4,23,7,6,12,6,4,17,11,9,5,1,1,10,5,12,1,1,11,26,33,7,3,6,1,17,7,1,5,12,1,11, + 2,4,1,8,14,17,23,1,2,1,7,8,16,11,9,6,5,2,6,4,16,2,8,14,1,11,8,9,1,1,1,9,25,4,11,19,7,2,15,2,12,8,52,7,5,19,2,16,4,36,8,1,16,8,24,26,4,6,2,9, + 5,4,36,3,28,12,25,15,37,27,17,12,59,38,5,32,127,1,2,9,17,14,4,1,2,1,1,8,11,50,4,14,2,19,16,4,17,5,4,5,26,12,45,2,23,45,104,30,12,8,3,10,2,2, + 3,3,1,4,20,7,2,9,6,15,2,20,1,3,16,4,11,15,6,134,2,5,59,1,2,2,2,1,9,17,3,26,137,10,211,59,1,2,4,1,4,1,1,1,2,6,2,3,1,1,2,3,2,3,1,3,4,4,2,3,3, + 1,4,3,1,7,2,2,3,1,2,1,3,3,3,2,2,3,2,1,3,14,6,1,3,2,9,6,15,27,9,34,145,1,1,2,1,1,1,1,2,1,1,1,1,2,2,2,3,1,2,1,1,1,2,3,5,8,3,5,2,4,1,3,2,2,2,12, + 4,1,1,1,10,4,5,1,20,4,16,1,15,9,5,12,2,9,2,5,4,2,26,19,7,1,26,4,30,12,15,42,1,6,8,172,1,1,4,2,1,1,11,2,2,4,2,1,2,1,10,8,1,2,1,4,5,1,2,5,1,8, + 4,1,3,4,2,1,6,2,1,3,4,1,2,1,1,1,1,12,5,7,2,4,3,1,1,1,3,3,6,1,2,2,3,3,3,2,1,2,12,14,11,6,6,4,12,2,8,1,7,10,1,35,7,4,13,15,4,3,23,21,28,52,5, + 26,5,6,1,7,10,2,7,53,3,2,1,1,1,2,163,532,1,10,11,1,3,3,4,8,2,8,6,2,2,23,22,4,2,2,4,2,1,3,1,3,3,5,9,8,2,1,2,8,1,10,2,12,21,20,15,105,2,3,1,1, + 3,2,3,1,1,2,5,1,4,15,11,19,1,1,1,1,5,4,5,1,1,2,5,3,5,12,1,2,5,1,11,1,1,15,9,1,4,5,3,26,8,2,1,3,1,1,15,19,2,12,1,2,5,2,7,2,19,2,20,6,26,7,5, + 2,2,7,34,21,13,70,2,128,1,1,2,1,1,2,1,1,3,2,2,2,15,1,4,1,3,4,42,10,6,1,49,85,8,1,2,1,1,4,4,2,3,6,1,5,7,4,3,211,4,1,2,1,2,5,1,2,4,2,2,6,5,6, + 10,3,4,48,100,6,2,16,296,5,27,387,2,2,3,7,16,8,5,38,15,39,21,9,10,3,7,59,13,27,21,47,5,21,6 +}; +// clang-format on + +// clang-format off +static char32_t chineseCommonBaseRanges[] = { + // not zero-terminated + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x2000, 0x206F, // General Punctuation + 0x3000, 0x30FF, // CJK Symbols and Punctuations, Hiragana, Katakana + 0x31F0, 0x31FF, // Katakana Phonetic Extensions + 0xFF00, 0xFFEF // Half-width characters +}; +// clang-format on +static char32_t chineseCommonFullRanges[std::size(chineseCommonBaseRanges) + std::size(kChineseAccumulativeOffsetsFrom0x4E00) * 2 + 1] = { 0 }; +const char32_t* Font::GetGlyphRangesChineseSimplifiedCommon() { + if (!chineseCommonFullRanges[0]) { + memcpy(chineseCommonFullRanges, chineseCommonBaseRanges, sizeof(chineseCommonBaseRanges)); + UnpackAccumulativeOffsetsIntoRanges(0x4E00, kChineseAccumulativeOffsetsFrom0x4E00, std::size(kChineseAccumulativeOffsetsFrom0x4E00), chineseCommonFullRanges + std::size(chineseCommonBaseRanges)); + } + return &chineseCommonFullRanges[0]; +} + +// clang-format off +static const char32_t kCryllicRanges[] = { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x0400, 0x052F, // Cyrillic + Cyrillic Supplement + 0x2DE0, 0x2DFF, // Cyrillic Extended-A + 0xA640, 0xA69F, // Cyrillic Extended-B + 0, +}; +// clang-format on +const char32_t* Font::GetGlyphRangesCyrillic() { + return &kCryllicRanges[0]; +} + +// clang-format off +static const char32_t kThaiRanges[] = { + 0x0020, 0x00FF, // Basic Latin + 0x2010, 0x205E, // Punctuations + 0x0E00, 0x0E7F, // Thai + 0, +}; +// clang-format on +const char32_t* Font::GetGlyphRangesThai() { + return &kThaiRanges[0]; +} + +// clang-format off +static const char32_t kVietnameseRanges[] = { + 0x0020, 0x00FF, // Basic Latin + 0x0102, 0x0103, + 0x0110, 0x0111, + 0x0128, 0x0129, + 0x0168, 0x0169, + 0x01A0, 0x01A1, + 0x01AF, 0x01B0, + 0x1EA0, 0x1EF9, + 0, +}; +// clang-format on +const char32_t* Font::GetGlyphRangesVietnamese() { + return &kVietnameseRanges[0]; +} diff --git a/source/30-game/Texture.cpp b/source/30-game/Texture.cpp index 6fa7c8a..e5c9705 100644 --- a/source/30-game/Texture.cpp +++ b/source/30-game/Texture.cpp @@ -14,7 +14,8 @@ Texture::~Texture() { glDeleteTextures(1, &mHandle); } -static GLenum MapTextureFilteringToGL(Tags::TexFilter option) { +namespace ProjectBrussel_UNITY_ID { +GLint MapTextureFilteringToGL(Tags::TexFilter option) { using namespace Tags; switch (option) { case TF_Linear: return GL_LINEAR; @@ -22,8 +23,11 @@ static GLenum MapTextureFilteringToGL(Tags::TexFilter option) { } return 0; } +} // namespace ProjectBrussel_UNITY_ID + +Texture::ErrorCode Texture::InitFromFile(const char* filePath, const TextureProperties& props) { + using namespace ProjectBrussel_UNITY_ID; -Texture::ErrorCode Texture::InitFromFile(const char* filePath) { if (IsValid()) { return EC_AlreadyInitialized; } @@ -40,21 +44,23 @@ Texture::ErrorCode Texture::InitFromFile(const char* filePath) { glGenTextures(1, &mHandle); glBindTexture(GL_TEXTURE_2D, mHandle); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, MapTextureFilteringToGL(props.minifyingFilter)); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, MapTextureFilteringToGL(props.magnifyingFilter)); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, result); - mInfo.size = { width, height }; + mProps.size = { width, height }; return EC_Success; } -Texture::ErrorCode Texture::InitFromImage(const Image& image) { +Texture::ErrorCode Texture::InitFromImage(const Image& image, const TextureProperties& props) { + using namespace ProjectBrussel_UNITY_ID; + if (IsValid()) { return EC_AlreadyInitialized; } - GLenum sourceFormat; + GLint sourceFormat; switch (image.GetChannels()) { case 1: sourceFormat = GL_RED; break; case 2: sourceFormat = GL_RG; break; @@ -69,11 +75,13 @@ Texture::ErrorCode Texture::InitFromImage(const Image& image) { glGenTextures(1, &mHandle); glBindTexture(GL_TEXTURE_2D, mHandle); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, MapTextureFilteringToGL(props.minifyingFilter)); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, MapTextureFilteringToGL(props.magnifyingFilter)); glTexImage2D(GL_TEXTURE_2D, 0, sourceFormat, size.x, size.y, 0, sourceFormat, GL_UNSIGNED_BYTE, dataPtr); - mInfo.size = size; + // Copy all [In] properties + mProps = props; + mProps.size = size; return EC_Success; } @@ -192,7 +200,7 @@ Texture::ErrorCode Texture::InitAtlas(const AtlasInput& in, AtlasOutput* out) { // 5. Generate atlas texture info mHandle = atlasTexture; - mInfo.size = { atlasWidth, atlasHeight }; + mProps.size = { atlasWidth, atlasHeight }; // 6. Generate output information if (out) { @@ -216,8 +224,8 @@ Texture::ErrorCode Texture::InitAtlas(const AtlasInput& in, AtlasOutput* out) { return EC_Success; } -const TextureInfo& Texture::GetInfo() const { - return mInfo; +const TextureProperties& Texture::GetInfo() const { + return mProps; } GLuint Texture::GetHandle() const { diff --git a/source/30-game/Texture.hpp b/source/30-game/Texture.hpp index 108dfa7..2931724 100644 --- a/source/30-game/Texture.hpp +++ b/source/30-game/Texture.hpp @@ -19,17 +19,19 @@ struct Subregion { float v1 = 0.0f; }; -struct TextureInfo { - glm::ivec2 size; - Tags::TexFilter minifyingFilter = Tags::TF_Linear; - Tags::TexFilter magnifyingFilter = Tags::TF_Linear; +struct TextureProperties { + // All [In] fields should be set by the user before initializing the texture + // All [Out] fields will be set by the initialization process, and any previously user-set value will be ignored + /* [In] */ Tags::TexFilter minifyingFilter = Tags::TF_Linear; + /* [In] */ Tags::TexFilter magnifyingFilter = Tags::TF_Linear; + /* [Out] */ glm::ivec2 size; }; class Texture : public RefCounted { friend class TextureStitcher; private: - TextureInfo mInfo; + TextureProperties mProps; GLuint mHandle = 0; public: @@ -47,9 +49,8 @@ public: EC_FileIoFailed, EC_InvalidImage, }; - - ErrorCode InitFromFile(const char* filePath); - ErrorCode InitFromImage(const Image& image); + ErrorCode InitFromFile(const char* filePath, const TextureProperties& props = {}); + ErrorCode InitFromImage(const Image& image, const TextureProperties& props = {}); struct AtlasSource { std::string name; @@ -77,7 +78,7 @@ public: }; ErrorCode InitAtlas(const AtlasInput& in, AtlasOutput* out = nullptr); - const TextureInfo& GetInfo() const; + const TextureProperties& GetInfo() const; GLuint GetHandle() const; bool IsValid() const; |