summaryrefslogtreecommitdiff
path: root/platform/darwin/src/local_glyph_rasterizer.mm
diff options
context:
space:
mode:
Diffstat (limited to 'platform/darwin/src/local_glyph_rasterizer.mm')
-rw-r--r--platform/darwin/src/local_glyph_rasterizer.mm175
1 files changed, 175 insertions, 0 deletions
diff --git a/platform/darwin/src/local_glyph_rasterizer.mm b/platform/darwin/src/local_glyph_rasterizer.mm
new file mode 100644
index 0000000000..14cee5063e
--- /dev/null
+++ b/platform/darwin/src/local_glyph_rasterizer.mm
@@ -0,0 +1,175 @@
+#include <mbgl/text/local_glyph_rasterizer.hpp>
+#include <mbgl/util/i18n.hpp>
+#include <mbgl/util/platform.hpp>
+
+#include <unordered_map>
+
+#import <Foundation/Foundation.h>
+#import <CoreText/CoreText.h>
+#import <ImageIO/ImageIO.h>
+
+#import "CFHandle.hpp"
+
+namespace mbgl {
+
+/*
+ Darwin implementation of LocalGlyphRasterizer:
+ Draws CJK glyphs using locally available fonts.
+
+ Mirrors GL JS implementation in that:
+ - Only CJK glyphs are drawn locally (because we can guess their metrics effectively)
+ * Render size/metrics determined experimentally by rendering a few different fonts
+ - Configuration is done at map creation time by setting a "font family"
+ * JS uses a CSS font-family, this uses kCTFontFamilyNameAttribute which has
+ somewhat different behavior.
+
+ Further improvements are possible:
+ - GL JS heuristically determines a font weight based on the strings included in
+ the FontStack. Android follows a simpler heuristic that just picks up the
+ "Bold" property from the FontStack. Although both should be possible with CoreText,
+ our initial implementation couldn't reliably control the font-weight, so we're
+ skipping that functionality on darwin.
+ (See commit history for attempted implementation)
+ - If we could reliably extract glyph metrics, we wouldn't be limited to CJK glyphs
+ - We could push the font configuration down to individual style layers, which would
+ allow any current style to be reproducible using local fonts.
+ - Instead of just exposing "font family" as a configuration, we could expose a richer
+ CTFontDescriptor configuration option (although we'd have to override font size to
+ make sure it stayed at 24pt).
+ - Because Apple exposes glyph paths via `CTFontCreatePathForGlyph` we could potentially
+ render directly to SDF instead of going through TinySDF -- although it's not clear
+ how much of an improvement it would be.
+*/
+
+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>;
+using CTFontDescriptorRefHandle = CFHandle<CTFontDescriptorRef, CFTypeRef, CFRelease>;
+using CTLineRefHandle = CFHandle<CTLineRef, CFTypeRef, CFRelease>;
+
+class LocalGlyphRasterizer::Impl {
+public:
+ Impl(const optional<std::string> fontFamily_)
+ : fontFamily(fontFamily_)
+ , fontHandle(NULL)
+ {}
+
+ ~Impl() {
+ if (fontHandle) {
+ CFRelease(fontHandle);
+ }
+ }
+
+
+ CTFontRef getFont() {
+ if (!fontFamily) {
+ return NULL;
+ }
+
+ if (!fontHandle) {
+ NSDictionary *fontAttributes = @{
+ (NSString *)kCTFontSizeAttribute: [NSNumber numberWithFloat:24.0],
+ (NSString *)kCTFontFamilyNameAttribute: [[NSString alloc] initWithCString:fontFamily->c_str() encoding:NSUTF8StringEncoding]
+ };
+
+ CTFontDescriptorRefHandle descriptor(CTFontDescriptorCreateWithAttributes((CFDictionaryRef)fontAttributes));
+ fontHandle = CTFontCreateWithFontDescriptor(*descriptor, 0.0, NULL);
+ if (!fontHandle) {
+ throw std::runtime_error("CTFontCreateWithFontDescriptor failed");
+ }
+ }
+ return fontHandle;
+ }
+
+private:
+ optional<std::string> fontFamily;
+ CTFontRef fontHandle;
+};
+
+LocalGlyphRasterizer::LocalGlyphRasterizer(const optional<std::string> fontFamily)
+ : impl(std::make_unique<Impl>(fontFamily))
+{}
+
+LocalGlyphRasterizer::~LocalGlyphRasterizer()
+{}
+
+bool LocalGlyphRasterizer::canRasterizeGlyph(const FontStack&, GlyphID glyphID) {
+ return util::i18n::allowsFixedWidthGlyphGeneration(glyphID) && impl->getFont();
+}
+
+PremultipliedImage drawGlyphBitmap(GlyphID glyphID, CTFontRef font, Size size) {
+ PremultipliedImage rgbaBitmap(size);
+
+ CFStringRefHandle string(CFStringCreateWithCharacters(NULL, reinterpret_cast<UniChar*>(&glyphID), 1));
+
+ CGColorSpaceHandle colorSpace(CGColorSpaceCreateDeviceRGB());
+ 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));
+
+ // Start drawing a little bit below the top of the bitmap
+ CGContextSetTextPosition(*context, 0.0, 5.0);
+ CTLineDraw(*line, *context);
+
+ return rgbaBitmap;
+}
+
+Glyph LocalGlyphRasterizer::rasterizeGlyph(const FontStack&, GlyphID glyphID) {
+ Glyph fixedMetrics;
+ CTFontRef font = impl->getFont();
+ if (!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, 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