diff options
27 files changed, 550 insertions, 114 deletions
diff --git a/include/mbgl/style/conversion/make_property_setters.hpp b/include/mbgl/style/conversion/make_property_setters.hpp index 9252297d75..ef96f534a9 100644 --- a/include/mbgl/style/conversion/make_property_setters.hpp +++ b/include/mbgl/style/conversion/make_property_setters.hpp @@ -45,6 +45,7 @@ auto makeLayoutPropertySetters() { result["icon-padding"] = &setProperty<V, SymbolLayer, PropertyValue<float>, &SymbolLayer::setIconPadding>; result["icon-keep-upright"] = &setProperty<V, SymbolLayer, PropertyValue<bool>, &SymbolLayer::setIconKeepUpright>; result["icon-offset"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<std::array<float, 2>>, &SymbolLayer::setIconOffset>; + result["icon-anchor"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<SymbolAnchorType>, &SymbolLayer::setIconAnchor>; result["icon-pitch-alignment"] = &setProperty<V, SymbolLayer, PropertyValue<AlignmentType>, &SymbolLayer::setIconPitchAlignment>; result["text-pitch-alignment"] = &setProperty<V, SymbolLayer, PropertyValue<AlignmentType>, &SymbolLayer::setTextPitchAlignment>; result["text-rotation-alignment"] = &setProperty<V, SymbolLayer, PropertyValue<AlignmentType>, &SymbolLayer::setTextRotationAlignment>; @@ -55,7 +56,7 @@ auto makeLayoutPropertySetters() { result["text-line-height"] = &setProperty<V, SymbolLayer, PropertyValue<float>, &SymbolLayer::setTextLineHeight>; result["text-letter-spacing"] = &setProperty<V, SymbolLayer, PropertyValue<float>, &SymbolLayer::setTextLetterSpacing>; result["text-justify"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<TextJustifyType>, &SymbolLayer::setTextJustify>; - result["text-anchor"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<TextAnchorType>, &SymbolLayer::setTextAnchor>; + result["text-anchor"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<SymbolAnchorType>, &SymbolLayer::setTextAnchor>; result["text-max-angle"] = &setProperty<V, SymbolLayer, PropertyValue<float>, &SymbolLayer::setTextMaxAngle>; result["text-rotate"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextRotate>; result["text-padding"] = &setProperty<V, SymbolLayer, PropertyValue<float>, &SymbolLayer::setTextPadding>; diff --git a/include/mbgl/style/layers/symbol_layer.hpp b/include/mbgl/style/layers/symbol_layer.hpp index 6e355c0057..2d5123573f 100644 --- a/include/mbgl/style/layers/symbol_layer.hpp +++ b/include/mbgl/style/layers/symbol_layer.hpp @@ -98,6 +98,10 @@ public: DataDrivenPropertyValue<std::array<float, 2>> getIconOffset() const; void setIconOffset(DataDrivenPropertyValue<std::array<float, 2>>); + static DataDrivenPropertyValue<SymbolAnchorType> getDefaultIconAnchor(); + DataDrivenPropertyValue<SymbolAnchorType> getIconAnchor() const; + void setIconAnchor(DataDrivenPropertyValue<SymbolAnchorType>); + static PropertyValue<AlignmentType> getDefaultIconPitchAlignment(); PropertyValue<AlignmentType> getIconPitchAlignment() const; void setIconPitchAlignment(PropertyValue<AlignmentType>); @@ -138,9 +142,9 @@ public: DataDrivenPropertyValue<TextJustifyType> getTextJustify() const; void setTextJustify(DataDrivenPropertyValue<TextJustifyType>); - static DataDrivenPropertyValue<TextAnchorType> getDefaultTextAnchor(); - DataDrivenPropertyValue<TextAnchorType> getTextAnchor() const; - void setTextAnchor(DataDrivenPropertyValue<TextAnchorType>); + static DataDrivenPropertyValue<SymbolAnchorType> getDefaultTextAnchor(); + DataDrivenPropertyValue<SymbolAnchorType> getTextAnchor() const; + void setTextAnchor(DataDrivenPropertyValue<SymbolAnchorType>); static PropertyValue<float> getDefaultTextMaxAngle(); PropertyValue<float> getTextMaxAngle() const; diff --git a/include/mbgl/style/types.hpp b/include/mbgl/style/types.hpp index 44b16f16e7..ec7358de8c 100644 --- a/include/mbgl/style/types.hpp +++ b/include/mbgl/style/types.hpp @@ -68,7 +68,7 @@ enum class TextJustifyType : uint8_t { Right }; -enum class TextAnchorType : uint8_t { +enum class SymbolAnchorType : uint8_t { Center, Left, Right, diff --git a/mapbox-gl-js b/mapbox-gl-js -Subproject 3730e8377faccd7d0b83508d2be610c4743b1dc +Subproject 5f4d86d2762db83c62f7e9eeb362f3fa3a8f217 diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/Property.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/Property.java index be24b65d27..8d5858217b 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/Property.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/Property.java @@ -160,6 +160,62 @@ public final class Property { @Retention(RetentionPolicy.SOURCE) public @interface ICON_TEXT_FIT {} + // ICON_ANCHOR: Part of the icon placed closest to the anchor. + + /** + * The center of the icon is placed closest to the anchor. + */ + public static final String ICON_ANCHOR_CENTER = "center"; + /** + * The left side of the icon is placed closest to the anchor. + */ + public static final String ICON_ANCHOR_LEFT = "left"; + /** + * The right side of the icon is placed closest to the anchor. + */ + public static final String ICON_ANCHOR_RIGHT = "right"; + /** + * The top of the icon is placed closest to the anchor. + */ + public static final String ICON_ANCHOR_TOP = "top"; + /** + * The bottom of the icon is placed closest to the anchor. + */ + public static final String ICON_ANCHOR_BOTTOM = "bottom"; + /** + * The top left corner of the icon is placed closest to the anchor. + */ + public static final String ICON_ANCHOR_TOP_LEFT = "top-left"; + /** + * The top right corner of the icon is placed closest to the anchor. + */ + public static final String ICON_ANCHOR_TOP_RIGHT = "top-right"; + /** + * The bottom left corner of the icon is placed closest to the anchor. + */ + public static final String ICON_ANCHOR_BOTTOM_LEFT = "bottom-left"; + /** + * The bottom right corner of the icon is placed closest to the anchor. + */ + public static final String ICON_ANCHOR_BOTTOM_RIGHT = "bottom-right"; + + /** + * Part of the icon placed closest to the anchor. + */ + @StringDef({ + ICON_ANCHOR_CENTER, + ICON_ANCHOR_LEFT, + ICON_ANCHOR_RIGHT, + ICON_ANCHOR_TOP, + ICON_ANCHOR_BOTTOM, + ICON_ANCHOR_TOP_LEFT, + ICON_ANCHOR_TOP_RIGHT, + ICON_ANCHOR_BOTTOM_LEFT, + ICON_ANCHOR_BOTTOM_RIGHT, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ICON_ANCHOR {} + // ICON_PITCH_ALIGNMENT: Orientation of icon when map is pitched. /** diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java index ef89c6809e..3e90605a92 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java @@ -1882,6 +1882,29 @@ public class PropertyFactory { } /** + * Part of the icon placed closest to the anchor. + * + * @param value a String value + * @return property wrapper around String + */ + public static PropertyValue<String> iconAnchor(@Property.ICON_ANCHOR String value) { + return new LayoutPropertyValue<>("icon-anchor", value); + } + + + + /** + * Part of the icon placed closest to the anchor. + * + * @param <T> the function input type + * @param function a wrapper function for String + * @return property wrapper around a String function + */ + public static <T> PropertyValue<Function<T, String>> iconAnchor(Function<T, String> function) { + return new LayoutPropertyValue<>("icon-anchor", function); + } + + /** * Orientation of icon when map is pitched. * * @param value a String value diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/SymbolLayer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/SymbolLayer.java index fe81dcb638..d0fb82dce5 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/SymbolLayer.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/SymbolLayer.java @@ -252,6 +252,16 @@ public class SymbolLayer extends Layer { } /** + * Get the IconAnchor property + * + * @return property wrapper value around String + */ + @SuppressWarnings("unchecked") + public PropertyValue<String> getIconAnchor() { + return (PropertyValue<String>) new PropertyValue("icon-anchor", nativeGetIconAnchor()); + } + + /** * Get the IconPitchAlignment property * * @return property wrapper value around String @@ -901,6 +911,8 @@ public class SymbolLayer extends Layer { private native Object nativeGetIconOffset(); + private native Object nativeGetIconAnchor(); + private native Object nativeGetIconPitchAlignment(); private native Object nativeGetTextPitchAlignment(); diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/SymbolLayerTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/SymbolLayerTest.java index e2694af348..1c9faeb9ea 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/SymbolLayerTest.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/SymbolLayerTest.java @@ -1214,6 +1214,111 @@ public class SymbolLayerTest extends BaseActivityTest { } @Test + public void testIconAnchorAsConstant() { + validateTestSetup(); + setupLayer(); + Timber.i("icon-anchor"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set and Get + layer.setProperties(iconAnchor(ICON_ANCHOR_CENTER)); + assertEquals((String) layer.getIconAnchor().getValue(), (String) ICON_ANCHOR_CENTER); + } + }); + } + + @Test + public void testIconAnchorAsCameraFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("icon-anchor"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + iconAnchor( + zoom( + interval( + stop(2, iconAnchor(ICON_ANCHOR_CENTER)) + ) + ) + ) + ); + + // Verify + assertNotNull(layer.getIconAnchor()); + assertNotNull(layer.getIconAnchor().getFunction()); + assertEquals(CameraFunction.class, layer.getIconAnchor().getFunction().getClass()); + assertEquals(IntervalStops.class, layer.getIconAnchor().getFunction().getStops().getClass()); + assertEquals(1, ((IntervalStops) layer.getIconAnchor().getFunction().getStops()).size()); + } + }); + } + + @Test + public void testIconAnchorAsIdentitySourceFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("icon-anchor"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + iconAnchor(property("FeaturePropertyA", Stops.<String>identity())) + ); + + // Verify + assertNotNull(layer.getIconAnchor()); + assertNotNull(layer.getIconAnchor().getFunction()); + assertEquals(SourceFunction.class, layer.getIconAnchor().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((SourceFunction) layer.getIconAnchor().getFunction()).getProperty()); + assertEquals(IdentityStops.class, layer.getIconAnchor().getFunction().getStops().getClass()); + } + }); + } + + @Test + public void testIconAnchorAsIntervalSourceFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("icon-anchor"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + iconAnchor( + property( + "FeaturePropertyA", + interval( + stop(1, iconAnchor(ICON_ANCHOR_CENTER)) + ) + ) + ) + ); + + // Verify + assertNotNull(layer.getIconAnchor()); + assertNotNull(layer.getIconAnchor().getFunction()); + assertEquals(SourceFunction.class, layer.getIconAnchor().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((SourceFunction) layer.getIconAnchor().getFunction()).getProperty()); + assertEquals(IntervalStops.class, layer.getIconAnchor().getFunction().getStops().getClass()); + } + }); + } + + @Test public void testIconPitchAlignmentAsConstant() { validateTestSetup(); setupLayer(); diff --git a/platform/android/scripts/generate-style-code.js b/platform/android/scripts/generate-style-code.js index a8b2c98c86..abc0796bc1 100644 --- a/platform/android/scripts/generate-style-code.js +++ b/platform/android/scripts/generate-style-code.js @@ -111,6 +111,9 @@ global.propertyNativeType = function (property) { if (/-(rotation|pitch|illumination)-alignment$/.test(property.name)) { return 'AlignmentType'; } + if (/^(text|icon)-anchor$/.test(property.name)) { + return 'SymbolAnchorType'; + } switch (property.type) { case 'boolean': return 'bool'; @@ -267,6 +270,9 @@ global.evaluatedType = function (property) { if (/-(rotation|pitch|illumination)-alignment$/.test(property.name)) { return 'AlignmentType'; } + if (/^(text|icon)-anchor$/.test(property.name)) { + return 'SymbolAnchorType'; + } if (/position/.test(property.name)) { return 'Position'; } diff --git a/platform/android/src/style/conversion/types.hpp b/platform/android/src/style/conversion/types.hpp index a00f668c24..375d1a33aa 100644 --- a/platform/android/src/style/conversion/types.hpp +++ b/platform/android/src/style/conversion/types.hpp @@ -58,15 +58,15 @@ struct Converter<jni::jobject*, mbgl::style::IconTextFitType> { }; template <> -struct Converter<jni::jobject*, mbgl::style::TextJustifyType> { - Result<jni::jobject*> operator()(jni::JNIEnv& env, const mbgl::style::TextJustifyType& value) const { +struct Converter<jni::jobject*, mbgl::style::SymbolAnchorType> { + Result<jni::jobject*> operator()(jni::JNIEnv& env, const mbgl::style::SymbolAnchorType& value) const { return convert<jni::jobject*, std::string>(env, toString(value)); } }; template <> -struct Converter<jni::jobject*, mbgl::style::TextAnchorType> { - Result<jni::jobject*> operator()(jni::JNIEnv& env, const mbgl::style::TextAnchorType& value) const { +struct Converter<jni::jobject*, mbgl::style::TextJustifyType> { + Result<jni::jobject*> operator()(jni::JNIEnv& env, const mbgl::style::TextJustifyType& value) const { return convert<jni::jobject*, std::string>(env, toString(value)); } }; diff --git a/platform/android/src/style/conversion/types_string_values.hpp b/platform/android/src/style/conversion/types_string_values.hpp index e96de3b01e..a19ca33a2f 100644 --- a/platform/android/src/style/conversion/types_string_values.hpp +++ b/platform/android/src/style/conversion/types_string_values.hpp @@ -109,51 +109,34 @@ namespace conversion { } } - // text-justify - inline std::string toString(mbgl::style::TextJustifyType value) { - switch (value) { - case mbgl::style::TextJustifyType::Left: - return "left"; - break; - case mbgl::style::TextJustifyType::Center: - return "center"; - break; - case mbgl::style::TextJustifyType::Right: - return "right"; - break; - default: - throw std::runtime_error("Not implemented"); - } - } - - // text-anchor - inline std::string toString(mbgl::style::TextAnchorType value) { + // icon-anchor + inline std::string toString(mbgl::style::SymbolAnchorType value) { switch (value) { - case mbgl::style::TextAnchorType::Center: + case mbgl::style::SymbolAnchorType::Center: return "center"; break; - case mbgl::style::TextAnchorType::Left: + case mbgl::style::SymbolAnchorType::Left: return "left"; break; - case mbgl::style::TextAnchorType::Right: + case mbgl::style::SymbolAnchorType::Right: return "right"; break; - case mbgl::style::TextAnchorType::Top: + case mbgl::style::SymbolAnchorType::Top: return "top"; break; - case mbgl::style::TextAnchorType::Bottom: + case mbgl::style::SymbolAnchorType::Bottom: return "bottom"; break; - case mbgl::style::TextAnchorType::TopLeft: + case mbgl::style::SymbolAnchorType::TopLeft: return "top-left"; break; - case mbgl::style::TextAnchorType::TopRight: + case mbgl::style::SymbolAnchorType::TopRight: return "top-right"; break; - case mbgl::style::TextAnchorType::BottomLeft: + case mbgl::style::SymbolAnchorType::BottomLeft: return "bottom-left"; break; - case mbgl::style::TextAnchorType::BottomRight: + case mbgl::style::SymbolAnchorType::BottomRight: return "bottom-right"; break; default: @@ -161,6 +144,23 @@ namespace conversion { } } + // text-justify + inline std::string toString(mbgl::style::TextJustifyType value) { + switch (value) { + case mbgl::style::TextJustifyType::Left: + return "left"; + break; + case mbgl::style::TextJustifyType::Center: + return "center"; + break; + case mbgl::style::TextJustifyType::Right: + return "right"; + break; + default: + throw std::runtime_error("Not implemented"); + } + } + // text-transform inline std::string toString(mbgl::style::TextTransformType value) { switch (value) { diff --git a/platform/android/src/style/layers/symbol_layer.cpp b/platform/android/src/style/layers/symbol_layer.cpp index b6cf51ec7e..d44744a6cf 100644 --- a/platform/android/src/style/layers/symbol_layer.cpp +++ b/platform/android/src/style/layers/symbol_layer.cpp @@ -125,6 +125,12 @@ namespace android { return jni::Object<jni::ObjectTag>(*converted); } + jni::Object<jni::ObjectTag> SymbolLayer::getIconAnchor(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + Result<jni::jobject*> converted = convert<jni::jobject*>(env, layer.as<mbgl::style::SymbolLayer>()->SymbolLayer::getIconAnchor()); + return jni::Object<jni::ObjectTag>(*converted); + } + jni::Object<jni::ObjectTag> SymbolLayer::getIconPitchAlignment(jni::JNIEnv& env) { using namespace mbgl::android::conversion; Result<jni::jobject*> converted = convert<jni::jobject*>(env, layer.as<mbgl::style::SymbolLayer>()->SymbolLayer::getIconPitchAlignment()); @@ -520,6 +526,7 @@ namespace android { METHOD(&SymbolLayer::getIconPadding, "nativeGetIconPadding"), METHOD(&SymbolLayer::getIconKeepUpright, "nativeGetIconKeepUpright"), METHOD(&SymbolLayer::getIconOffset, "nativeGetIconOffset"), + METHOD(&SymbolLayer::getIconAnchor, "nativeGetIconAnchor"), METHOD(&SymbolLayer::getIconPitchAlignment, "nativeGetIconPitchAlignment"), METHOD(&SymbolLayer::getTextPitchAlignment, "nativeGetTextPitchAlignment"), METHOD(&SymbolLayer::getTextRotationAlignment, "nativeGetTextRotationAlignment"), diff --git a/platform/android/src/style/layers/symbol_layer.hpp b/platform/android/src/style/layers/symbol_layer.hpp index 6d3da13ae9..417e5e143f 100644 --- a/platform/android/src/style/layers/symbol_layer.hpp +++ b/platform/android/src/style/layers/symbol_layer.hpp @@ -59,6 +59,8 @@ public: jni::Object<jni::ObjectTag> getIconOffset(jni::JNIEnv&); + jni::Object<jni::ObjectTag> getIconAnchor(jni::JNIEnv&); + jni::Object<jni::ObjectTag> getIconPitchAlignment(jni::JNIEnv&); jni::Object<jni::ObjectTag> getTextPitchAlignment(jni::JNIEnv&); diff --git a/platform/darwin/scripts/generate-style-code.js b/platform/darwin/scripts/generate-style-code.js index 5a4936c0ee..4ee86e65da 100644 --- a/platform/darwin/scripts/generate-style-code.js +++ b/platform/darwin/scripts/generate-style-code.js @@ -148,6 +148,9 @@ global.mbglTestValue = function (property, layerType) { if (/-(rotation|pitch)-alignment$/.test(originalPropertyName(property))) { type = 'Alignment'; } + if (/^(text|icon)-anchor$/.test(originalPropertyName(property))) { + type = 'SymbolAnchor' + } let value = camelize(_.last(_.keys(property.values))); if (property['light-property']) { return `mbgl::style::Light${type}Type::${value}`; @@ -500,6 +503,9 @@ global.mbglType = function(property) { if (/-(rotation|pitch)-alignment$/.test(originalPropertyName(property))) { type = 'Alignment'; } + if (/^(text|icon)-anchor$/.test(originalPropertyName(property))) { + type = 'SymbolAnchor' + } return `mbgl::style::${type}Type`; } case 'color': @@ -648,10 +654,10 @@ while ((match = exampleRegex.exec(examplesSrc)) !== null) { let testMethodName = match[1], indentation = match[2], exampleCode = match[3]; - + // Trim leading whitespace from the example code. exampleCode = exampleCode.replace(new RegExp('^' + indentation, 'gm'), ''); - + examples[testMethodName] = exampleCode; } @@ -663,13 +669,13 @@ global.guideExample = function (guide, exampleId, os) { console.error(`MGLDocumentationExampleTests.test${testMethodName}() not found.`); process.exit(1); } - + // Resolve conditional compilation blocks. example = example.replace(/^(\s*)#if\s+os\((iOS|macOS)\)\n([^]*?)(?:^\1#else\n([^]*?))?^\1#endif\b\n?/gm, function (m, indentation, ifOs, ifCase, elseCase) { return (os === ifOs ? ifCase : elseCase).replace(new RegExp('^ ', 'gm'), ''); }).replace(/\n$/, ''); - + return '```swift\n' + example + '\n```'; }; diff --git a/platform/darwin/src/MGLSymbolStyleLayer.h b/platform/darwin/src/MGLSymbolStyleLayer.h index d8dded7dbd..bc39df5b16 100644 --- a/platform/darwin/src/MGLSymbolStyleLayer.h +++ b/platform/darwin/src/MGLSymbolStyleLayer.h @@ -8,6 +8,51 @@ NS_ASSUME_NONNULL_BEGIN /** + Part of the icon placed closest to the anchor. + + Values of this type are used in the `MGLSymbolStyleLayer.iconAnchor` + property. + */ +typedef NS_ENUM(NSUInteger, MGLIconAnchor) { + /** + The center of the icon is placed closest to the anchor. + */ + MGLIconAnchorCenter, + /** + The left side of the icon is placed closest to the anchor. + */ + MGLIconAnchorLeft, + /** + The right side of the icon is placed closest to the anchor. + */ + MGLIconAnchorRight, + /** + The top of the icon is placed closest to the anchor. + */ + MGLIconAnchorTop, + /** + The bottom of the icon is placed closest to the anchor. + */ + MGLIconAnchorBottom, + /** + The top left corner of the icon is placed closest to the anchor. + */ + MGLIconAnchorTopLeft, + /** + The top right corner of the icon is placed closest to the anchor. + */ + MGLIconAnchorTopRight, + /** + The bottom left corner of the icon is placed closest to the anchor. + */ + MGLIconAnchorBottomLeft, + /** + The bottom right corner of the icon is placed closest to the anchor. + */ + MGLIconAnchorBottomRight, +}; + +/** Orientation of icon when map is pitched. Values of this type are used in the `MGLSymbolStyleLayer.iconPitchAlignment` @@ -345,6 +390,34 @@ MGL_EXPORT @property (nonatomic, null_resettable) MGLStyleValue<NSNumber *> *iconAllowOverlap __attribute__((unavailable("Use iconAllowsOverlap instead."))); /** + Part of the icon placed closest to the anchor. + + The default value of this property is an `MGLStyleValue` object containing an + `NSValue` object containing `MGLIconAnchorCenter`. Set this property to `nil` + to reset it to the default value. + + This property is only applied to the style if `iconImageName` is non-`nil`. + Otherwise, it is ignored. + + You can set this property to an instance of: + + * `MGLConstantStyleValue` + * `MGLCameraStyleFunction` with an interpolation mode of: + * `MGLInterpolationModeExponential` + * `MGLInterpolationModeInterval` + * `MGLSourceStyleFunction` with an interpolation mode of: + * `MGLInterpolationModeExponential` + * `MGLInterpolationModeInterval` + * `MGLInterpolationModeCategorical` + * `MGLInterpolationModeIdentity` + * `MGLCompositeStyleFunction` with an interpolation mode of: + * `MGLInterpolationModeExponential` + * `MGLInterpolationModeInterval` + * `MGLInterpolationModeCategorical` + */ +@property (nonatomic, null_resettable) MGLStyleValue<NSValue *> *iconAnchor; + +/** If true, other symbols can be visible even if they collide with the icon. The default value of this property is an `MGLStyleValue` object containing an @@ -1984,6 +2057,19 @@ MGL_EXPORT #pragma mark Working with Symbol Style Layer Attribute Values /** + Creates a new value object containing the given `MGLIconAnchor` enumeration. + + @param iconAnchor The value for the new object. + @return A new value object that contains the enumeration value. + */ ++ (instancetype)valueWithMGLIconAnchor:(MGLIconAnchor)iconAnchor; + +/** + The `MGLIconAnchor` enumeration representation of the value. + */ +@property (readonly) MGLIconAnchor MGLIconAnchorValue; + +/** Creates a new value object containing the given `MGLIconPitchAlignment` enumeration. @param iconPitchAlignment The value for the new object. diff --git a/platform/darwin/src/MGLSymbolStyleLayer.mm b/platform/darwin/src/MGLSymbolStyleLayer.mm index 2541e6b0a4..7e8b0b247b 100644 --- a/platform/darwin/src/MGLSymbolStyleLayer.mm +++ b/platform/darwin/src/MGLSymbolStyleLayer.mm @@ -13,6 +13,18 @@ namespace mbgl { + MBGL_DEFINE_ENUM(MGLIconAnchor, { + { MGLIconAnchorCenter, "center" }, + { MGLIconAnchorLeft, "left" }, + { MGLIconAnchorRight, "right" }, + { MGLIconAnchorTop, "top" }, + { MGLIconAnchorBottom, "bottom" }, + { MGLIconAnchorTopLeft, "top-left" }, + { MGLIconAnchorTopRight, "top-right" }, + { MGLIconAnchorBottomLeft, "bottom-left" }, + { MGLIconAnchorBottomRight, "bottom-right" }, + }); + MBGL_DEFINE_ENUM(MGLIconPitchAlignment, { { MGLIconPitchAlignmentMap, "map" }, { MGLIconPitchAlignmentViewport, "viewport" }, @@ -166,6 +178,23 @@ namespace mbgl { return self.iconAllowsOverlap; } +- (void)setIconAnchor:(MGLStyleValue<NSValue *> *)iconAnchor { + MGLAssertStyleLayerIsValid(); + + auto mbglValue = MGLStyleValueTransformer<mbgl::style::SymbolAnchorType, NSValue *, mbgl::style::SymbolAnchorType, MGLIconAnchor>().toDataDrivenPropertyValue(iconAnchor); + self.rawLayer->setIconAnchor(mbglValue); +} + +- (MGLStyleValue<NSValue *> *)iconAnchor { + MGLAssertStyleLayerIsValid(); + + auto propertyValue = self.rawLayer->getIconAnchor(); + if (propertyValue.isUndefined()) { + return MGLStyleValueTransformer<mbgl::style::SymbolAnchorType, NSValue *, mbgl::style::SymbolAnchorType, MGLIconAnchor>().toDataDrivenStyleValue(self.rawLayer->getDefaultIconAnchor()); + } + return MGLStyleValueTransformer<mbgl::style::SymbolAnchorType, NSValue *, mbgl::style::SymbolAnchorType, MGLIconAnchor>().toDataDrivenStyleValue(propertyValue); +} + - (void)setIconIgnoresPlacement:(MGLStyleValue<NSNumber *> *)iconIgnoresPlacement { MGLAssertStyleLayerIsValid(); @@ -586,7 +615,7 @@ namespace mbgl { - (void)setTextAnchor:(MGLStyleValue<NSValue *> *)textAnchor { MGLAssertStyleLayerIsValid(); - auto mbglValue = MGLStyleValueTransformer<mbgl::style::TextAnchorType, NSValue *, mbgl::style::TextAnchorType, MGLTextAnchor>().toDataDrivenPropertyValue(textAnchor); + auto mbglValue = MGLStyleValueTransformer<mbgl::style::SymbolAnchorType, NSValue *, mbgl::style::SymbolAnchorType, MGLTextAnchor>().toDataDrivenPropertyValue(textAnchor); self.rawLayer->setTextAnchor(mbglValue); } @@ -595,9 +624,9 @@ namespace mbgl { auto propertyValue = self.rawLayer->getTextAnchor(); if (propertyValue.isUndefined()) { - return MGLStyleValueTransformer<mbgl::style::TextAnchorType, NSValue *, mbgl::style::TextAnchorType, MGLTextAnchor>().toDataDrivenStyleValue(self.rawLayer->getDefaultTextAnchor()); + return MGLStyleValueTransformer<mbgl::style::SymbolAnchorType, NSValue *, mbgl::style::SymbolAnchorType, MGLTextAnchor>().toDataDrivenStyleValue(self.rawLayer->getDefaultTextAnchor()); } - return MGLStyleValueTransformer<mbgl::style::TextAnchorType, NSValue *, mbgl::style::TextAnchorType, MGLTextAnchor>().toDataDrivenStyleValue(propertyValue); + return MGLStyleValueTransformer<mbgl::style::SymbolAnchorType, NSValue *, mbgl::style::SymbolAnchorType, MGLTextAnchor>().toDataDrivenStyleValue(propertyValue); } - (void)setTextFontNames:(MGLStyleValue<NSArray<NSString *> *> *)textFontNames { @@ -1344,6 +1373,16 @@ namespace mbgl { @implementation NSValue (MGLSymbolStyleLayerAdditions) ++ (NSValue *)valueWithMGLIconAnchor:(MGLIconAnchor)iconAnchor { + return [NSValue value:&iconAnchor withObjCType:@encode(MGLIconAnchor)]; +} + +- (MGLIconAnchor)MGLIconAnchorValue { + MGLIconAnchor iconAnchor; + [self getValue:&iconAnchor]; + return iconAnchor; +} + + (NSValue *)valueWithMGLIconPitchAlignment:(MGLIconPitchAlignment)iconPitchAlignment { return [NSValue value:&iconPitchAlignment withObjCType:@encode(MGLIconPitchAlignment)]; } diff --git a/platform/darwin/test/MGLSymbolStyleLayerTests.mm b/platform/darwin/test/MGLSymbolStyleLayerTests.mm index 6b0b20354b..abbaef9159 100644 --- a/platform/darwin/test/MGLSymbolStyleLayerTests.mm +++ b/platform/darwin/test/MGLSymbolStyleLayerTests.mm @@ -87,6 +87,40 @@ XCTAssertThrowsSpecificNamed(layer.iconAllowsOverlap = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); } + // icon-anchor + { + XCTAssertTrue(rawLayer->getIconAnchor().isUndefined(), + @"icon-anchor should be unset initially."); + MGLStyleValue<NSValue *> *defaultStyleValue = layer.iconAnchor; + + MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLIconAnchor:MGLIconAnchorBottomRight]]; + layer.iconAnchor = constantStyleValue; + mbgl::style::DataDrivenPropertyValue<mbgl::style::SymbolAnchorType> propertyValue = { mbgl::style::SymbolAnchorType::BottomRight }; + XCTAssertEqual(rawLayer->getIconAnchor(), propertyValue, + @"Setting iconAnchor to a constant value should update icon-anchor."); + XCTAssertEqualObjects(layer.iconAnchor, constantStyleValue, + @"iconAnchor should round-trip constant values."); + + MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; + layer.iconAnchor = functionStyleValue; + + mbgl::style::IntervalStops<mbgl::style::SymbolAnchorType> intervalStops = { {{18, mbgl::style::SymbolAnchorType::BottomRight}} }; + propertyValue = mbgl::style::CameraFunction<mbgl::style::SymbolAnchorType> { intervalStops }; + + XCTAssertEqual(rawLayer->getIconAnchor(), propertyValue, + @"Setting iconAnchor to a camera function should update icon-anchor."); + XCTAssertEqualObjects(layer.iconAnchor, functionStyleValue, + @"iconAnchor should round-trip camera functions."); + + + + layer.iconAnchor = nil; + XCTAssertTrue(rawLayer->getIconAnchor().isUndefined(), + @"Unsetting iconAnchor should return icon-anchor to the default value."); + XCTAssertEqualObjects(layer.iconAnchor, defaultStyleValue, + @"iconAnchor should return the default value after being unset."); + } + // icon-ignore-placement { XCTAssertTrue(rawLayer->getIconIgnorePlacement().isUndefined(), @@ -931,7 +965,7 @@ MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLTextAnchor:MGLTextAnchorBottomRight]]; layer.textAnchor = constantStyleValue; - mbgl::style::DataDrivenPropertyValue<mbgl::style::TextAnchorType> propertyValue = { mbgl::style::TextAnchorType::BottomRight }; + mbgl::style::DataDrivenPropertyValue<mbgl::style::SymbolAnchorType> propertyValue = { mbgl::style::SymbolAnchorType::BottomRight }; XCTAssertEqual(rawLayer->getTextAnchor(), propertyValue, @"Setting textAnchor to a constant value should update text-anchor."); XCTAssertEqualObjects(layer.textAnchor, constantStyleValue, @@ -940,8 +974,8 @@ MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil]; layer.textAnchor = functionStyleValue; - mbgl::style::IntervalStops<mbgl::style::TextAnchorType> intervalStops = { {{18, mbgl::style::TextAnchorType::BottomRight}} }; - propertyValue = mbgl::style::CameraFunction<mbgl::style::TextAnchorType> { intervalStops }; + mbgl::style::IntervalStops<mbgl::style::SymbolAnchorType> intervalStops = { {{18, mbgl::style::SymbolAnchorType::BottomRight}} }; + propertyValue = mbgl::style::CameraFunction<mbgl::style::SymbolAnchorType> { intervalStops }; XCTAssertEqual(rawLayer->getTextAnchor(), propertyValue, @"Setting textAnchor to a camera function should update text-anchor."); @@ -2345,6 +2379,7 @@ - (void)testPropertyNames { [self testPropertyName:@"icon-allows-overlap" isBoolean:YES]; + [self testPropertyName:@"icon-anchor" isBoolean:NO]; [self testPropertyName:@"icon-ignores-placement" isBoolean:YES]; [self testPropertyName:@"icon-image-name" isBoolean:NO]; [self testPropertyName:@"icon-offset" isBoolean:NO]; @@ -2396,6 +2431,15 @@ } - (void)testValueAdditions { + XCTAssertEqual([NSValue valueWithMGLIconAnchor:MGLIconAnchorCenter].MGLIconAnchorValue, MGLIconAnchorCenter); + XCTAssertEqual([NSValue valueWithMGLIconAnchor:MGLIconAnchorLeft].MGLIconAnchorValue, MGLIconAnchorLeft); + XCTAssertEqual([NSValue valueWithMGLIconAnchor:MGLIconAnchorRight].MGLIconAnchorValue, MGLIconAnchorRight); + XCTAssertEqual([NSValue valueWithMGLIconAnchor:MGLIconAnchorTop].MGLIconAnchorValue, MGLIconAnchorTop); + XCTAssertEqual([NSValue valueWithMGLIconAnchor:MGLIconAnchorBottom].MGLIconAnchorValue, MGLIconAnchorBottom); + XCTAssertEqual([NSValue valueWithMGLIconAnchor:MGLIconAnchorTopLeft].MGLIconAnchorValue, MGLIconAnchorTopLeft); + XCTAssertEqual([NSValue valueWithMGLIconAnchor:MGLIconAnchorTopRight].MGLIconAnchorValue, MGLIconAnchorTopRight); + XCTAssertEqual([NSValue valueWithMGLIconAnchor:MGLIconAnchorBottomLeft].MGLIconAnchorValue, MGLIconAnchorBottomLeft); + XCTAssertEqual([NSValue valueWithMGLIconAnchor:MGLIconAnchorBottomRight].MGLIconAnchorValue, MGLIconAnchorBottomRight); XCTAssertEqual([NSValue valueWithMGLIconPitchAlignment:MGLIconPitchAlignmentMap].MGLIconPitchAlignmentValue, MGLIconPitchAlignmentMap); XCTAssertEqual([NSValue valueWithMGLIconPitchAlignment:MGLIconPitchAlignmentViewport].MGLIconPitchAlignmentValue, MGLIconPitchAlignmentViewport); XCTAssertEqual([NSValue valueWithMGLIconPitchAlignment:MGLIconPitchAlignmentAuto].MGLIconPitchAlignmentValue, MGLIconPitchAlignmentAuto); diff --git a/scripts/generate-style-code.js b/scripts/generate-style-code.js index b1d0ed28ec..fe9a1a906b 100644 --- a/scripts/generate-style-code.js +++ b/scripts/generate-style-code.js @@ -29,6 +29,9 @@ global.evaluatedType = function (property) { if (/-(rotation|pitch|illumination)-alignment$/.test(property.name)) { return 'AlignmentType'; } + if (/^(text|icon)-anchor$/.test(property.name)) { + return 'SymbolAnchorType'; + } if (/position/.test(property.name)) { return 'Position'; } diff --git a/src/mbgl/layout/symbol_layout.cpp b/src/mbgl/layout/symbol_layout.cpp index 229b8f2ee2..adb8ce5927 100644 --- a/src/mbgl/layout/symbol_layout.cpp +++ b/src/mbgl/layout/symbol_layout.cpp @@ -233,6 +233,7 @@ void SymbolLayout::prepare(const GlyphMap& glyphMap, const GlyphPositions& glyph shapedIcon = PositionedIcon::shapeIcon( imagePositions.at(*feature.icon), layout.evaluate<IconOffset>(zoom, feature), + layout.evaluate<IconAnchor>(zoom, feature), layout.evaluate<IconRotate>(zoom, feature) * util::DEG2RAD); if (image->second->sdf) { sdfIcons = true; diff --git a/src/mbgl/style/function/categorical_stops.cpp b/src/mbgl/style/function/categorical_stops.cpp index 1a30a1f1c7..dd179f5376 100644 --- a/src/mbgl/style/function/categorical_stops.cpp +++ b/src/mbgl/style/function/categorical_stops.cpp @@ -34,7 +34,7 @@ template class CategoricalStops<std::array<float, 2>>; template class CategoricalStops<std::string>; template class CategoricalStops<TextTransformType>; template class CategoricalStops<TextJustifyType>; -template class CategoricalStops<TextAnchorType>; +template class CategoricalStops<SymbolAnchorType>; template class CategoricalStops<LineJoinType>; } // namespace style diff --git a/src/mbgl/style/function/identity_stops.cpp b/src/mbgl/style/function/identity_stops.cpp index 7815f4aca0..0ac6fda846 100644 --- a/src/mbgl/style/function/identity_stops.cpp +++ b/src/mbgl/style/function/identity_stops.cpp @@ -50,12 +50,12 @@ optional<TextJustifyType> IdentityStops<TextJustifyType>::evaluate(const Value& } template <> -optional<TextAnchorType> IdentityStops<TextAnchorType>::evaluate(const Value& value) const { +optional<SymbolAnchorType> IdentityStops<SymbolAnchorType>::evaluate(const Value& value) const { if (!value.is<std::string>()) { return {}; } - return Enum<TextAnchorType>::toEnum(value.get<std::string>()); + return Enum<SymbolAnchorType>::toEnum(value.get<std::string>()); } template <> diff --git a/src/mbgl/style/layers/symbol_layer.cpp b/src/mbgl/style/layers/symbol_layer.cpp index 803ae7397e..2b86b26025 100644 --- a/src/mbgl/style/layers/symbol_layer.cpp +++ b/src/mbgl/style/layers/symbol_layer.cpp @@ -332,6 +332,22 @@ void SymbolLayer::setIconOffset(DataDrivenPropertyValue<std::array<float, 2>> va baseImpl = std::move(impl_); observer->onLayerChanged(*this); } +DataDrivenPropertyValue<SymbolAnchorType> SymbolLayer::getDefaultIconAnchor() { + return IconAnchor::defaultValue(); +} + +DataDrivenPropertyValue<SymbolAnchorType> SymbolLayer::getIconAnchor() const { + return impl().layout.get<IconAnchor>(); +} + +void SymbolLayer::setIconAnchor(DataDrivenPropertyValue<SymbolAnchorType> value) { + if (value == getIconAnchor()) + return; + auto impl_ = mutableImpl(); + impl_->layout.get<IconAnchor>() = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} PropertyValue<AlignmentType> SymbolLayer::getDefaultIconPitchAlignment() { return IconPitchAlignment::defaultValue(); } @@ -492,15 +508,15 @@ void SymbolLayer::setTextJustify(DataDrivenPropertyValue<TextJustifyType> value) baseImpl = std::move(impl_); observer->onLayerChanged(*this); } -DataDrivenPropertyValue<TextAnchorType> SymbolLayer::getDefaultTextAnchor() { +DataDrivenPropertyValue<SymbolAnchorType> SymbolLayer::getDefaultTextAnchor() { return TextAnchor::defaultValue(); } -DataDrivenPropertyValue<TextAnchorType> SymbolLayer::getTextAnchor() const { +DataDrivenPropertyValue<SymbolAnchorType> SymbolLayer::getTextAnchor() const { return impl().layout.get<TextAnchor>(); } -void SymbolLayer::setTextAnchor(DataDrivenPropertyValue<TextAnchorType> value) { +void SymbolLayer::setTextAnchor(DataDrivenPropertyValue<SymbolAnchorType> value) { if (value == getTextAnchor()) return; auto impl_ = mutableImpl(); diff --git a/src/mbgl/style/layers/symbol_layer_properties.hpp b/src/mbgl/style/layers/symbol_layer_properties.hpp index fe6ab38e92..0d163b9fb9 100644 --- a/src/mbgl/style/layers/symbol_layer_properties.hpp +++ b/src/mbgl/style/layers/symbol_layer_properties.hpp @@ -87,6 +87,11 @@ struct IconOffset : DataDrivenLayoutProperty<std::array<float, 2>> { static std::array<float, 2> defaultValue() { return {{ 0, 0 }}; } }; +struct IconAnchor : DataDrivenLayoutProperty<SymbolAnchorType> { + static constexpr const char * key = "icon-anchor"; + static SymbolAnchorType defaultValue() { return SymbolAnchorType::Center; } +}; + struct IconPitchAlignment : LayoutProperty<AlignmentType> { static constexpr const char * key = "icon-pitch-alignment"; static AlignmentType defaultValue() { return AlignmentType::Auto; } @@ -137,9 +142,9 @@ struct TextJustify : DataDrivenLayoutProperty<TextJustifyType> { static TextJustifyType defaultValue() { return TextJustifyType::Center; } }; -struct TextAnchor : DataDrivenLayoutProperty<TextAnchorType> { +struct TextAnchor : DataDrivenLayoutProperty<SymbolAnchorType> { static constexpr const char * key = "text-anchor"; - static TextAnchorType defaultValue() { return TextAnchorType::Center; } + static SymbolAnchorType defaultValue() { return SymbolAnchorType::Center; } }; struct TextMaxAngle : LayoutProperty<float> { @@ -259,6 +264,7 @@ class SymbolLayoutProperties : public Properties< IconPadding, IconKeepUpright, IconOffset, + IconAnchor, IconPitchAlignment, TextPitchAlignment, TextRotationAlignment, diff --git a/src/mbgl/style/types.cpp b/src/mbgl/style/types.cpp index 4fbf767e11..0a1781e01b 100644 --- a/src/mbgl/style/types.cpp +++ b/src/mbgl/style/types.cpp @@ -53,16 +53,16 @@ MBGL_DEFINE_ENUM(SymbolPlacementType, { { SymbolPlacementType::Line, "line" }, }); -MBGL_DEFINE_ENUM(TextAnchorType, { - { TextAnchorType::Center, "center" }, - { TextAnchorType::Left, "left" }, - { TextAnchorType::Right, "right" }, - { TextAnchorType::Top, "top" }, - { TextAnchorType::Bottom, "bottom" }, - { TextAnchorType::TopLeft, "top-left" }, - { TextAnchorType::TopRight, "top-right" }, - { TextAnchorType::BottomLeft, "bottom-left" }, - { TextAnchorType::BottomRight, "bottom-right" } +MBGL_DEFINE_ENUM(SymbolAnchorType, { + { SymbolAnchorType::Center, "center" }, + { SymbolAnchorType::Left, "left" }, + { SymbolAnchorType::Right, "right" }, + { SymbolAnchorType::Top, "top" }, + { SymbolAnchorType::Bottom, "bottom" }, + { SymbolAnchorType::TopLeft, "top-left" }, + { SymbolAnchorType::TopRight, "top-right" }, + { SymbolAnchorType::BottomLeft, "bottom-left" }, + { SymbolAnchorType::BottomRight, "bottom-right" } }); MBGL_DEFINE_ENUM(TextJustifyType, { diff --git a/src/mbgl/text/shaping.cpp b/src/mbgl/text/shaping.cpp index a40ef0cf39..5d688ea539 100644 --- a/src/mbgl/text/shaping.cpp +++ b/src/mbgl/text/shaping.cpp @@ -11,12 +11,63 @@ namespace mbgl { -PositionedIcon PositionedIcon::shapeIcon(const ImagePosition& image, const std::array<float, 2>& iconOffset, const float iconRotation) { +struct AnchorAlignment { + AnchorAlignment(float horizontal_, float vertical_) + : horizontalAlign(horizontal_), verticalAlign(vertical_) { + } + + float horizontalAlign; + float verticalAlign; +}; + +AnchorAlignment getAnchorAlignment(style::SymbolAnchorType anchor) { + float horizontalAlign = 0.5; + float verticalAlign = 0.5; + + switch (anchor) { + case style::SymbolAnchorType::Top: + case style::SymbolAnchorType::Bottom: + case style::SymbolAnchorType::Center: + break; + case style::SymbolAnchorType::Right: + case style::SymbolAnchorType::TopRight: + case style::SymbolAnchorType::BottomRight: + horizontalAlign = 1; + break; + case style::SymbolAnchorType::Left: + case style::SymbolAnchorType::TopLeft: + case style::SymbolAnchorType::BottomLeft: + horizontalAlign = 0; + break; + } + + switch (anchor) { + case style::SymbolAnchorType::Left: + case style::SymbolAnchorType::Right: + case style::SymbolAnchorType::Center: + break; + case style::SymbolAnchorType::Bottom: + case style::SymbolAnchorType::BottomLeft: + case style::SymbolAnchorType::BottomRight: + verticalAlign = 1; + break; + case style::SymbolAnchorType::Top: + case style::SymbolAnchorType::TopLeft: + case style::SymbolAnchorType::TopRight: + verticalAlign = 0; + break; + } + + return AnchorAlignment(horizontalAlign, verticalAlign); +} + +PositionedIcon PositionedIcon::shapeIcon(const ImagePosition& image, const std::array<float, 2>& iconOffset, style::SymbolAnchorType iconAnchor, const float iconRotation) { + AnchorAlignment anchorAlign = getAnchorAlignment(iconAnchor); float dx = iconOffset[0]; float dy = iconOffset[1]; - float x1 = dx - image.displaySize()[0] / 2.0f; + float x1 = dx - image.displaySize()[0] * anchorAlign.horizontalAlign; float x2 = x1 + image.displaySize()[0]; - float y1 = dy - image.displaySize()[1] / 2.0f; + float y1 = dy - image.displaySize()[1] * anchorAlign.verticalAlign; float y2 = y1 + image.displaySize()[1]; return PositionedIcon { image, y1, y2, x1, x2, iconRotation }; @@ -200,7 +251,7 @@ void shapeLines(Shaping& shaping, const std::vector<std::u16string>& lines, const float spacing, const float lineHeight, - const style::TextAnchorType textAnchor, + const style::SymbolAnchorType textAnchor, const style::TextJustifyType textJustify, const float verticalHeight, const WritingModeType writingMode, @@ -258,58 +309,23 @@ void shapeLines(Shaping& shaping, y += lineHeight; } - float horizontalAlign = 0.5; - float verticalAlign = 0.5; - - switch (textAnchor) { - case style::TextAnchorType::Top: - case style::TextAnchorType::Bottom: - case style::TextAnchorType::Center: - break; - case style::TextAnchorType::Right: - case style::TextAnchorType::TopRight: - case style::TextAnchorType::BottomRight: - horizontalAlign = 1; - break; - case style::TextAnchorType::Left: - case style::TextAnchorType::TopLeft: - case style::TextAnchorType::BottomLeft: - horizontalAlign = 0; - break; - } - - switch (textAnchor) { - case style::TextAnchorType::Left: - case style::TextAnchorType::Right: - case style::TextAnchorType::Center: - break; - case style::TextAnchorType::Bottom: - case style::TextAnchorType::BottomLeft: - case style::TextAnchorType::BottomRight: - verticalAlign = 1; - break; - case style::TextAnchorType::Top: - case style::TextAnchorType::TopLeft: - case style::TextAnchorType::TopRight: - verticalAlign = 0; - break; - } + auto anchorAlign = getAnchorAlignment(textAnchor); - align(shaping, justify, horizontalAlign, verticalAlign, - maxLineLength, lineHeight, lines.size()); + align(shaping, justify, anchorAlign.horizontalAlign, anchorAlign.verticalAlign, maxLineLength, + lineHeight, lines.size()); const uint32_t height = lines.size() * lineHeight; // Calculate the bounding box - shaping.top += -verticalAlign * height; + shaping.top += -anchorAlign.verticalAlign * height; shaping.bottom = shaping.top + height; - shaping.left += -horizontalAlign * maxLineLength; + shaping.left += -anchorAlign.horizontalAlign * maxLineLength; shaping.right = shaping.left + maxLineLength; } const Shaping getShaping(const std::u16string& logicalInput, const float maxWidth, const float lineHeight, - const style::TextAnchorType textAnchor, + const style::SymbolAnchorType textAnchor, const style::TextJustifyType textJustify, const float spacing, const Point<float>& translate, diff --git a/src/mbgl/text/shaping.hpp b/src/mbgl/text/shaping.hpp index 00e4ec55f8..0a961849e5 100644 --- a/src/mbgl/text/shaping.hpp +++ b/src/mbgl/text/shaping.hpp @@ -32,7 +32,10 @@ private: float _angle; public: - static PositionedIcon shapeIcon(const ImagePosition&, const std::array<float, 2>& iconOffset, const float iconRotation); + static PositionedIcon shapeIcon(const ImagePosition&, + const std::array<float, 2>& iconOffset, + style::SymbolAnchorType iconAnchor, + const float iconRotation); const ImagePosition& image() const { return _image; } float top() const { return _top; } @@ -45,7 +48,7 @@ public: const Shaping getShaping(const std::u16string& string, float maxWidth, float lineHeight, - style::TextAnchorType textAnchor, + style::SymbolAnchorType textAnchor, style::TextJustifyType textJustify, float spacing, const Point<float>& translate, diff --git a/test/text/quads.test.cpp b/test/text/quads.test.cpp index c4c1a7ac15..8eedd9bd2e 100644 --- a/test/text/quads.test.cpp +++ b/test/text/quads.test.cpp @@ -17,7 +17,7 @@ TEST(getIconQuads, normal) { style::Image::Impl("test", PremultipliedImage({1,1}), 1.0) }; - auto shapedIcon = PositionedIcon::shapeIcon(image, {{ -6.5f, -4.5f }}, 0); + auto shapedIcon = PositionedIcon::shapeIcon(image, {{ -6.5f, -4.5f }}, SymbolAnchorType::Center, 0); GeometryCoordinates line; Shaping shapedText; @@ -42,7 +42,7 @@ TEST(getIconQuads, style) { style::Image::Impl("test", PremultipliedImage({1,1}), 1.0) }; - auto shapedIcon = PositionedIcon::shapeIcon(image, {{ -9.5f, -9.5f }}, 0); + auto shapedIcon = PositionedIcon::shapeIcon(image, {{ -9.5f, -9.5f }}, SymbolAnchorType::Center, 0); GeometryCoordinates line; Shaping shapedText; |