diff --git a/platform/darwin/scripts/style-spec-overrides-v8.json b/platform/darwin/scripts/style-spec-overrides-v8.json index e478ccaf0..d3ecdae54 100644 --- a/platform/darwin/scripts/style-spec-overrides-v8.json +++ b/platform/darwin/scripts/style-spec-overrides-v8.json @@ -57,6 +57,9 @@ } } }, + "icon-text-fit": { + "doc": "The directions in which the icon stretches to fit around the text. If the icon image is a resizable image, the resizable areas may be stretched, while the cap insets are always drawn at the original scale." + }, "icon-text-fit-padding": { "doc": "Size of the additional area added to dimensions determined by `icon-text-fit`." }, diff --git a/platform/darwin/src/MGLSymbolStyleLayer.h b/platform/darwin/src/MGLSymbolStyleLayer.h index 9ca662887..712ed55b8 100644 --- a/platform/darwin/src/MGLSymbolStyleLayer.h +++ b/platform/darwin/src/MGLSymbolStyleLayer.h @@ -102,7 +102,9 @@ typedef NS_ENUM(NSUInteger, MGLIconRotationAlignment) { }; /** - Scales the icon to fit around the associated text. + The directions in which the icon stretches to fit around the text. If the icon + image is a resizable image, the resizable areas may be stretched, while the cap + insets are always drawn at the original scale. Values of this type are used in the `MGLSymbolStyleLayer.iconTextFit` property. @@ -776,7 +778,9 @@ MGL_EXPORT @property (nonatomic, null_resettable) NSExpression *iconSize __attribute__((unavailable("Use iconScale instead."))); /** - Scales the icon to fit around the associated text. + The directions in which the icon stretches to fit around the text. If the icon + image is a resizable image, the resizable areas may be stretched, while the cap + insets are always drawn at the original scale. The default value of this property is an expression that evaluates to `none`. Set this property to `nil` to reset it to the default value. diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 516a68219..88b42e2ad 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -9,6 +9,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT * Added the `in` expression function for testing whether a value is included in an array expression or whether a string is a substring of another string. Use this function in expressions in style JSON or with the `MGL_FUNCTION()` syntax in an `NSExpression` format string. ([#16162](https://github.com/mapbox/mapbox-gl-native/pull/16162)) * Added the `within` expression function for testing whether the evaluated feature lies within the given GeoJSON object. Use this function in expressions in style JSON or with the `MGL_FUNCTION()` syntax in an `NSExpression` format string. ([#16157](https://github.com/mapbox/mapbox-gl-native/pull/16157)) * Added the `MGLLineStyleLayer.lineSortKey` and `MGLFillStyleLayer.fillSortKey` properties. ([#179](https://github.com/mapbox/mapbox-gl-native-ios/pull/179), [#16194](https://github.com/mapbox/mapbox-gl-native/pull/16194), [#16220](https://github.com/mapbox/mapbox-gl-native/pull/16220)) +* The `MGLSymbolStyleLayer.iconTextFit` property now respects the cap insets of any [nine-part stretchable image](https://developer.apple.com/documentation/uikit/uiimage#1658362) passed into the `-[MGLStyle setImage:forName:]` method. You can define the stretchable area in Xcode’s asset catalog or by calling the `-[UIImage resizableImageWithCapInsets:]` method. ([#182](https://github.com/mapbox/mapbox-gl-native-ios/pull/182)) * The `-[MGLStyle localizeLabelsIntoLocale:]` and `-[NSExpression mgl_expressionLocalizedIntoLocale:]` methods can now localize text into Traditional Chinese and Vietnamese. ([#173](https://github.com/mapbox/mapbox-gl-native-ios/pull/173)) * Fixed an issue where an `MGLSymbolStyleLayer.lineDashPattern` value of `{1, 0}` resulted in hairline gaps. ([#16202](https://github.com/mapbox/mapbox-gl-native/pull/16202)) * Improved the performance of loading a style that has many style images. ([#16187](https://github.com/mapbox/mapbox-gl-native/pull/16187)) diff --git a/platform/ios/src/UIImage+MGLAdditions.mm b/platform/ios/src/UIImage+MGLAdditions.mm index e2f8ea09d..d5ce19cfc 100644 --- a/platform/ios/src/UIImage+MGLAdditions.mm +++ b/platform/ios/src/UIImage+MGLAdditions.mm @@ -5,6 +5,10 @@ const MGLExceptionName MGLResourceNotFoundException = @"MGLResourceNotFoundException"; +BOOL MGLEdgeInsetsIsZero(UIEdgeInsets edgeInsets) { + return edgeInsets.left == 0 && edgeInsets.top == 0 && edgeInsets.right == 0 && edgeInsets.bottom == 0; +} + @implementation UIImage (MGLAdditions) - (nullable instancetype)initWithMGLStyleImage:(const mbgl::style::Image &)styleImage @@ -39,10 +43,30 @@ const MGLExceptionName MGLResourceNotFoundException = @"MGLResourceNotFoundExcep } - (std::unique_ptr)mgl_styleImageWithIdentifier:(NSString *)identifier { + mbgl::style::ImageStretches stretchX = {{ + self.capInsets.left / self.scale, (self.size.width - self.capInsets.right) / self.scale, + }}; + mbgl::style::ImageStretches stretchY = {{ + self.capInsets.top / self.scale, (self.size.height - self.capInsets.bottom) / self.scale, + }}; + + mbgl::optional imageContent; + if (!MGLEdgeInsetsIsZero(self.capInsets)) { + imageContent = (mbgl::style::ImageContent){ + .left = static_cast(self.capInsets.left * self.scale), + .top = static_cast(self.capInsets.top * self.scale), + .right = static_cast((self.size.width - self.capInsets.right) * self.scale), + .bottom = static_cast((self.size.height - self.capInsets.bottom) * self.scale), + }; + } + BOOL isTemplate = self.renderingMode == UIImageRenderingModeAlwaysTemplate; return std::make_unique([identifier UTF8String], self.mgl_premultipliedImage, - float(self.scale), isTemplate); + static_cast(self.scale), + isTemplate, + stretchX, stretchY, + imageContent); } - (mbgl::PremultipliedImage)mgl_premultipliedImage { diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md index ee41770aa..74be7d930 100644 --- a/platform/macos/CHANGELOG.md +++ b/platform/macos/CHANGELOG.md @@ -10,7 +10,8 @@ * Added the `within` expression function for testing whether the evaluated feature lies within the given GeoJSON object. Use this function in expressions in style JSON or with the `MGL_FUNCTION()` syntax in an `NSExpression` format string. ([#16157](https://github.com/mapbox/mapbox-gl-native/pull/16157), [#16194](https://github.com/mapbox/mapbox-gl-native/pull/16194), [#16220](https://github.com/mapbox/mapbox-gl-native/pull/16220)) * Added the `MGLSymbolStyleLayer.textWritingModes` layout property. This property can be set to `MGLTextWritingModeHorizontal` or `MGLTextWritingModeVertical`. ([#14932](https://github.com/mapbox/mapbox-gl-native/pull/14932)) * Added the `MGLLineStyleLayer.lineSortKey` and `MGLFillStyleLayer.fillSortKey` properties. ([#179](https://github.com/mapbox/mapbox-gl-native-ios/pull/179)) -* The `MGLIdeographicFontFamilyName` Info.plist key now also accepts an array of font family names, to customize font fallback behavior. It can also be set to a Boolean value of `NO` to force the SDK to typeset CJK characters in a remote font specified by `MGLSymbolStyleLayer.textFontNames`. ([#14862](https://github.com/mapbox/mapbox-gl-native/pull/14862)) +* The `MGLIdeographicFontFamilyName` Info.plist key now also accepts an array of font family names, to customize font fallback behavior. It can also be set to a Boolean value of `NO` to force the SDK to typeset CJK characters in a remote font specified by `MGLSymbolStyleLayer.textFontNames`. ([#14862](https://github.com/mapbox/mapbox-gl-native/pull/14862)) +* The `MGLSymbolStyleLayer.iconTextFit` property now respects the cap insets of any nine-part stretchable image passed into the `-[MGLStyle setImage:forName:]` method. You can define the stretchable area in Xcode’s asset catalog or by setting the `NSImage.capInsets` property. ([#182](https://github.com/mapbox/mapbox-gl-native-ios/pull/182)) * The `-[MGLStyle localizeLabelsIntoLocale:]` and `-[NSExpression mgl_expressionLocalizedIntoLocale:]` methods can now localize text into Traditional Chinese and Vietnamese. ([#173](https://github.com/mapbox/mapbox-gl-native-ios/pull/173)) * Fixed crashes triggered when `MGLSource` and `MGLStyleLayer` objects are accessed after having been invalidated after a style change. ([#15539](https://github.com/mapbox/mapbox-gl-native/pull/15539)) * Fixed an issue where fill extrusion layers would be incorrectly rendered above other layers. ([#15065](https://github.com/mapbox/mapbox-gl-native/pull/15065)) diff --git a/platform/macos/app/Assets.xcassets/ohio.imageset/Contents.json b/platform/macos/app/Assets.xcassets/ohio.imageset/Contents.json new file mode 100644 index 000000000..aa28df8d1 --- /dev/null +++ b/platform/macos/app/Assets.xcassets/ohio.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ohio.pdf", + "resizing" : { + "mode" : "9-part", + "center" : { + "mode" : "stretch", + "width" : 15, + "height" : 18 + }, + "cap-insets" : { + "bottom" : 3, + "top" : 3, + "right" : 4, + "left" : 5 + } + } + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/platform/macos/app/Assets.xcassets/ohio.imageset/ohio.pdf b/platform/macos/app/Assets.xcassets/ohio.imageset/ohio.pdf new file mode 100644 index 000000000..a09b2e0b4 Binary files /dev/null and b/platform/macos/app/Assets.xcassets/ohio.imageset/ohio.pdf differ diff --git a/platform/macos/app/MapDocument.m b/platform/macos/app/MapDocument.m index 1ab8b690b..3431f4e25 100644 --- a/platform/macos/app/MapDocument.m +++ b/platform/macos/app/MapDocument.m @@ -1012,9 +1012,9 @@ NSArray> *MBXFlattenedShapes(NSArray> *sha MGLSource *streetsSource = [self.mapView.style sourceWithIdentifier:@"composite"]; if (streetsSource) { - NSImage *image = [NSImage imageNamed:NSImageNameIChatTheaterTemplate]; - [self.mapView.style setImage:image forName:NSImageNameIChatTheaterTemplate]; - + NSImage *image = [NSImage imageNamed:NSImageNameIChatTheaterTemplate]; + [self.mapView.style setImage:image forName:NSImageNameIChatTheaterTemplate]; + MGLSymbolStyleLayer *theaterLayer = [[MGLSymbolStyleLayer alloc] initWithIdentifier:@"theaters" source:streetsSource]; theaterLayer.sourceLayerIdentifier = @"poi_label"; theaterLayer.predicate = [NSPredicate predicateWithFormat:@"maki == 'theatre'"]; @@ -1026,6 +1026,23 @@ NSArray> *MBXFlattenedShapes(NSArray> *sha @20.0: [NSColor blackColor], }]; [self.mapView.style addLayer:theaterLayer]; + + NSImage *ohio = [NSImage imageNamed:@"ohio"]; + [self.mapView.style setImage:ohio forName:@"ohio"]; + + MGLSymbolStyleLayer *ohioLayer = [[MGLSymbolStyleLayer alloc] initWithIdentifier:@"ohio" source:streetsSource]; + ohioLayer.sourceLayerIdentifier = @"road"; + ohioLayer.predicate = [NSPredicate predicateWithFormat:@"shield = 'circle-white' and iso_3166_2 = 'US-OH'"]; + ohioLayer.symbolPlacement = [NSExpression expressionForConstantValue:@"line"]; + ohioLayer.text = [NSExpression expressionForKeyPath:@"ref"]; + ohioLayer.textFontNames = [NSExpression expressionWithFormat:@"{'DIN Offc Pro Bold', 'Arial Unicode MS Bold'}"]; + ohioLayer.textFontSize = [NSExpression expressionForConstantValue:@10]; + ohioLayer.textRotationAlignment = [NSExpression expressionForConstantValue:@"viewport"]; + ohioLayer.iconImageName = [NSExpression expressionForConstantValue:@"ohio"]; + ohioLayer.iconTextFit = [NSExpression expressionForConstantValue:@"both"]; + ohioLayer.iconTextFitPadding = [NSExpression expressionForConstantValue:[NSValue valueWithEdgeInsets:NSEdgeInsetsMake(1, 2, 1, 3)]]; + ohioLayer.iconRotationAlignment = [NSExpression expressionForConstantValue:@"viewport"]; + [self.mapView.style addLayer:ohioLayer]; } NSURL *imageURL = [NSURL URLWithString:@"https://www.mapbox.com/mapbox-gl-js/assets/radar.gif"]; diff --git a/platform/macos/src/NSImage+MGLAdditions.mm b/platform/macos/src/NSImage+MGLAdditions.mm index a1671a276..34f0325d6 100644 --- a/platform/macos/src/NSImage+MGLAdditions.mm +++ b/platform/macos/src/NSImage+MGLAdditions.mm @@ -2,6 +2,10 @@ #include +BOOL MGLEdgeInsetsIsZero(NSEdgeInsets edgeInsets) { + return edgeInsets.left == 0 && edgeInsets.top == 0 && edgeInsets.right == 0 && edgeInsets.bottom == 0; +} + @implementation NSImage (MGLAdditions) - (nullable instancetype)initWithMGLPremultipliedImage:(mbgl::PremultipliedImage&&)src { @@ -35,10 +39,31 @@ - (std::unique_ptr)mgl_styleImageWithIdentifier:(NSString *)identifier { mbgl::PremultipliedImage cPremultipliedImage = self.mgl_premultipliedImage; auto imageWidth = cPremultipliedImage.size.width; + + float scale = static_cast(imageWidth) / self.size.width; + mbgl::style::ImageStretches stretchX = {{ + self.capInsets.left * scale, (self.size.width - self.capInsets.right) * scale, + }}; + mbgl::style::ImageStretches stretchY = {{ + self.capInsets.top * scale, (self.size.height - self.capInsets.bottom) * scale, + }}; + + mbgl::optional imageContent; + if (!MGLEdgeInsetsIsZero(self.capInsets)) { + imageContent = (mbgl::style::ImageContent){ + .left = static_cast(self.capInsets.left * scale), + .top = static_cast(self.capInsets.top * scale), + .right = static_cast((self.size.width - self.capInsets.right) * scale), + .bottom = static_cast((self.size.height - self.capInsets.bottom) * scale), + }; + } + return std::make_unique([identifier UTF8String], std::move(cPremultipliedImage), - (float)(imageWidth / self.size.width), - [self isTemplate]); + scale, + [self isTemplate], + stretchX, stretchY, + imageContent); } - (mbgl::PremultipliedImage)mgl_premultipliedImage {