This repository has been archived by the owner on Aug 8, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
[ios] POC: implementation of a CoreText-based LocalGlyphRasterizer #10572
Merged
Merged
Changes from 10 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
6a1a5ee
POC implementation of a CoreText-based LocalGlyphRasterizer.
ChrisLoer c43dd57
Fix build for iOS.
ChrisLoer 629565f
Adjust drawing positions and glyph metrics to fit entire glyph within…
ChrisLoer ae6dd46
Reduce padding on CJK glyphs (should make rendering slightly faster)
ChrisLoer 8e55428
Moving towards configurable darwin implementation of LocalGlyphRaster…
ChrisLoer e5c4d96
Share CFHandle code between image.mm and local_glyph_rasterizer.mm
ChrisLoer 3114947
LocalGlyphRasterizer takes a void* configuration input. If nothing is…
ChrisLoer bfefc93
Change LocalGlyphRasterizer configuration to just a plain "font famil…
ChrisLoer 3d7669b
Fix unit tests.
ChrisLoer 8a9009c
Fix non ios/macos builds.
ChrisLoer 6451d4a
Document CFHandle
ChrisLoer bf53bf4
Load fonts per-fontstack (but still no heuristics for choosing font w…
ChrisLoer 0845907
Implement font stack-based heuristics for font loading.
ChrisLoer 34e7ae2
Remove entry point for setting local ideographic font family, so that…
ChrisLoer File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
|
||
namespace { | ||
|
||
template <typename T, typename S, void (*Releaser)(S)> | ||
struct CFHandle { | ||
CFHandle(T t_): t(t_) {} | ||
~CFHandle() { Releaser(t); } | ||
T operator*() { return t; } | ||
operator bool() { return t; } | ||
private: | ||
T t; | ||
}; | ||
|
||
using CGImageHandle = CFHandle<CGImageRef, CGImageRef, CGImageRelease>; | ||
using CFDataHandle = CFHandle<CFDataRef, CFTypeRef, CFRelease>; | ||
using CGImageSourceHandle = CFHandle<CGImageSourceRef, CFTypeRef, CFRelease>; | ||
using CGDataProviderHandle = CFHandle<CGDataProviderRef, CGDataProviderRef, CGDataProviderRelease>; | ||
using CGColorSpaceHandle = CFHandle<CGColorSpaceRef, CGColorSpaceRef, CGColorSpaceRelease>; | ||
using CGContextHandle = CFHandle<CGContextRef, CGContextRef, CGContextRelease>; | ||
|
||
using CFStringRefHandle = CFHandle<CFStringRef, CFTypeRef, CFRelease>; | ||
using CFAttributedStringRefHandle = CFHandle<CFAttributedStringRef, CFTypeRef, CFRelease>; | ||
using CFDictionaryRefHandle = CFHandle<CFDictionaryRef, CFTypeRef, CFRelease>; | ||
|
||
|
||
} // namespace | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
#include <mbgl/text/local_glyph_rasterizer.hpp> | ||
#include <mbgl/util/i18n.hpp> | ||
|
||
#import <Foundation/Foundation.h> | ||
#import <CoreText/CoreText.h> | ||
#import <ImageIO/ImageIO.h> | ||
|
||
#import "CFHandle.hpp" | ||
|
||
namespace mbgl { | ||
|
||
/* | ||
Initial implementation of darwin TinySDF support: | ||
Draw any CJK glyphs using a default system font | ||
|
||
Where to take this: | ||
- Configure whether to use local fonts, and which fonts to use, | ||
based on map or even style layer options | ||
- Build heuristics for choosing fonts based on input FontStack | ||
(maybe a globally configurable FontStack -> UIFontDescriptor map would make sense? | ||
- Extract glyph metrics so that this can be used with more than just fixed width glyphs | ||
*/ | ||
|
||
using CTFontDescriptorRefHandle = CFHandle<CTFontDescriptorRef, CFTypeRef, CFRelease>; | ||
using CTLineRefHandle = CFHandle<CTLineRef, CFTypeRef, CFRelease>; | ||
|
||
class LocalGlyphRasterizer::Impl { | ||
public: | ||
Impl(CTFontRef font_) | ||
: font(font_) | ||
{} | ||
|
||
~Impl() { | ||
if (font) { | ||
CFRelease(font); | ||
} | ||
} | ||
|
||
CTFontRef font; | ||
}; | ||
|
||
LocalGlyphRasterizer::LocalGlyphRasterizer(const optional<std::string> fontFamily) | ||
{ | ||
if (fontFamily) { | ||
NSDictionary *fontAttributes = @{ | ||
(NSString *)kCTFontSizeAttribute: [NSNumber numberWithFloat:24.0], | ||
(NSString *)kCTFontFamilyNameAttribute: [[NSString alloc] initWithCString:fontFamily->c_str() encoding:NSUTF8StringEncoding] | ||
}; | ||
|
||
CTFontDescriptorRefHandle descriptor(CTFontDescriptorCreateWithAttributes((CFDictionaryRef)fontAttributes)); | ||
|
||
impl = std::make_unique<Impl>(CTFontCreateWithFontDescriptor(*descriptor, 0.0, NULL)); | ||
} else { | ||
impl = std::make_unique<Impl>((CTFontRef)NULL); | ||
} | ||
} | ||
|
||
LocalGlyphRasterizer::~LocalGlyphRasterizer() | ||
{} | ||
|
||
bool LocalGlyphRasterizer::canRasterizeGlyph(const FontStack&, GlyphID glyphID) { | ||
// TODO: This is a rough approximation of the set of glyphs that will work with fixed glyph metrics | ||
// Either narrow this down to be conservative, or actually extract glyph metrics in rasterizeGlyph | ||
return impl->font && util::i18n::allowsIdeographicBreaking(glyphID); | ||
} | ||
|
||
// TODO: In theory we should be able to transform user-coordinate bounding box and advance | ||
// values into pixel glyph metrics. This would remove the need to use fixed glyph metrics | ||
// (which will be slightly off depending on the font), and allow us to return non CJK glyphs | ||
// (which will have variable "advance" values). | ||
void extractGlyphMetrics(CTFontRef font, CTLineRef line) { | ||
CFArrayRef glyphRuns = CTLineGetGlyphRuns(line); | ||
CFIndex runCount = CFArrayGetCount(glyphRuns); | ||
assert(runCount == 1); | ||
CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(glyphRuns, 0); | ||
CFIndex glyphCount = CTRunGetGlyphCount(run); | ||
assert(glyphCount == 1); | ||
const CGGlyph *glyphs = CTRunGetGlyphsPtr(run); | ||
|
||
CGRect boundingRects[1]; | ||
boundingRects[0] = CGRectMake(0, 0, 0, 0); | ||
CGSize advances[1]; | ||
advances[0] = CGSizeMake(0,0); | ||
CGRect totalBoundingRect = CTFontGetBoundingRectsForGlyphs(font, kCTFontOrientationDefault, glyphs, boundingRects, 1); | ||
double totalAdvance = CTFontGetAdvancesForGlyphs(font, kCTFontOrientationDefault, glyphs, advances, 1); | ||
|
||
// Break in the debugger to see these values: translating from "user coordinates" to bitmap pixel coordinates | ||
// should be OK, but a lot of glyphs seem to have empty bounding boxes...? | ||
(void)totalBoundingRect; | ||
(void)totalAdvance; | ||
} | ||
|
||
PremultipliedImage drawGlyphBitmap(GlyphID glyphID, CTFontRef font, Size size) { | ||
PremultipliedImage rgbaBitmap(size); | ||
|
||
CFStringRefHandle string(CFStringCreateWithCharacters(NULL, reinterpret_cast<UniChar*>(&glyphID), 1)); | ||
|
||
CGColorSpaceHandle colorSpace(CGColorSpaceCreateDeviceRGB()); | ||
// TODO: Is there a way to just draw a single alpha channel instead of copying it out of an RGB image? Doesn't seem like the grayscale colorspace is what I'm looking for... | ||
if (!colorSpace) { | ||
throw std::runtime_error("CGColorSpaceCreateDeviceRGB failed"); | ||
} | ||
|
||
constexpr const size_t bitsPerComponent = 8; | ||
constexpr const size_t bytesPerPixel = 4; | ||
const size_t bytesPerRow = bytesPerPixel * size.width; | ||
|
||
CGContextHandle context(CGBitmapContextCreate( | ||
rgbaBitmap.data.get(), | ||
size.width, | ||
size.height, | ||
bitsPerComponent, | ||
bytesPerRow, | ||
*colorSpace, | ||
kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast)); | ||
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)); | ||
|
||
// For debugging only, doesn't get useful metrics yet | ||
extractGlyphMetrics(font, *line); | ||
|
||
// Set text position and draw the line into the graphics context | ||
CGContextSetTextPosition(*context, 0.0, 5.0); | ||
CTLineDraw(*line, *context); | ||
|
||
return rgbaBitmap; | ||
} | ||
|
||
Glyph LocalGlyphRasterizer::rasterizeGlyph(const FontStack&, GlyphID glyphID) { | ||
Glyph fixedMetrics; | ||
if (!impl->font) { | ||
return fixedMetrics; | ||
} | ||
|
||
fixedMetrics.id = glyphID; | ||
|
||
Size size(35, 35); | ||
|
||
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, impl->font, size); | ||
|
||
// Copy alpha values from RGBA bitmap into the AlphaImage output | ||
fixedMetrics.bitmap = AlphaImage(size); | ||
for (uint32_t i = 0; i < size.width * size.height; i++) { | ||
fixedMetrics.bitmap.data[i] = rgbaBitmap.data[4 * i + 3]; | ||
} | ||
|
||
return fixedMetrics; | ||
} | ||
|
||
} // namespace mbgl |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing
#pragma once
We also typically make sure that header files include all of the symbols they reference. Instead of defining all of the aliases in the header file, we can continue to define them where we need them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Maybe we don't need CFHandle at all? It seems like something we could replace with a portable "unique handle holder" or maybe just
std::unique_ptr
with custom deleters?