Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

[ios] POC: implementation of a CoreText-based LocalGlyphRasterizer #10572

Merged
merged 14 commits into from
Nov 29, 2017
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion include/mbgl/renderer/renderer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ class Renderer {
public:
Renderer(RendererBackend&, float pixelRatio_, FileSource&, Scheduler&,
GLContextMode = GLContextMode::Unique,
const optional<std::string> programCacheDir = {});
const optional<std::string> programCacheDir = {},
const optional<std::string> localFontFamily = {});
~Renderer();

void markContextLost();
Expand Down
27 changes: 27 additions & 0 deletions platform/darwin/src/CFHandle.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@

Copy link
Contributor

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.

Copy link
Contributor Author

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?

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

21 changes: 1 addition & 20 deletions platform/darwin/src/image.mm
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,7 @@

#import <ImageIO/ImageIO.h>

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;
};

} // namespace

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>;
#import "CFHandle.hpp"

CGImageRef CGImageFromMGLPremultipliedImage(mbgl::PremultipliedImage&& src) {
// We're converting the PremultipliedImage's backing store to a CGDataProvider, and are taking
Expand Down
170 changes: 170 additions & 0 deletions platform/darwin/src/local_glyph_rasterizer.mm
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
9 changes: 9 additions & 0 deletions platform/default/local_glyph_rasterizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

namespace mbgl {

class LocalGlyphRasterizer::Impl {
};

LocalGlyphRasterizer::LocalGlyphRasterizer(const optional<std::string>)
{}

LocalGlyphRasterizer::~LocalGlyphRasterizer()
{}

bool LocalGlyphRasterizer::canRasterizeGlyph(const FontStack&, GlyphID) {
return false;
}
Expand Down
4 changes: 3 additions & 1 deletion platform/ios/config.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,12 @@ macro(mbgl_platform_core)
# Misc
PRIVATE platform/darwin/mbgl/storage/reachability.h
PRIVATE platform/darwin/mbgl/storage/reachability.m
PRIVATE platform/darwin/src/CFHandle.hpp
PRIVATE platform/darwin/src/local_glyph_rasterizer.mm
PRIVATE platform/darwin/src/logging_nslog.mm
PRIVATE platform/darwin/src/nsthread.mm
PRIVATE platform/darwin/src/string_nsstring.mm
PRIVATE platform/default/bidi.cpp
PRIVATE platform/default/local_glyph_rasterizer.cpp
PRIVATE platform/default/thread_local.cpp
PRIVATE platform/default/utf.cpp

Expand Down Expand Up @@ -84,6 +85,7 @@ macro(mbgl_platform_core)
target_link_libraries(mbgl-core
PUBLIC "-lz"
PUBLIC "-framework Foundation"
PUBLIC "-framework CoreText"
PUBLIC "-framework CoreGraphics"
PUBLIC "-framework OpenGLES"
PUBLIC "-framework ImageIO"
Expand Down
4 changes: 3 additions & 1 deletion platform/macos/config.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ macro(mbgl_platform_core)
# Misc
PRIVATE platform/darwin/mbgl/storage/reachability.h
PRIVATE platform/darwin/mbgl/storage/reachability.m
PRIVATE platform/darwin/src/CFHandle.hpp
PRIVATE platform/darwin/src/local_glyph_rasterizer.mm
PRIVATE platform/darwin/src/logging_nslog.mm
PRIVATE platform/darwin/src/nsthread.mm
PRIVATE platform/darwin/src/string_nsstring.mm
PRIVATE platform/default/bidi.cpp
PRIVATE platform/default/local_glyph_rasterizer.cpp
PRIVATE platform/default/thread_local.cpp
PRIVATE platform/default/utf.cpp

Expand Down Expand Up @@ -64,6 +65,7 @@ macro(mbgl_platform_core)
target_link_libraries(mbgl-core
PUBLIC "-lz"
PUBLIC "-framework Foundation"
PUBLIC "-framework CoreText"
PUBLIC "-framework CoreGraphics"
PUBLIC "-framework OpenGL"
PUBLIC "-framework ImageIO"
Expand Down
12 changes: 6 additions & 6 deletions platform/macos/src/MGLMapView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -217,23 +217,23 @@ + (void)initialize {

- (instancetype)initWithFrame:(NSRect)frameRect {
if (self = [super initWithFrame:frameRect]) {
[self commonInit];
[self commonInit:nil];
self.styleURL = nil;
}
return self;
}

- (instancetype)initWithFrame:(NSRect)frame styleURL:(nullable NSURL *)styleURL {
- (instancetype)initWithFrame:(NSRect)frame styleURL:(nullable NSURL *)styleURL localIdeographFontFamily:(nullable NSString *)fontFamily {
if (self = [super initWithFrame:frame]) {
[self commonInit];
[self commonInit:fontFamily];
self.styleURL = styleURL;
}
return self;
}

- (instancetype)initWithCoder:(nonnull NSCoder *)decoder {
if (self = [super initWithCoder:decoder]) {
[self commonInit];
[self commonInit:nil];
}
return self;
}
Expand All @@ -252,7 +252,7 @@ + (NSArray *)restorableStateKeyPaths {
return @[@"camera", @"debugMask"];
}

- (void)commonInit {
- (void)commonInit:(nullable NSString*)fontFamily {
_isTargetingInterfaceBuilder = NSProcessInfo.processInfo.mgl_isInterfaceBuilderDesignablesAgent;

// Set up cross-platform controllers and resources.
Expand All @@ -274,7 +274,7 @@ - (void)commonInit {

_mbglThreadPool = mbgl::sharedThreadPool();

auto renderer = std::make_unique<mbgl::Renderer>(*_mbglView, [NSScreen mainScreen].backingScaleFactor, *mbglFileSource, *_mbglThreadPool, mbgl::GLContextMode::Unique);
auto renderer = std::make_unique<mbgl::Renderer>(*_mbglView, [NSScreen mainScreen].backingScaleFactor, *mbglFileSource, *_mbglThreadPool, mbgl::GLContextMode::Unique, mbgl::optional<std::string>(), fontFamily ? std::string([fontFamily UTF8String]) : mbgl::optional<std::string>());
_rendererFrontend = std::make_unique<MGLRenderFrontend>(std::move(renderer), self, *_mbglView, true);
_mbglMap = new mbgl::Map(*_rendererFrontend, *_mbglView, self.size, [NSScreen mainScreen].backingScaleFactor, *mbglFileSource, *_mbglThreadPool, mbgl::MapMode::Continuous, mbgl::ConstrainMode::None, mbgl::ViewportMode::Default);

Expand Down
5 changes: 3 additions & 2 deletions src/mbgl/renderer/renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ Renderer::Renderer(RendererBackend& backend,
FileSource& fileSource_,
Scheduler& scheduler_,
GLContextMode contextMode_,
const optional<std::string> programCacheDir_)
const optional<std::string> programCacheDir_,
const optional<std::string> localFontFamily_)
: impl(std::make_unique<Impl>(backend, pixelRatio_, fileSource_, scheduler_,
contextMode_, std::move(programCacheDir_))) {
contextMode_, std::move(programCacheDir_), std::move(localFontFamily_))) {
}

Renderer::~Renderer() {
Expand Down
5 changes: 3 additions & 2 deletions src/mbgl/renderer/renderer_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,16 @@ Renderer::Impl::Impl(RendererBackend& backend_,
FileSource& fileSource_,
Scheduler& scheduler_,
GLContextMode contextMode_,
const optional<std::string> programCacheDir_)
const optional<std::string> programCacheDir_,
const optional<std::string> localFontFamily_)
: backend(backend_)
, scheduler(scheduler_)
, fileSource(fileSource_)
, observer(&nullObserver())
, contextMode(contextMode_)
, pixelRatio(pixelRatio_)
, programCacheDir(programCacheDir_)
, glyphManager(std::make_unique<GlyphManager>(fileSource))
, glyphManager(std::make_unique<GlyphManager>(fileSource, std::make_unique<LocalGlyphRasterizer>(localFontFamily_)))
, imageManager(std::make_unique<ImageManager>())
, lineAtlas(std::make_unique<LineAtlas>(Size{ 256, 512 }))
, imageImpls(makeMutable<std::vector<Immutable<style::Image::Impl>>>())
Expand Down
2 changes: 1 addition & 1 deletion src/mbgl/renderer/renderer_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class Renderer::Impl : public GlyphManagerObserver,
public RenderSourceObserver{
public:
Impl(RendererBackend&, float pixelRatio_, FileSource&, Scheduler&, GLContextMode,
const optional<std::string> programCacheDir);
const optional<std::string> programCacheDir, const optional<std::string> localFontFamily);
~Impl() final;

void markContextLost() {
Expand Down
2 changes: 1 addition & 1 deletion src/mbgl/text/glyph_manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class GlyphRequestor {

class GlyphManager : public util::noncopyable {
public:
GlyphManager(FileSource&, std::unique_ptr<LocalGlyphRasterizer> = std::make_unique<LocalGlyphRasterizer>());
GlyphManager(FileSource&, std::unique_ptr<LocalGlyphRasterizer> = std::make_unique<LocalGlyphRasterizer>(optional<std::string>()));
~GlyphManager();

// Workers send a `getGlyphs` message to the main thread once they have determined
Expand Down
Loading