aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMinh Nguyễn <mxn@1ec5.org>2020-04-16 11:06:26 -0700
committerMinh Nguyễn <mxn@1ec5.org>2020-04-22 21:28:30 -0700
commit98427268c5f2b6e669b7e564cf450e79d374cca7 (patch)
treed0d6e0c0ae066787baa809534c030079775e7fe8
parent3970dd9d27c157a2a0fbda158c556910791972e0 (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.txt1
-rw-r--r--platform/android/android.cmake1
-rw-r--r--platform/darwin/src/local_glyph_rasterizer.mm286
-rw-r--r--platform/darwin/src/shaping.mm271
-rw-r--r--platform/default/src/mbgl/text/shaping.cpp (renamed from src/mbgl/text/shaping.cpp)0
-rw-r--r--platform/ios/ios.cmake1
-rw-r--r--platform/linux/linux.cmake1
-rw-r--r--platform/macos/macos.cmake1
-rw-r--r--platform/qt/qt.cmake1
-rw-r--r--src/mbgl/layout/symbol_layout.cpp47
-rw-r--r--src/mbgl/text/local_glyph_rasterizer.hpp3
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