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

@@ -71,7 +71,8 @@ void GlyphManager::getGlyphs(GlyphRequestor& requestor, GlyphDependencies glyphD
Glyph GlyphManager::generateLocalSDF(const FontStack& fontStack, GlyphID glyphID) {
Glyph local = localGlyphRasterizer->rasterizeGlyph(fontStack, glyphID);
local.bitmap = util::transformRasterToSDF(local.bitmap, 8, .25);
const double radius = local.metrics.isDoubleResolution ? 16.0 : 8.0;
local.bitmap = util::transformRasterToSDF(local.bitmap, radius, .25);
return local;
}

View File

@@ -11,21 +11,13 @@ namespace mbgl {
the glyph and appropriate metrics. GlyphManager will then use TinySDF to
transform the rasterized bitmap into an SDF.
The JS equivalent of this functionality will only generate glyphs in the
'CJK Unified Ideographs' and 'Hangul Syllables' ranges, for which it can
get away with rendering a fixed 30px square image and GlyphMetrics of:
width: 24,
height: 24,
left: 0,
top: -8,
advance: 24
The JS equivalent also uses heuristic evaluation of the font stack name
to control the font-weight it uses during rasterization.
The shared default codepoint filter is
util::i18n::allowsFixedWidthGlyphGeneration.
Following maplibre-gl-js, platform implementations use 2x bitmaps with
1x metrics via GlyphMetrics::isDoubleResolution.
It is left to platform-specific implementation to decide how best to
map a FontStack to a particular rasterization.
map a FontStack to a particular rasterization (font, weight, style).
The default implementation simply refuses to rasterize any glyphs.
*/

View File

@@ -267,11 +267,15 @@ SymbolQuads getGlyphQuads(const Shaping& shapedText,
builtInOffset = {0.0f, 0.0f};
}
// rectBuffer is in 1x logical units; only rect.w/h carry the texture
// scale. GL JS keeps 2x glyphs centered by storing fractional
// left/top metrics, so native local glyphs do the same.
const float textureScale = positionedGlyph.metrics.isDoubleResolution ? 2.0f : 1.0f;
const float x1 = (positionedGlyph.metrics.left - rectBuffer) * positionedGlyph.scale - halfAdvance +
builtInOffset.x;
const float y1 = (-positionedGlyph.metrics.top - rectBuffer) * positionedGlyph.scale + builtInOffset.y;
const float x2 = x1 + rect.w * positionedGlyph.scale / pixelRatio;
const float y2 = y1 + rect.h * positionedGlyph.scale / pixelRatio;
const float x2 = x1 + rect.w / textureScale * positionedGlyph.scale / pixelRatio;
const float y2 = y1 + rect.h / textureScale * positionedGlyph.scale / pixelRatio;
Point<float> tl{x1, y1};
Point<float> tr{x2, y1};

View File

@@ -18,6 +18,10 @@ namespace {
return codepoint >= (first) && codepoint <= (last); \
}
inline bool isInRange(char16_t codepoint, char16_t first, char16_t last) {
return codepoint >= first && codepoint <= last;
}
// The following table comes from <http://www.unicode.org/Public/12.0.0/ucd/Blocks.txt>.
// Keep it synchronized with <http://www.unicode.org/Public/UCD/latest/ucd/Blocks.txt>.
@@ -411,8 +415,59 @@ bool allowsIdeographicBreaking(char16_t chr) {
}
bool allowsFixedWidthGlyphGeneration(char16_t chr) {
// Mirrors conservative set of characters used in glyph_manager.js/_tinySDF
return isInCJKUnifiedIdeographs(chr) || isInHangulSyllables(chr) || isInKatakana(chr) || isInHiragana(chr);
// Mirrors the BMP ranges in GL JS's codePointUsesLocalIdeographFontFamily.
// Astral-plane ranges are not included because GlyphID only carries BMP
// codepoints for local glyph generation.
return
// Bopomofo tone marks.
isInRange(chr, 0x02EA, 0x02EB) ||
// Hangul Jamo.
isInHangulJamo(chr) ||
// CJK Radicals Supplement, Kangxi Radicals.
isInCJKRadicalsSupplement(chr) || isInKangxiRadicals(chr) ||
// CJK Symbols and Punctuation, Hiragana, Katakana.
isInCJKSymbolsandPunctuation(chr) || isInHiragana(chr) || isInKatakana(chr) ||
// Bopomofo.
isInRange(chr, 0x3105, 0x312F) ||
// Hangul Compatibility Jamo.
isInRange(chr, 0x3131, 0x318E) ||
// Bopomofo Extended through CJK Unified Ideographs Extension A.
isInBopomofoExtended(chr) || isInCJKStrokes(chr) || isInKatakanaPhoneticExtensions(chr) ||
isInEnclosedCJKLettersandMonths(chr) || isInCJKCompatibility(chr) || isInCJKUnifiedIdeographsExtensionA(chr) ||
// CJK Unified Ideographs and Yi Syllables.
isInCJKUnifiedIdeographs(chr) || isInRange(chr, 0xA000, 0xA48C) ||
// Yi Radicals.
isInRange(chr, 0xA490, 0xA4C6) ||
// Hangul Jamo Extended-A.
isInRange(chr, 0xA960, 0xA97C) ||
// Hangul Syllables and Hangul Jamo Extended-B.
isInHangulSyllables(chr) || isInRange(chr, 0xD7B0, 0xD7C6) ||
// Hangul Jamo Extended-B.
isInRange(chr, 0xD7CB, 0xD7FB) ||
// CJK Compatibility Ideographs.
isInRange(chr, 0xF900, 0xFA6D) ||
// CJK Compatibility Ideographs.
isInRange(chr, 0xFA70, 0xFAD9) ||
// Vertical Forms.
isInVerticalForms(chr) ||
// CJK Compatibility Forms.
isInCJKCompatibilityForms(chr) ||
// Halfwidth and Fullwidth Forms.
isInHalfwidthandFullwidthForms(chr);
}
bool glyphRangeIsEntirelyLocallyGenerated(char16_t rangeStart) {
// Some ranges (notably 0xFF00, where Halfwidth/Fullwidth Forms ends at
// 0xFFEF and the Specials block continues to 0xFFFD) mix locally-rendered
// and non-local codepoints, so it is not safe to infer the whole range
// from the first codepoint alone.
constexpr uint32_t glyphsPerRange = 256;
for (uint32_t offset = 0; offset < glyphsPerRange; ++offset) {
if (!allowsFixedWidthGlyphGeneration(static_cast<char16_t>(rangeStart + offset))) {
return false;
}
}
return true;
}
bool allowsVerticalWritingMode(const std::u16string& string) {

View File

@@ -27,6 +27,10 @@ bool allowsIdeographicBreaking(char16_t chr);
advances */
bool allowsFixedWidthGlyphGeneration(char16_t chr);
/** Returns whether the whole 256-codepoint glyph range can be generated
locally. Use this for range-level decisions because some ranges are mixed. */
bool glyphRangeIsEntirelyLocallyGenerated(char16_t rangeStart);
/** Returns whether any substring of the given string can be drawn as vertical
text with upright glyphs. */
bool allowsVerticalWritingMode(const std::u16string& string);