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

@@ -7,9 +7,15 @@
#include <QtGui/QImage>
#include <QtGui/QPainter>
#include <qglobal.h>
namespace mbgl {
// 2x bitmap with 1x logical metrics.
constexpr int kLocalGlyphTextureScale = 2;
constexpr uint32_t kLocalGlyphLogicalSize = 30;
constexpr uint32_t kLocalGlyphBitmapSize = kLocalGlyphLogicalSize * kLocalGlyphTextureScale;
constexpr int kLocalGlyphRasterBuffer = Glyph::borderSize * kLocalGlyphTextureScale;
constexpr double kLocalGlyphTopAdjustment = 26.5;
class LocalGlyphRasterizer::Impl {
public:
Impl(const std::optional<std::string>& fontFamily_);
@@ -25,7 +31,7 @@ LocalGlyphRasterizer::Impl::Impl(const std::optional<std::string>& fontFamily_)
: fontFamily(fontFamily_) {
if (isConfigured()) {
font.setFamily(QString::fromStdString(*fontFamily));
font.setPixelSize(util::ONE_EM);
font.setPixelSize(util::ONE_EM * kLocalGlyphTextureScale);
metrics = QFontMetrics(font);
}
}
@@ -53,25 +59,39 @@ Glyph LocalGlyphRasterizer::rasterizeGlyph(const FontStack&, GlyphID glyphID) {
return glyph;
}
glyph.metrics.width = impl->metrics->horizontalAdvance(glyphID.complex.code);
glyph.metrics.height = impl->metrics->height();
glyph.metrics.left = 3;
glyph.metrics.top = -8;
// QFontMetrics returns measurements at the 2x rasterization pixel size; the
// glyph's metrics are reported in 1x logical units.
const QString text(QChar(glyphID.complex.code));
const int advance2x = impl->metrics->horizontalAdvance(glyphID.complex.code);
const int height2x = impl->metrics->height();
const QRect glyphBounds = impl->metrics->tightBoundingRect(text);
const int glyphTop2x = std::max(0, -glyphBounds.top());
glyph.metrics.width = advance2x / kLocalGlyphTextureScale;
glyph.metrics.height = height2x / kLocalGlyphTextureScale;
glyph.metrics.left = 3.5f;
glyph.metrics.top = glyphTop2x / static_cast<double>(kLocalGlyphTextureScale) - kLocalGlyphTopAdjustment;
glyph.metrics.advance = glyph.metrics.width;
glyph.metrics.isDoubleResolution = true;
// Set width of a glyph's backing image to be util::ONE_EM.
Size size(util::ONE_EM, glyph.metrics.height);
// The backing image is drawn at 2x texture resolution.
Size size(kLocalGlyphBitmapSize, kLocalGlyphBitmapSize);
QImage image(QSize(size.width, size.height), QImage::Format_Alpha8);
image.fill(qRgba(0, 0, 0, 0));
QPainter painter(&image);
painter.setFont(impl->font);
painter.setRenderHints(QPainter::TextAntialiasing);
// Render at constant baseline, to align with glyphs that are rendered by node-fontnik.
painter.drawText(QPointF(0, 20), QString(QChar(glyphID.complex.code)));
// Align the glyph using its measured top, matching the local TinySDF path
// in GL JS rather than relying on a platform-wide fixed top value.
const double baselineY = kLocalGlyphRasterBuffer + glyphTop2x;
painter.drawText(QPointF(0, baselineY), text);
auto img = std::make_unique<uint8_t[]>(image.sizeInBytes());
memcpy(img.get(), image.constBits(), image.sizeInBytes());
// QImage::Format_Alpha8 may pad each row for alignment. Copy row-by-row
// using the unpadded width so AlphaImage's tight layout matches.
auto img = std::make_unique<uint8_t[]>(static_cast<size_t>(size.width) * size.height);
for (uint32_t y = 0; y < size.height; ++y) {
std::memcpy(img.get() + static_cast<size_t>(y) * size.width, image.constScanLine(y), size.width);
}
glyph.bitmap = AlphaImage{size, std::move(img)};