From 95483659046532ac2912f26babcf20fd3e32e4fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Tue, 21 Apr 2020 21:54:25 -0700 Subject: [ios, macos] Added and updated documentation --- platform/darwin/src/local_glyph_rasterizer.mm | 134 ++++++++++++++++++-------- 1 file changed, 96 insertions(+), 38 deletions(-) diff --git a/platform/darwin/src/local_glyph_rasterizer.mm b/platform/darwin/src/local_glyph_rasterizer.mm index b324184ba1..974f75b7b4 100644 --- a/platform/darwin/src/local_glyph_rasterizer.mm +++ b/platform/darwin/src/local_glyph_rasterizer.mm @@ -11,36 +11,10 @@ #import "CFHandle.hpp" -namespace mbgl { +/// Enables local glyph rasterization for all writing systems, not just CJK. +#define MBGL_DARWIN_NO_REMOTE_FONTS 0 -/* - 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. -*/ +namespace mbgl { using CGColorSpaceHandle = CFHandle; using CGContextHandle = CFHandle; @@ -52,13 +26,60 @@ using CTFontRefHandle = CFHandle; using CTFontDescriptorRefHandle = CFHandle; using CTLineRefHandle = CFHandle; +/** + Draws glyphs applying fonts that are installed on the system or bundled with + the application. This is a more flexible and performant alternative to + typesetting text using glyph sheets downloaded from a server. + + This implementation is similar to the local glyph rasterization in GL JS: + - Only CJK glyphs are drawn locally, because it’s much more noticeable when a + non-CJK font mismatches the style than when a CJK font mismatches the style. + (Unlike GL JS, this implementation does respect the font’s metrics, so it + does work with variable-width fonts.) + - Fallback fonts can be specified globally, similar to the seldom-used + advanced font preferences in most Web browsers. + + This is a first step toward fully local font rendering: + . Further improvements: + - Make sure the font size is 24 points (`util::ONE_EM`) at all times, not the + system default of 12 points, to avoid blobbiness after rasterization. + - Sniff an appropriate font weight and style from the font stack’s font names, + as GL JS and the Android map SDK do. + - Enable local glyph rasterization for all writing systems, not just CJK. This + would require providing a more attractive default Latin font than Helvetica + or Arial Unicode MS. + - Allow the developer to specify a `CTFontDescriptor` or + `NSFontDescriptor`/`UIFontDescriptor` to customize more attributes besides + the font (but not the font size). + - Render glyphs directly to SDF using Core Text’s `CTFontCreatePathForGlyph()` + instead of going through TinySDF. + - Typeset an entire text label in one shot using `CTFramesetter` to take + advantage of Core Text’s superior complex text shaping capabilities and + support for astral plane characters. mbgl would need to stop conflating + codepoints with glyph IDs. +*/ class LocalGlyphRasterizer::Impl { public: - Impl(const optional fontFamily_) + /** + Creates a new rasterizer with the given font names as a fallback. + + The fallback font names can also be specified in the style as a font stack + or in the `MGLIdeographicFontFamilyName` key of + `NSUserDefaults.standardUserDefaults`. The font stack takes precedence, + followed by the `MGLIdeographicFontFamilyName` user default, then finally + the `fallbackFontNames_` parameter as a last resort. + + @param fallbackFontNames_ A list of font names, one per line. Each font + name can be the PostScript name or display name of a specific font face + or a font family name. Set this parameter to `nullptr` to disable local + glyph rasterization globally. The system font is a good default value to + pass into this constructor. + */ + Impl(const optional fallbackFontNames_) { fallbackFontNames = [[NSUserDefaults standardUserDefaults] stringArrayForKey:@"MGLIdeographicFontFamilyName"]; - if (fontFamily_) { - fallbackFontNames = [fallbackFontNames ?: @[] arrayByAddingObjectsFromArray:[@(fontFamily_->c_str()) componentsSeparatedByString:@"\n"]]; + if (fallbackFontNames_) { + fallbackFontNames = [fallbackFontNames ?: @[] arrayByAddingObjectsFromArray:[@(fallbackFontNames_->c_str()) componentsSeparatedByString:@"\n"]]; } } @@ -70,6 +91,17 @@ public: */ bool isEnabled() { return fallbackFontNames; } + /** + Creates a font descriptor representing the given font stack and any global + fallback fonts. + + @param fontStack The font stack that takes precedence. + @returns A font descriptor representing the first font in the font stack + with a cascade list representing the rest of the fonts in the font stack + and any fallback fonts. The font descriptor is not cached. + + @post The caller is responsible for releasing the font descriptor. + */ CTFontDescriptorRef createFontDescriptor(const FontStack& fontStack) { NSMutableArray *fontNames = [NSMutableArray arrayWithCapacity:fontStack.size() + fallbackFontNames.count]; for (auto& fontName : fontStack) { @@ -127,11 +159,6 @@ public: &kCFTypeDictionaryValueCallBacks)); return CTFontDescriptorCreateWithAttributes(*attributes); } - - CTFontRef createFont(const FontStack& fontStack) { - CTFontDescriptorRefHandle descriptor(createFontDescriptor(fontStack)); - return CTFontCreateWithFontDescriptor(*descriptor, 0.0, NULL); - } private: NSArray *fallbackFontNames; @@ -144,10 +171,30 @@ LocalGlyphRasterizer::LocalGlyphRasterizer(const optional& fontFami LocalGlyphRasterizer::~LocalGlyphRasterizer() {} +/** + Returns whether the rasterizer can rasterize a glyph for the given codepoint. + + @param glyphID A font-agnostic Unicode codepoint, not a glyph index. + @returns Whether a glyph for the codepoint can be rasterized. + */ bool LocalGlyphRasterizer::canRasterizeGlyph(const FontStack&, GlyphID glyphID) { +#if MBGL_DARWIN_NO_REMOTE_FONTS + return impl->isEnabled(); +#else return util::i18n::allowsFixedWidthGlyphGeneration(glyphID) && impl->isEnabled(); +#endif } +/** + Draws the given codepoint into an image, gathers metrics about the glyph, and + returns the image. + + @param glyphID A font-agnostic Unicode codepoint, not a glyph index. + @param font The font to apply to the codepoint. + @param metrics Upon return, the metrics match the font’s metrics for the glyph + representing the codepoint. + @returns An image containing the glyph. + */ PremultipliedImage drawGlyphBitmap(GlyphID glyphID, CTFontRef font, GlyphMetrics& metrics) { CFStringRefHandle string(CFStringCreateWithCharacters(NULL, reinterpret_cast(&glyphID), 1)); if (!string) { @@ -223,9 +270,20 @@ PremultipliedImage drawGlyphBitmap(GlyphID glyphID, CTFontRef font, GlyphMetrics return rgbaBitmap; } +/** + Returns all the information mbgl needs about a glyph representation of the + given codepoint. + + @param fontStack The best matching font in this font stack is applied to the + codepoint. + @param glyphID A font-agnostic Unicode codepoint, not a glyph index. + @returns A glyph representation of the given codepoint with its bitmap data and + metrics set. + */ Glyph LocalGlyphRasterizer::rasterizeGlyph(const FontStack& fontStack, GlyphID glyphID) { Glyph manufacturedGlyph; - CTFontRefHandle font(impl->createFont(fontStack)); + CTFontDescriptorRefHandle descriptor(impl->createFontDescriptor(fontStack)); + CTFontRefHandle font(CTFontCreateWithFontDescriptor(*descriptor, 0.0, NULL)); if (!font) { return manufacturedGlyph; } -- cgit v1.2.1