feat(core): render local CJK glyphs at 2x texture resolution (#4304)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Bart Louwers <bart@emeel.net>
This commit is contained in:
Taku Fukada
2026-07-01 20:52:01 +09:00
committed by GitHub
parent 60f15190c3
commit 887e6021e6
22 changed files with 357 additions and 84 deletions

View File

@@ -119,6 +119,13 @@ public:
}
};
class KatakanaOnlyLocalGlyphRasterizer : public StubLocalGlyphRasterizer {
public:
bool canRasterizeGlyph(const FontStack&, GlyphID glyphID) override {
return static_cast<char16_t>(glyphID) == u'テ';
}
};
class StubGlyphManagerObserver : public GlyphManagerObserver {
public:
void onGlyphsLoaded(const FontStack& fontStack, const GlyphRange& glyphRange) override {
@@ -148,7 +155,11 @@ public:
StubFileSource fileSource;
StubGlyphManagerObserver observer;
StubGlyphRequestor requestor;
GlyphManager glyphManager{std::make_unique<StubLocalGlyphRasterizer>()};
GlyphManager glyphManager;
GlyphManagerTest(
std::unique_ptr<LocalGlyphRasterizer> localGlyphRasterizer = std::make_unique<StubLocalGlyphRasterizer>())
: glyphManager(std::move(localGlyphRasterizer)) {}
void run(const std::string& url, GlyphDependencies dependencies) {
// Squelch logging.
@@ -317,8 +328,40 @@ TEST(GlyphManager, LoadLocalCJKGlyph) {
GlyphDependencies{.glyphs = {{{{"Test Stack"}}, {u'中'}}}, .shapes = {}});
}
TEST(GlyphManager, LoadLocalCJKGlyphAfterLoadingRangeFromURL) {
TEST(GlyphManager, LoadLocalCJKSymbols) {
GlyphManagerTest test;
int glyphResponses = 0;
test.fileSource.glyphsResponse = [&](const Resource&) {
glyphResponses++;
return std::optional<Response>();
};
test.observer.glyphsLoaded = [&](const FontStack&, const GlyphRange&) {
glyphResponses++;
};
test.requestor.glyphsAvailable = [&](GlyphMap glyphs) {
EXPECT_EQ(glyphResponses, 0); // Local generation should prevent requesting any glyphs.
const auto& testPositions = glyphs.at(FontStackHasher()({{"Test Stack"}}));
ASSERT_EQ(testPositions.size(), 2u);
ASSERT_EQ(testPositions.count(u'〒'), 1u);
ASSERT_EQ(testPositions.count(u'!'), 1u);
test.end();
};
test.run("test/fixtures/resources/glyphs.pbf",
GlyphDependencies{.glyphs = {{{{"Test Stack"}}, {u'〒', u'!'}}}, .shapes = {}});
}
TEST(GlyphManager, LocalCJKGlyphPrefersLocalRasterizationOverRange) {
// Verifies that locally-rasterized glyphs (like テ) are produced by the
// local rasterizer instead of using a glyph from an already-loaded PBF
// range.
GlyphManagerTest test(std::make_unique<KatakanaOnlyLocalGlyphRasterizer>());
bool firstGlyphResponse = false;
test.fileSource.glyphsResponse = [&](const Resource&) {
@@ -337,9 +380,6 @@ TEST(GlyphManager, LoadLocalCJKGlyphAfterLoadingRangeFromURL) {
ASSERT_EQ(testPositions.size(), 1u);
ASSERT_EQ(testPositions.count(u'々'), 1u);
// Katakana letter te, should be locally rasterized
// instead of using the glyph recieved from the range
// for the ideagraphic mark
test.glyphManager.getGlyphs(test.requestor,
GlyphDependencies{.glyphs = {{{{"Test Stack"}}, {u'テ'}}}, // 0x30c6
.shapes = {}},
@@ -443,3 +483,16 @@ TEST(GlyphManager, ImmediateFileSource) {
test.run("test/fixtures/resources/glyphs.pbf",
GlyphDependencies{.glyphs = {{{{"Test Stack"}}, {u'a', u'å', u' '}}}, .shapes = {}});
}
// Guards against silent drift between allowsFixedWidthGlyphGeneration's range
// coverage and the precomputed NON_IDEOGRAPH_GLYPH_RANGES_PER_FONT_STACK used
// for offline download progress estimation.
TEST(i18n, GlyphRangeLocalGenerationCountMatchesConstant) {
uint32_t locallyGeneratedRanges = 0;
for (uint32_t i = 0; i < GLYPH_RANGES_PER_FONT_STACK; ++i) {
if (util::i18n::glyphRangeIsEntirelyLocallyGenerated(static_cast<char16_t>(i * GLYPHS_PER_GLYPH_RANGE))) {
++locallyGeneratedRanges;
}
}
EXPECT_EQ(GLYPH_RANGES_PER_FONT_STACK - locallyGeneratedRanges, NON_IDEOGRAPH_GLYPH_RANGES_PER_FONT_STACK);
}