diff options
author | Minh Nguyễn <mxn@1ec5.org> | 2020-04-16 11:06:26 -0700 |
---|---|---|
committer | Minh Nguyễn <mxn@1ec5.org> | 2020-04-22 21:28:30 -0700 |
commit | 98427268c5f2b6e669b7e564cf450e79d374cca7 (patch) | |
tree | d0d6e0c0ae066787baa809534c030079775e7fe8 | |
parent | 3970dd9d27c157a2a0fbda158c556910791972e0 (diff) |
[core, ios, macos] Shape text using Core Text
Started implementing a Core Text–based iOS/macOS shaper separate from the default implementation in C++.
Consult the local glyph rasterizer to translate Unicode codepoints to glyph indices.
Temporarily reverted many of the metrics improvements in the glyph rasterizer to narrow down coordinate transformation issues.
-rw-r--r-- | CMakeLists.txt | 1 | ||||
-rw-r--r-- | platform/android/android.cmake | 1 | ||||
-rw-r--r-- | platform/darwin/src/local_glyph_rasterizer.mm | 286 | ||||
-rw-r--r-- | platform/darwin/src/shaping.mm | 271 | ||||
-rw-r--r-- | platform/default/src/mbgl/text/shaping.cpp (renamed from src/mbgl/text/shaping.cpp) | 0 | ||||
-rw-r--r-- | platform/ios/ios.cmake | 1 | ||||
-rw-r--r-- | platform/linux/linux.cmake | 1 | ||||
-rw-r--r-- | platform/macos/macos.cmake | 1 | ||||
-rw-r--r-- | platform/qt/qt.cmake | 1 | ||||
-rw-r--r-- | src/mbgl/layout/symbol_layout.cpp | 47 | ||||
-rw-r--r-- | src/mbgl/text/local_glyph_rasterizer.hpp | 3 |
11 files changed, 459 insertions, 154 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 6232c7df1..2bdfdc420 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -712,7 +712,6 @@ add_library( ${PROJECT_SOURCE_DIR}/src/mbgl/text/placement.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/text/quads.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/text/quads.hpp - ${PROJECT_SOURCE_DIR}/src/mbgl/text/shaping.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/text/shaping.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/text/tagged_string.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/text/tagged_string.hpp diff --git a/platform/android/android.cmake b/platform/android/android.cmake index 6547496bc..ff5937b7a 100644 --- a/platform/android/android.cmake +++ b/platform/android/android.cmake @@ -186,6 +186,7 @@ add_library( ${PROJECT_SOURCE_DIR}/platform/android/src/test/benchmark_runner.cpp ${PROJECT_SOURCE_DIR}/platform/android/src/test/test_runner_common.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/text/local_glyph_rasterizer.cpp + ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/text/shaping.cpp ${PROJECT_SOURCE_DIR}/platform/android/src/test/collator_test_stub.cpp ${PROJECT_SOURCE_DIR}/platform/android/src/test/number_format_test_stub.cpp ${PROJECT_SOURCE_DIR}/platform/android/src/test/http_file_source_test_stub.cpp diff --git a/platform/darwin/src/local_glyph_rasterizer.mm b/platform/darwin/src/local_glyph_rasterizer.mm index 1dc2bcdcb..c83a06ac8 100644 --- a/platform/darwin/src/local_glyph_rasterizer.mm +++ b/platform/darwin/src/local_glyph_rasterizer.mm @@ -4,6 +4,7 @@ #include <mbgl/util/constants.hpp> #include <unordered_map> +#include <iterator> #import <Foundation/Foundation.h> #import <CoreText/CoreText.h> @@ -26,6 +27,8 @@ using CTFontRefHandle = CFHandle<CTFontRef, CFTypeRef, CFRelease>; using CTFontDescriptorRefHandle = CFHandle<CTFontDescriptorRef, CFTypeRef, CFRelease>; using CTLineRefHandle = CFHandle<CTLineRef, CFTypeRef, CFRelease>; +CTFontDescriptorRef createFontDescriptor(const FontStack& fontStack, NSArray<NSString *>* fallbackFontNames, bool isVertical); + /** Draws glyphs applying fonts that are installed on the system or bundled with the application. This is a more flexible and performant alternative to @@ -91,75 +94,6 @@ 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) { - // Per the Mapbox Style Specification, the text-font property comes - // with these last resort fonts by default, but they shouldn’t take - // precedence over any application or system fallback font that may - // be more appropriate to the current device. - if (fontName != util::LAST_RESORT_ALPHABETIC_FONT && fontName != util::LAST_RESORT_PAN_UNICODE_FONT) { - [fontNames addObject:@(fontName.c_str())]; - } - } - [fontNames addObjectsFromArray:fallbackFontNames]; - - if (!fontNames.count) { - NSDictionary *fontAttributes = @{ - (NSString *)kCTFontSizeAttribute: @(util::ONE_EM), - }; - return CTFontDescriptorCreateWithAttributes((CFDictionaryRef)fontAttributes); - } - - // Apply the first font name to the returned font descriptor; apply the - // rest of the font names to the cascade list. - CFStringRef mainFontName = (__bridge CFStringRef)fontNames.firstObject; - CFMutableArrayRefHandle fallbackDescriptors(CFArrayCreateMutable(kCFAllocatorDefault, fontNames.count, &kCFTypeArrayCallBacks)); - for (NSString *name in [fontNames subarrayWithRange:NSMakeRange(1, fontNames.count - 1)]) { - NSDictionary *fontAttributes = @{ - (NSString *)kCTFontSizeAttribute: @(util::ONE_EM), - // The name could be any of these three attributes of the font. - // It’s OK if it doesn’t match all three; Core Text will pick - // the font that matches the most attributes. - (NSString *)kCTFontNameAttribute: name, - (NSString *)kCTFontDisplayNameAttribute: name, - (NSString *)kCTFontFamilyNameAttribute: name, - }; - - CTFontDescriptorRefHandle descriptor(CTFontDescriptorCreateWithAttributes((CFDictionaryRef)fontAttributes)); - CFArrayAppendValue(*fallbackDescriptors, *descriptor); - } - - CFStringRef keys[] = { - kCTFontSizeAttribute, - kCTFontNameAttribute, kCTFontDisplayNameAttribute, kCTFontFamilyNameAttribute, - kCTFontCascadeListAttribute, - }; - CFTypeRef values[] = { - (__bridge CFNumberRef)@(util::ONE_EM), - mainFontName, mainFontName, mainFontName, - *fallbackDescriptors, - }; - - CFDictionaryRefHandle attributes( - CFDictionaryCreate(kCFAllocatorDefault, (const void**)&keys, - (const void**)&values, sizeof(keys) / sizeof(keys[0]), - &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks)); - return CTFontDescriptorCreateWithAttributes(*attributes); - } - private: NSArray<NSString *> *fallbackFontNames; }; @@ -191,41 +125,11 @@ bool LocalGlyphRasterizer::canRasterizeGlyph(const FontStack&, GlyphID glyphID) @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. + @param size The size of the glyph. @returns An image containing the glyph. */ -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 drawGlyphBitmap(GlyphID glyphID, CTFontRef font, Size size) { + CGGlyph glyphs[] = { glyphID }; PremultipliedImage rgbaBitmap(size); CGColorSpaceHandle colorSpace(CGColorSpaceCreateDeviceRGB()); @@ -249,31 +153,10 @@ PremultipliedImage drawGlyphBitmap(GlyphID glyphID, CTFontRef font, GlyphMetrics throw std::runtime_error("CGBitmapContextCreate failed"); } - 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); - - CTFontRef glyphFont = (CTFontRef)CFDictionaryGetValue(CTRunGetAttributes(glyphRun), kCTFontAttributeName); - - CGGlyph glyphs[wholeRunRange.length]; - CTRunGetGlyphs(glyphRun, wholeRunRange, glyphs); - CGPoint positions[wholeRunRange.length]; - CTRunGetPositions(glyphRun, wholeRunRange, positions); - CTFontDrawGlyphs(glyphFont, glyphs, positions, wholeRunRange.length, *context); + CGPoint positions[1]; + positions[0] = CGPointMake(0.0, 5.0); + CTFontDrawGlyphs(font, glyphs, positions, 1, *context); return rgbaBitmap; } @@ -290,17 +173,33 @@ PremultipliedImage drawGlyphBitmap(GlyphID glyphID, CTFontRef font, GlyphMetrics */ Glyph LocalGlyphRasterizer::rasterizeGlyph(const FontStack& fontStack, GlyphID glyphID) { Glyph manufacturedGlyph; - CTFontDescriptorRefHandle descriptor(impl->createFontDescriptor(fontStack)); - CTFontRefHandle font(CTFontCreateWithFontDescriptor(*descriptor, 0.0, NULL)); - if (!font) { - return manufacturedGlyph; - } + CFStringRef fontName = (__bridge CFStringRef)@(fontStack.front().c_str()); + CTFontRefHandle font(CTFontCreateWithName(fontName, 0.0, NULL)); manufacturedGlyph.id = glyphID; - PremultipliedImage rgbaBitmap = drawGlyphBitmap(glyphID, *font, manufacturedGlyph.metrics); + // TODO: Plumb through vertical text. + CTFontOrientation orientation = kCTFontOrientationHorizontal; + + CGRect boundingRects[1]; + CGGlyph glyphs[] = { glyphID }; + CGRect boundingRect = CTFontGetBoundingRectsForGlyphs(*font, orientation, glyphs, boundingRects, 1); + if (CGRectIsNull(boundingRect)) { + throw std::runtime_error("CTFontGetBoundingRectsForGlyphs failed"); + } + // Mimic glyph PBF metrics. + manufacturedGlyph.metrics.left = 3; + manufacturedGlyph.metrics.top = -1; + manufacturedGlyph.metrics.width = 35; + manufacturedGlyph.metrics.height = 35; + + CGSize advances[1]; + CTFontGetAdvancesForGlyphs(*font, orientation, glyphs, advances, 1); + manufacturedGlyph.metrics.advance = 24; Size size(manufacturedGlyph.metrics.width, manufacturedGlyph.metrics.height); + PremultipliedImage rgbaBitmap = drawGlyphBitmap(glyphID, *font, size); + // Copy alpha values from RGBA bitmap into the AlphaImage output manufacturedGlyph.bitmap = AlphaImage(size); for (uint32_t i = 0; i < size.width * size.height; i++) { @@ -310,4 +209,127 @@ Glyph LocalGlyphRasterizer::rasterizeGlyph(const FontStack& fontStack, GlyphID g return manufacturedGlyph; } +/** + Creates a font descriptor representing the given font stack and any global + fallback fonts. + + @param fontStack The font stack that takes precedence. + @param fallbackFontNames A list of font names to use as fallbacks if none of + the fonts in the font stack is available. + @param isVertical Whether the text to be drawn is laid out vertically. + @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, NSArray<NSString *>* fallbackFontNames, bool isVertical) { + NSMutableArray *fontNames = [NSMutableArray arrayWithCapacity:fontStack.size() + fallbackFontNames.count]; + for (auto& fontName : fontStack) { + // Per the Mapbox Style Specification, the text-font property comes with + // these last resort fonts by default, but they shouldn’t take + // precedence over any application or system fallback font that may be + // more appropriate to the current device. + if (fontName != util::LAST_RESORT_ALPHABETIC_FONT && fontName != util::LAST_RESORT_PAN_UNICODE_FONT) { + [fontNames addObject:@(fontName.c_str())]; + } + } + [fontNames addObjectsFromArray:fallbackFontNames]; + + if (!fontNames.count) { + NSDictionary *fontAttributes = @{ + (NSString *)kCTFontSizeAttribute: @(util::ONE_EM), + }; + return CTFontDescriptorCreateWithAttributes((CFDictionaryRef)fontAttributes); + } + + // Apply the first font name to the returned font descriptor; apply the rest + // of the font names to the cascade list. + CFStringRef mainFontName = (__bridge CFStringRef)fontNames.firstObject; + CFMutableArrayRefHandle fallbackDescriptors(CFArrayCreateMutable(kCFAllocatorDefault, fontNames.count, &kCFTypeArrayCallBacks)); + for (NSString *name in fontNames) { + NSDictionary *fontAttributes = @{ + (NSString *)kCTFontSizeAttribute: @(util::ONE_EM), + // The name could be any of these three attributes of the font. It’s + // OK if it doesn’t match all three; Core Text will pick the font + // that matches the most attributes. + (NSString *)kCTFontNameAttribute: name, + (NSString *)kCTFontDisplayNameAttribute: name, + (NSString *)kCTFontFamilyNameAttribute: name, + }; + + CTFontDescriptorRefHandle descriptor(CTFontDescriptorCreateWithAttributes((CFDictionaryRef)fontAttributes)); + CFArrayAppendValue(*fallbackDescriptors, *descriptor); + } + + CTFontOrientation orientation = isVertical ? kCTFontOrientationVertical : kCTFontOrientationHorizontal; + + CFStringRef keys[] = { + kCTFontSizeAttribute, + kCTFontNameAttribute, kCTFontDisplayNameAttribute, kCTFontFamilyNameAttribute, + kCTFontCascadeListAttribute, + kCTFontOrientationAttribute, + }; + CFTypeRef values[] = { + (__bridge CFNumberRef)@(util::ONE_EM), + mainFontName, mainFontName, mainFontName, + *fallbackDescriptors, + (__bridge CFNumberRef)@(orientation), + }; + + CFDictionaryRefHandle attributes( + CFDictionaryCreate(kCFAllocatorDefault, (const void**)&keys, + (const void**)&values, sizeof(keys) / sizeof(keys[0]), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + return CTFontDescriptorCreateWithAttributes(*attributes); +} + +GlyphDependencies getGlyphDependencies(const FontStack& fontStack, const std::string& text, bool isVertical) { + // TODO: Implement global fallback fonts. + CTFontDescriptorRefHandle descriptor(createFontDescriptor(fontStack, @[], isVertical)); + CTFontRefHandle font(CTFontCreateWithFontDescriptor(*descriptor, 0.0, NULL)); + + 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"); + } + + CFStringRef string = (__bridge CFStringRef)@(text.c_str()); + 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"); + } + + CFArrayRef glyphRuns = CTLineGetGlyphRuns(*line); + GlyphDependencies dependencies; + for (CFIndex i = 0; i < CFArrayGetCount(glyphRuns); i++) { + CTRunRef glyphRun = (CTRunRef)CFArrayGetValueAtIndex(glyphRuns, 0); + CFRange wholeRunRange = CFRangeMake(0, CTRunGetGlyphCount(glyphRun)); + + CTFontRef glyphFont = (CTFontRef)CFDictionaryGetValue(CTRunGetAttributes(glyphRun), kCTFontAttributeName); + CFStringRefHandle glyphFontName(CTFontCopyName(glyphFont, kCTFontPostScriptNameKey)); + FontStack glyphFontStack = {{ [(__bridge NSString *)*glyphFontName UTF8String] }}; + + // Use CTRunGetGlyphsPtr() if available. + CGGlyph glyphs[wholeRunRange.length]; + CTRunGetGlyphs(glyphRun, wholeRunRange, glyphs); + + GlyphIDs& glyphIDs = dependencies[glyphFontStack]; + glyphIDs.insert(glyphs, glyphs + wholeRunRange.length); + } + return dependencies; +} + } // namespace mbgl diff --git a/platform/darwin/src/shaping.mm b/platform/darwin/src/shaping.mm new file mode 100644 index 000000000..0ecf93c0c --- /dev/null +++ b/platform/darwin/src/shaping.mm @@ -0,0 +1,271 @@ +#include <mbgl/text/shaping.hpp> +#include <mbgl/util/constants.hpp> +#include <mbgl/util/i18n.hpp> +#include <mbgl/layout/symbol_feature.hpp> +#include <mbgl/math/minmax.hpp> +#include <mbgl/text/bidi.hpp> +#include <mbgl/text/local_glyph_rasterizer.hpp> +#include <mbgl/util/utf.hpp> + +#include <algorithm> +#include <list> +#include <cmath> + +#import "CFHandle.hpp" + +#import <Foundation/Foundation.h> +#import <CoreText/CoreText.h> + +using CFAttributedStringRefHandle = CFHandle<CFAttributedStringRef, CFTypeRef, CFRelease>; +using CFDictionaryRefHandle = CFHandle<CFDictionaryRef, CFTypeRef, CFRelease>; +using CFMutableAttributedStringRefHandle = CFHandle<CFMutableAttributedStringRef, CFTypeRef, CFRelease>; +using CGPathRefHandle = CFHandle<CGPathRef, CGPathRef, CGPathRelease>; +using CTFontRefHandle = CFHandle<CTFontRef, CFTypeRef, CFRelease>; +using CTFontDescriptorRefHandle = CFHandle<CTFontDescriptorRef, CFTypeRef, CFRelease>; +using CTParagraphStyleRefHandle = CFHandle<CTParagraphStyleRef, CFTypeRef, CFRelease>; +using CTFrameRefHandle = CFHandle<CTFrameRef, CFTypeRef, CFRelease>; +using CTFramesetterRefHandle = CFHandle<CTFramesetterRef, CFTypeRef, CFRelease>; + +namespace mbgl { + +// static +AnchorAlignment AnchorAlignment::getAnchorAlignment(style::SymbolAnchorType anchor) { + AnchorAlignment result(0.5f, 0.5f); + + switch (anchor) { + case style::SymbolAnchorType::Right: + case style::SymbolAnchorType::TopRight: + case style::SymbolAnchorType::BottomRight: + result.horizontalAlign = 1.0f; + break; + case style::SymbolAnchorType::Left: + case style::SymbolAnchorType::TopLeft: + case style::SymbolAnchorType::BottomLeft: + result.horizontalAlign = 0.0f; + break; + default: + break; + } + + switch (anchor) { + case style::SymbolAnchorType::Bottom: + case style::SymbolAnchorType::BottomLeft: + case style::SymbolAnchorType::BottomRight: + result.verticalAlign = 1.0f; + break; + case style::SymbolAnchorType::Top: + case style::SymbolAnchorType::TopLeft: + case style::SymbolAnchorType::TopRight: + result.verticalAlign = 0.0f; + break; + default: + break; + } + + return result; +} + +style::TextJustifyType getAnchorJustification(style::SymbolAnchorType anchor) { + switch (anchor) { + case style::SymbolAnchorType::Right: + case style::SymbolAnchorType::TopRight: + case style::SymbolAnchorType::BottomRight: + return style::TextJustifyType::Right; + case style::SymbolAnchorType::Left: + case style::SymbolAnchorType::TopLeft: + case style::SymbolAnchorType::BottomLeft: + return style::TextJustifyType::Left; + default: + return style::TextJustifyType::Center; + } +} + +PositionedIcon PositionedIcon::shapeIcon(const ImagePosition& image, + const std::array<float, 2>& iconOffset, + style::SymbolAnchorType iconAnchor) { + AnchorAlignment anchorAlign = AnchorAlignment::getAnchorAlignment(iconAnchor); + float dx = iconOffset[0]; + float dy = iconOffset[1]; + float left = dx - image.displaySize()[0] * anchorAlign.horizontalAlign; + float right = left + image.displaySize()[0]; + float top = dy - image.displaySize()[1] * anchorAlign.verticalAlign; + float bottom = top + image.displaySize()[1]; + + Padding collisionPadding; + if (image.content) { + auto& content = *image.content; + const auto pixelRatio = image.pixelRatio; + collisionPadding.left = content.left / pixelRatio; + collisionPadding.top = content.top / pixelRatio; + collisionPadding.right = image.displaySize()[0] - content.right / pixelRatio; + collisionPadding.bottom = image.displaySize()[1] - content.bottom / pixelRatio; + } + + return PositionedIcon{image, top, bottom, left, right, collisionPadding}; +} + +void PositionedIcon::fitIconToText(const Shaping& shapedText, + const style::IconTextFitType textFit, + const std::array<float, 4>& padding, + const std::array<float, 2>& iconOffset, + const float fontScale) { + assert(textFit != style::IconTextFitType::None); + assert(shapedText); + + // We don't respect the icon-anchor, because icon-text-fit is set. Instead, + // the icon will be centered on the text, then stretched in the given + // dimensions. + + const float textLeft = shapedText.left * fontScale; + const float textRight = shapedText.right * fontScale; + + if (textFit == style::IconTextFitType::Width || textFit == style::IconTextFitType::Both) { + // Stretched horizontally to the text width + _left = iconOffset[0] + textLeft - padding[3]; + _right = iconOffset[0] + textRight + padding[1]; + } else { + // Centered on the text + _left = iconOffset[0] + (textLeft + textRight - image().displaySize()[0]) / 2.0f; + _right = _left + image().displaySize()[0]; + } + + const float textTop = shapedText.top * fontScale; + const float textBottom = shapedText.bottom * fontScale; + if (textFit == style::IconTextFitType::Height || textFit == style::IconTextFitType::Both) { + // Stretched vertically to the text height + _top = iconOffset[1] + textTop - padding[0]; + _bottom = iconOffset[1] + textBottom + padding[2]; + } else { + // Centered on the text + _top = iconOffset[1] + (textTop + textBottom - image().displaySize()[1]) / 2.0f; + _bottom = _top + image().displaySize()[1]; + } +} + +// Defined in local_glyph_rasterizer.mm +CTFontDescriptorRef createFontDescriptor(const FontStack& fontStack, NSArray<NSString *>* fallbackFontNames, bool isVertical); + +Shaping getShaping(const TaggedString& formattedString, + const float maxWidth, + const float lineHeight, + const style::SymbolAnchorType textAnchor, + const style::TextJustifyType textJustify, + const float spacing, + const std::array<float, 2>& translate, + const WritingModeType writingMode, + BiDi& bidi, + const GlyphMap& glyphMap, + const GlyphPositions& glyphPositions, + const ImagePositions& imagePositions, + float layoutTextSize, + float layoutTextSizeAtBucketZoomLevel, + bool allowVerticalPlacement) { +// const float maxHeight = 200; // TODO: What’s a big enough height to not constrain any label? + const bool vertical = writingMode != WritingModeType::Horizontal && allowVerticalPlacement; + CTFontOrientation orientation = vertical ? kCTFontOrientationVertical : kCTFontOrientationHorizontal; + + CTTextAlignment textAlignment; + switch (textJustify) { + case style::TextJustifyType::Auto: + textAlignment = kCTTextAlignmentNatural; + break; + case style::TextJustifyType::Center: + textAlignment = kCTTextAlignmentCenter; + break; + case style::TextJustifyType::Left: + textAlignment = kCTTextAlignmentLeft; + break; + case style::TextJustifyType::Right: + textAlignment = kCTTextAlignmentRight; + break; + } + CTParagraphStyleSetting paragraphSettings[] = { + { kCTParagraphStyleSpecifierAlignment, sizeof(CTTextAlignment), &textAlignment }, + { kCTParagraphStyleSpecifierLineHeightMultiple, sizeof(float), &lineHeight }, + }; + CTParagraphStyleRefHandle paragraphStyle(CTParagraphStyleCreate(paragraphSettings, sizeof(paragraphSettings) / sizeof(paragraphSettings[0]))); + + CFStringRef keys[] = { kCTParagraphStyleAttributeName }; + CFTypeRef values[] = { *paragraphStyle }; + + CFDictionaryRefHandle attributes( + CFDictionaryCreate(kCFAllocatorDefault, (const void**)&keys, + (const void**)&values, sizeof(keys) / sizeof(keys[0]), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + const auto& string = util::convertUTF16ToUTF8(formattedString.rawText()); + CFAttributedStringRefHandle attrString(CFAttributedStringCreate(kCFAllocatorDefault, (CFStringRef)@(string.c_str()), *attributes)); + CFMutableAttributedStringRefHandle mutableAttrString(CFAttributedStringCreateMutableCopy(kCFAllocatorDefault, 0, *attrString)); + for (std::size_t i = 0; i < formattedString.length(); i++) { + const auto& section = formattedString.getSection(i); + if (section.imageID) continue; + + // TODO: Coalesce attributes across entire runs, because TaggedString has already chopped up all the attribute runs by character. + CTFontDescriptorRefHandle descriptor(createFontDescriptor(section.fontStack, @[], allowVerticalPlacement)); + CTFontRefHandle font(CTFontCreateWithFontDescriptor(*descriptor, 0.0, NULL)); + CFAttributedStringSetAttribute(*mutableAttrString, CFRangeMake(i, 1), kCTFontAttributeName, *font); + } + + Shaping shaping(translate[0], translate[1], writingMode); + + CTFramesetterRefHandle framesetter(CTFramesetterCreateWithAttributedString(*mutableAttrString)); +// CFRange wholeStringRange = CFRangeMake(0, CFAttributedStringGetLength(*mutableAttrString)); + CGPathRefHandle path(CGPathCreateWithRect(CGRectMake(0, 0, maxWidth, CGFLOAT_MAX), NULL)); + CTFrameRefHandle frame(CTFramesetterCreateFrame(*framesetter, CFRangeMake(0, 0), *path, NULL)); + CFArrayRef lines = CTFrameGetLines(*frame); + for (CFIndex i = 0; i < CFArrayGetCount(lines); i++) { + CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(lines, i); + shaping.positionedLines.emplace_back(); + auto& positionedLine = shaping.positionedLines.back(); + + CGRect bounds = CTLineGetBoundsWithOptions(line, 0); + positionedLine.lineOffset = CGRectGetHeight(bounds); + + CFArrayRef runs = CTLineGetGlyphRuns(line); + for (CFIndex j = 0; j < CFArrayGetCount(runs); j++) { + CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, j); + CFRange wholeRunRange = CFRangeMake(0, CTRunGetGlyphCount(run)); + + CGGlyph glyphs[wholeRunRange.length]; + CTRunGetGlyphs(run, wholeRunRange, glyphs); + + CGPoint positions[wholeRunRange.length]; + CTRunGetPositions(run, wholeRunRange, positions); + + CTFontRef glyphFont = (CTFontRef)CFDictionaryGetValue(CTRunGetAttributes(run), kCTFontAttributeName); + CGRect boundingRects[wholeRunRange.length]; + CTFontGetBoundingRectsForGlyphs(glyphFont, orientation, glyphs, boundingRects, wholeRunRange.length); + + CGSize advances[wholeRunRange.length]; + CTFontGetAdvancesForGlyphs(glyphFont, orientation, glyphs, advances, wholeRunRange.length); + + CFIndex stringIndices[CTRunGetStringRange(run).length]; + CTRunGetStringIndices(run, wholeRunRange, stringIndices); + + for (CFIndex k = 0; k < wholeRunRange.length; k++) { + const auto sectionIndex = formattedString.getSectionIndex(stringIndices[k]); + const auto& section = formattedString.getSection(stringIndices[k]); + + Rect<uint16_t> rect = { + static_cast<unsigned short>(CGRectGetMinX(boundingRects[k])), + static_cast<unsigned short>(CGFLOAT_MAX - CGRectGetMinY(boundingRects[k])), + static_cast<unsigned short>(CGRectGetWidth(boundingRects[k])), + static_cast<unsigned short>(CGRectGetHeight(boundingRects[k])), + }; + + GlyphMetrics metrics; + metrics.left = CGRectGetMinX(boundingRects[k]); + metrics.top = CGFLOAT_MAX - CGRectGetMinY(boundingRects[k]); + metrics.width = CGRectGetWidth(boundingRects[k]); + metrics.height = CGRectGetHeight(boundingRects[k]); + metrics.advance = advances[k].width; + + positionedLine.positionedGlyphs.emplace_back(glyphs[k], positions[k].x, positions[k].y, vertical, section.fontStackHash, section.scale, rect, metrics, section.imageID, sectionIndex); + } + } + } + + return shaping; +} + +} // namespace mbgl diff --git a/src/mbgl/text/shaping.cpp b/platform/default/src/mbgl/text/shaping.cpp index f84df7880..f84df7880 100644 --- a/src/mbgl/text/shaping.cpp +++ b/platform/default/src/mbgl/text/shaping.cpp diff --git a/platform/ios/ios.cmake b/platform/ios/ios.cmake index cab63e282..ff3a29d84 100644 --- a/platform/ios/ios.cmake +++ b/platform/ios/ios.cmake @@ -43,6 +43,7 @@ target_sources( ${PROJECT_SOURCE_DIR}/platform/darwin/src/nsthread.mm ${PROJECT_SOURCE_DIR}/platform/darwin/src/number_format.mm ${PROJECT_SOURCE_DIR}/platform/darwin/src/run_loop.cpp + ${PROJECT_SOURCE_DIR}/platform/darwin/src/shaping.mm ${PROJECT_SOURCE_DIR}/platform/darwin/src/string_nsstring.mm ${PROJECT_SOURCE_DIR}/platform/darwin/src/timer.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/gfx/headless_backend.cpp diff --git a/platform/linux/linux.cmake b/platform/linux/linux.cmake index 47395df7c..c2dd1058e 100644 --- a/platform/linux/linux.cmake +++ b/platform/linux/linux.cmake @@ -34,6 +34,7 @@ target_sources( ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/storage/sqlite3.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/text/bidi.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/text/local_glyph_rasterizer.cpp + ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/text/shaping.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/util/async_task.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/util/compression.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/util/image.cpp diff --git a/platform/macos/macos.cmake b/platform/macos/macos.cmake index 94beead50..b6d384ce5 100644 --- a/platform/macos/macos.cmake +++ b/platform/macos/macos.cmake @@ -34,6 +34,7 @@ target_sources( ${PROJECT_SOURCE_DIR}/platform/darwin/src/nsthread.mm ${PROJECT_SOURCE_DIR}/platform/darwin/src/number_format.mm ${PROJECT_SOURCE_DIR}/platform/darwin/src/run_loop.cpp + ${PROJECT_SOURCE_DIR}/platform/darwin/src/shaping.mm ${PROJECT_SOURCE_DIR}/platform/darwin/src/string_nsstring.mm ${PROJECT_SOURCE_DIR}/platform/darwin/src/timer.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/gfx/headless_backend.cpp diff --git a/platform/qt/qt.cmake b/platform/qt/qt.cmake index fdb26e530..3ee764b54 100644 --- a/platform/qt/qt.cmake +++ b/platform/qt/qt.cmake @@ -56,6 +56,7 @@ target_sources( ${PROJECT_SOURCE_DIR}/platform/qt/src/http_request.cpp ${PROJECT_SOURCE_DIR}/platform/qt/src/http_request.hpp ${PROJECT_SOURCE_DIR}/platform/qt/src/local_glyph_rasterizer.cpp + ${PROJECT_SOURCE_DIR}/platform/qt/src/shaping.cpp ${PROJECT_SOURCE_DIR}/platform/qt/src/qt_image.cpp ${PROJECT_SOURCE_DIR}/platform/qt/src/qt_logging.cpp ${PROJECT_SOURCE_DIR}/platform/qt/src/run_loop.cpp diff --git a/src/mbgl/layout/symbol_layout.cpp b/src/mbgl/layout/symbol_layout.cpp index 48e2f9d5c..61eae0852 100644 --- a/src/mbgl/layout/symbol_layout.cpp +++ b/src/mbgl/layout/symbol_layout.cpp @@ -6,6 +6,7 @@ #include <mbgl/renderer/image_atlas.hpp> #include <mbgl/text/get_anchors.hpp> #include <mbgl/text/shaping.hpp> +#include <mbgl/text/local_glyph_rasterizer.hpp> #include <mbgl/util/utf.hpp> #include <mbgl/util/constants.hpp> #include <mbgl/util/string.hpp> @@ -156,36 +157,40 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters, u8string = platform::lowercase(u8string); } + const auto& sectionFontStack = section.fontStack ? *section.fontStack : baseFontStack; ft.formattedText->addTextSection(applyArabicShaping(util::convertUTF8ToUTF16(u8string)), section.fontScale ? *section.fontScale : 1.0, - section.fontStack ? *section.fontStack : baseFontStack, + sectionFontStack, section.textColor); + + const auto& dependencies = mbgl::getGlyphDependencies(sectionFontStack, u8string, allowVerticalPlacement); + layoutParameters.glyphDependencies.insert(dependencies.begin(), dependencies.end()); } else { layoutParameters.imageDependencies.emplace(section.image->id(), ImageType::Icon); ft.formattedText->addImageSection(section.image->id()); } } - const bool canVerticalizeText = layout->get<TextRotationAlignment>() == AlignmentType::Map - && layout->get<SymbolPlacement>() != SymbolPlacementType::Point - && ft.formattedText->allowsVerticalWritingMode(); - - // Loop through all characters of this text and collect unique codepoints. - for (std::size_t j = 0; j < ft.formattedText->length(); j++) { - const auto& section = formatted.sections[ft.formattedText->getSectionIndex(j)]; - if (section.image) continue; - - const auto& sectionFontStack = section.fontStack; - GlyphIDs& dependencies = - layoutParameters.glyphDependencies[sectionFontStack ? *sectionFontStack : baseFontStack]; - char16_t codePoint = ft.formattedText->getCharCodeAt(j); - dependencies.insert(codePoint); - if (canVerticalizeText || (allowVerticalPlacement && ft.formattedText->allowsVerticalWritingMode())) { - if (char16_t verticalChr = util::i18n::verticalizePunctuation(codePoint)) { - dependencies.insert(verticalChr); - } - } - } +// const bool canVerticalizeText = layout->get<TextRotationAlignment>() == AlignmentType::Map +// && layout->get<SymbolPlacement>() != SymbolPlacementType::Point +// && ft.formattedText->allowsVerticalWritingMode(); +// +// // Loop through all characters of this text and collect unique codepoints. +// for (std::size_t j = 0; j < ft.formattedText->length(); j++) { +// const auto& section = formatted.sections[ft.formattedText->getSectionIndex(j)]; +// if (section.image) continue; +// +// const auto& sectionFontStack = section.fontStack; +// GlyphIDs& dependencies = +// layoutParameters.glyphDependencies[sectionFontStack ? *sectionFontStack : baseFontStack]; +// char16_t codePoint = ft.formattedText->getCharCodeAt(j); +// dependencies.insert(codePoint); +// if (canVerticalizeText || (allowVerticalPlacement && ft.formattedText->allowsVerticalWritingMode())) { +// if (char16_t verticalChr = util::i18n::verticalizePunctuation(codePoint)) { +// dependencies.insert(verticalChr); +// } +// } +// } } if (hasIcon) { diff --git a/src/mbgl/text/local_glyph_rasterizer.hpp b/src/mbgl/text/local_glyph_rasterizer.hpp index 4582aab10..47d077d19 100644 --- a/src/mbgl/text/local_glyph_rasterizer.hpp +++ b/src/mbgl/text/local_glyph_rasterizer.hpp @@ -1,6 +1,7 @@ #pragma once #include <mbgl/text/glyph.hpp> +#include <mbgl/util/font_stack.hpp> namespace mbgl { @@ -43,4 +44,6 @@ private: std::unique_ptr<Impl> impl; }; +GlyphDependencies getGlyphDependencies(const FontStack& fontStack, const std::string& text, bool isVertical); + } // namespace mbgl |