From ae9d793d1c0ae5d0a5a6808653b3005dd16dbe60 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 | 31 +++++ platform/darwin/src/MGLAttributionInfo.mm | 19 ++++ platform/darwin/src/MGLMapSnapshotter.mm | 180 ++++++++++++++++-------------- 3 files changed, 144 insertions(+), 86 deletions(-) diff --git a/platform/darwin/src/MGLAttributionInfo.h b/platform/darwin/src/MGLAttributionInfo.h index 031a10060f..2e2967a6c6 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,19 @@ MGL_EXPORT */ - (nullable NSURL *)feedbackURLAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel; +/** + Returns a copy of the current `title` formatted accordingly to `style`. + + Example: If `style` = `MGLAttributionInfoStyleShort` and + `MGLAttributionInfo.title` = `OpenStreetMap` then it will format + `MGLAttributionInfo.title` = `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