From a0cfac4c1a0785fae57ef80839e3e28124c6a541 Mon Sep 17 00:00:00 2001 From: Jordan Kiley Date: Fri, 10 Nov 2017 10:02:45 -0800 Subject: Update MGLMapSnapshotter docs (#10438) --- platform/darwin/src/MGLMapSnapshotter.h | 81 +++++++++++++++++++++++---------- 1 file changed, 57 insertions(+), 24 deletions(-) (limited to 'platform/darwin/src') diff --git a/platform/darwin/src/MGLMapSnapshotter.h b/platform/darwin/src/MGLMapSnapshotter.h index 541bc68b93..978e19dc20 100644 --- a/platform/darwin/src/MGLMapSnapshotter.h +++ b/platform/darwin/src/MGLMapSnapshotter.h @@ -14,9 +14,10 @@ MGL_EXPORT /** Creates a set of options with the minimum required information. - @param styleURL URL of the map style to snapshot. The URL may be a full HTTP or HTTPS URL, - a Mapbox URL indicating the style’s map ID (`mapbox://styles/{user}/{style`}), or a path - to a local file relative to the application’s resource path. Specify `nil` for the default style. + @param styleURL URL of the map style to snapshot. The URL may be a full HTTP or + HTTPS URL, a Mapbox URL indicating the style’s map ID + (`mapbox://styles/{user}/{style}`), or a path to a local file relative to + the application’s resource path. Specify `nil` for the default style. @param size The image size. */ - (instancetype)initWithStyleURL:(nullable NSURL *)styleURL camera:(MGLMapCamera *)camera size:(CGSize)size; @@ -31,17 +32,18 @@ MGL_EXPORT /** The zoom level. - The default zoom level is 0. If this property is non-zero and the camera property - is non-nil, the camera’s altitude is ignored in favor of this property’s value. + The default zoom level is 0. If this property is non-zero and the camera + property is non-nil, the camera’s altitude is ignored in favor of this + property’s value. */ @property (nonatomic) double zoomLevel; /** A camera representing the viewport visible in the snapshot. - If this property is non-nil and the `coordinateBounds` property is set to a non-empty - coordinate bounds, the camera’s center coordinate and altitude are ignored in favor - of the `coordinateBounds` property. + If this property is non-nil and the `coordinateBounds` property is set to a + non-empty coordinate bounds, the camera’s center coordinate and altitude are + ignored in favor of the `coordinateBounds` property. */ @property (nonatomic) MGLMapCamera *camera; @@ -78,24 +80,26 @@ MGL_EXPORT #if TARGET_OS_IPHONE /** - Converts the specified map coordinate to a point in the coordinate space of the image. + Converts the specified map coordinate to a point in the coordinate space of the + image. */ - (CGPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate; /** The image of the map’s content. */ -@property(nonatomic, readonly) UIImage *image; +@property (nonatomic, readonly) UIImage *image; #else /** - Converts the specified map coordinate to a point in the coordinate space of the image. + Converts the specified map coordinate to a point in the coordinate space of the + image. */ - (NSPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate; /** The image of the map’s content. */ -@property(nonatomic, readonly) NSImage *image; +@property (nonatomic, readonly) NSImage *image; #endif @end @@ -103,13 +107,33 @@ MGL_EXPORT /** A block to processes the result or error of a snapshot request. - @param snapshot The `MGLMapSnapshot` that was generated or `nil` if an error occurred. + @param snapshot The `MGLMapSnapshot` that was generated or `nil` if an error + occurred. @param error The error that occured or `nil` when successful. */ typedef void (^MGLMapSnapshotCompletionHandler)(MGLMapSnapshot* _Nullable snapshot, NSError* _Nullable error); /** - An immutable utility object for capturing map-based images. + An `MGLMapSnapshotter` generates static raster images of the map. Each snapshot + image depicts a portion of a map defined by an `MGLMapSnapshotOptions` object + you provide. The snapshotter generates an `MGLMapSnapshot` object + asynchronously, passing it into a completion handler once tiles and other + resources needed for the snapshot are finished loading. + + You can change the snapshotter’s options at any time and reuse the snapshotter + for multiple distinct snapshots; however, the snapshotter can only generate one + snapshot at a time. If you need to generate multiple snapshots concurrently, + create multiple snapshotter objects. + + For an interactive map, use the `MGLMapView` class. Both `MGLMapSnapshotter` + and `MGLMapView` are compatible with offline packs managed by the + `MGLOfflineStorage` class. + + From a snapshot, you can obtain an image and convert geographic coordinates to + the image’s coordinate space in order to superimpose markers and overlays. If + you do not need offline map functionality, you can use the `Snapshot` class in + [MapboxStatic.swift](https://github.com/mapbox/MapboxStatic.swift/) to generate + static map images with overlays. ### Example @@ -132,7 +156,14 @@ typedef void (^MGLMapSnapshotCompletionHandler)(MGLMapSnapshot* _Nullable snapsh MGL_EXPORT @interface MGLMapSnapshotter : NSObject -- (instancetype)initWithOptions:(MGLMapSnapshotOptions*)options; +/** + Initializes and returns a map snapshotter object that produces snapshots + according to the given options. + + @param options The options to use when generating a map snapshot. + @return An initialized map snapshotter. + */ +- (instancetype)initWithOptions:(MGLMapSnapshotOptions *)options; /** Starts the snapshot creation and executes the specified block with the result. @@ -142,7 +173,8 @@ MGL_EXPORT - (void)startWithCompletionHandler:(MGLMapSnapshotCompletionHandler)completionHandler; /** - Starts the snapshot creation and executes the specified block with the result on the specified queue. + Starts the snapshot creation and executes the specified block with the result + on the specified queue. @param queue The queue to handle the result on. @param completionHandler The block to handle the result in. @@ -152,25 +184,26 @@ MGL_EXPORT /** Cancels the snapshot creation request, if any. - Once you call this method, you cannot resume the snapshot. In order to obtain the - snapshot, create a new `MGLMapSnapshotter` object. + Once you call this method, you cannot resume the snapshot. In order to obtain + the snapshot, create a new `MGLMapSnapshotter` object. */ - (void)cancel; /** The zoom level. - The default zoom level is 0. If this property is non-zero and the camera property - is non-nil, the camera’s altitude is ignored in favor of this property’s value. + The default zoom level is 0. If this property is non-zero and the camera + property is non-nil, the camera’s altitude is ignored in favor of this + property’s value. */ @property (nonatomic) double zoomLevel; /** A camera representing the viewport visible in the snapshot. - If this property is non-nil and the `coordinateBounds` property is set to a non-empty - coordinate bounds, the camera’s center coordinate and altitude are ignored in favor - of the `coordinateBounds` property. + If this property is non-nil and the `coordinateBounds` property is set to a + non-empty coordinate bounds, the camera’s center coordinate and altitude are + ignored in favor of the `coordinateBounds` property. */ @property (nonatomic) MGLMapCamera *camera; @@ -186,7 +219,7 @@ MGL_EXPORT URL of the map style to snapshot. The URL may be a full HTTP or HTTPS URL, a Mapbox URL indicating the style’s - map ID (`mapbox://styles/{user}/{style`}), or a path to a local file relative + map ID (`mapbox://styles/{user}/{style}`), or a path to a local file relative to the application’s resource path. Specify `nil` for the default style. */ @property (nonatomic, nullable) NSURL *styleURL; -- cgit v1.2.1 From 5eb050dc35d3d780136806aee0f72bae592011a4 Mon Sep 17 00:00:00 2001 From: Fabian Guerra Date: Mon, 13 Nov 2017 17:29:54 -0500 Subject: [ios, macos] Update the attribution format for small snapshots. --- platform/darwin/src/MGLAttributionInfo.mm | 9 ++ platform/darwin/src/MGLMapSnapshotter.mm | 150 ++++++++++++++++++++++++------ 2 files changed, 130 insertions(+), 29 deletions(-) (limited to 'platform/darwin/src') diff --git a/platform/darwin/src/MGLAttributionInfo.mm b/platform/darwin/src/MGLAttributionInfo.mm index 770aeb25e7..92fb9b689f 100644 --- a/platform/darwin/src/MGLAttributionInfo.mm +++ b/platform/darwin/src/MGLAttributionInfo.mm @@ -127,6 +127,15 @@ return self; } +- (id)copyWithZone:(nullable NSZone *)zone +{ + MGLAttributionInfo *info = [[[self class] allocWithZone:zone] initWithTitle:_title + URL:_URL]; + info.feedbackLink = _feedbackLink; + + return info; +} + - (nullable NSURL *)feedbackURLAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel { return [self feedbackURLForStyleURL:nil atCenterCoordinate:centerCoordinate zoomLevel:zoomLevel direction:0 pitch:0]; } diff --git a/platform/darwin/src/MGLMapSnapshotter.mm b/platform/darwin/src/MGLMapSnapshotter.mm index dea93eba34..31b3ed41d6 100644 --- a/platform/darwin/src/MGLMapSnapshotter.mm +++ b/platform/darwin/src/MGLMapSnapshotter.mm @@ -27,6 +27,24 @@ const CGPoint MGLLogoImagePosition = CGPointMake(8, 8); const CGFloat MGLSnapshotterMinimumPixelSize = 64; +@interface MGLSnapshotAttributionOptions : NSObject +#if TARGET_OS_IPHONE +@property (nonatomic) UIImage *logoImage; +#else +@property (nonatomic) NSImage *logoImage; +#endif + +@property (nonatomic) CGSize attributionBackgroundSize; + +@property (nonatomic) NS_ARRAY_OF(MGLAttributionInfo *) *attributionInfo; + +@end + +@implementation MGLSnapshotAttributionOptions +@end + + + @implementation MGLMapSnapshotOptions - (instancetype _Nonnull)initWithStyleURL:(nullable NSURL *)styleURL camera:(MGLMapCamera *)camera size:(CGSize) size @@ -61,6 +79,7 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64; @implementation MGLMapSnapshot { mbgl::MapSnapshotter::PointForFn _pointForFn; } + - (instancetype)initWithImage:(nullable MGLImage *)image scale:(CGFloat)scale pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn { self = [super init]; @@ -88,6 +107,7 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64; std::shared_ptr _mbglThreadPool; std::unique_ptr _mbglMapSnapshotter; std::unique_ptr> _snapshotCallback; + CGFloat _defaultLogoHeight; } - (instancetype)initWithOptions:(MGLMapSnapshotOptions *)options @@ -167,15 +187,6 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64; [infos growArrayByAddingAttributionInfosFromArray:tileSetInfos]; } - CGSize attributionBackgroundSize = CGSizeMake(10, 0); - for (MGLAttributionInfo *info in infos) { - if (info.isFeedbackLink) { - continue; - } - attributionBackgroundSize.width += [info.title size].width + 10; - attributionBackgroundSize.height = MAX([info.title size].height, attributionBackgroundSize.height); - } - if (mbglError) { NSString *description = @(mbgl::util::toString(mbglError).c_str()); NSDictionary *userInfo = @{NSLocalizedDescriptionKey: description}; @@ -198,20 +209,28 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64; dispatch_queue_t workQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(workQueue, ^{ #if TARGET_OS_IPHONE - UIImage *logoImage = [UIImage imageNamed:@"mapbox" inBundle:[NSBundle mgl_frameworkBundle] compatibleWithTraitCollection:nil]; + MGLSnapshotAttributionOptions *option = [self attributionOptionsForSize:mglImage.size attributionInfo:infos]; + UIImage *logoImage = option.logoImage; CGRect logoImageRect = CGRectMake(MGLLogoImagePosition.x, mglImage.size.height - (MGLLogoImagePosition.y + logoImage.size.height), logoImage.size.width, logoImage.size.height); - CGRect attributionBackgroundFrame = CGRectMake(mglImage.size.width - 10 - attributionBackgroundSize.width, - logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2) + 1, - attributionBackgroundSize.width, - attributionBackgroundSize.height); + CGPoint attributionOrigin = CGPointMake(mglImage.size.width - 10 - option.attributionBackgroundSize.width, + logoImageRect.origin.y + (logoImageRect.size.height / 2) - (option.attributionBackgroundSize.height / 2) + 1); + if (!logoImage) { + logoImageRect = CGRectMake(0, mglImage.size.height - (MGLLogoImagePosition.y + _defaultLogoHeight), 0, _defaultLogoHeight); + attributionOrigin = CGPointMake(10, logoImageRect.origin.y + (logoImageRect.size.height / 2) - (option.attributionBackgroundSize.height / 2) + 1); + } + + CGRect attributionBackgroundFrame = CGRectMake(attributionOrigin.x, + attributionOrigin.y, + option.attributionBackgroundSize.width, + option.attributionBackgroundSize.height); CGPoint attributionTextPosition = CGPointMake(attributionBackgroundFrame.origin.x + 10, attributionBackgroundFrame.origin.y - 1); CGRect cropRect = CGRectMake(attributionBackgroundFrame.origin.x * mglImage.scale, attributionBackgroundFrame.origin.y * mglImage.scale, - attributionBackgroundSize.width * mglImage.scale, - attributionBackgroundSize.height * mglImage.scale); + option.attributionBackgroundSize.width * mglImage.scale, + option.attributionBackgroundSize.height * mglImage.scale); UIGraphicsBeginImageContextWithOptions(mglImage.size, NO, self.options.scale); @@ -231,24 +250,32 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64; [blurredAttributionBackground drawInRect:attributionBackgroundFrame]; - [self drawAttributionText:infos origin:attributionTextPosition]; + [self drawAttributionText:option.attributionInfo origin:attributionTextPosition]; UIImage *compositedImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); #else - NSImage *logoImage = [[NSImage alloc] initWithContentsOfFile:[[NSBundle mgl_frameworkBundle] pathForResource:@"mapbox" ofType:@"pdf"]]; - NSImage *sourceImage = mglImage; - NSSize targetSize = NSMakeSize(self.options.size.width, self.options.size.height); NSRect targetFrame = NSMakeRect(0, 0, targetSize.width, targetSize.height); + MGLSnapshotAttributionOptions *option = [self attributionOptionsForSize:targetSize attributionInfo:infos]; + NSImage *logoImage = option.logoImage; + NSImage *sourceImage = mglImage; + CGRect logoImageRect = CGRectMake(MGLLogoImagePosition.x, MGLLogoImagePosition.y, logoImage.size.width, logoImage.size.height); - CGRect attributionBackgroundFrame = CGRectMake(targetFrame.size.width - 10 - attributionBackgroundSize.width, - MGLLogoImagePosition.y + 1, - attributionBackgroundSize.width, - attributionBackgroundSize.height); + CGPoint attributionOrigin = CGPointMake(targetFrame.size.width - 10 - option.attributionBackgroundSize.width, + MGLLogoImagePosition.y + 1); + if (!logoImage) { + logoImageRect = CGRectMake(0, MGLLogoImagePosition.y, 0, _defaultLogoHeight); + attributionOrigin = CGPointMake(10, attributionOrigin.y); + } + + CGRect attributionBackgroundFrame = CGRectMake(attributionOrigin.x, + attributionOrigin.y, + option.attributionBackgroundSize.width, + option.attributionBackgroundSize.height); CGPoint attributionTextPosition = CGPointMake(attributionBackgroundFrame.origin.x + 10, - logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2)); + logoImageRect.origin.y + (logoImageRect.size.height / 2) - (option.attributionBackgroundSize.height / 2)); NSImage *compositedImage = nil; @@ -261,7 +288,9 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64; [sourceImageRep drawInRect: targetFrame]; - [logoImage drawInRect:logoImageRect]; + if (logoImage) { + [logoImage drawInRect:logoImageRect]; + } NSBitmapImageRep *attributionBackground = [[NSBitmapImageRep alloc] initWithFocusedViewRect:attributionBackgroundFrame]; @@ -271,7 +300,7 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64; [blurredAttributionBackground drawInRect:attributionBackgroundFrame]; - [self drawAttributionText:infos origin:attributionTextPosition]; + [self drawAttributionText:option.attributionInfo origin:attributionTextPosition]; [compositedImage unlockFocus]; @@ -323,14 +352,77 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64; CGImageRef cgimg = [ctx createCGImage:blurredImage fromRect:[backgroundImage extent]]; #if TARGET_OS_IPHONE - return [UIImage imageWithCGImage:cgimg]; #else - return [[NSImage alloc] initWithCGImage:cgimg size:[backgroundImage extent].size]; #endif } +- (MGLSnapshotAttributionOptions *)attributionOptionsForSize:(CGSize)snapshotSize attributionInfo:(NSArray *)attributionInfo +{ + NSMutableArray *options = [NSMutableArray array]; + MGLSnapshotAttributionOptions *largeLogoAttribution = [self attributionOptionsForAttributionInfo:attributionInfo abbreviated:NO]; +#if TARGET_OS_IPHONE + largeLogoAttribution.logoImage = [UIImage imageNamed:@"mapbox" inBundle:[NSBundle mgl_frameworkBundle] compatibleWithTraitCollection:nil]; +#else + largeLogoAttribution.logoImage = [[NSImage alloc] initWithContentsOfFile:[[NSBundle mgl_frameworkBundle] pathForResource:@"mapbox" ofType:@"pdf"]]; +#endif + _defaultLogoHeight = largeLogoAttribution.logoImage.size.height; + + MGLSnapshotAttributionOptions *smallLogoAttribution = [self attributionOptionsForAttributionInfo:attributionInfo abbreviated:NO]; +#if TARGET_OS_IPHONE + smallLogoAttribution.logoImage = [UIImage imageNamed:@"mapbox_helmet" inBundle:[NSBundle mgl_frameworkBundle] compatibleWithTraitCollection:nil]; +#else + smallLogoAttribution.logoImage = [[NSImage alloc] initWithContentsOfFile:[[NSBundle mgl_frameworkBundle] pathForResource:@"mapbox_helmet" ofType:@"pdf"]]; +#endif + + MGLSnapshotAttributionOptions *noLogoAttribution = [self attributionOptionsForAttributionInfo:attributionInfo abbreviated:YES]; + + [options addObject:largeLogoAttribution]; + [options addObject:smallLogoAttribution]; + [options addObject:noLogoAttribution]; + + for (MGLSnapshotAttributionOptions *attributionOptions in options) { + // -[Mapbox Logo]-[Attribution Background]- + CGFloat origin = attributionOptions.logoImage ? MGLLogoImagePosition.x : 0; + CGFloat width = origin + attributionOptions.logoImage.size.width + 10 + attributionOptions.attributionBackgroundSize.width + 10; + if (width <= snapshotSize.width) { + return attributionOptions; + } + } + + return noLogoAttribution; +} + +- (MGLSnapshotAttributionOptions *)attributionOptionsForAttributionInfo:(NSArray *)attributionInfo abbreviated:(BOOL)isAbbreviated +{ + NSString *openStreetMap = NSLocalizedStringWithDefaultValue(@"OSM_FULL_NAME", nil, nil, @"OpenStreetMap", @"OpenStreetMap full name attribution"); + NSString *OSM = NSLocalizedStringWithDefaultValue(@"OSM_SHORT_NAME", nil, nil, @"OSM", @"OpenStreetMap short name attribution"); + NSMutableArray *infos = [NSMutableArray array]; + CGSize attributionBackgroundSize = CGSizeMake(10, 0); + for (MGLAttributionInfo *info in attributionInfo) { + if (info.isFeedbackLink) { + continue; + } + MGLAttributionInfo *attribution = [info copy]; + NSMutableAttributedString *title = [[NSMutableAttributedString alloc] initWithAttributedString:info.title]; + [title removeAttribute:NSUnderlineStyleAttributeName range:NSMakeRange(0, [title.string length])]; + if ([title.string rangeOfString:@"OpenStreetMap"].location != NSNotFound) { + [title.mutableString replaceOccurrencesOfString:@"OpenStreetMap" withString:isAbbreviated ? OSM : openStreetMap options:NSCaseInsensitiveSearch range:NSMakeRange(0, [title.mutableString length])]; + } + attribution.title = title; + attributionBackgroundSize.width += [attribution.title size].width + 10; + attributionBackgroundSize.height = MAX([attribution.title size].height, attributionBackgroundSize.height); + [infos addObject:attribution]; + } + + MGLSnapshotAttributionOptions *attributionOptions = [[MGLSnapshotAttributionOptions alloc] init]; + attributionOptions.attributionBackgroundSize = attributionBackgroundSize; + attributionOptions.attributionInfo = infos; + + return attributionOptions; +} + - (void)cancel { _snapshotCallback.reset(); -- cgit v1.2.1 From 784b71a81c469e9fc5297c4006846238f91fe724 Mon Sep 17 00:00:00 2001 From: Fabian Guerra Date: Tue, 21 Nov 2017 16:32:05 -0500 Subject: [ios, macos] Refactor snapshot attribution. --- platform/darwin/src/MGLAttributionInfo.h | 30 +++++ platform/darwin/src/MGLAttributionInfo.mm | 19 ++++ platform/darwin/src/MGLMapSnapshotter.mm | 180 ++++++++++++++++-------------- 3 files changed, 143 insertions(+), 86 deletions(-) (limited to 'platform/darwin/src') diff --git a/platform/darwin/src/MGLAttributionInfo.h b/platform/darwin/src/MGLAttributionInfo.h index 031a10060f..1de37c3b24 100644 --- a/platform/darwin/src/MGLAttributionInfo.h +++ b/platform/darwin/src/MGLAttributionInfo.h @@ -7,6 +7,24 @@ NS_ASSUME_NONNULL_BEGIN +/** + The attribution info is represented in the longest format available. + */ +typedef NS_ENUM(NSUInteger, MGLAttributionInfoStyle) { + /** + Specifies a short attribution info style. + */ + MGLAttributionInfoStyleShort = 1, + /** + Specifies a medium attribution info style. + */ + MGLAttributionInfoStyleMedium, + /** + Specifies a long attribution info style. + */ + MGLAttributionInfoStyleLong +}; + /** Information about an attribution statement, usually a copyright or trademark statement, associated with a map content source. @@ -59,6 +77,18 @@ MGL_EXPORT */ - (nullable NSURL *)feedbackURLAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel; +/** + Returns a copy of the current `title` formatted accordingly to `style`. + + Example: If the `style` property is set to `MGLAttributionInfoStyleShort` and the + `title` property is set to `OpenStreetMap`, then this method returns `OSM`. + + @param style The attribution info style. + + @return The `NSAttributedString` styled title. + */ +- (NSAttributedString *)titleWithStyle:(MGLAttributionInfoStyle)style; + @end NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MGLAttributionInfo.mm b/platform/darwin/src/MGLAttributionInfo.mm index 92fb9b689f..29dd5229a7 100644 --- a/platform/darwin/src/MGLAttributionInfo.mm +++ b/platform/darwin/src/MGLAttributionInfo.mm @@ -168,6 +168,25 @@ return components.URL; } +- (NSAttributedString *)titleWithStyle:(MGLAttributionInfoStyle)style +{ + NSString *openStreetMap = NSLocalizedStringWithDefaultValue(@"OSM_FULL_NAME", nil, nil, @"OpenStreetMap", @"OpenStreetMap full name attribution"); + NSString *OSM = NSLocalizedStringWithDefaultValue(@"OSM_SHORT_NAME", nil, nil, @"OSM", @"OpenStreetMap short name attribution"); + + NSMutableAttributedString *title = [[NSMutableAttributedString alloc] initWithAttributedString:self.title]; + [title removeAttribute:NSUnderlineStyleAttributeName range:NSMakeRange(0, [title.string length])]; + + BOOL isAbbreviated = (style == MGLAttributionInfoStyleShort); + + if ([title.string rangeOfString:@"OpenStreetMap"].location != NSNotFound) { + [title.mutableString replaceOccurrencesOfString:@"OpenStreetMap" withString:isAbbreviated ? OSM : openStreetMap + options:NSCaseInsensitiveSearch + range:NSMakeRange(0, [title.mutableString length])]; + } + + return title; +} + - (BOOL)isEqual:(id)object { return [object isKindOfClass:[self class]] && [[object title] isEqual:self.title] && [[object URL] isEqual:self.URL]; } diff --git a/platform/darwin/src/MGLMapSnapshotter.mm b/platform/darwin/src/MGLMapSnapshotter.mm index 31b3ed41d6..8b54488814 100644 --- a/platform/darwin/src/MGLMapSnapshotter.mm +++ b/platform/darwin/src/MGLMapSnapshotter.mm @@ -27,24 +27,6 @@ const CGPoint MGLLogoImagePosition = CGPointMake(8, 8); const CGFloat MGLSnapshotterMinimumPixelSize = 64; -@interface MGLSnapshotAttributionOptions : NSObject -#if TARGET_OS_IPHONE -@property (nonatomic) UIImage *logoImage; -#else -@property (nonatomic) NSImage *logoImage; -#endif - -@property (nonatomic) CGSize attributionBackgroundSize; - -@property (nonatomic) NS_ARRAY_OF(MGLAttributionInfo *) *attributionInfo; - -@end - -@implementation MGLSnapshotAttributionOptions -@end - - - @implementation MGLMapSnapshotOptions - (instancetype _Nonnull)initWithStyleURL:(nullable NSURL *)styleURL camera:(MGLMapCamera *)camera size:(CGSize) size @@ -107,7 +89,7 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64; std::shared_ptr _mbglThreadPool; std::unique_ptr _mbglMapSnapshotter; std::unique_ptr> _snapshotCallback; - CGFloat _defaultLogoHeight; + NS_ARRAY_OF(MGLAttributionInfo *) *_attributionInfo; } - (instancetype)initWithOptions:(MGLMapSnapshotOptions *)options @@ -187,6 +169,8 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64; [infos growArrayByAddingAttributionInfosFromArray:tileSetInfos]; } + _attributionInfo = infos; + if (mbglError) { NSString *description = @(mbgl::util::toString(mbglError).c_str()); NSDictionary *userInfo = @{NSLocalizedDescriptionKey: description}; @@ -209,28 +193,38 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64; dispatch_queue_t workQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(workQueue, ^{ #if TARGET_OS_IPHONE - MGLSnapshotAttributionOptions *option = [self attributionOptionsForSize:mglImage.size attributionInfo:infos]; - UIImage *logoImage = option.logoImage; + MGLAttributionInfoStyle attributionInfoStyle = MGLAttributionInfoStyleLong; + for (NSUInteger styleValue = MGLAttributionInfoStyleLong; styleValue >= MGLAttributionInfoStyleShort; styleValue--) { + attributionInfoStyle = (MGLAttributionInfoStyle)styleValue; + CGSize attributionSize = [self attributionSizeWithLogoStyle:attributionInfoStyle sourceAttributionStyle:attributionInfoStyle]; + if (attributionSize.width <= mglImage.size.width) { + break; + } + } + + UIImage *logoImage = [self logoImageWithStyle:attributionInfoStyle]; + CGSize attributionBackgroundSize = [self attributionTextSizeWithStyle:attributionInfoStyle]; CGRect logoImageRect = CGRectMake(MGLLogoImagePosition.x, mglImage.size.height - (MGLLogoImagePosition.y + logoImage.size.height), logoImage.size.width, logoImage.size.height); - CGPoint attributionOrigin = CGPointMake(mglImage.size.width - 10 - option.attributionBackgroundSize.width, - logoImageRect.origin.y + (logoImageRect.size.height / 2) - (option.attributionBackgroundSize.height / 2) + 1); + CGPoint attributionOrigin = CGPointMake(mglImage.size.width - 10 - attributionBackgroundSize.width, + logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2) + 1); if (!logoImage) { - logoImageRect = CGRectMake(0, mglImage.size.height - (MGLLogoImagePosition.y + _defaultLogoHeight), 0, _defaultLogoHeight); - attributionOrigin = CGPointMake(10, logoImageRect.origin.y + (logoImageRect.size.height / 2) - (option.attributionBackgroundSize.height / 2) + 1); + CGSize defaultLogoSize = [self mapboxLongStyleLogo].size; + logoImageRect = CGRectMake(0, mglImage.size.height - (MGLLogoImagePosition.y + defaultLogoSize.height), 0, defaultLogoSize.height); + attributionOrigin = CGPointMake(10, logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2) + 1); } CGRect attributionBackgroundFrame = CGRectMake(attributionOrigin.x, attributionOrigin.y, - option.attributionBackgroundSize.width, - option.attributionBackgroundSize.height); + attributionBackgroundSize.width, + attributionBackgroundSize.height); CGPoint attributionTextPosition = CGPointMake(attributionBackgroundFrame.origin.x + 10, attributionBackgroundFrame.origin.y - 1); CGRect cropRect = CGRectMake(attributionBackgroundFrame.origin.x * mglImage.scale, attributionBackgroundFrame.origin.y * mglImage.scale, - option.attributionBackgroundSize.width * mglImage.scale, - option.attributionBackgroundSize.height * mglImage.scale); + attributionBackgroundSize.width * mglImage.scale, + attributionBackgroundSize.height * mglImage.scale); UIGraphicsBeginImageContextWithOptions(mglImage.size, NO, self.options.scale); @@ -250,7 +244,7 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64; [blurredAttributionBackground drawInRect:attributionBackgroundFrame]; - [self drawAttributionText:option.attributionInfo origin:attributionTextPosition]; + [self drawAttributionTextWithStyle:attributionInfoStyle origin:attributionTextPosition]; UIImage *compositedImage = UIGraphicsGetImageFromCurrentImageContext(); @@ -258,24 +252,35 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64; #else NSSize targetSize = NSMakeSize(self.options.size.width, self.options.size.height); NSRect targetFrame = NSMakeRect(0, 0, targetSize.width, targetSize.height); - MGLSnapshotAttributionOptions *option = [self attributionOptionsForSize:targetSize attributionInfo:infos]; - NSImage *logoImage = option.logoImage; + + MGLAttributionInfoStyle attributionInfoStyle = MGLAttributionInfoStyleLong; + for (NSUInteger styleValue = MGLAttributionInfoStyleLong; styleValue >= MGLAttributionInfoStyleShort; styleValue--) { + attributionInfoStyle = (MGLAttributionInfoStyle)styleValue; + CGSize attributionSize = [self attributionSizeWithLogoStyle:attributionInfoStyle sourceAttributionStyle:attributionInfoStyle]; + if (attributionSize.width <= mglImage.size.width) { + break; + } + } + + NSImage *logoImage = [self logoImageWithStyle:attributionInfoStyle]; + CGSize attributionBackgroundSize = [self attributionTextSizeWithStyle:attributionInfoStyle]; NSImage *sourceImage = mglImage; CGRect logoImageRect = CGRectMake(MGLLogoImagePosition.x, MGLLogoImagePosition.y, logoImage.size.width, logoImage.size.height); - CGPoint attributionOrigin = CGPointMake(targetFrame.size.width - 10 - option.attributionBackgroundSize.width, + CGPoint attributionOrigin = CGPointMake(targetFrame.size.width - 10 - attributionBackgroundSize.width, MGLLogoImagePosition.y + 1); if (!logoImage) { - logoImageRect = CGRectMake(0, MGLLogoImagePosition.y, 0, _defaultLogoHeight); + CGSize defaultLogoSize = [self mapboxLongStyleLogo].size; + logoImageRect = CGRectMake(0, MGLLogoImagePosition.y, 0, defaultLogoSize.height); attributionOrigin = CGPointMake(10, attributionOrigin.y); } CGRect attributionBackgroundFrame = CGRectMake(attributionOrigin.x, attributionOrigin.y, - option.attributionBackgroundSize.width, - option.attributionBackgroundSize.height); + attributionBackgroundSize.width, + attributionBackgroundSize.height); CGPoint attributionTextPosition = CGPointMake(attributionBackgroundFrame.origin.x + 10, - logoImageRect.origin.y + (logoImageRect.size.height / 2) - (option.attributionBackgroundSize.height / 2)); + logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2)); NSImage *compositedImage = nil; @@ -300,7 +305,7 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64; [blurredAttributionBackground drawInRect:attributionBackgroundFrame]; - [self drawAttributionText:option.attributionInfo origin:attributionTextPosition]; + [self drawAttributionTextWithStyle:attributionInfoStyle origin:attributionTextPosition]; [compositedImage unlockFocus]; @@ -319,15 +324,16 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64; }); } -- (void)drawAttributionText:(NSArray *)attributionInfo origin:(CGPoint)origin +- (void)drawAttributionTextWithStyle:(MGLAttributionInfoStyle)attributionInfoStyle origin:(CGPoint)origin { - for (MGLAttributionInfo *info in attributionInfo) { + for (MGLAttributionInfo *info in _attributionInfo) { if (info.isFeedbackLink) { continue; } - [info.title drawAtPoint:origin]; + NSAttributedString *attribution = [info titleWithStyle:attributionInfoStyle]; + [attribution drawAtPoint:origin]; - origin.x += [info.title size].width + 10; + origin.x += [attribution size].width + 10; } } @@ -350,77 +356,79 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64; CIContext *ctx = [CIContext contextWithOptions:nil]; CGImageRef cgimg = [ctx createCGImage:blurredImage fromRect:[backgroundImage extent]]; + MGLImage *image; #if TARGET_OS_IPHONE - return [UIImage imageWithCGImage:cgimg]; + image = [UIImage imageWithCGImage:cgimg]; #else - return [[NSImage alloc] initWithCGImage:cgimg size:[backgroundImage extent].size]; + image = [[NSImage alloc] initWithCGImage:cgimg size:[backgroundImage extent].size]; #endif + CGImageRelease(cgimg); + return image; } -- (MGLSnapshotAttributionOptions *)attributionOptionsForSize:(CGSize)snapshotSize attributionInfo:(NSArray *)attributionInfo +- (MGLImage *)logoImageWithStyle:(MGLAttributionInfoStyle)style { - NSMutableArray *options = [NSMutableArray array]; - MGLSnapshotAttributionOptions *largeLogoAttribution = [self attributionOptionsForAttributionInfo:attributionInfo abbreviated:NO]; + MGLImage *logoImage; + switch (style) { + case MGLAttributionInfoStyleLong: + logoImage = [self mapboxLongStyleLogo]; + break; + case MGLAttributionInfoStyleMedium: #if TARGET_OS_IPHONE - largeLogoAttribution.logoImage = [UIImage imageNamed:@"mapbox" inBundle:[NSBundle mgl_frameworkBundle] compatibleWithTraitCollection:nil]; + logoImage = [UIImage imageNamed:@"mapbox_helmet" inBundle:[NSBundle mgl_frameworkBundle] compatibleWithTraitCollection:nil]; #else - largeLogoAttribution.logoImage = [[NSImage alloc] initWithContentsOfFile:[[NSBundle mgl_frameworkBundle] pathForResource:@"mapbox" ofType:@"pdf"]]; + logoImage = [[NSImage alloc] initWithContentsOfFile:[[NSBundle mgl_frameworkBundle] pathForResource:@"mapbox_helmet" ofType:@"pdf"]]; #endif - _defaultLogoHeight = largeLogoAttribution.logoImage.size.height; - - MGLSnapshotAttributionOptions *smallLogoAttribution = [self attributionOptionsForAttributionInfo:attributionInfo abbreviated:NO]; + break; + case MGLAttributionInfoStyleShort: + logoImage = nil; + break; + } + return logoImage; +} + +- (MGLImage *)mapboxLongStyleLogo +{ + MGLImage *logoImage; #if TARGET_OS_IPHONE - smallLogoAttribution.logoImage = [UIImage imageNamed:@"mapbox_helmet" inBundle:[NSBundle mgl_frameworkBundle] compatibleWithTraitCollection:nil]; + logoImage =[UIImage imageNamed:@"mapbox" inBundle:[NSBundle mgl_frameworkBundle] compatibleWithTraitCollection:nil]; #else - smallLogoAttribution.logoImage = [[NSImage alloc] initWithContentsOfFile:[[NSBundle mgl_frameworkBundle] pathForResource:@"mapbox_helmet" ofType:@"pdf"]]; + logoImage = [[NSImage alloc] initWithContentsOfFile:[[NSBundle mgl_frameworkBundle] pathForResource:@"mapbox" ofType:@"pdf"]]; #endif + return logoImage; +} + +- (CGSize)attributionSizeWithLogoStyle:(MGLAttributionInfoStyle)logoStyle sourceAttributionStyle:(MGLAttributionInfoStyle)attributionStyle +{ + MGLImage *logoImage = [self logoImageWithStyle:logoStyle]; - MGLSnapshotAttributionOptions *noLogoAttribution = [self attributionOptionsForAttributionInfo:attributionInfo abbreviated:YES]; + CGSize attributionBackgroundSize = [self attributionTextSizeWithStyle:attributionStyle]; - [options addObject:largeLogoAttribution]; - [options addObject:smallLogoAttribution]; - [options addObject:noLogoAttribution]; + CGSize attributionSize = CGSizeZero; - for (MGLSnapshotAttributionOptions *attributionOptions in options) { - // -[Mapbox Logo]-[Attribution Background]- - CGFloat origin = attributionOptions.logoImage ? MGLLogoImagePosition.x : 0; - CGFloat width = origin + attributionOptions.logoImage.size.width + 10 + attributionOptions.attributionBackgroundSize.width + 10; - if (width <= snapshotSize.width) { - return attributionOptions; - } + if (logoImage) { + attributionSize.width = MGLLogoImagePosition.x + logoImage.size.width + 10; } + attributionSize.width = attributionSize.width + 10 + attributionBackgroundSize.width + 10; + attributionSize.height = MAX(logoImage.size.height, attributionBackgroundSize.height); - return noLogoAttribution; + return attributionSize; } -- (MGLSnapshotAttributionOptions *)attributionOptionsForAttributionInfo:(NSArray *)attributionInfo abbreviated:(BOOL)isAbbreviated +- (CGSize)attributionTextSizeWithStyle:(MGLAttributionInfoStyle)attributionStyle { - NSString *openStreetMap = NSLocalizedStringWithDefaultValue(@"OSM_FULL_NAME", nil, nil, @"OpenStreetMap", @"OpenStreetMap full name attribution"); - NSString *OSM = NSLocalizedStringWithDefaultValue(@"OSM_SHORT_NAME", nil, nil, @"OSM", @"OpenStreetMap short name attribution"); - NSMutableArray *infos = [NSMutableArray array]; CGSize attributionBackgroundSize = CGSizeMake(10, 0); - for (MGLAttributionInfo *info in attributionInfo) { + for (MGLAttributionInfo *info in _attributionInfo) { if (info.isFeedbackLink) { continue; } - MGLAttributionInfo *attribution = [info copy]; - NSMutableAttributedString *title = [[NSMutableAttributedString alloc] initWithAttributedString:info.title]; - [title removeAttribute:NSUnderlineStyleAttributeName range:NSMakeRange(0, [title.string length])]; - if ([title.string rangeOfString:@"OpenStreetMap"].location != NSNotFound) { - [title.mutableString replaceOccurrencesOfString:@"OpenStreetMap" withString:isAbbreviated ? OSM : openStreetMap options:NSCaseInsensitiveSearch range:NSMakeRange(0, [title.mutableString length])]; - } - attribution.title = title; - attributionBackgroundSize.width += [attribution.title size].width + 10; - attributionBackgroundSize.height = MAX([attribution.title size].height, attributionBackgroundSize.height); - [infos addObject:attribution]; + CGSize attributionSize = [info titleWithStyle:attributionStyle].size; + attributionBackgroundSize.width += attributionSize.width + 10; + attributionBackgroundSize.height = MAX(attributionSize.height, attributionBackgroundSize.height); } - MGLSnapshotAttributionOptions *attributionOptions = [[MGLSnapshotAttributionOptions alloc] init]; - attributionOptions.attributionBackgroundSize = attributionBackgroundSize; - attributionOptions.attributionInfo = infos; - - return attributionOptions; + return attributionBackgroundSize; } - (void)cancel -- cgit v1.2.1 From 966b6a53cec1796e675e776fd421b6f1bdcf0acd Mon Sep 17 00:00:00 2001 From: Chris Loer Date: Wed, 22 Nov 2017 13:18:22 -0800 Subject: [core, ios, qt, android] Close race condition in RunLoop (issue #9620) Because a message we queue from the foreground may cause the background to complete, exit, and tear down the AsyncTask, we have to block queue processing until we've finished our call to AsyncTask::send(). Broadening the scope of a mutex is scary, but I audited the code of our four implementations of AsyncTask and I don't see any way this could cause a deadlock. --- platform/darwin/src/run_loop.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'platform/darwin/src') diff --git a/platform/darwin/src/run_loop.cpp b/platform/darwin/src/run_loop.cpp index 2ba8f8415b..d60a88cf52 100644 --- a/platform/darwin/src/run_loop.cpp +++ b/platform/darwin/src/run_loop.cpp @@ -30,8 +30,10 @@ RunLoop::~RunLoop() { } void RunLoop::push(std::shared_ptr task) { - withMutex([&] { queue.push(std::move(task)); }); - impl->async->send(); + withMutex([&] { + queue.push(std::move(task)); + impl->async->send(); + }); } void RunLoop::run() { -- cgit v1.2.1 From f89afa0b277beb733d330f2d40a0b9ca84dfba40 Mon Sep 17 00:00:00 2001 From: Jordan Kiley Date: Mon, 4 Dec 2017 16:40:14 -0800 Subject: [ios, macos] Rename the iOS and macOS SDKs (#10610) --- platform/darwin/src/MGLAccountManager.m | 2 +- platform/darwin/src/NSBundle+MGLAdditions.m | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'platform/darwin/src') diff --git a/platform/darwin/src/MGLAccountManager.m b/platform/darwin/src/MGLAccountManager.m index 0f5d033031..7e55779d04 100644 --- a/platform/darwin/src/MGLAccountManager.m +++ b/platform/darwin/src/MGLAccountManager.m @@ -55,7 +55,7 @@ } + (BOOL)mapboxMetricsEnabledSettingShownInApp { - NSLog(@"mapboxMetricsEnabledSettingShownInApp is no longer necessary; the Mapbox iOS SDK has changed to always provide a telemetry setting in-app."); + NSLog(@"mapboxMetricsEnabledSettingShownInApp is no longer necessary; the Mapbox Maps SDK for iOS has changed to always provide a telemetry setting in-app."); return YES; } diff --git a/platform/darwin/src/NSBundle+MGLAdditions.m b/platform/darwin/src/NSBundle+MGLAdditions.m index f383a50300..f55059324e 100644 --- a/platform/darwin/src/NSBundle+MGLAdditions.m +++ b/platform/darwin/src/NSBundle+MGLAdditions.m @@ -15,7 +15,7 @@ bundle = [self bundleWithPath:bundlePath]; } else { [NSException raise:@"MGLBundleNotFoundException" format: - @"The Mapbox framework bundle could not be found. If using the Mapbox iOS SDK as a static framework, make sure that Mapbox.bundle is copied into the root of the app bundle."]; + @"The Mapbox framework bundle could not be found. If using the Mapbox Maps SDK for iOS as a static framework, make sure that Mapbox.bundle is copied into the root of the app bundle."]; } } -- cgit v1.2.1 From 9c1dfe46cf39147ebe25a98eb0603828d4e8c61d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Mon, 18 Dec 2017 09:43:26 -0800 Subject: [ios, macos] Cleaned up base localization files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Localizable strings in Darwin classes need to specify “Foundation” as the table so that NSBundle consults the right .strings files. Reran make genstrings to add some missing strings to the base localizations and undo some manual edits to the base localizations that contained errors. --- platform/darwin/src/MGLAttributionInfo.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'platform/darwin/src') diff --git a/platform/darwin/src/MGLAttributionInfo.mm b/platform/darwin/src/MGLAttributionInfo.mm index 29dd5229a7..52a83fd18e 100644 --- a/platform/darwin/src/MGLAttributionInfo.mm +++ b/platform/darwin/src/MGLAttributionInfo.mm @@ -170,8 +170,8 @@ - (NSAttributedString *)titleWithStyle:(MGLAttributionInfoStyle)style { - NSString *openStreetMap = NSLocalizedStringWithDefaultValue(@"OSM_FULL_NAME", nil, nil, @"OpenStreetMap", @"OpenStreetMap full name attribution"); - NSString *OSM = NSLocalizedStringWithDefaultValue(@"OSM_SHORT_NAME", nil, nil, @"OSM", @"OpenStreetMap short name attribution"); + NSString *openStreetMap = NSLocalizedStringWithDefaultValue(@"OSM_FULL_NAME", @"Foundation", nil, @"OpenStreetMap", @"OpenStreetMap full name attribution"); + NSString *OSM = NSLocalizedStringWithDefaultValue(@"OSM_SHORT_NAME", @"Foundation", nil, @"OSM", @"OpenStreetMap short name attribution"); NSMutableAttributedString *title = [[NSMutableAttributedString alloc] initWithAttributedString:self.title]; [title removeAttribute:NSUnderlineStyleAttributeName range:NSMakeRange(0, [title.string length])]; -- cgit v1.2.1 From f644d188be719fc6a7a5e6b66b2a69c42e28a970 Mon Sep 17 00:00:00 2001 From: Fabian Guerra Date: Thu, 14 Dec 2017 16:44:00 -0600 Subject: [ios, macos] Remove duplicated variables between MGLMapSnapshotOptions and MGLMapSnapshotter --- platform/darwin/src/MGLMapSnapshotter.h | 39 +-------- platform/darwin/src/MGLMapSnapshotter.mm | 138 ++++++++----------------------- 2 files changed, 35 insertions(+), 142 deletions(-) (limited to 'platform/darwin/src') diff --git a/platform/darwin/src/MGLMapSnapshotter.h b/platform/darwin/src/MGLMapSnapshotter.h index 978e19dc20..fc68204a7d 100644 --- a/platform/darwin/src/MGLMapSnapshotter.h +++ b/platform/darwin/src/MGLMapSnapshotter.h @@ -190,44 +190,9 @@ MGL_EXPORT - (void)cancel; /** - The zoom level. - - The default zoom level is 0. If this property is non-zero and the camera - property is non-nil, the camera’s altitude is ignored in favor of this - property’s value. - */ -@property (nonatomic) double zoomLevel; - -/** - A camera representing the viewport visible in the snapshot. - - If this property is non-nil and the `coordinateBounds` property is set to a - non-empty coordinate bounds, the camera’s center coordinate and altitude are - ignored in favor of the `coordinateBounds` property. - */ -@property (nonatomic) MGLMapCamera *camera; - -/** - The coordinate rectangle that encompasses the bounds to capture. - - If this property is non-empty and the camera property is non-nil, the camera’s - center coordinate and altitude are ignored in favor of this property’s value. - */ -@property (nonatomic) MGLCoordinateBounds coordinateBounds; - -/** - URL of the map style to snapshot. - - The URL may be a full HTTP or HTTPS URL, a Mapbox URL indicating the style’s - map ID (`mapbox://styles/{user}/{style}`), or a path to a local file relative - to the application’s resource path. Specify `nil` for the default style. - */ -@property (nonatomic, nullable) NSURL *styleURL; - -/** - The size of the output image, measured in points. + The options to use when generating a map snapshot. */ -@property (nonatomic) CGSize size; +@property (nonatomic) MGLMapSnapshotOptions *options; /** Indicates whether a snapshot is currently being generated. diff --git a/platform/darwin/src/MGLMapSnapshotter.mm b/platform/darwin/src/MGLMapSnapshotter.mm index 8b54488814..15a8f6c8ca 100644 --- a/platform/darwin/src/MGLMapSnapshotter.mm +++ b/platform/darwin/src/MGLMapSnapshotter.mm @@ -81,7 +81,7 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64; @end @interface MGLMapSnapshotter() -@property (nonatomic) MGLMapSnapshotOptions *options; + @end @implementation MGLMapSnapshotter { @@ -96,40 +96,9 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64; { self = [super init]; if (self) { - _options = options; + [self setOptions:options]; _loading = false; - - mbgl::DefaultFileSource *mbglFileSource = [MGLOfflineStorage sharedOfflineStorage].mbglFileSource; - _mbglThreadPool = mbgl::sharedThreadPool(); - - std::string styleURL = std::string([options.styleURL.absoluteString UTF8String]); - - // Size; taking into account the minimum texture size for OpenGL ES - // For non retina screens the ratio is 1:1 MGLSnapshotterMinimumPixelSize - mbgl::Size size = { - static_cast(MAX(options.size.width, MGLSnapshotterMinimumPixelSize)), - static_cast(MAX(options.size.height, MGLSnapshotterMinimumPixelSize)) - }; - - float pixelRatio = MAX(options.scale, 1); - - // Camera options - mbgl::CameraOptions cameraOptions; - if (CLLocationCoordinate2DIsValid(options.camera.centerCoordinate)) { - cameraOptions.center = MGLLatLngFromLocationCoordinate2D(options.camera.centerCoordinate); - } - cameraOptions.angle = MAX(0, options.camera.heading) * mbgl::util::DEG2RAD; - cameraOptions.zoom = MAX(0, options.zoomLevel); - cameraOptions.pitch = MAX(0, options.camera.pitch); - - // Region - mbgl::optional coordinateBounds; - if (!MGLCoordinateBoundsIsEmpty(options.coordinateBounds)) { - coordinateBounds = MGLLatLngBoundsFromCoordinateBounds(options.coordinateBounds); - } - - // Create the snapshotter - _mbglMapSnapshotter = std::make_unique(*mbglFileSource, *_mbglThreadPool, styleURL, size, pixelRatio, cameraOptions, coordinateBounds); + } return self; } @@ -437,81 +406,40 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64; _mbglMapSnapshotter.reset(); } -- (NSURL *)styleURL -{ - NSString *styleURLString = @(_mbglMapSnapshotter->getStyleURL().c_str()); - return styleURLString.length ? [NSURL URLWithString:styleURLString] : nil; -} - -- (void)setStyleURL:(NSURL *)url -{ - _mbglMapSnapshotter->setStyleURL(std::string([url.absoluteString UTF8String])); -} - -- (CGSize)size -{ - mbgl::Size size = _mbglMapSnapshotter->getSize(); - return CGSizeMake(size.width, size.height); -} - -- (void)setSize:(CGSize)size -{ - _mbglMapSnapshotter->setSize({ - static_cast(MAX(size.width, MGLSnapshotterMinimumPixelSize)), - static_cast(MAX(size.height, MGLSnapshotterMinimumPixelSize)) - }); -} - -- (MGLMapCamera *)camera -{ - mbgl::CameraOptions cameraOptions = _mbglMapSnapshotter->getCameraOptions(); - CGFloat pitch = *cameraOptions.pitch; - CLLocationDirection heading = mbgl::util::wrap(*cameraOptions.angle, 0., 360.); - CLLocationDistance distance = MGLAltitudeForZoomLevel(*cameraOptions.zoom, pitch, cameraOptions.center->latitude(), [self size]); - return [MGLMapCamera cameraLookingAtCenterCoordinate:MGLLocationCoordinate2DFromLatLng(*cameraOptions.center) - fromDistance:distance - pitch:pitch - heading:heading]; -} - -- (void)setCamera:(MGLMapCamera *)camera +- (void)setOptions:(MGLMapSnapshotOptions *)options { + _options = options; + mbgl::DefaultFileSource *mbglFileSource = [MGLOfflineStorage sharedOfflineStorage].mbglFileSource; + _mbglThreadPool = mbgl::sharedThreadPool(); + + std::string styleURL = std::string([options.styleURL.absoluteString UTF8String]); + + // Size; taking into account the minimum texture size for OpenGL ES + // For non retina screens the ratio is 1:1 MGLSnapshotterMinimumPixelSize + mbgl::Size size = { + static_cast(MAX(options.size.width, MGLSnapshotterMinimumPixelSize)), + static_cast(MAX(options.size.height, MGLSnapshotterMinimumPixelSize)) + }; + + float pixelRatio = MAX(options.scale, 1); + + // Camera options mbgl::CameraOptions cameraOptions; - CLLocationCoordinate2D center; - if (CLLocationCoordinate2DIsValid(camera.centerCoordinate)) { - cameraOptions.center = MGLLatLngFromLocationCoordinate2D(camera.centerCoordinate); - center = camera.centerCoordinate; - } else { - // Center is optional, but always set. - center = MGLLocationCoordinate2DFromLatLng(*_mbglMapSnapshotter->getCameraOptions().center); + if (CLLocationCoordinate2DIsValid(options.camera.centerCoordinate)) { + cameraOptions.center = MGLLatLngFromLocationCoordinate2D(options.camera.centerCoordinate); } + cameraOptions.angle = MAX(0, options.camera.heading) * mbgl::util::DEG2RAD; + cameraOptions.zoom = MAX(0, options.zoomLevel); + cameraOptions.pitch = MAX(0, options.camera.pitch); - cameraOptions.angle = MAX(0, camera.heading) * mbgl::util::DEG2RAD; - cameraOptions.zoom = MAX(0, MGLZoomLevelForAltitude(camera.altitude, camera.pitch, center.latitude, [self size])); - cameraOptions.pitch = MAX(0, camera.pitch); -} - -- (double)zoomLevel -{ - mbgl::CameraOptions cameraOptions = _mbglMapSnapshotter->getCameraOptions(); - return MGLAltitudeForZoomLevel(*cameraOptions.zoom, *cameraOptions.pitch, cameraOptions.center->latitude(), [self size]); -} - -- (void)setZoomLevel:(double)zoomLevel -{ - mbgl::CameraOptions cameraOptions = _mbglMapSnapshotter->getCameraOptions(); - cameraOptions.zoom = zoomLevel; - _mbglMapSnapshotter->setCameraOptions(cameraOptions); -} - -- (MGLCoordinateBounds)coordinateBounds -{ - return MGLCoordinateBoundsFromLatLngBounds(_mbglMapSnapshotter->getRegion()); -} - -- (void)setCoordinateBounds:(MGLCoordinateBounds)coordinateBounds -{ - _mbglMapSnapshotter->setRegion(MGLLatLngBoundsFromCoordinateBounds(coordinateBounds)); + // Region + mbgl::optional coordinateBounds; + if (!MGLCoordinateBoundsIsEmpty(options.coordinateBounds)) { + coordinateBounds = MGLLatLngBoundsFromCoordinateBounds(options.coordinateBounds); + } + + // Create the snapshotter + _mbglMapSnapshotter = std::make_unique(*mbglFileSource, *_mbglThreadPool, styleURL, size, pixelRatio, cameraOptions, coordinateBounds); } @end -- cgit v1.2.1 From 6a9eec8ed5dee4a4527c25523d4a8b1aee758212 Mon Sep 17 00:00:00 2001 From: Asheem Mamoowala Date: Wed, 20 Dec 2017 14:51:30 -0800 Subject: Fix CustomLayer context retain count (#10765) * [core] RenderCustomLayer should de-initialize old context * [iOS, macOS] Use toll-free bridging to retain/release MGLOpenGLStyleLayer instead of a separate array. --- platform/darwin/src/MGLOpenGLStyleLayer.mm | 6 ++---- platform/darwin/src/MGLStyle.mm | 2 -- platform/darwin/src/MGLStyle_Private.h | 2 -- 3 files changed, 2 insertions(+), 8 deletions(-) (limited to 'platform/darwin/src') diff --git a/platform/darwin/src/MGLOpenGLStyleLayer.mm b/platform/darwin/src/MGLOpenGLStyleLayer.mm index 36a3c20c97..8933a77382 100644 --- a/platform/darwin/src/MGLOpenGLStyleLayer.mm +++ b/platform/darwin/src/MGLOpenGLStyleLayer.mm @@ -47,7 +47,7 @@ void MGLDrawCustomStyleLayer(void *context, const mbgl::style::CustomLayerRender when creating an OpenGL style layer. */ void MGLFinishCustomStyleLayer(void *context) { - MGLOpenGLStyleLayer *layer = (__bridge MGLOpenGLStyleLayer *)context; + MGLOpenGLStyleLayer *layer = (__bridge_transfer MGLOpenGLStyleLayer *)context; [layer willMoveFromMapView:layer.style.mapView]; } @@ -101,7 +101,7 @@ void MGLFinishCustomStyleLayer(void *context) { MGLPrepareCustomStyleLayer, MGLDrawCustomStyleLayer, MGLFinishCustomStyleLayer, - (__bridge void *)self); + (__bridge_retained void *)self); return self = [super initWithPendingLayer:std::move(layer)]; } @@ -116,9 +116,7 @@ void MGLFinishCustomStyleLayer(void *context) { [NSException raise:@"MGLLayerReuseException" format:@"%@ cannot be added to more than one MGLStyle at a time.", self]; } - _style.openGLLayers[self.identifier] = nil; _style = style; - _style.openGLLayers[self.identifier] = self; } - (void)addToStyle:(MGLStyle *)style belowLayer:(MGLStyleLayer *)otherLayer { diff --git a/platform/darwin/src/MGLStyle.mm b/platform/darwin/src/MGLStyle.mm index 244fb94ef9..987ae5f063 100644 --- a/platform/darwin/src/MGLStyle.mm +++ b/platform/darwin/src/MGLStyle.mm @@ -77,7 +77,6 @@ @property (nonatomic, readonly, weak) MGLMapView *mapView; @property (nonatomic, readonly) mbgl::style::Style *rawStyle; @property (readonly, copy, nullable) NSURL *URL; -@property (nonatomic, readwrite, strong) NS_MUTABLE_DICTIONARY_OF(NSString *, MGLOpenGLStyleLayer *) *openGLLayers; @property (nonatomic) NS_MUTABLE_DICTIONARY_OF(NSString *, NS_DICTIONARY_OF(NSObject *, MGLTextLanguage *) *) *localizedLayersByIdentifier; @end @@ -169,7 +168,6 @@ static NSURL *MGLStyleURL_trafficNight; if (self = [super init]) { _mapView = mapView; _rawStyle = rawStyle; - _openGLLayers = [NSMutableDictionary dictionary]; _localizedLayersByIdentifier = [NSMutableDictionary dictionary]; } return self; diff --git a/platform/darwin/src/MGLStyle_Private.h b/platform/darwin/src/MGLStyle_Private.h index e5bd79dc02..4cbe953a44 100644 --- a/platform/darwin/src/MGLStyle_Private.h +++ b/platform/darwin/src/MGLStyle_Private.h @@ -26,8 +26,6 @@ namespace mbgl { - (nullable NS_ARRAY_OF(MGLAttributionInfo *) *)attributionInfosWithFontSize:(CGFloat)fontSize linkColor:(nullable MGLColor *)linkColor; -@property (nonatomic, readonly, strong) NS_MUTABLE_DICTIONARY_OF(NSString *, MGLOpenGLStyleLayer *) *openGLLayers; - - (void)setStyleClasses:(NS_ARRAY_OF(NSString *) *)appliedClasses transitionDuration:(NSTimeInterval)transitionDuration; @end -- cgit v1.2.1 From 1b407e85e9ee03124e24b71a856045c254775e6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Thu, 21 Dec 2017 00:28:19 -0800 Subject: [ios, macos] Fixed MGLMapSnapshotter test crash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed a crash in MGLDocumentationExampleTests caused by an asynchronous snapshot call that the test wasn’t waiting on, compounded by the use of a style that requires an access token (as opposed to the one-liner style that uses no remote resources. Employed some Swiftisms to fulfill expectations without making the example code look weird. --- platform/darwin/src/MGLMapSnapshotter.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'platform/darwin/src') diff --git a/platform/darwin/src/MGLMapSnapshotter.h b/platform/darwin/src/MGLMapSnapshotter.h index fc68204a7d..426ab1bb00 100644 --- a/platform/darwin/src/MGLMapSnapshotter.h +++ b/platform/darwin/src/MGLMapSnapshotter.h @@ -145,11 +145,11 @@ typedef void (^MGLMapSnapshotCompletionHandler)(MGLMapSnapshot* _Nullable snapsh let snapshotter = MGLMapSnapshotter(options: options) snapshotter.start { (snapshot, error) in - if error != nil { - // error handler - } else { - // image handler + if let error = error { + fatalError(error.localizedDescription) } + + image = snapshot?.image } ``` */ -- cgit v1.2.1 From 3685cb35898cbcb6d34af2a7f49eff345083f487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Thu, 21 Dec 2017 00:29:09 -0800 Subject: [ios, macos] Updated MGLImageSource example Ran make darwin-update-examples to ensure that headers reflect the tested example code. --- platform/darwin/src/MGLImageSource.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'platform/darwin/src') diff --git a/platform/darwin/src/MGLImageSource.h b/platform/darwin/src/MGLImageSource.h index 5088f6bac0..21487d9739 100644 --- a/platform/darwin/src/MGLImageSource.h +++ b/platform/darwin/src/MGLImageSource.h @@ -38,11 +38,8 @@ MGL_EXPORT bottomLeft: CLLocationCoordinate2D(latitude: 37.936, longitude: -80.425), bottomRight: CLLocationCoordinate2D(latitude: 37.936, longitude: -71.516), topRight: CLLocationCoordinate2D(latitude: 46.437, longitude: -71.516)) - let source = MGLImageSource(identifier: "radar-source", coordinateQuad: coordinates, url: URL(string: "https://www.mapbox.com/mapbox-gl-js/assets/radar.gif")!) + let source = MGLImageSource(identifier: "radar", coordinateQuad: coordinates, url: URL(string: "https://www.mapbox.com/mapbox-gl-js/assets/radar.gif")!) mapView.style?.addSource(source) - - let layer = MGLRasterStyleLayer(identifier: "radar-layer", source: source) - style.addLayer(layer) ``` */ MGL_EXPORT -- cgit v1.2.1 From 0dc00ad877de2a68183257488763be3cb32482c2 Mon Sep 17 00:00:00 2001 From: Chris Loer Date: Mon, 27 Nov 2017 12:50:28 -0800 Subject: [ios,macos] Darwin implementation of a CoreText-based LocalGlyphRasterizer. - Changing font weight does not currently appear to be working. - Glyph metric extraction code not working; currently unused. --- platform/darwin/src/CFHandle.hpp | 31 ++++ platform/darwin/src/image.mm | 14 +- platform/darwin/src/local_glyph_rasterizer.mm | 233 ++++++++++++++++++++++++++ 3 files changed, 265 insertions(+), 13 deletions(-) create mode 100644 platform/darwin/src/CFHandle.hpp create mode 100644 platform/darwin/src/local_glyph_rasterizer.mm (limited to 'platform/darwin/src') diff --git a/platform/darwin/src/CFHandle.hpp b/platform/darwin/src/CFHandle.hpp new file mode 100644 index 0000000000..edcc9aafdf --- /dev/null +++ b/platform/darwin/src/CFHandle.hpp @@ -0,0 +1,31 @@ +#pragma once + +/* + CFHandle is a minimal wrapper designed to hold and release CoreFoundation-style handles + It is non-transferrable: wrap it in something like a unique_ptr if you need to pass it around, + or just use unique_ptr with a custom deleter. + CFHandle has no special treatment for null handles -- be careful not to let it hold a null + handle if the behavior of the Releaser isn't defined for null. + + ex: + using CFDataHandle = CFHandle; + + CFDataHandle data(CFDataCreateWithBytesNoCopy( + kCFAllocatorDefault, reinterpret_cast(source.data()), source.size(), + kCFAllocatorNull)); +*/ + +namespace { + +template +struct CFHandle { + CFHandle(HandleType handle_): handle(handle_) {} + ~CFHandle() { Releaser(handle); } + HandleType operator*() { return handle; } + operator bool() { return handle; } +private: + HandleType handle; +}; + +} // namespace + diff --git a/platform/darwin/src/image.mm b/platform/darwin/src/image.mm index 57b680fbdb..8c0d5fa484 100644 --- a/platform/darwin/src/image.mm +++ b/platform/darwin/src/image.mm @@ -2,19 +2,7 @@ #import -namespace { - -template -struct CFHandle { - CFHandle(T t_): t(t_) {} - ~CFHandle() { Releaser(t); } - T operator*() { return t; } - operator bool() { return t; } -private: - T t; -}; - -} // namespace +#import "CFHandle.hpp" using CGImageHandle = CFHandle; using CFDataHandle = CFHandle; diff --git a/platform/darwin/src/local_glyph_rasterizer.mm b/platform/darwin/src/local_glyph_rasterizer.mm new file mode 100644 index 0000000000..35c9c6733b --- /dev/null +++ b/platform/darwin/src/local_glyph_rasterizer.mm @@ -0,0 +1,233 @@ +#include +#include +#include + +#include + +#import +#import +#import + +#import "CFHandle.hpp" + +namespace mbgl { + +/* + Darwin implementation of LocalGlyphRasterizer: + Draws CJK glyphs using locally available fonts. + + Mirrors GL JS implementation in that: + - Only CJK glyphs are drawn locally (because we can guess their metrics effectively) + * Render size/metrics determined experimentally by rendering a few different fonts + - Configuration is done at map creation time by setting a "font family" + * JS uses a CSS font-family, this uses kCTFontFamilyNameAttribute which has + somewhat different behavior. + - We use heuristics to extract a font-weight based on the incoming font stack + + Further improvements are possible: + - If we could reliably extract glyph metrics, we wouldn't be limited to CJK glyphs + - We could push the font configuration down to individual style layers, which would + allow any current style to be reproducible using local fonts. + - Instead of just exposing "font family" as a configuration, we could expose a richer + CTFontDescriptor configuration option (although we'd have to override font size to + make sure it stayed at 24pt). + - Because Apple exposes glyph paths via `CTFontCreatePathForGlyph` we could potentially + render directly to SDF instead of going through TinySDF -- although it's not clear + how much of an improvement it would be. +*/ + +using CGColorSpaceHandle = CFHandle; +using CGContextHandle = CFHandle; +using CFStringRefHandle = CFHandle; +using CFAttributedStringRefHandle = CFHandle; +using CFDictionaryRefHandle = CFHandle; +using CTFontDescriptorRefHandle = CFHandle; +using CTLineRefHandle = CFHandle; + +class LocalGlyphRasterizer::Impl { +public: + Impl(const optional fontFamily_) + : fontFamily(fontFamily_) + {} + + ~Impl() { + for (auto& pair : fontHandles) { + CFRelease(pair.second); + } + } + + + CTFontRef getFont(const FontStack& fontStack) { + if (!fontFamily) { + return NULL; + } + + if (fontHandles.find(fontStack) == fontHandles.end()) { + + NSDictionary* fontTraits = @{ (NSString *)kCTFontWeightTrait: [NSNumber numberWithFloat:getFontWeight(fontStack)] }; + + NSDictionary *fontAttributes = @{ + (NSString *)kCTFontSizeAttribute: [NSNumber numberWithFloat:24.0], + (NSString *)kCTFontFamilyNameAttribute: [[NSString alloc] initWithCString:fontFamily->c_str() encoding:NSUTF8StringEncoding], + (NSString *)kCTFontTraitsAttribute: fontTraits + //(NSString *)kCTFontStyleNameAttribute: (getFontWeight(fontStack) > .3) ? @"Bold" : @"Regular" + }; + + CTFontDescriptorRefHandle descriptor(CTFontDescriptorCreateWithAttributes((CFDictionaryRef)fontAttributes)); + CTFontRef font = CTFontCreateWithFontDescriptor(*descriptor, 0.0, NULL); + if (!font) { + throw std::runtime_error("CTFontCreateWithFontDescriptor failed"); + } + + fontHandles[fontStack] = font; + } + return fontHandles[fontStack]; + } + +private: + float getFontWeight(const FontStack& fontStack) { + // Analog to logic in glyph_manager.js + // From NSFontDescriptor.h (macOS 10.11+) NSFontWeight*: + constexpr float light = -.4; + constexpr float regular = 0.0; + constexpr float medium = .23; + constexpr float bold = .4; + + float fontWeight = regular; + for (auto font : fontStack) { + // Last font in the fontstack "wins" + std::string lowercaseFont = mbgl::platform::lowercase(font); + if (lowercaseFont.find("bold") != std::string::npos) { + fontWeight = bold; + } else if (lowercaseFont.find("medium") != std::string::npos) { + fontWeight = medium; + } else if (lowercaseFont.find("light") != std::string::npos) { + fontWeight = light; + } + } + + return fontWeight; + } + + std::unordered_map fontHandles; + optional fontFamily; +}; + +LocalGlyphRasterizer::LocalGlyphRasterizer(const optional fontFamily) + : impl(std::make_unique(fontFamily)) +{} + +LocalGlyphRasterizer::~LocalGlyphRasterizer() +{} + +bool LocalGlyphRasterizer::canRasterizeGlyph(const FontStack& fontStack, GlyphID glyphID) { + return util::i18n::allowsFixedWidthGlyphGeneration(glyphID) && impl->getFont(fontStack); +} + +/* +// 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(&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); + + // Start drawing a little bit below the top of the bitmap + CGContextSetTextPosition(*context, 0.0, 5.0); + CTLineDraw(*line, *context); + + return rgbaBitmap; +} + +Glyph LocalGlyphRasterizer::rasterizeGlyph(const FontStack& fontStack, GlyphID glyphID) { + Glyph fixedMetrics; + CTFontRef font = impl->getFont(fontStack); + if (!font) { + return fixedMetrics; + } + + fixedMetrics.id = glyphID; + + Size size(35, 35); + + fixedMetrics.metrics.width = size.width; + fixedMetrics.metrics.height = size.height; + fixedMetrics.metrics.left = 3; + fixedMetrics.metrics.top = -1; + fixedMetrics.metrics.advance = 24; + + PremultipliedImage rgbaBitmap = drawGlyphBitmap(glyphID, font, size); + + // Copy alpha values from RGBA bitmap into the AlphaImage output + fixedMetrics.bitmap = AlphaImage(size); + for (uint32_t i = 0; i < size.width * size.height; i++) { + fixedMetrics.bitmap.data[i] = rgbaBitmap.data[4 * i + 3]; + } + + return fixedMetrics; +} + +} // namespace mbgl -- cgit v1.2.1 From 314c51611c6cbd0315df9cd3551363f6e6cf121b Mon Sep 17 00:00:00 2001 From: Andrew Kitchen Date: Mon, 4 Dec 2017 13:29:16 -0800 Subject: [darwin, ios, macos] Introduces an MGLRendererConfiguration class Instructions for enabling client-side rendering of CJK glyphs live in this header, and this class provides the rest of the values needed for instantiating the renderer on iOS and macOS. --- platform/darwin/src/MGLRendererConfiguration.h | 40 +++++++++++++++++++++++ platform/darwin/src/MGLRendererConfiguration.mm | 43 +++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 platform/darwin/src/MGLRendererConfiguration.h create mode 100644 platform/darwin/src/MGLRendererConfiguration.mm (limited to 'platform/darwin/src') diff --git a/platform/darwin/src/MGLRendererConfiguration.h b/platform/darwin/src/MGLRendererConfiguration.h new file mode 100644 index 0000000000..35cf828536 --- /dev/null +++ b/platform/darwin/src/MGLRendererConfiguration.h @@ -0,0 +1,40 @@ +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + The MGLRendererConfiguration object represents configuration values for the + renderer. + */ +@interface MGLRendererConfiguration : NSObject + +/** Returns an instance of the current renderer configuration. */ ++ (instancetype)currentConfiguration; + +/** The file source to use. Defaults to `mbgl::DefaultFileSource` */ +@property (nonatomic, readonly) mbgl::DefaultFileSource *fileSource; + +/** The GL context mode to use. Defaults to `mbgl::GLContextMode::Unique` */ +@property (nonatomic, readonly) mbgl::GLContextMode contextMode; + +/** The scale factor to use. + + Based on the native scale where available, otherwise the standard screen scale. */ +@property (nonatomic, readonly) const float scaleFactor; + +/** The cache dir to use. */ +@property (nonatomic, readonly) mbgl::optional cacheDir; + +/** The name of the font family to use for client-side text rendering. + + Currently only used for CJK glyphs. Changing this at run time is not currently + supported. Enable client-side rendering of CJK glyphs by setting + `MGLIdeographicFontFamilyName` in your containing app's Info.plist to a value + which will be available at runtime, i.e. "Arial Unicode MS". */ +@property (nonatomic, readonly) mbgl::optional localFontFamilyName; + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MGLRendererConfiguration.mm b/platform/darwin/src/MGLRendererConfiguration.mm new file mode 100644 index 0000000000..ae7d7dd9fe --- /dev/null +++ b/platform/darwin/src/MGLRendererConfiguration.mm @@ -0,0 +1,43 @@ +#import "MGLRendererConfiguration.h" +#import "MGLOfflineStorage_Private.h" + +#if TARGET_OS_IPHONE +#import +#else +#import +#endif + + +@implementation MGLRendererConfiguration + ++ (instancetype)currentConfiguration { + return [[self alloc] init]; +} + +- (mbgl::DefaultFileSource *)fileSource { + return [MGLOfflineStorage sharedOfflineStorage].mbglFileSource; +} + +- (mbgl::GLContextMode)contextMode { + return mbgl::GLContextMode::Unique; +} + +- (const float)scaleFactor { +#if TARGET_OS_IPHONE + return [UIScreen instancesRespondToSelector:@selector(nativeScale)] ? [[UIScreen mainScreen] nativeScale] : [[UIScreen mainScreen] scale]; +#else + return [NSScreen mainScreen].backingScaleFactor; +#endif +} + +- (mbgl::optional)cacheDir { + return mbgl::optional(); +} + +- (mbgl::optional)localFontFamilyName { + NSString *fontFamilyName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"MGLIdeographicFontFamilyName"]; + + return fontFamilyName ? std::string([fontFamilyName UTF8String]) : mbgl::optional(); +} + +@end -- cgit v1.2.1 From 38bffda85a51a44dd6ce6088afcdb7a24790f6ee Mon Sep 17 00:00:00 2001 From: Andrew Kitchen Date: Fri, 8 Dec 2017 10:49:39 -0500 Subject: [darwin, macos] Rename Info.plist key for consistency Also removes related dead code in macos MGLMapView.mm --- platform/darwin/src/MGLRendererConfiguration.h | 4 ++-- platform/darwin/src/MGLRendererConfiguration.mm | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'platform/darwin/src') diff --git a/platform/darwin/src/MGLRendererConfiguration.h b/platform/darwin/src/MGLRendererConfiguration.h index 35cf828536..676cc91719 100644 --- a/platform/darwin/src/MGLRendererConfiguration.h +++ b/platform/darwin/src/MGLRendererConfiguration.h @@ -31,8 +31,8 @@ NS_ASSUME_NONNULL_BEGIN Currently only used for CJK glyphs. Changing this at run time is not currently supported. Enable client-side rendering of CJK glyphs by setting - `MGLIdeographicFontFamilyName` in your containing app's Info.plist to a value - which will be available at runtime, i.e. "Arial Unicode MS". */ + `MGLIdeographFontFamilyName` in your containing app's Info.plist to a value + which will be available at run time, i.e. "Arial Unicode MS". */ @property (nonatomic, readonly) mbgl::optional localFontFamilyName; @end diff --git a/platform/darwin/src/MGLRendererConfiguration.mm b/platform/darwin/src/MGLRendererConfiguration.mm index ae7d7dd9fe..be60c3c787 100644 --- a/platform/darwin/src/MGLRendererConfiguration.mm +++ b/platform/darwin/src/MGLRendererConfiguration.mm @@ -35,7 +35,7 @@ } - (mbgl::optional)localFontFamilyName { - NSString *fontFamilyName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"MGLIdeographicFontFamilyName"]; + NSString *fontFamilyName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"MGLIdeographFontFamilyName"]; return fontFamilyName ? std::string([fontFamilyName UTF8String]) : mbgl::optional(); } -- cgit v1.2.1 From 45e59e4da8ac6fb8d7197ea9ae8261d9370503bd Mon Sep 17 00:00:00 2001 From: Chris Loer Date: Fri, 8 Dec 2017 10:38:51 -0800 Subject: [ios,macos] Remove dead/non-functioning font-weight and glyph metric code. Add local font family to default iosapp configuration. --- platform/darwin/src/local_glyph_rasterizer.mm | 100 ++++++-------------------- 1 file changed, 21 insertions(+), 79 deletions(-) (limited to 'platform/darwin/src') diff --git a/platform/darwin/src/local_glyph_rasterizer.mm b/platform/darwin/src/local_glyph_rasterizer.mm index 35c9c6733b..14cee5063e 100644 --- a/platform/darwin/src/local_glyph_rasterizer.mm +++ b/platform/darwin/src/local_glyph_rasterizer.mm @@ -22,9 +22,14 @@ namespace mbgl { - 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. - - We use heuristics to extract a font-weight based on the incoming font stack 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. @@ -48,69 +53,39 @@ class LocalGlyphRasterizer::Impl { public: Impl(const optional fontFamily_) : fontFamily(fontFamily_) + , fontHandle(NULL) {} ~Impl() { - for (auto& pair : fontHandles) { - CFRelease(pair.second); + if (fontHandle) { + CFRelease(fontHandle); } } - CTFontRef getFont(const FontStack& fontStack) { + CTFontRef getFont() { if (!fontFamily) { return NULL; } - if (fontHandles.find(fontStack) == fontHandles.end()) { - - NSDictionary* fontTraits = @{ (NSString *)kCTFontWeightTrait: [NSNumber numberWithFloat:getFontWeight(fontStack)] }; - - NSDictionary *fontAttributes = @{ + if (!fontHandle) { + NSDictionary *fontAttributes = @{ (NSString *)kCTFontSizeAttribute: [NSNumber numberWithFloat:24.0], - (NSString *)kCTFontFamilyNameAttribute: [[NSString alloc] initWithCString:fontFamily->c_str() encoding:NSUTF8StringEncoding], - (NSString *)kCTFontTraitsAttribute: fontTraits - //(NSString *)kCTFontStyleNameAttribute: (getFontWeight(fontStack) > .3) ? @"Bold" : @"Regular" + (NSString *)kCTFontFamilyNameAttribute: [[NSString alloc] initWithCString:fontFamily->c_str() encoding:NSUTF8StringEncoding] }; CTFontDescriptorRefHandle descriptor(CTFontDescriptorCreateWithAttributes((CFDictionaryRef)fontAttributes)); - CTFontRef font = CTFontCreateWithFontDescriptor(*descriptor, 0.0, NULL); - if (!font) { + fontHandle = CTFontCreateWithFontDescriptor(*descriptor, 0.0, NULL); + if (!fontHandle) { throw std::runtime_error("CTFontCreateWithFontDescriptor failed"); } - - fontHandles[fontStack] = font; } - return fontHandles[fontStack]; + return fontHandle; } private: - float getFontWeight(const FontStack& fontStack) { - // Analog to logic in glyph_manager.js - // From NSFontDescriptor.h (macOS 10.11+) NSFontWeight*: - constexpr float light = -.4; - constexpr float regular = 0.0; - constexpr float medium = .23; - constexpr float bold = .4; - - float fontWeight = regular; - for (auto font : fontStack) { - // Last font in the fontstack "wins" - std::string lowercaseFont = mbgl::platform::lowercase(font); - if (lowercaseFont.find("bold") != std::string::npos) { - fontWeight = bold; - } else if (lowercaseFont.find("medium") != std::string::npos) { - fontWeight = medium; - } else if (lowercaseFont.find("light") != std::string::npos) { - fontWeight = light; - } - } - - return fontWeight; - } - - std::unordered_map fontHandles; optional fontFamily; + CTFontRef fontHandle; }; LocalGlyphRasterizer::LocalGlyphRasterizer(const optional fontFamily) @@ -120,45 +95,16 @@ LocalGlyphRasterizer::LocalGlyphRasterizer(const optional fontFamil LocalGlyphRasterizer::~LocalGlyphRasterizer() {} -bool LocalGlyphRasterizer::canRasterizeGlyph(const FontStack& fontStack, GlyphID glyphID) { - return util::i18n::allowsFixedWidthGlyphGeneration(glyphID) && impl->getFont(fontStack); +bool LocalGlyphRasterizer::canRasterizeGlyph(const FontStack&, GlyphID glyphID) { + return util::i18n::allowsFixedWidthGlyphGeneration(glyphID) && impl->getFont(); } -/* -// 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(&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"); } @@ -189,12 +135,8 @@ PremultipliedImage drawGlyphBitmap(GlyphID glyphID, CTFontRef font, Size size) { &kCFTypeDictionaryValueCallBacks)); CFAttributedStringRefHandle attrString(CFAttributedStringCreate(kCFAllocatorDefault, *string, *attributes)); - CTLineRefHandle line(CTLineCreateWithAttributedString(*attrString)); - // For debugging only, doesn't get useful metrics yet - //extractGlyphMetrics(font, *line); - // Start drawing a little bit below the top of the bitmap CGContextSetTextPosition(*context, 0.0, 5.0); CTLineDraw(*line, *context); @@ -202,9 +144,9 @@ PremultipliedImage drawGlyphBitmap(GlyphID glyphID, CTFontRef font, Size size) { return rgbaBitmap; } -Glyph LocalGlyphRasterizer::rasterizeGlyph(const FontStack& fontStack, GlyphID glyphID) { +Glyph LocalGlyphRasterizer::rasterizeGlyph(const FontStack&, GlyphID glyphID) { Glyph fixedMetrics; - CTFontRef font = impl->getFont(fontStack); + CTFontRef font = impl->getFont(); if (!font) { return fixedMetrics; } -- cgit v1.2.1 From b94929ca08f706a372d1336850d1346f09677ca4 Mon Sep 17 00:00:00 2001 From: Chris Loer Date: Fri, 8 Dec 2017 10:44:05 -0800 Subject: [ios,macos] Update docs to use Apple-friendly "PingFang" font example. --- platform/darwin/src/MGLRendererConfiguration.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'platform/darwin/src') diff --git a/platform/darwin/src/MGLRendererConfiguration.h b/platform/darwin/src/MGLRendererConfiguration.h index 676cc91719..dc434a4fbb 100644 --- a/platform/darwin/src/MGLRendererConfiguration.h +++ b/platform/darwin/src/MGLRendererConfiguration.h @@ -32,7 +32,7 @@ NS_ASSUME_NONNULL_BEGIN Currently only used for CJK glyphs. Changing this at run time is not currently supported. Enable client-side rendering of CJK glyphs by setting `MGLIdeographFontFamilyName` in your containing app's Info.plist to a value - which will be available at run time, i.e. "Arial Unicode MS". */ + which will be available at run time, e.g. "PingFang". */ @property (nonatomic, readonly) mbgl::optional localFontFamilyName; @end -- cgit v1.2.1 From e8abb8c81abe85e85b07c11af7c57a77aa14111e Mon Sep 17 00:00:00 2001 From: Chris Loer Date: Fri, 8 Dec 2017 13:29:15 -0800 Subject: [ios,macos] Revert ideographic->ideograph name change. Original GL JS name was meant to represent "font family to use for locally generating ideographs", but "ideographic font family" communicates a similar intent more concisely. --- platform/darwin/src/MGLRendererConfiguration.h | 2 +- platform/darwin/src/MGLRendererConfiguration.mm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'platform/darwin/src') diff --git a/platform/darwin/src/MGLRendererConfiguration.h b/platform/darwin/src/MGLRendererConfiguration.h index dc434a4fbb..31aad0a742 100644 --- a/platform/darwin/src/MGLRendererConfiguration.h +++ b/platform/darwin/src/MGLRendererConfiguration.h @@ -31,7 +31,7 @@ NS_ASSUME_NONNULL_BEGIN Currently only used for CJK glyphs. Changing this at run time is not currently supported. Enable client-side rendering of CJK glyphs by setting - `MGLIdeographFontFamilyName` in your containing app's Info.plist to a value + `MGLIdeographicFontFamilyName` in your containing app's Info.plist to a value which will be available at run time, e.g. "PingFang". */ @property (nonatomic, readonly) mbgl::optional localFontFamilyName; diff --git a/platform/darwin/src/MGLRendererConfiguration.mm b/platform/darwin/src/MGLRendererConfiguration.mm index be60c3c787..ae7d7dd9fe 100644 --- a/platform/darwin/src/MGLRendererConfiguration.mm +++ b/platform/darwin/src/MGLRendererConfiguration.mm @@ -35,7 +35,7 @@ } - (mbgl::optional)localFontFamilyName { - NSString *fontFamilyName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"MGLIdeographFontFamilyName"]; + NSString *fontFamilyName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"MGLIdeographicFontFamilyName"]; return fontFamilyName ? std::string([fontFamilyName UTF8String]) : mbgl::optional(); } -- cgit v1.2.1 From cbb9002d4f358d66078df50224d56f392a4e99b7 Mon Sep 17 00:00:00 2001 From: Fabian Guerra Date: Tue, 2 Jan 2018 13:24:53 -0600 Subject: [ios, macos] Update mode.hpp file path --- platform/darwin/src/MGLRendererConfiguration.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'platform/darwin/src') diff --git a/platform/darwin/src/MGLRendererConfiguration.h b/platform/darwin/src/MGLRendererConfiguration.h index 31aad0a742..fbc6fc564d 100644 --- a/platform/darwin/src/MGLRendererConfiguration.h +++ b/platform/darwin/src/MGLRendererConfiguration.h @@ -1,6 +1,6 @@ #import #import -#import +#import NS_ASSUME_NONNULL_BEGIN -- cgit v1.2.1