[ios, macos] Get glyph metrics from font

Get glyph metrics from the font in the process of drawing each glyph into a bitmap context. These metrics result in more accurate kerning and better aligned baselines than the previous hard-coded values.

Align iOS/macOS local glyph rasterization test fixture to Qt.
This commit is contained in:
Minh Nguyễn
2020-03-19 17:46:13 -07:00
committed by Minh Nguyễn
parent bcfdbd187d
commit 0588c50b09
3 changed files with 60 additions and 33 deletions

View File

@@ -34,6 +34,10 @@
- [ios, macos] Fixed error receiving local file URL response ([#16428](https://github.com/mapbox/mapbox-gl-native/pull/16428))
- [ios, macos] Corrected metrics of locally rendered fonts ([#16253](https://github.com/mapbox/mapbox-gl-native/pull/16253))
CJK characters are now laid out according to the font, so fonts with nonsquare glyphs have the correct kerning. This also fixes an issue where the baseline for CJK characters was too low compared to non-CJK characters.
## maps-v1.6.0-rc.1
### ✨ New features

View File

@@ -148,11 +148,39 @@ bool LocalGlyphRasterizer::canRasterizeGlyph(const FontStack&, GlyphID glyphID)
return util::i18n::allowsFixedWidthGlyphGeneration(glyphID) && impl->isEnabled();
}
PremultipliedImage drawGlyphBitmap(GlyphID glyphID, CTFontRef font, Size size) {
PremultipliedImage drawGlyphBitmap(GlyphID glyphID, CTFontRef font, GlyphMetrics& metrics) {
CFStringRefHandle string(CFStringCreateWithCharacters(NULL, reinterpret_cast<UniChar*>(&glyphID), 1));
if (!string) {
throw std::runtime_error("Unable to create string from codepoint");
}
CFStringRef keys[] = { kCTFontAttributeName };
CFTypeRef values[] = { font };
CFDictionaryRefHandle attributes(
CFDictionaryCreate(kCFAllocatorDefault, (const void**)&keys,
(const void**)&values, sizeof(keys) / sizeof(keys[0]),
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
if (!attributes) {
throw std::runtime_error("Unable to create attributed string attributes dictionary");
}
CFAttributedStringRefHandle attrString(CFAttributedStringCreate(kCFAllocatorDefault, *string, *attributes));
if (!attrString) {
throw std::runtime_error("Unable to create attributed string");
}
CTLineRefHandle line(CTLineCreateWithAttributedString(*attrString));
if (!line) {
throw std::runtime_error("Unable to create line from attributed string");
}
Size size(35, 35);
metrics.width = size.width;
metrics.height = size.height;
PremultipliedImage rgbaBitmap(size);
CFStringRefHandle string(CFStringCreateWithCharacters(NULL, reinterpret_cast<UniChar*>(&glyphID), 1));
CGColorSpaceHandle colorSpace(CGColorSpaceCreateDeviceRGB());
if (!colorSpace) {
throw std::runtime_error("CGColorSpaceCreateDeviceRGB failed");
@@ -173,52 +201,47 @@ PremultipliedImage drawGlyphBitmap(GlyphID glyphID, CTFontRef font, Size size) {
if (!context) {
throw std::runtime_error("CGBitmapContextCreate failed");
}
CFStringRef keys[] = { kCTFontAttributeName };
CFTypeRef values[] = { font };
CFDictionaryRefHandle attributes(
CFDictionaryCreate(kCFAllocatorDefault, (const void**)&keys,
(const void**)&values, sizeof(keys) / sizeof(keys[0]),
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
CFAttributedStringRefHandle attrString(CFAttributedStringCreate(kCFAllocatorDefault, *string, *attributes));
CTLineRefHandle line(CTLineCreateWithAttributedString(*attrString));
// Start drawing a little bit below the top of the bitmap
CGContextSetTextPosition(*context, 0.0, 5.0);
CFArrayRef glyphRuns = CTLineGetGlyphRuns(*line);
CTRunRef glyphRun = (CTRunRef)CFArrayGetValueAtIndex(glyphRuns, 0);
CFRange wholeRunRange = CFRangeMake(0, CTRunGetGlyphCount(glyphRun));
CGSize advances[wholeRunRange.length];
CTRunGetAdvances(glyphRun, wholeRunRange, advances);
metrics.advance = std::round(advances[0].width);
// Mimic glyph PBF metrics.
metrics.left = Glyph::borderSize;
metrics.top = -1;
// Move the text upward to avoid clipping off descenders.
CGFloat descent;
CTRunGetTypographicBounds(glyphRun, wholeRunRange, NULL, &descent, NULL);
CGContextSetTextPosition(*context, 0.0, descent);
CTLineDraw(*line, *context);
return rgbaBitmap;
}
Glyph LocalGlyphRasterizer::rasterizeGlyph(const FontStack& fontStack, GlyphID glyphID) {
Glyph fixedMetrics;
CTFontRef font = impl->createFont(fontStack);
Glyph manufacturedGlyph;
CTFontRefHandle font(impl->createFont(fontStack));
if (!font) {
return fixedMetrics;
return manufacturedGlyph;
}
fixedMetrics.id = glyphID;
manufacturedGlyph.id = glyphID;
Size size(35, 35);
PremultipliedImage rgbaBitmap = drawGlyphBitmap(glyphID, *font, manufacturedGlyph.metrics);
fixedMetrics.metrics.width = size.width;
fixedMetrics.metrics.height = size.height;
fixedMetrics.metrics.left = 3;
fixedMetrics.metrics.top = -1;
fixedMetrics.metrics.advance = 24;
PremultipliedImage rgbaBitmap = drawGlyphBitmap(glyphID, font, size);
Size size(manufacturedGlyph.metrics.width, manufacturedGlyph.metrics.height);
// Copy alpha values from RGBA bitmap into the AlphaImage output
fixedMetrics.bitmap = AlphaImage(size);
manufacturedGlyph.bitmap = AlphaImage(size);
for (uint32_t i = 0; i < size.width * size.height; i++) {
fixedMetrics.bitmap.data[i] = rgbaBitmap.data[4 * i + 3];
manufacturedGlyph.bitmap.data[i] = rgbaBitmap.data[4 * i + 3];
}
return fixedMetrics;
return manufacturedGlyph;
}
} // namespace mbgl

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB