From 9616f8911d8b0407f2e2a6aa283d4e0e0f2ea64a Mon Sep 17 00:00:00 2001 From: Mikhail Pozdnyakov Date: Thu, 7 Mar 2019 18:34:45 +0200 Subject: [PATCH 01/12] [core] Enable text-variable-anchor property --- include/mbgl/style/conversion/constant.hpp | 5 ++++ include/mbgl/style/layers/symbol_layer.hpp | 4 +++ include/mbgl/style/types.hpp | 2 ++ .../style/layers/PropertyFactory.java | 20 +++++++++++++ .../mapboxsdk/style/layers/SymbolLayer.java | 16 ++++++++++ .../testapp/style/SymbolLayerTest.java | 13 ++++++++ .../android/scripts/generate-style-code.js | 1 + platform/android/src/conversion/constant.hpp | 11 +++++++ .../android/src/style/layers/symbol_layer.cpp | 6 ++++ .../android/src/style/layers/symbol_layer.hpp | 2 ++ scripts/generate-style-code.js | 2 +- scripts/style-spec.js | 3 +- src/mbgl/style/conversion/constant.cpp | 27 +++++++++++++++++ src/mbgl/style/conversion/function.cpp | 2 ++ src/mbgl/style/conversion/property_value.cpp | 1 + src/mbgl/style/expression/value.cpp | 3 ++ src/mbgl/style/layers/symbol_layer.cpp | 30 +++++++++++++++++++ .../style/layers/symbol_layer_properties.hpp | 6 ++++ src/mbgl/style/types.cpp | 2 +- 19 files changed, 152 insertions(+), 4 deletions(-) diff --git a/include/mbgl/style/conversion/constant.hpp b/include/mbgl/style/conversion/constant.hpp index 40657528c4b..3a5833be643 100644 --- a/include/mbgl/style/conversion/constant.hpp +++ b/include/mbgl/style/conversion/constant.hpp @@ -34,6 +34,11 @@ struct Converter::value>> { optional operator()(const Convertible& value, Error& error) const; }; +template +struct Converter, typename std::enable_if_t::value>> { + optional> operator()(const Convertible& value, Error& error) const; +}; + template <> struct Converter { optional operator()(const Convertible& value, Error& error) const; diff --git a/include/mbgl/style/layers/symbol_layer.hpp b/include/mbgl/style/layers/symbol_layer.hpp index 764f1585f6f..46935ef535c 100644 --- a/include/mbgl/style/layers/symbol_layer.hpp +++ b/include/mbgl/style/layers/symbol_layer.hpp @@ -134,6 +134,10 @@ class SymbolLayer : public Layer { PropertyValue getTextJustify() const; void setTextJustify(PropertyValue); + static PropertyValue> getDefaultTextVariableAnchor(); + PropertyValue> getTextVariableAnchor() const; + void setTextVariableAnchor(PropertyValue>); + static PropertyValue getDefaultTextAnchor(); PropertyValue getTextAnchor() const; void setTextAnchor(PropertyValue); diff --git a/include/mbgl/style/types.hpp b/include/mbgl/style/types.hpp index ed875733c77..a472c4fcec4 100644 --- a/include/mbgl/style/types.hpp +++ b/include/mbgl/style/types.hpp @@ -98,6 +98,8 @@ enum class SymbolAnchorType : uint8_t { BottomRight }; +using TextVariableAnchorType = SymbolAnchorType; + enum class TextTransformType : uint8_t { None, Uppercase, 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 3d8b921a79f..88770b10afc 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 @@ -2275,6 +2275,26 @@ public static PropertyValue textJustify(Expression value) { return new LayoutPropertyValue<>("text-justify", value); } + /** + * To increase the chance of placing high-priority labels on the map, you can provide an array of {@link Property.TEXT_ANCHOR} locations: the render will attempt to place the label at each location, in order, before moving onto the next label. Use `text-justify: auto` to choose justification based on anchor position. To apply an offset, use the `text-radial-offset` instead of the two-dimensional {@link PropertyFactory#textOffset}. + * + * @param value a String[] value + * @return property wrapper around String[] + */ + public static PropertyValue textVariableAnchor(String[] value) { + return new LayoutPropertyValue<>("text-variable-anchor", value); + } + + /** + * To increase the chance of placing high-priority labels on the map, you can provide an array of {@link Property.TEXT_ANCHOR} locations: the render will attempt to place the label at each location, in order, before moving onto the next label. Use `text-justify: auto` to choose justification based on anchor position. To apply an offset, use the `text-radial-offset` instead of the two-dimensional {@link PropertyFactory#textOffset}. + * + * @param value a String[] value + * @return property wrapper around String[] + */ + public static PropertyValue textVariableAnchor(Expression value) { + return new LayoutPropertyValue<>("text-variable-anchor", value); + } + /** * Part of the text placed closest to the anchor. * 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 ab45cb04f25..4b5e755f7d9 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 @@ -469,6 +469,18 @@ public PropertyValue getTextJustify() { return (PropertyValue) new PropertyValue("text-justify", nativeGetTextJustify()); } + /** + * Get the TextVariableAnchor property + * + * @return property wrapper value around String[] + */ + @NonNull + @SuppressWarnings("unchecked") + public PropertyValue getTextVariableAnchor() { + checkThread(); + return (PropertyValue) new PropertyValue("text-variable-anchor", nativeGetTextVariableAnchor()); + } + /** * Get the TextAnchor property * @@ -1185,6 +1197,10 @@ public PropertyValue getTextTranslateAnchor() { @Keep private native Object nativeGetTextJustify(); + @NonNull + @Keep + private native Object nativeGetTextVariableAnchor(); + @NonNull @Keep private native Object nativeGetTextAnchor(); 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 149064d6840..67260f9b3c7 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 @@ -577,6 +577,19 @@ public void testTextJustifyAsExpression() { assertEquals(layer.getTextJustify().getExpression(), expression); } + @Test + @UiThreadTest + public void testTextVariableAnchorAsConstant() { + Timber.i("text-variable-anchor"); + assertNotNull(layer); + assertNull(layer.getTextVariableAnchor().getValue()); + + // Set and Get + String[] propertyValue = [undefined]; + layer.setProperties(textVariableAnchor(propertyValue)); + assertEquals(layer.getTextVariableAnchor().getValue(), propertyValue); + } + @Test @UiThreadTest public void testTextAnchorAsConstant() { diff --git a/platform/android/scripts/generate-style-code.js b/platform/android/scripts/generate-style-code.js index 56e75113627..6f64a38a674 100755 --- a/platform/android/scripts/generate-style-code.js +++ b/platform/android/scripts/generate-style-code.js @@ -197,6 +197,7 @@ global.defaultValueJava = function(property) { case 'array': switch (property.value) { case 'string': + case 'enum': return '[' + property['default'] + "]"; case 'number': var result ='new Float[] {'; diff --git a/platform/android/src/conversion/constant.hpp b/platform/android/src/conversion/constant.hpp index 4def670f3c1..839e6e84dc9 100644 --- a/platform/android/src/conversion/constant.hpp +++ b/platform/android/src/conversion/constant.hpp @@ -88,6 +88,17 @@ struct Converter>, T, typename std::enable_if_t +struct Converter>, std::vector, typename std::enable_if_t::value>> { + Result>> operator()(jni::JNIEnv& env, const std::vector& value) const { + auto result = jni::Array::New(env, value.size()); + for (std::size_t i = 0; i < value.size(); ++i) { + result.Set(env, i, jni::Make(env, Enum::toString(value.at(i)))); + } + return result; + } +}; + } // namespace conversion } // namespace android } // namespace mbgl diff --git a/platform/android/src/style/layers/symbol_layer.cpp b/platform/android/src/style/layers/symbol_layer.cpp index 61e4d59326b..e9b149eaf30 100644 --- a/platform/android/src/style/layers/symbol_layer.cpp +++ b/platform/android/src/style/layers/symbol_layer.cpp @@ -176,6 +176,11 @@ namespace android { return std::move(*convert>>(env, toSymbolLayer(layer).getTextJustify())); } + jni::Local> SymbolLayer::getTextVariableAnchor(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + return std::move(*convert>>(env, toSymbolLayer(layer).getTextVariableAnchor())); + } + jni::Local> SymbolLayer::getTextAnchor(jni::JNIEnv& env) { using namespace mbgl::android::conversion; return std::move(*convert>>(env, toSymbolLayer(layer).getTextAnchor())); @@ -514,6 +519,7 @@ namespace android { METHOD(&SymbolLayer::getTextLineHeight, "nativeGetTextLineHeight"), METHOD(&SymbolLayer::getTextLetterSpacing, "nativeGetTextLetterSpacing"), METHOD(&SymbolLayer::getTextJustify, "nativeGetTextJustify"), + METHOD(&SymbolLayer::getTextVariableAnchor, "nativeGetTextVariableAnchor"), METHOD(&SymbolLayer::getTextAnchor, "nativeGetTextAnchor"), METHOD(&SymbolLayer::getTextMaxAngle, "nativeGetTextMaxAngle"), METHOD(&SymbolLayer::getTextRotate, "nativeGetTextRotate"), diff --git a/platform/android/src/style/layers/symbol_layer.hpp b/platform/android/src/style/layers/symbol_layer.hpp index f52597ef6fa..c93961f70ab 100644 --- a/platform/android/src/style/layers/symbol_layer.hpp +++ b/platform/android/src/style/layers/symbol_layer.hpp @@ -80,6 +80,8 @@ class SymbolLayer : public Layer { jni::Local> getTextJustify(jni::JNIEnv&); + jni::Local> getTextVariableAnchor(jni::JNIEnv&); + jni::Local> getTextAnchor(jni::JNIEnv&); jni::Local> getTextMaxAngle(jni::JNIEnv&); diff --git a/scripts/generate-style-code.js b/scripts/generate-style-code.js index 72f4857a96a..2622ae5ef66 100755 --- a/scripts/generate-style-code.js +++ b/scripts/generate-style-code.js @@ -72,7 +72,7 @@ global.evaluatedType = function (property) { if (property.length) { return `std::array<${evaluatedType({type: property.value})}, ${property.length}>`; } else { - return `std::vector<${evaluatedType({type: property.value})}>`; + return `std::vector<${evaluatedType({type: property.value, name: property.name})}>`; } default: throw new Error(`unknown type for ${property.name}`) } diff --git a/scripts/style-spec.js b/scripts/style-spec.js index 4bbd453a869..695b5b7a59e 100644 --- a/scripts/style-spec.js +++ b/scripts/style-spec.js @@ -5,7 +5,6 @@ delete spec.layout_symbol['symbol-sort-key']; delete spec.layout_symbol['symbol-z-order'].values['auto']; spec.layout_symbol['symbol-z-order'].default = 'viewport-y'; -delete spec.layout_symbol['text-variable-anchor']; delete spec.layout_symbol['text-radial-offset']; delete spec.layout_symbol['text-justify'].values['auto']; -spec.layout_symbol['text-offset'].requires.splice(1, 1); // { "!": "text-radial-offset" } \ No newline at end of file +spec.layout_symbol['text-offset'].requires.splice(1, 1); // { "!": "text-radial-offset" } diff --git a/src/mbgl/style/conversion/constant.cpp b/src/mbgl/style/conversion/constant.cpp index bdc6371722e..1942779aaac 100644 --- a/src/mbgl/style/conversion/constant.cpp +++ b/src/mbgl/style/conversion/constant.cpp @@ -49,6 +49,27 @@ optional Converter::value>>::ope return *result; } +template +auto Converter, typename std::enable_if_t::value>>::operator()(const Convertible& value, Error& error) const -> optional> { + if (!isArray(value)) { + error.message = "value must be an array"; + return nullopt; + } + + std::vector result; + result.reserve(arrayLength(value)); + + for (std::size_t i = 0; i < arrayLength(value); ++i) { + optional enumItem = Converter{}(arrayMember(value, i), error); + if (!enumItem) { + return nullopt; + } + result.push_back(*enumItem); + } + + return result; +} + template optional Converter::operator()(const Convertible&, Error&) const; template optional Converter::operator()(const Convertible&, Error&) const; template optional Converter::operator()(const Convertible&, Error&) const; @@ -64,6 +85,7 @@ template optional Converter::operator()(const template optional Converter::operator()(const Convertible&, Error&) const; template optional Converter::operator()(const Convertible&, Error&) const; template optional Converter::operator()(const Convertible&, Error&) const; +template optional> Converter>::operator()(const Convertible&, Error&) const; optional Converter::operator()(const Convertible& value, Error& error) const { optional string = toString(value); @@ -125,6 +147,11 @@ optional> Converter>::operator()(const Con return result; } + +namespace { + +} // namespace + optional> Converter>::operator()(const Convertible& value, Error& error) const { if (!isArray(value)) { error.message = "value must be an array"; diff --git a/src/mbgl/style/conversion/function.cpp b/src/mbgl/style/conversion/function.cpp index 79ad2fc7d80..df4decc73e1 100644 --- a/src/mbgl/style/conversion/function.cpp +++ b/src/mbgl/style/conversion/function.cpp @@ -136,6 +136,8 @@ template optional>> convertFunctionToExpression>(const Convertible&, Error&, bool); template optional> convertFunctionToExpression(const Convertible&, Error&, bool); +template optional>> + convertFunctionToExpression>(const Convertible&, Error&, bool); template optional> convertFunctionToExpression(const Convertible&, Error&, bool); template optional> diff --git a/src/mbgl/style/conversion/property_value.cpp b/src/mbgl/style/conversion/property_value.cpp index ff038908b6c..6e1d747324f 100644 --- a/src/mbgl/style/conversion/property_value.cpp +++ b/src/mbgl/style/conversion/property_value.cpp @@ -73,6 +73,7 @@ template optional> Converter> Converter>::operator()(conversion::Convertible const&, conversion::Error&, bool, bool) const; template optional> Converter>::operator()(conversion::Convertible const&, conversion::Error&, bool, bool) const; template optional> Converter>::operator()(conversion::Convertible const&, conversion::Error&, bool, bool) const; +template optional>> Converter>>::operator()(conversion::Convertible const&, conversion::Error&, bool, bool) const; template optional> Converter>::operator()(conversion::Convertible const&, conversion::Error&, bool, bool) const; template optional> Converter>::operator()(conversion::Convertible const&, conversion::Error&, bool, bool) const; template optional> Converter>::operator()(conversion::Convertible const&, conversion::Error&, bool, bool) const; diff --git a/src/mbgl/style/expression/value.cpp b/src/mbgl/style/expression/value.cpp index 436ed83ecd4..46b554d4e7b 100644 --- a/src/mbgl/style/expression/value.cpp +++ b/src/mbgl/style/expression/value.cpp @@ -324,6 +324,9 @@ template struct ValueConverter>; template type::Type valueTypeToExpressionType>(); template struct ValueConverter>; +template type::Type valueTypeToExpressionType>(); +template struct ValueConverter>; + template type::Type valueTypeToExpressionType(); template struct ValueConverter; diff --git a/src/mbgl/style/layers/symbol_layer.cpp b/src/mbgl/style/layers/symbol_layer.cpp index 75ed881058a..2195f703d7d 100644 --- a/src/mbgl/style/layers/symbol_layer.cpp +++ b/src/mbgl/style/layers/symbol_layer.cpp @@ -492,6 +492,22 @@ void SymbolLayer::setTextJustify(PropertyValue value) { baseImpl = std::move(impl_); observer->onLayerChanged(*this); } +PropertyValue> SymbolLayer::getDefaultTextVariableAnchor() { + return TextVariableAnchor::defaultValue(); +} + +PropertyValue> SymbolLayer::getTextVariableAnchor() const { + return impl().layout.get(); +} + +void SymbolLayer::setTextVariableAnchor(PropertyValue> value) { + if (value == getTextVariableAnchor()) + return; + auto impl_ = mutableImpl(); + impl_->layout.get() = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} PropertyValue SymbolLayer::getDefaultTextAnchor() { return TextAnchor::defaultValue(); } @@ -1325,6 +1341,7 @@ optional SymbolLayer::setLayoutProperty(const std::string& name, const Co TextLineHeight, TextLetterSpacing, TextJustify, + TextVariableAnchor, TextAnchor, TextMaxAngle, TextRotate, @@ -1364,6 +1381,7 @@ optional SymbolLayer::setLayoutProperty(const std::string& name, const Co { "text-line-height", static_cast(Property::TextLineHeight) }, { "text-letter-spacing", static_cast(Property::TextLetterSpacing) }, { "text-justify", static_cast(Property::TextJustify) }, + { "text-variable-anchor", static_cast(Property::TextVariableAnchor) }, { "text-anchor", static_cast(Property::TextAnchor) }, { "text-max-angle", static_cast(Property::TextMaxAngle) }, { "text-rotate", static_cast(Property::TextRotate) }, @@ -1674,6 +1692,18 @@ optional SymbolLayer::setLayoutProperty(const std::string& name, const Co } + if (property == Property::TextVariableAnchor) { + Error error; + optional>> typedValue = convert>>(value, error, false, false); + if (!typedValue) { + return error; + } + + setTextVariableAnchor(*typedValue); + return nullopt; + + } + if (property == Property::TextTransform) { Error error; optional> typedValue = convert>(value, error, true, false); diff --git a/src/mbgl/style/layers/symbol_layer_properties.hpp b/src/mbgl/style/layers/symbol_layer_properties.hpp index 8645823cac1..8ccad4efecc 100644 --- a/src/mbgl/style/layers/symbol_layer_properties.hpp +++ b/src/mbgl/style/layers/symbol_layer_properties.hpp @@ -147,6 +147,11 @@ struct TextJustify : DataDrivenLayoutProperty { static TextJustifyType defaultValue() { return TextJustifyType::Center; } }; +struct TextVariableAnchor : LayoutProperty> { + static constexpr const char *name() { return "text-variable-anchor"; } + static std::vector defaultValue() { return { }; } +}; + struct TextAnchor : DataDrivenLayoutProperty { static constexpr const char *name() { return "text-anchor"; } static SymbolAnchorType defaultValue() { return SymbolAnchorType::Center; } @@ -284,6 +289,7 @@ class SymbolLayoutProperties : public Properties< TextLineHeight, TextLetterSpacing, TextJustify, + TextVariableAnchor, TextAnchor, TextMaxAngle, TextRotate, diff --git a/src/mbgl/style/types.cpp b/src/mbgl/style/types.cpp index 51174cf152c..48016ce76ac 100644 --- a/src/mbgl/style/types.cpp +++ b/src/mbgl/style/types.cpp @@ -76,7 +76,7 @@ MBGL_DEFINE_ENUM(SymbolAnchorType, { { SymbolAnchorType::BottomLeft, "bottom-left" }, { SymbolAnchorType::BottomRight, "bottom-right" } }); - + MBGL_DEFINE_ENUM(SymbolZOrderType, { { SymbolZOrderType::ViewportY, "viewport-y" }, { SymbolZOrderType::Source, "source" } From 296038107799f8bddc2bc9533ac97211d1e7f6bb Mon Sep 17 00:00:00 2001 From: Mikhail Pozdnyakov Date: Thu, 7 Mar 2019 18:52:00 +0200 Subject: [PATCH 02/12] [core] Enable 'text-radial-offset' property --- include/mbgl/style/layers/symbol_layer.hpp | 4 +++ .../style/layers/PropertyFactory.java | 24 ++++++++++++++++-- .../mapboxsdk/style/layers/SymbolLayer.java | 16 ++++++++++++ .../testapp/style/SymbolLayerTest.java | 13 ++++++++++ .../android/src/style/layers/symbol_layer.cpp | 6 +++++ .../android/src/style/layers/symbol_layer.hpp | 2 ++ scripts/generate-style-code.js | 4 +++ scripts/style-spec.js | 4 +-- src/mbgl/style/layers/symbol_layer.cpp | 25 ++++++++++++++++++- .../style/layers/symbol_layer_properties.hpp | 6 +++++ 10 files changed, 98 insertions(+), 6 deletions(-) diff --git a/include/mbgl/style/layers/symbol_layer.hpp b/include/mbgl/style/layers/symbol_layer.hpp index 46935ef535c..35fe72e6bf4 100644 --- a/include/mbgl/style/layers/symbol_layer.hpp +++ b/include/mbgl/style/layers/symbol_layer.hpp @@ -134,6 +134,10 @@ class SymbolLayer : public Layer { PropertyValue getTextJustify() const; void setTextJustify(PropertyValue); + static PropertyValue getDefaultTextRadialOffset(); + PropertyValue getTextRadialOffset() const; + void setTextRadialOffset(PropertyValue); + static PropertyValue> getDefaultTextVariableAnchor(); PropertyValue> getTextVariableAnchor() const; void setTextVariableAnchor(PropertyValue>); 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 88770b10afc..01908b1b0b8 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 @@ -2276,7 +2276,27 @@ public static PropertyValue textJustify(Expression value) { } /** - * To increase the chance of placing high-priority labels on the map, you can provide an array of {@link Property.TEXT_ANCHOR} locations: the render will attempt to place the label at each location, in order, before moving onto the next label. Use `text-justify: auto` to choose justification based on anchor position. To apply an offset, use the `text-radial-offset` instead of the two-dimensional {@link PropertyFactory#textOffset}. + * Radial offset of text, in the direction of the symbol's anchor. Useful in combination with {@link PropertyFactory#textVariableAnchor}, which doesn't support the two-dimensional {@link PropertyFactory#textOffset}. + * + * @param value a Float value + * @return property wrapper around Float + */ + public static PropertyValue textRadialOffset(Float value) { + return new LayoutPropertyValue<>("text-radial-offset", value); + } + + /** + * Radial offset of text, in the direction of the symbol's anchor. Useful in combination with {@link PropertyFactory#textVariableAnchor}, which doesn't support the two-dimensional {@link PropertyFactory#textOffset}. + * + * @param value a Float value + * @return property wrapper around Float + */ + public static PropertyValue textRadialOffset(Expression value) { + return new LayoutPropertyValue<>("text-radial-offset", value); + } + + /** + * To increase the chance of placing high-priority labels on the map, you can provide an array of {@link Property.TEXT_ANCHOR} locations: the render will attempt to place the label at each location, in order, before moving onto the next label. Use `text-justify: auto` to choose justification based on anchor position. To apply an offset, use the {@link PropertyFactory#textRadialOffset} instead of the two-dimensional {@link PropertyFactory#textOffset}. * * @param value a String[] value * @return property wrapper around String[] @@ -2286,7 +2306,7 @@ public static PropertyValue textVariableAnchor(String[] value) { } /** - * To increase the chance of placing high-priority labels on the map, you can provide an array of {@link Property.TEXT_ANCHOR} locations: the render will attempt to place the label at each location, in order, before moving onto the next label. Use `text-justify: auto` to choose justification based on anchor position. To apply an offset, use the `text-radial-offset` instead of the two-dimensional {@link PropertyFactory#textOffset}. + * To increase the chance of placing high-priority labels on the map, you can provide an array of {@link Property.TEXT_ANCHOR} locations: the render will attempt to place the label at each location, in order, before moving onto the next label. Use `text-justify: auto` to choose justification based on anchor position. To apply an offset, use the {@link PropertyFactory#textRadialOffset} instead of the two-dimensional {@link PropertyFactory#textOffset}. * * @param value a String[] value * @return property wrapper around String[] 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 4b5e755f7d9..75473f0f30c 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 @@ -469,6 +469,18 @@ public PropertyValue getTextJustify() { return (PropertyValue) new PropertyValue("text-justify", nativeGetTextJustify()); } + /** + * Get the TextRadialOffset property + * + * @return property wrapper value around Float + */ + @NonNull + @SuppressWarnings("unchecked") + public PropertyValue getTextRadialOffset() { + checkThread(); + return (PropertyValue) new PropertyValue("text-radial-offset", nativeGetTextRadialOffset()); + } + /** * Get the TextVariableAnchor property * @@ -1197,6 +1209,10 @@ public PropertyValue getTextTranslateAnchor() { @Keep private native Object nativeGetTextJustify(); + @NonNull + @Keep + private native Object nativeGetTextRadialOffset(); + @NonNull @Keep private native Object nativeGetTextVariableAnchor(); 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 67260f9b3c7..9775a5184b7 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 @@ -577,6 +577,19 @@ public void testTextJustifyAsExpression() { assertEquals(layer.getTextJustify().getExpression(), expression); } + @Test + @UiThreadTest + public void testTextRadialOffsetAsConstant() { + Timber.i("text-radial-offset"); + assertNotNull(layer); + assertNull(layer.getTextRadialOffset().getValue()); + + // Set and Get + Float propertyValue = 0.3f; + layer.setProperties(textRadialOffset(propertyValue)); + assertEquals(layer.getTextRadialOffset().getValue(), propertyValue); + } + @Test @UiThreadTest public void testTextVariableAnchorAsConstant() { diff --git a/platform/android/src/style/layers/symbol_layer.cpp b/platform/android/src/style/layers/symbol_layer.cpp index e9b149eaf30..810848e9cb3 100644 --- a/platform/android/src/style/layers/symbol_layer.cpp +++ b/platform/android/src/style/layers/symbol_layer.cpp @@ -176,6 +176,11 @@ namespace android { return std::move(*convert>>(env, toSymbolLayer(layer).getTextJustify())); } + jni::Local> SymbolLayer::getTextRadialOffset(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + return std::move(*convert>>(env, toSymbolLayer(layer).getTextRadialOffset())); + } + jni::Local> SymbolLayer::getTextVariableAnchor(jni::JNIEnv& env) { using namespace mbgl::android::conversion; return std::move(*convert>>(env, toSymbolLayer(layer).getTextVariableAnchor())); @@ -519,6 +524,7 @@ namespace android { METHOD(&SymbolLayer::getTextLineHeight, "nativeGetTextLineHeight"), METHOD(&SymbolLayer::getTextLetterSpacing, "nativeGetTextLetterSpacing"), METHOD(&SymbolLayer::getTextJustify, "nativeGetTextJustify"), + METHOD(&SymbolLayer::getTextRadialOffset, "nativeGetTextRadialOffset"), METHOD(&SymbolLayer::getTextVariableAnchor, "nativeGetTextVariableAnchor"), METHOD(&SymbolLayer::getTextAnchor, "nativeGetTextAnchor"), METHOD(&SymbolLayer::getTextMaxAngle, "nativeGetTextMaxAngle"), diff --git a/platform/android/src/style/layers/symbol_layer.hpp b/platform/android/src/style/layers/symbol_layer.hpp index c93961f70ab..3b0f8ee5d11 100644 --- a/platform/android/src/style/layers/symbol_layer.hpp +++ b/platform/android/src/style/layers/symbol_layer.hpp @@ -80,6 +80,8 @@ class SymbolLayer : public Layer { jni::Local> getTextJustify(jni::JNIEnv&); + jni::Local> getTextRadialOffset(jni::JNIEnv&); + jni::Local> getTextVariableAnchor(jni::JNIEnv&); jni::Local> getTextAnchor(jni::JNIEnv&); diff --git a/scripts/generate-style-code.js b/scripts/generate-style-code.js index 2622ae5ef66..5145755cec1 100755 --- a/scripts/generate-style-code.js +++ b/scripts/generate-style-code.js @@ -153,7 +153,11 @@ global.defaultValue = function (property) { switch (property.type) { case 'number': + if (property.default === undefined) { + return 0; + } else { return property.default; + } case 'formatted': case 'string': return JSON.stringify(property.default || ""); diff --git a/scripts/style-spec.js b/scripts/style-spec.js index 695b5b7a59e..bcfb7e5c179 100644 --- a/scripts/style-spec.js +++ b/scripts/style-spec.js @@ -5,6 +5,4 @@ delete spec.layout_symbol['symbol-sort-key']; delete spec.layout_symbol['symbol-z-order'].values['auto']; spec.layout_symbol['symbol-z-order'].default = 'viewport-y'; -delete spec.layout_symbol['text-radial-offset']; -delete spec.layout_symbol['text-justify'].values['auto']; -spec.layout_symbol['text-offset'].requires.splice(1, 1); // { "!": "text-radial-offset" } +delete spec.layout_symbol['text-justify'].values['auto']; \ No newline at end of file diff --git a/src/mbgl/style/layers/symbol_layer.cpp b/src/mbgl/style/layers/symbol_layer.cpp index 2195f703d7d..1c56888f730 100644 --- a/src/mbgl/style/layers/symbol_layer.cpp +++ b/src/mbgl/style/layers/symbol_layer.cpp @@ -492,6 +492,22 @@ void SymbolLayer::setTextJustify(PropertyValue value) { baseImpl = std::move(impl_); observer->onLayerChanged(*this); } +PropertyValue SymbolLayer::getDefaultTextRadialOffset() { + return TextRadialOffset::defaultValue(); +} + +PropertyValue SymbolLayer::getTextRadialOffset() const { + return impl().layout.get(); +} + +void SymbolLayer::setTextRadialOffset(PropertyValue value) { + if (value == getTextRadialOffset()) + return; + auto impl_ = mutableImpl(); + impl_->layout.get() = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} PropertyValue> SymbolLayer::getDefaultTextVariableAnchor() { return TextVariableAnchor::defaultValue(); } @@ -1341,6 +1357,7 @@ optional SymbolLayer::setLayoutProperty(const std::string& name, const Co TextLineHeight, TextLetterSpacing, TextJustify, + TextRadialOffset, TextVariableAnchor, TextAnchor, TextMaxAngle, @@ -1381,6 +1398,7 @@ optional SymbolLayer::setLayoutProperty(const std::string& name, const Co { "text-line-height", static_cast(Property::TextLineHeight) }, { "text-letter-spacing", static_cast(Property::TextLetterSpacing) }, { "text-justify", static_cast(Property::TextJustify) }, + { "text-radial-offset", static_cast(Property::TextRadialOffset) }, { "text-variable-anchor", static_cast(Property::TextVariableAnchor) }, { "text-anchor", static_cast(Property::TextAnchor) }, { "text-max-angle", static_cast(Property::TextMaxAngle) }, @@ -1543,7 +1561,7 @@ optional SymbolLayer::setLayoutProperty(const std::string& name, const Co } - if (property == Property::IconSize || property == Property::IconRotate || property == Property::TextSize || property == Property::TextMaxWidth || property == Property::TextLetterSpacing || property == Property::TextRotate) { + if (property == Property::IconSize || property == Property::IconRotate || property == Property::TextSize || property == Property::TextMaxWidth || property == Property::TextLetterSpacing || property == Property::TextRadialOffset || property == Property::TextRotate) { Error error; optional> typedValue = convert>(value, error, true, false); if (!typedValue) { @@ -1575,6 +1593,11 @@ optional SymbolLayer::setLayoutProperty(const std::string& name, const Co return nullopt; } + if (property == Property::TextRadialOffset) { + setTextRadialOffset(*typedValue); + return nullopt; + } + if (property == Property::TextRotate) { setTextRotate(*typedValue); return nullopt; diff --git a/src/mbgl/style/layers/symbol_layer_properties.hpp b/src/mbgl/style/layers/symbol_layer_properties.hpp index 8ccad4efecc..cf8a9ab0d03 100644 --- a/src/mbgl/style/layers/symbol_layer_properties.hpp +++ b/src/mbgl/style/layers/symbol_layer_properties.hpp @@ -147,6 +147,11 @@ struct TextJustify : DataDrivenLayoutProperty { static TextJustifyType defaultValue() { return TextJustifyType::Center; } }; +struct TextRadialOffset : DataDrivenLayoutProperty { + static constexpr const char *name() { return "text-radial-offset"; } + static float defaultValue() { return 0; } +}; + struct TextVariableAnchor : LayoutProperty> { static constexpr const char *name() { return "text-variable-anchor"; } static std::vector defaultValue() { return { }; } @@ -289,6 +294,7 @@ class SymbolLayoutProperties : public Properties< TextLineHeight, TextLetterSpacing, TextJustify, + TextRadialOffset, TextVariableAnchor, TextAnchor, TextMaxAngle, From 8c951401c212394021c19a7f5b5149894810bc56 Mon Sep 17 00:00:00 2001 From: Mikhail Pozdnyakov Date: Thu, 7 Mar 2019 19:09:28 +0200 Subject: [PATCH 03/12] [core] auto value for 'text-justify' field --- include/mbgl/style/types.hpp | 1 + .../java/com/mapbox/mapboxsdk/style/layers/Property.java | 5 +++++ .../com/mapbox/mapboxsdk/testapp/style/SymbolLayerTest.java | 2 +- scripts/style-spec.js | 4 +--- src/mbgl/style/types.cpp | 1 + 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/include/mbgl/style/types.hpp b/include/mbgl/style/types.hpp index a472c4fcec4..628e0d23950 100644 --- a/include/mbgl/style/types.hpp +++ b/include/mbgl/style/types.hpp @@ -81,6 +81,7 @@ enum class AlignmentType : uint8_t { }; enum class TextJustifyType : uint8_t { + Auto, Center, Left, Right 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 1c87b9004b8..57cf6271c95 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 @@ -322,6 +322,10 @@ public final class Property { // TEXT_JUSTIFY: Text justification options. + /** + * The text is aligned towards the anchor position. + */ + public static final String TEXT_JUSTIFY_AUTO = "auto"; /** * The text is aligned to the left. */ @@ -339,6 +343,7 @@ public final class Property { * Text justification options. */ @StringDef({ + TEXT_JUSTIFY_AUTO, TEXT_JUSTIFY_LEFT, TEXT_JUSTIFY_CENTER, TEXT_JUSTIFY_RIGHT, 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 9775a5184b7..94e6303c3a8 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 @@ -559,7 +559,7 @@ public void testTextJustifyAsConstant() { assertNull(layer.getTextJustify().getValue()); // Set and Get - String propertyValue = TEXT_JUSTIFY_LEFT; + String propertyValue = TEXT_JUSTIFY_AUTO; layer.setProperties(textJustify(propertyValue)); assertEquals(layer.getTextJustify().getValue(), propertyValue); } diff --git a/scripts/style-spec.js b/scripts/style-spec.js index bcfb7e5c179..b2686a6a77b 100644 --- a/scripts/style-spec.js +++ b/scripts/style-spec.js @@ -3,6 +3,4 @@ var spec = module.exports = require('../mapbox-gl-js/src/style-spec/reference/v8 // Make temporary modifications here when Native doesn't have all features that JS has. delete spec.layout_symbol['symbol-sort-key']; delete spec.layout_symbol['symbol-z-order'].values['auto']; -spec.layout_symbol['symbol-z-order'].default = 'viewport-y'; - -delete spec.layout_symbol['text-justify'].values['auto']; \ No newline at end of file +spec.layout_symbol['symbol-z-order'].default = 'viewport-y'; \ No newline at end of file diff --git a/src/mbgl/style/types.cpp b/src/mbgl/style/types.cpp index 48016ce76ac..889f6029e24 100644 --- a/src/mbgl/style/types.cpp +++ b/src/mbgl/style/types.cpp @@ -83,6 +83,7 @@ MBGL_DEFINE_ENUM(SymbolZOrderType, { }); MBGL_DEFINE_ENUM(TextJustifyType, { + { TextJustifyType::Auto, "auto" }, { TextJustifyType::Center, "center" }, { TextJustifyType::Left, "left" }, { TextJustifyType::Right, "right" }, From 8fd67179a8656a81069d56ea870ba5b5e5892c83 Mon Sep 17 00:00:00 2001 From: Mikhail Pozdnyakov Date: Wed, 20 Mar 2019 18:08:01 +0200 Subject: [PATCH 04/12] [core] Introduce variable text placement for point labels - Layout part --- include/mbgl/util/constants.hpp | 3 + src/mbgl/layout/symbol_instance.cpp | 68 +++++-- src/mbgl/layout/symbol_instance.hpp | 27 ++- src/mbgl/layout/symbol_layout.cpp | 203 +++++++++++++++++--- src/mbgl/layout/symbol_layout.hpp | 7 +- src/mbgl/layout/symbol_projection.cpp | 6 +- src/mbgl/renderer/buckets/symbol_bucket.cpp | 20 +- src/mbgl/renderer/buckets/symbol_bucket.hpp | 6 +- src/mbgl/text/glyph.hpp | 8 +- src/mbgl/text/placement.cpp | 11 +- src/mbgl/text/placement.hpp | 12 +- src/mbgl/text/quads.cpp | 6 +- src/mbgl/text/quads.hpp | 1 + src/mbgl/text/shaping.cpp | 71 +++---- src/mbgl/text/shaping.hpp | 15 +- test/gl/bucket.test.cpp | 2 +- test/text/cross_tile_symbol_index.test.cpp | 24 +-- 17 files changed, 361 insertions(+), 129 deletions(-) diff --git a/include/mbgl/util/constants.hpp b/include/mbgl/util/constants.hpp index b39b3a83e95..f7799a953f7 100644 --- a/include/mbgl/util/constants.hpp +++ b/include/mbgl/util/constants.hpp @@ -41,6 +41,9 @@ constexpr float MIN_ZOOM_F = MIN_ZOOM; constexpr float MAX_ZOOM_F = MAX_ZOOM; constexpr uint8_t DEFAULT_MAX_ZOOM = 22; +// ONE_EM constant used to go between "em" units used in style spec and "points" used internally for layout. +constexpr float ONE_EM = 24.0f; + constexpr uint8_t DEFAULT_PREFETCH_ZOOM_DELTA = 4; constexpr uint64_t DEFAULT_MAX_CACHE_SIZE = 50 * 1024 * 1024; diff --git a/src/mbgl/layout/symbol_instance.cpp b/src/mbgl/layout/symbol_instance.cpp index 139a42113c3..fddaaf7c2de 100644 --- a/src/mbgl/layout/symbol_instance.cpp +++ b/src/mbgl/layout/symbol_instance.cpp @@ -5,13 +5,25 @@ namespace mbgl { using namespace style; +namespace { + +const Shaping& getAnyShaping(const ShapedTextOrientations& shapedTextOrientations) { + if (shapedTextOrientations.right) return shapedTextOrientations.right; + if (shapedTextOrientations.center) return shapedTextOrientations.center; + if (shapedTextOrientations.left) return shapedTextOrientations.left; + if (shapedTextOrientations.vertical) return shapedTextOrientations.vertical; + return shapedTextOrientations.horizontal; +} + +} // namespace + SymbolInstance::SymbolInstance(Anchor& anchor_, GeometryCoordinates line_, - const std::pair& shapedTextOrientations, + const ShapedTextOrientations& shapedTextOrientations, optional shapedIcon, const SymbolLayoutProperties::Evaluated& layout, const float layoutTextSize, - const float textBoxScale, + const float textBoxScale_, const float textPadding, const SymbolPlacementType textPlacement, const std::array textOffset_, @@ -24,43 +36,59 @@ SymbolInstance::SymbolInstance(Anchor& anchor_, const std::size_t dataFeatureIndex_, const std::u16string& key_, const float overscaling, - const float rotate) : + const float rotate, + float radialTextOffset_) : anchor(anchor_), line(line_), hasText(false), hasIcon(shapedIcon), // Create the collision features that will be used to check whether this symbol instance can be placed - textCollisionFeature(line_, anchor, shapedTextOrientations.first, textBoxScale, textPadding, textPlacement, indexedFeature, overscaling, rotate), + // As a collision approximation, we can use either the vertical or any of the horizontal versions of the feature + textCollisionFeature(line_, anchor, getAnyShaping(shapedTextOrientations), textBoxScale_, textPadding, textPlacement, indexedFeature, overscaling, rotate), iconCollisionFeature(line_, anchor, shapedIcon, iconBoxScale, iconPadding, indexedFeature, rotate), + writingModes(WritingModeType::None), layoutFeatureIndex(layoutFeatureIndex_), dataFeatureIndex(dataFeatureIndex_), textOffset(textOffset_), iconOffset(iconOffset_), - key(key_) { + key(key_), + textBoxScale(textBoxScale_), + radialTextOffset(radialTextOffset_) { // Create the quads used for rendering the icon and glyphs. if (shapedIcon) { - iconQuad = getIconQuad(*shapedIcon, layout, layoutTextSize, shapedTextOrientations.first); + iconQuad = getIconQuad(*shapedIcon, layout, layoutTextSize, shapedTextOrientations.horizontal); + } + + if (shapedTextOrientations.right) { + writingModes |= WritingModeType::Horizontal; + rightJustifiedGlyphQuads = getGlyphQuads(shapedTextOrientations.right, textOffset, layout, textPlacement, positions); } - if (shapedTextOrientations.first) { - horizontalGlyphQuads = getGlyphQuads(shapedTextOrientations.first, layout, textPlacement, positions); + + if (shapedTextOrientations.center) { + writingModes |= WritingModeType::Horizontal; + centerJustifiedGlyphQuads = getGlyphQuads(shapedTextOrientations.center, textOffset, layout, textPlacement, positions); } - if (shapedTextOrientations.second) { - verticalGlyphQuads = getGlyphQuads(shapedTextOrientations.second, layout, textPlacement, positions); + + if (shapedTextOrientations.left) { + writingModes |= WritingModeType::Horizontal; + leftJustifiedGlyphQuads = getGlyphQuads(shapedTextOrientations.left, textOffset, layout, textPlacement, positions); } - // 'hasText' depends on finding at least one glyph in the shaping that's also in the GlyphPositionMap - hasText = horizontalGlyphQuads.size() > 0 || verticalGlyphQuads.size() > 0; - if (shapedTextOrientations.first && shapedTextOrientations.second) { - writingModes = WritingModeType::Horizontal | WritingModeType::Vertical; - } else if (shapedTextOrientations.first) { - writingModes = WritingModeType::Horizontal; - } else if (shapedTextOrientations.second) { - writingModes = WritingModeType::Vertical; - } else { - writingModes = WritingModeType::None; + if (shapedTextOrientations.vertical) { + writingModes |= WritingModeType::Vertical; + verticalGlyphQuads = getGlyphQuads(shapedTextOrientations.vertical, textOffset, layout, textPlacement, positions); } + + // 'hasText' depends on finding at least one glyph in the shaping that's also in the GlyphPositionMap + hasText = !rightJustifiedGlyphQuads.empty() || !centerJustifiedGlyphQuads.empty() || !leftJustifiedGlyphQuads.empty() || !verticalGlyphQuads.empty(); } +optional SymbolInstance::getDefaultHorizontalPlacedTextIndex() const { + if (placedRightTextIndex) return placedRightTextIndex; + if (placedCenterTextIndex) return placedCenterTextIndex; + if (placedLeftTextIndex) return placedLeftTextIndex; + return nullopt; +} } // namespace mbgl diff --git a/src/mbgl/layout/symbol_instance.hpp b/src/mbgl/layout/symbol_instance.hpp index 6148d7fe889..44d81ae1e5f 100644 --- a/src/mbgl/layout/symbol_instance.hpp +++ b/src/mbgl/layout/symbol_instance.hpp @@ -11,11 +11,20 @@ namespace mbgl { class Anchor; class IndexedSubfeature; +struct ShapedTextOrientations { + Shaping horizontal; + Shaping vertical; + // The following are used with variable text placement on. + Shaping& right = horizontal; + Shaping center; + Shaping left; +}; + class SymbolInstance { public: SymbolInstance(Anchor& anchor, GeometryCoordinates line, - const std::pair& shapedTextOrientations, + const ShapedTextOrientations& shapedTextOrientations, optional shapedIcon, const style::SymbolLayoutProperties::Evaluated&, const float layoutTextSize, @@ -32,14 +41,20 @@ class SymbolInstance { const std::size_t dataFeatureIndex, const std::u16string& key, const float overscaling, - const float rotate); + const float rotate, + float radialTextOffset); + + optional getDefaultHorizontalPlacedTextIndex() const; Anchor anchor; GeometryCoordinates line; bool hasText; bool hasIcon; - SymbolQuads horizontalGlyphQuads; + SymbolQuads rightJustifiedGlyphQuads; + SymbolQuads centerJustifiedGlyphQuads; + SymbolQuads leftJustifiedGlyphQuads; SymbolQuads verticalGlyphQuads; + optional iconQuad; CollisionFeature textCollisionFeature; CollisionFeature iconCollisionFeature; @@ -50,9 +65,13 @@ class SymbolInstance { std::array iconOffset; std::u16string key; bool isDuplicate; - optional placedTextIndex; + optional placedRightTextIndex; + optional placedCenterTextIndex; + optional placedLeftTextIndex; optional placedVerticalTextIndex; optional placedIconIndex; + float textBoxScale; + float radialTextOffset; uint32_t crossTileID = 0; }; diff --git a/src/mbgl/layout/symbol_layout.cpp b/src/mbgl/layout/symbol_layout.cpp index c40a705d7f1..52ec2aa8432 100644 --- a/src/mbgl/layout/symbol_layout.cpp +++ b/src/mbgl/layout/symbol_layout.cpp @@ -174,44 +174,171 @@ bool SymbolLayout::hasSymbolInstances() const { return !symbolInstances.empty(); } +namespace { + +// The radial offset is to the edge of the text box +// In the horizontal direction, the edge of the text box is where glyphs start +// But in the vertical direction, the glyphs appear to "start" at the baseline +// We don't actually load baseline data, but we assume an offset of ONE_EM - 17 +// (see "yOffset" in shaping.js) +const float baselineOffset = 7.0f; + +// We don't care which shaping we get because this is used for collision purposes +// and all the justifications have the same collision box. +const Shaping& getDefaultHorizontalShaping(const ShapedTextOrientations& shapedTextOrientations) { + if (shapedTextOrientations.right) return shapedTextOrientations.right; + if (shapedTextOrientations.center) return shapedTextOrientations.center; + if (shapedTextOrientations.left) return shapedTextOrientations.left; + return shapedTextOrientations.horizontal; +} + +Shaping& shapingForTextJustifyType(ShapedTextOrientations& shapedTextOrientations, style::TextJustifyType type) { + switch(type) { + case style::TextJustifyType::Right: return shapedTextOrientations.right; + case style::TextJustifyType::Left: return shapedTextOrientations.left; + case style::TextJustifyType::Center: return shapedTextOrientations.center; + default: + assert(false); + return shapedTextOrientations.horizontal; + } +} + +} // namespace + +// static +Point SymbolLayout::evaluateRadialOffset(SymbolAnchorType anchor, float radialOffset) { + Point result{}; + // solve for r where r^2 + r^2 = radialOffset^2 + const float sqrt2 = 1.41421356237f; + const float hypotenuse = radialOffset / sqrt2; + + switch (anchor) { + case SymbolAnchorType::TopRight: + case SymbolAnchorType::TopLeft: + result.y = hypotenuse - baselineOffset; + break; + case SymbolAnchorType::BottomRight: + case SymbolAnchorType::BottomLeft: + result.y = -hypotenuse + baselineOffset; + break; + case SymbolAnchorType::Bottom: + result.y = -radialOffset + baselineOffset; + break; + case SymbolAnchorType::Top: + result.y = radialOffset - baselineOffset; + break; + default: + break; + } + + switch (anchor) { + case SymbolAnchorType::TopRight: + case SymbolAnchorType::BottomRight: + result.x = -hypotenuse; + break; + case SymbolAnchorType::TopLeft: + case SymbolAnchorType::BottomLeft: + result.x = hypotenuse; + break; + case SymbolAnchorType::Left: + result.x = radialOffset; + break; + case SymbolAnchorType::Right: + result.x = -radialOffset; + break; + default: + break; + } + + return result; +} + void SymbolLayout::prepareSymbols(const GlyphMap& glyphMap, const GlyphPositions& glyphPositions, const ImageMap& imageMap, const ImagePositions& imagePositions) { - const bool textAlongLine = layout.get() == AlignmentType::Map && - layout.get() != SymbolPlacementType::Point; + const bool isPointPlacement = layout.get() == SymbolPlacementType::Point; + const bool textAlongLine = layout.get() == AlignmentType::Map && !isPointPlacement; for (auto it = features.begin(); it != features.end(); ++it) { auto& feature = *it; if (feature.geometry.empty()) continue; - std::pair shapedTextOrientations; + ShapedTextOrientations shapedTextOrientations; optional shapedIcon; + Point textOffset; // if feature has text, shape the text if (feature.formattedText) { - auto applyShaping = [&] (const TaggedString& formattedText, WritingModeType writingMode) { - const float oneEm = 24.0f; + const float lineHeight = layout.get() * util::ONE_EM; + const float spacing = util::i18n::allowsLetterSpacing(feature.formattedText->rawText()) ? layout.evaluate(zoom, feature) * util::ONE_EM : 0.0f; + + auto applyShaping = [&] (const TaggedString& formattedText, WritingModeType writingMode, SymbolAnchorType textAnchor, TextJustifyType textJustify) { const Shaping result = getShaping( /* string */ formattedText, - /* maxWidth: ems */ layout.get() == SymbolPlacementType::Point ? - layout.evaluate(zoom, feature) * oneEm : 0, - /* lineHeight: ems */ layout.get() * oneEm, - /* anchor */ layout.evaluate(zoom, feature), - /* justify */ layout.evaluate(zoom, feature), - /* spacing: ems */ util::i18n::allowsLetterSpacing(feature.formattedText->rawText()) ? layout.evaluate(zoom, feature) * oneEm : 0.0f, - /* translate */ Point(layout.evaluate(zoom, feature)[0] * oneEm, layout.evaluate(zoom, feature)[1] * oneEm), - /* verticalHeight */ oneEm, + /* maxWidth: ems */ isPointPlacement ? layout.evaluate(zoom, feature) * util::ONE_EM : 0.0f, + /* ems */ lineHeight, + textAnchor, + textJustify, + /* ems */ spacing, + /* translate */ textOffset, /* writingMode */ writingMode, /* bidirectional algorithm object */ bidi, /* glyphs */ glyphMap); return result; }; + const std::vector variableTextAnchor = layout.evaluate(zoom, feature); + const float radialOffset = layout.evaluate(zoom, feature); + const SymbolAnchorType textAnchor = layout.evaluate(zoom, feature); + if (variableTextAnchor.empty()) { + // Layers with variable anchors use the `text-radial-offset` property and the [x, y] offset vector + // is calculated at placement time instead of layout time + if (radialOffset > 0.0f) { + // The style spec says don't use `text-offset` and `text-radial-offset` together + // but doesn't actually specify what happens if you use both. We go with the radial offset. + textOffset = evaluateRadialOffset(textAnchor, radialOffset * util::ONE_EM); + } else { + textOffset = { layout.evaluate(zoom, feature)[0] * util::ONE_EM, + layout.evaluate(zoom, feature)[1] * util::ONE_EM}; + } + } + TextJustifyType textJustify = textAlongLine ? TextJustifyType::Center : layout.evaluate(zoom, feature); + // If this layer uses text-variable-anchor, generate shapings for all justification possibilities. + if (!textAlongLine && !variableTextAnchor.empty()) { + std::vector justifications; + if (textJustify != TextJustifyType::Auto) { + justifications.push_back(textJustify); + } else { + for (auto anchor : variableTextAnchor) { + justifications.push_back(getAnchorJustification(anchor)); + } + } - shapedTextOrientations.first = applyShaping(*feature.formattedText, WritingModeType::Horizontal); + for (TextJustifyType justification: justifications) { + Shaping& shapingForJustification = shapingForTextJustifyType(shapedTextOrientations, justification); + if (shapingForJustification) { + continue; + } + // If using text-variable-anchor for the layer, we use a center anchor for all shapings and apply + // the offsets for the anchor in the placement step. + Shaping shaping = applyShaping(*feature.formattedText, WritingModeType::Horizontal, SymbolAnchorType::Center, justification); + if (shaping) { + shapingForJustification = std::move(shaping); + } + // TODO: use 'singleLine' optimization. + } + } else { + if (textJustify == TextJustifyType::Auto) { + textJustify = getAnchorJustification(textAnchor); + } + Shaping shaping = applyShaping(*feature.formattedText, WritingModeType::Horizontal, textAnchor, textJustify); + if (shaping) { + shapedTextOrientations.horizontal = std::move(shaping); + } - if (util::i18n::allowsVerticalWritingMode(feature.formattedText->rawText()) && textAlongLine) { - feature.formattedText->verticalizePunctuation(); - shapedTextOrientations.second = applyShaping(*feature.formattedText, WritingModeType::Vertical); + if (util::i18n::allowsVerticalWritingMode(feature.formattedText->rawText()) && textAlongLine) { + feature.formattedText->verticalizePunctuation(); + shapedTextOrientations.vertical = applyShaping(*feature.formattedText, WritingModeType::Vertical, textAnchor, textJustify); + } } } @@ -236,8 +363,8 @@ void SymbolLayout::prepareSymbols(const GlyphMap& glyphMap, const GlyphPositions } // if either shapedText or icon position is present, add the feature - if (shapedTextOrientations.first || shapedIcon) { - addFeature(std::distance(features.begin(), it), feature, shapedTextOrientations, shapedIcon, glyphPositions); + if (getDefaultHorizontalShaping(shapedTextOrientations) || shapedIcon) { + addFeature(std::distance(features.begin(), it), feature, shapedTextOrientations, shapedIcon, glyphPositions, textOffset); } feature.geometry.clear(); @@ -248,15 +375,17 @@ void SymbolLayout::prepareSymbols(const GlyphMap& glyphMap, const GlyphPositions void SymbolLayout::addFeature(const std::size_t layoutFeatureIndex, const SymbolFeature& feature, - const std::pair& shapedTextOrientations, + const ShapedTextOrientations& shapedTextOrientations, optional shapedIcon, - const GlyphPositions& glyphPositions) { + const GlyphPositions& glyphPositions, + Point offset) { const float minScale = 0.5f; const float glyphSize = 24.0f; const float layoutTextSize = layout.evaluate(zoom + 1, feature); const float layoutIconSize = layout.evaluate(zoom + 1, feature); - const std::array textOffset = layout.evaluate(zoom, feature); + const std::array textOffset = {{ offset.x, offset.y }}; + const std::array iconOffset = layout.evaluate(zoom, feature); // To reduce the number of labels that jump around when zooming we need @@ -274,6 +403,7 @@ void SymbolLayout::addFeature(const std::size_t layoutFeatureIndex, const float iconPadding = layout.get() * tilePixelRatio; const float textMaxAngle = layout.get() * util::DEG2RAD; const float rotation = layout.evaluate(zoom, feature); + const float radialTextOffset = layout.evaluate(zoom, feature) * util::ONE_EM; const SymbolPlacementType textPlacement = layout.get() != AlignmentType::Map ? SymbolPlacementType::Point : layout.get(); @@ -296,7 +426,7 @@ void SymbolLayout::addFeature(const std::size_t layoutFeatureIndex, textBoxScale, textPadding, textPlacement, textOffset, iconBoxScale, iconPadding, iconOffset, glyphPositions, indexedFeature, layoutFeatureIndex, feature.index, - feature.formattedText ? feature.formattedText->rawText() : std::u16string(), overscaling, rotation); + feature.formattedText ? feature.formattedText->rawText() : std::u16string(), overscaling, rotation, radialTextOffset); } }; @@ -308,8 +438,8 @@ void SymbolLayout::addFeature(const std::size_t layoutFeatureIndex, Anchors anchors = getAnchors(line, symbolSpacing, textMaxAngle, - (shapedTextOrientations.second ?: shapedTextOrientations.first).left, - (shapedTextOrientations.second ?: shapedTextOrientations.first).right, + (shapedTextOrientations.vertical ?: getDefaultHorizontalShaping(shapedTextOrientations)).left, + (shapedTextOrientations.vertical ?: getDefaultHorizontalShaping(shapedTextOrientations)).right, (shapedIcon ? shapedIcon->left() : 0), (shapedIcon ? shapedIcon->right() : 0), glyphSize, @@ -329,8 +459,8 @@ void SymbolLayout::addFeature(const std::size_t layoutFeatureIndex, if (line.size() > 1) { optional anchor = getCenterAnchor(line, textMaxAngle, - (shapedTextOrientations.second ?: shapedTextOrientations.first).left, - (shapedTextOrientations.second ?: shapedTextOrientations.first).right, + (shapedTextOrientations.vertical ?: getDefaultHorizontalShaping(shapedTextOrientations)).left, + (shapedTextOrientations.vertical ?: getDefaultHorizontalShaping(shapedTextOrientations)).right, (shapedIcon ? shapedIcon->left() : 0), (shapedIcon ? shapedIcon->right() : 0), glyphSize, @@ -414,7 +544,7 @@ void SymbolLayout::createBucket(const ImagePositions&, std::unique_ptr() || layout.get() || layout.get() || layout.get()); - auto bucket = std::make_shared(layout, layerPaintProperties, textSize, iconSize, zoom, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(symbolInstances)); + auto bucket = std::make_shared(layout, layerPaintProperties, textSize, iconSize, zoom, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(symbolInstances), tilePixelRatio); for (SymbolInstance &symbolInstance : bucket->symbolInstances) { @@ -426,13 +556,22 @@ void SymbolLayout::createBucket(const ImagePositions&, std::unique_ptr lastAddedSection; + if (!symbolInstance.rightJustifiedGlyphQuads.empty()) { + lastAddedSection = addSymbolGlyphQuads(*bucket, symbolInstance, feature, symbolInstance.writingModes, symbolInstance.placedRightTextIndex, symbolInstance.rightJustifiedGlyphQuads, lastAddedSection); + } + if (!symbolInstance.centerJustifiedGlyphQuads.empty()) { + lastAddedSection = addSymbolGlyphQuads(*bucket, symbolInstance, feature, symbolInstance.writingModes, symbolInstance.placedCenterTextIndex, symbolInstance.centerJustifiedGlyphQuads, lastAddedSection); + } + if (!symbolInstance.leftJustifiedGlyphQuads.empty()) { + lastAddedSection = addSymbolGlyphQuads(*bucket, symbolInstance, feature, symbolInstance.writingModes, symbolInstance.placedLeftTextIndex, symbolInstance.leftJustifiedGlyphQuads, lastAddedSection); + } if (symbolInstance.writingModes & WritingModeType::Vertical) { - index = addSymbolGlyphQuads(*bucket, symbolInstance, feature, WritingModeType::Vertical, symbolInstance.placedVerticalTextIndex, symbolInstance.verticalGlyphQuads, index); + lastAddedSection = addSymbolGlyphQuads(*bucket, symbolInstance, feature, WritingModeType::Vertical, symbolInstance.placedVerticalTextIndex, symbolInstance.verticalGlyphQuads, lastAddedSection); } - - updatePaintPropertiesForSection(*bucket, feature, index); + assert(lastAddedSection); // True, as hasText == true; + updatePaintPropertiesForSection(*bucket, feature, *lastAddedSection); } if (hasIcon) { diff --git a/src/mbgl/layout/symbol_layout.hpp b/src/mbgl/layout/symbol_layout.hpp index 53c66d31fed..d88c79c552d 100644 --- a/src/mbgl/layout/symbol_layout.hpp +++ b/src/mbgl/layout/symbol_layout.hpp @@ -46,12 +46,15 @@ class SymbolLayout final : public Layout { const std::string bucketLeaderID; std::vector symbolInstances; + static Point evaluateRadialOffset(style::SymbolAnchorType anchor, float radialOffset); + private: void addFeature(const size_t, const SymbolFeature&, - const std::pair& shapedTextOrientations, + const ShapedTextOrientations& shapedTextOrientations, optional shapedIcon, - const GlyphPositions&); + const GlyphPositions&, + Point textOffset); bool anchorIsTooClose(const std::u16string& text, const float repeatDistance, const Anchor&); std::map> compareText; diff --git a/src/mbgl/layout/symbol_projection.cpp b/src/mbgl/layout/symbol_projection.cpp index dff2a569ac7..b7858f8deb3 100644 --- a/src/mbgl/layout/symbol_projection.cpp +++ b/src/mbgl/layout/symbol_projection.cpp @@ -291,9 +291,9 @@ namespace mbgl { gfx::VertexVector>& dynamicVertexArray, const Point& projectedAnchorPoint, const float aspectRatio) { - const float fontScale = fontSize / 24.0; - const float lineOffsetX = symbol.lineOffset[0] * fontSize; - const float lineOffsetY = symbol.lineOffset[1] * fontSize; + const float fontScale = fontSize / util::ONE_EM; + const float lineOffsetX = symbol.lineOffset[0] * fontScale; + const float lineOffsetY = symbol.lineOffset[1] * fontScale; std::vector placedGlyphs; if (symbol.glyphOffsets.size() > 1) { diff --git a/src/mbgl/renderer/buckets/symbol_bucket.cpp b/src/mbgl/renderer/buckets/symbol_bucket.cpp index 9220235f1dc..38342b44eeb 100644 --- a/src/mbgl/renderer/buckets/symbol_bucket.cpp +++ b/src/mbgl/renderer/buckets/symbol_bucket.cpp @@ -17,7 +17,8 @@ SymbolBucket::SymbolBucket(style::SymbolLayoutProperties::PossiblyEvaluated layo bool iconsNeedLinear_, bool sortFeaturesByY_, const std::string bucketName_, - const std::vector&& symbolInstances_) + const std::vector&& symbolInstances_, + float tilePixelRatio_) : layout(std::move(layout_)), sdfIcons(sdfIcons_), iconsNeedLinear(iconsNeedLinear_ || iconSize.isDataDriven() || !iconSize.isZoomConstant()), @@ -25,7 +26,8 @@ SymbolBucket::SymbolBucket(style::SymbolLayoutProperties::PossiblyEvaluated layo bucketLeaderID(std::move(bucketName_)), symbolInstances(std::move(symbolInstances_)), textSizeBinder(SymbolSizeBinder::create(zoom, textSize, TextSize::defaultValue())), - iconSizeBinder(SymbolSizeBinder::create(zoom, iconSize, IconSize::defaultValue())) { + iconSizeBinder(SymbolSizeBinder::create(zoom, iconSize, IconSize::defaultValue())), + tilePixelRatio(tilePixelRatio_) { for (const auto& pair : paintProperties_) { auto layerPaintProperties = pair.second; @@ -218,12 +220,22 @@ void SymbolBucket::sortFeatures(const float angle) { const SymbolInstance& symbolInstance = symbolInstances[i]; featureSortOrder->push_back(symbolInstance.dataFeatureIndex); - if (symbolInstance.placedTextIndex) { - addPlacedSymbol(text.triangles, text.placedSymbols[*symbolInstance.placedTextIndex]); + if (symbolInstance.placedRightTextIndex) { + addPlacedSymbol(text.triangles, text.placedSymbols[*symbolInstance.placedRightTextIndex]); } + + if (symbolInstance.placedCenterTextIndex) { + addPlacedSymbol(text.triangles, text.placedSymbols[*symbolInstance.placedCenterTextIndex]); + } + + if (symbolInstance.placedLeftTextIndex) { + addPlacedSymbol(text.triangles, text.placedSymbols[*symbolInstance.placedLeftTextIndex]); + } + if (symbolInstance.placedVerticalTextIndex) { addPlacedSymbol(text.triangles, text.placedSymbols[*symbolInstance.placedVerticalTextIndex]); } + if (symbolInstance.placedIconIndex) { addPlacedSymbol(icon.triangles, icon.placedSymbols[*symbolInstance.placedIconIndex]); } diff --git a/src/mbgl/renderer/buckets/symbol_bucket.hpp b/src/mbgl/renderer/buckets/symbol_bucket.hpp index fafa2592fe2..f8ffc1a8eb6 100644 --- a/src/mbgl/renderer/buckets/symbol_bucket.hpp +++ b/src/mbgl/renderer/buckets/symbol_bucket.hpp @@ -35,6 +35,8 @@ class PlacedSymbol { std::vector glyphOffsets; bool hidden; size_t vertexStartIndex; + // The crossTileID is only filled/used on the foreground for variable text anchors + uint32_t crossTileID = 0u; }; class SymbolBucket final : public Bucket { @@ -48,7 +50,8 @@ class SymbolBucket final : public Bucket { bool iconsNeedLinear, bool sortFeaturesByY, const std::string bucketLeaderID, - const std::vector&&); + const std::vector&&, + const float tilePixelRatio); ~SymbolBucket() override; void upload(gfx::Context&) override; @@ -130,6 +133,7 @@ class SymbolBucket final : public Bucket { optional indexBuffer; } collisionCircle; + const float tilePixelRatio; uint32_t bucketInstanceId = 0; bool justReloaded = false; optional hasFormatSectionOverrides_; diff --git a/src/mbgl/text/glyph.hpp b/src/mbgl/text/glyph.hpp index c97b242c107..7d6415c0577 100644 --- a/src/mbgl/text/glyph.hpp +++ b/src/mbgl/text/glyph.hpp @@ -78,15 +78,17 @@ enum class WritingModeType : uint8_t; class Shaping { public: - explicit Shaping() = default; - explicit Shaping(float x, float y, WritingModeType writingMode_) - : top(y), bottom(y), left(x), right(x), writingMode(writingMode_) {} + Shaping() = default; + explicit Shaping(float x, float y, WritingModeType writingMode_, std::size_t lineCount_) + : top(y), bottom(y), left(x), right(x), writingMode(writingMode_), lineCount(lineCount_) {} std::vector positionedGlyphs; float top = 0; float bottom = 0; float left = 0; float right = 0; WritingModeType writingMode; + std::size_t lineCount = 0u; + std::string text = {}; explicit operator bool() const { return !positionedGlyphs.empty(); } }; diff --git a/src/mbgl/text/placement.cpp b/src/mbgl/text/placement.cpp index 4cc12b09801..bb7e2d1a6c1 100644 --- a/src/mbgl/text/placement.cpp +++ b/src/mbgl/text/placement.cpp @@ -176,8 +176,8 @@ void Placement::placeLayerBucket( bool placeIcon = false; bool offscreen = true; - if (symbolInstance.placedTextIndex) { - PlacedSymbol& placedSymbol = bucket.text.placedSymbols.at(*symbolInstance.placedTextIndex); + if (symbolInstance.placedRightTextIndex) { + PlacedSymbol& placedSymbol = bucket.text.placedSymbols.at(*symbolInstance.placedRightTextIndex); const float fontSize = evaluateSizeForFeature(partiallyEvaluatedTextSize, placedSymbol); auto placed = collisionIndex.placeFeature(symbolInstance.textCollisionFeature, @@ -339,14 +339,15 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, std::set& if (symbolInstance.hasText) { auto opacityVertex = SymbolSDFTextProgram::opacityVertex(opacityState.text.placed, opacityState.text.opacity); - for (size_t i = 0; i < symbolInstance.horizontalGlyphQuads.size() * 4; i++) { + for (size_t i = 0; i < symbolInstance.rightJustifiedGlyphQuads.size() * 4; i++) { bucket.text.opacityVertices.emplace_back(opacityVertex); } for (size_t i = 0; i < symbolInstance.verticalGlyphQuads.size() * 4; i++) { bucket.text.opacityVertices.emplace_back(opacityVertex); } - if (symbolInstance.placedTextIndex) { - bucket.text.placedSymbols[*symbolInstance.placedTextIndex].hidden = opacityState.isHidden(); + if (symbolInstance.placedRightTextIndex) { + PlacedSymbol& placed = bucket.text.placedSymbols[*symbolInstance.placedRightTextIndex]; + placed.hidden = opacityState.isHidden(); } if (symbolInstance.placedVerticalTextIndex) { bucket.text.placedSymbols[*symbolInstance.placedVerticalTextIndex].hidden = opacityState.isHidden(); diff --git a/src/mbgl/text/placement.hpp b/src/mbgl/text/placement.hpp index cc23110e54c..32310f723e6 100644 --- a/src/mbgl/text/placement.hpp +++ b/src/mbgl/text/placement.hpp @@ -31,6 +31,16 @@ class JointOpacityState { OpacityState text; }; +class VariableOffset { +public: + float radialOffset; + float width; + float height; + style::TextVariableAnchorType anchor; + float textBoxScale; + optional prevAnchor; +}; + class JointPlacement { public: JointPlacement(bool text_, bool icon_, bool skipFade_) @@ -45,7 +55,7 @@ class JointPlacement { // visible right away. const bool skipFade; }; - + struct RetainedQueryData { uint32_t bucketInstanceId; std::shared_ptr featureIndex; diff --git a/src/mbgl/text/quads.cpp b/src/mbgl/text/quads.cpp index ec0045caad4..6be5d8c01ec 100644 --- a/src/mbgl/text/quads.cpp +++ b/src/mbgl/text/quads.cpp @@ -92,16 +92,12 @@ SymbolQuad getIconQuad(const PositionedIcon& shapedIcon, } SymbolQuads getGlyphQuads(const Shaping& shapedText, + const std::array textOffset, const SymbolLayoutProperties::Evaluated& layout, const style::SymbolPlacementType placement, const GlyphPositions& positions) { const float textRotate = layout.get() * util::DEG2RAD; - const float oneEm = 24.0; - std::array textOffset = layout.get(); - textOffset[0] *= oneEm; - textOffset[1] *= oneEm; - SymbolQuads quads; for (const PositionedGlyph &positionedGlyph: shapedText.positionedGlyphs) { diff --git a/src/mbgl/text/quads.hpp b/src/mbgl/text/quads.hpp index f41a4fec66e..0bb892e4d1b 100644 --- a/src/mbgl/text/quads.hpp +++ b/src/mbgl/text/quads.hpp @@ -49,6 +49,7 @@ SymbolQuad getIconQuad(const PositionedIcon& shapedIcon, const Shaping& shapedText); SymbolQuads getGlyphQuads(const Shaping& shapedText, + const std::array textOffset, const style::SymbolLayoutProperties::Evaluated&, style::SymbolPlacementType placement, const GlyphPositions& positions); diff --git a/src/mbgl/text/shaping.cpp b/src/mbgl/text/shaping.cpp index 02dbf146e12..348c2ddccc0 100644 --- a/src/mbgl/text/shaping.cpp +++ b/src/mbgl/text/shaping.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -10,58 +11,61 @@ namespace mbgl { -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; +// static +AnchorAlignment AnchorAlignment::getAnchorAlignment(style::SymbolAnchorType anchor) { + AnchorAlignment result(0.5f, 0.5f); 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; + result.horizontalAlign = 1.0f; break; case style::SymbolAnchorType::Left: case style::SymbolAnchorType::TopLeft: case style::SymbolAnchorType::BottomLeft: - horizontalAlign = 0; + result.horizontalAlign = 0.0f; break; + default: + 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; + result.verticalAlign = 1.0f; break; case style::SymbolAnchorType::Top: case style::SymbolAnchorType::TopLeft: case style::SymbolAnchorType::TopRight: - verticalAlign = 0; + result.verticalAlign = 0.0f; + break; + default: break; } - return AnchorAlignment(horizontalAlign, verticalAlign); + return result; +} + +style::TextJustifyType getAnchorJustification(style::SymbolAnchorType anchor) { + switch (anchor) { + case style::SymbolAnchorType::Right: + case style::SymbolAnchorType::TopRight: + case style::SymbolAnchorType::BottomRight: + return style::TextJustifyType::Right; + case style::SymbolAnchorType::Left: + case style::SymbolAnchorType::TopLeft: + case style::SymbolAnchorType::BottomLeft: + return style::TextJustifyType::Left; + default: + return style::TextJustifyType::Center; + } } PositionedIcon PositionedIcon::shapeIcon(const ImagePosition& image, const std::array& iconOffset, style::SymbolAnchorType iconAnchor, const float iconRotation) { - AnchorAlignment anchorAlign = getAnchorAlignment(iconAnchor); + AnchorAlignment anchorAlign = AnchorAlignment::getAnchorAlignment(iconAnchor); float dx = iconOffset[0]; float dy = iconOffset[1]; float x1 = dx - image.displaySize()[0] * anchorAlign.horizontalAlign; @@ -269,7 +273,6 @@ void shapeLines(Shaping& shaping, const float lineHeight, const style::SymbolAnchorType textAnchor, const style::TextJustifyType textJustify, - const float verticalHeight, const WritingModeType writingMode, const GlyphMap& glyphMap) { @@ -314,7 +317,7 @@ void shapeLines(Shaping& shaping, // We don't know the baseline, but since we're laying out // at 24 points, we can calculate how much it will move when // we scale up or down. - const double baselineOffset = (lineMaxScale - section.scale) * 24; + const double baselineOffset = (lineMaxScale - section.scale) * util::ONE_EM; const Glyph& glyph = **it->second; @@ -323,7 +326,7 @@ void shapeLines(Shaping& shaping, x += glyph.metrics.advance * section.scale + spacing; } else { shaping.positionedGlyphs.emplace_back(codePoint, x, baselineOffset, true, section.fontStackHash, section.scale, sectionIndex); - x += verticalHeight * section.scale + spacing; + x += util::ONE_EM * section.scale + spacing; } } @@ -340,7 +343,7 @@ void shapeLines(Shaping& shaping, y += lineHeight * lineMaxScale; } - auto anchorAlign = getAnchorAlignment(textAnchor); + auto anchorAlign = AnchorAlignment::getAnchorAlignment(textAnchor); align(shaping, justify, anchorAlign.horizontalAlign, anchorAlign.verticalAlign, maxLineLength, lineHeight, lines.size()); @@ -360,12 +363,10 @@ const Shaping getShaping(const TaggedString& formattedString, const style::TextJustifyType textJustify, const float spacing, const Point& translate, - const float verticalHeight, + //const float verticalHeight, const WritingModeType writingMode, BiDi& bidi, - const GlyphMap& glyphs) { - Shaping shaping(translate.x, translate.y, writingMode); - + const GlyphMap& glyphs) { std::vector reorderedLines; if (formattedString.sectionCount() == 1) { auto untaggedLines = bidi.processText(formattedString.rawText(), @@ -380,9 +381,9 @@ const Shaping getShaping(const TaggedString& formattedString, reorderedLines.emplace_back(line, formattedString.getSections()); } } - + Shaping shaping(translate.x, translate.y, writingMode, reorderedLines.size()); shapeLines(shaping, reorderedLines, spacing, lineHeight, textAnchor, - textJustify, verticalHeight, writingMode, glyphs); + textJustify, writingMode, glyphs); return shaping; } diff --git a/src/mbgl/text/shaping.hpp b/src/mbgl/text/shaping.hpp index 50ac893098c..766b1ce233f 100644 --- a/src/mbgl/text/shaping.hpp +++ b/src/mbgl/text/shaping.hpp @@ -7,6 +7,20 @@ namespace mbgl { +struct AnchorAlignment { + AnchorAlignment(float horizontal, float vertical) + : horizontalAlign(horizontal), verticalAlign(vertical) { + } + + static AnchorAlignment getAnchorAlignment(style::SymbolAnchorType anchor); + + float horizontalAlign; + float verticalAlign; +}; + +// Choose the justification that matches the direction of the TextAnchor +style::TextJustifyType getAnchorJustification(style::SymbolAnchorType anchor); + class SymbolFeature; class BiDi; @@ -53,7 +67,6 @@ const Shaping getShaping(const TaggedString& string, style::TextJustifyType textJustify, float spacing, const Point& translate, - float verticalHeight, const WritingModeType, BiDi& bidi, const GlyphMap& glyphs); diff --git a/test/gl/bucket.test.cpp b/test/gl/bucket.test.cpp index bc9f6aac5a9..2be224382a2 100644 --- a/test/gl/bucket.test.cpp +++ b/test/gl/bucket.test.cpp @@ -116,7 +116,7 @@ TEST(Buckets, SymbolBucket) { std::vector symbolInstances; gl::Context context; - SymbolBucket bucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(symbolInstances) }; + SymbolBucket bucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(symbolInstances), 1.0f }; ASSERT_FALSE(bucket.hasIconData()); ASSERT_FALSE(bucket.hasTextData()); ASSERT_FALSE(bucket.hasCollisionBoxData()); diff --git a/test/text/cross_tile_symbol_index.test.cpp b/test/text/cross_tile_symbol_index.test.cpp index 67f82413b2e..f121781766d 100644 --- a/test/text/cross_tile_symbol_index.test.cpp +++ b/test/text/cross_tile_symbol_index.test.cpp @@ -7,11 +7,11 @@ using namespace mbgl; SymbolInstance makeSymbolInstance(float x, float y, std::u16string key) { GeometryCoordinates line; GlyphPositions positions; - const std::pair shaping(Shaping{}, Shaping{}); + const ShapedTextOrientations shaping{}; style::SymbolLayoutProperties::Evaluated layout_; IndexedSubfeature subfeature(0, "", "", 0); Anchor anchor(x, y, 0, 0); - return {anchor, line, shaping, {}, layout_, 0, 0, 0, style::SymbolPlacementType::Point, {{0, 0}}, 0, 0, {{0, 0}}, positions, subfeature, 0, 0, key, 0, 0}; + return SymbolInstance(anchor, line, shaping, {}, layout_, 0, 0, 0, style::SymbolPlacementType::Point, {{0, 0}}, 0, 0, {{0, 0}}, positions, subfeature, 0, 0, key, 0, 0, 0.0f); } @@ -31,7 +31,7 @@ TEST(CrossTileSymbolLayerIndex, addBucket) { std::vector mainInstances; mainInstances.push_back(makeSymbolInstance(1000, 1000, u"Detroit")); mainInstances.push_back(makeSymbolInstance(2000, 2000, u"Toronto")); - SymbolBucket mainBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(mainInstances) }; + SymbolBucket mainBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(mainInstances), 1.0f }; mainBucket.bucketInstanceId = ++maxBucketInstanceId; index.addBucket(mainID, mainBucket, maxCrossTileID); @@ -46,7 +46,7 @@ TEST(CrossTileSymbolLayerIndex, addBucket) { childInstances.push_back(makeSymbolInstance(2000, 2000, u"Windsor")); childInstances.push_back(makeSymbolInstance(3000, 3000, u"Toronto")); childInstances.push_back(makeSymbolInstance(4001, 4001, u"Toronto")); - SymbolBucket childBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(childInstances) }; + SymbolBucket childBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(childInstances), 1.0f }; childBucket.bucketInstanceId = ++maxBucketInstanceId; index.addBucket(childID, childBucket, maxCrossTileID); @@ -62,7 +62,7 @@ TEST(CrossTileSymbolLayerIndex, addBucket) { OverscaledTileID parentID(5, 0, 5, 4, 4); std::vector parentInstances; parentInstances.push_back(makeSymbolInstance(500, 500, u"Detroit")); - SymbolBucket parentBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(parentInstances) }; + SymbolBucket parentBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(parentInstances), 1.0f }; parentBucket.bucketInstanceId = ++maxBucketInstanceId; index.addBucket(parentID, parentBucket, maxCrossTileID); @@ -78,7 +78,7 @@ TEST(CrossTileSymbolLayerIndex, addBucket) { std::vector grandchildInstances; grandchildInstances.push_back(makeSymbolInstance(4000, 4000, u"Detroit")); grandchildInstances.push_back(makeSymbolInstance(4000, 4000, u"Windsor")); - SymbolBucket grandchildBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(grandchildInstances) }; + SymbolBucket grandchildBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(grandchildInstances), 1.0f }; grandchildBucket.bucketInstanceId = ++maxBucketInstanceId; index.addBucket(grandchildID, grandchildBucket, maxCrossTileID); @@ -104,13 +104,13 @@ TEST(CrossTileSymbolLayerIndex, resetIDs) { OverscaledTileID mainID(6, 0, 6, 8, 8); std::vector mainInstances; mainInstances.push_back(makeSymbolInstance(1000, 1000, u"Detroit")); - SymbolBucket mainBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(mainInstances) }; + SymbolBucket mainBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(mainInstances), 1.0f }; mainBucket.bucketInstanceId = ++maxBucketInstanceId; OverscaledTileID childID(7, 0, 7, 16, 16); std::vector childInstances; childInstances.push_back(makeSymbolInstance(2000, 2000, u"Detroit")); - SymbolBucket childBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(childInstances) }; + SymbolBucket childBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(childInstances), 1.0f }; childBucket.bucketInstanceId = ++maxBucketInstanceId; // assigns a new id @@ -145,7 +145,7 @@ TEST(CrossTileSymbolLayerIndex, noDuplicatesWithinZoomLevel) { std::vector mainInstances; mainInstances.push_back(makeSymbolInstance(1000, 1000, u"")); // A mainInstances.push_back(makeSymbolInstance(1000, 1000, u"")); // B - SymbolBucket mainBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(mainInstances) }; + SymbolBucket mainBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(mainInstances), 1.0f }; mainBucket.bucketInstanceId = ++maxBucketInstanceId; OverscaledTileID childID(7, 0, 7, 16, 16); @@ -153,7 +153,7 @@ TEST(CrossTileSymbolLayerIndex, noDuplicatesWithinZoomLevel) { childInstances.push_back(makeSymbolInstance(2000, 2000, u"")); // A' childInstances.push_back(makeSymbolInstance(2000, 2000, u"")); // B' childInstances.push_back(makeSymbolInstance(2000, 2000, u"")); // C' - SymbolBucket childBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(childInstances) }; + SymbolBucket childBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(childInstances), 1.0f }; childBucket.bucketInstanceId = ++maxBucketInstanceId; // assigns new ids @@ -183,14 +183,14 @@ TEST(CrossTileSymbolLayerIndex, bucketReplacement) { std::vector firstInstances; firstInstances.push_back(makeSymbolInstance(1000, 1000, u"")); // A firstInstances.push_back(makeSymbolInstance(1000, 1000, u"")); // B - SymbolBucket firstBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(firstInstances) }; + SymbolBucket firstBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(firstInstances), 1.0f }; firstBucket.bucketInstanceId = ++maxBucketInstanceId; std::vector secondInstances; secondInstances.push_back(makeSymbolInstance(1000, 1000, u"")); // A' secondInstances.push_back(makeSymbolInstance(1000, 1000, u"")); // B' secondInstances.push_back(makeSymbolInstance(1000, 1000, u"")); // C' - SymbolBucket secondBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(secondInstances) }; + SymbolBucket secondBucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(secondInstances), 1.0f }; secondBucket.bucketInstanceId = ++maxBucketInstanceId; // assigns new ids From 9711b9d0f8497e6dd42eb7f20587c9851eb71bc8 Mon Sep 17 00:00:00 2001 From: Alexander Shalamov Date: Wed, 13 Mar 2019 19:01:58 +0200 Subject: [PATCH 05/12] Export hideGlyphs and addDynamicAttributes symbols --- src/mbgl/layout/symbol_projection.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/mbgl/layout/symbol_projection.hpp b/src/mbgl/layout/symbol_projection.hpp index 03e660b4749..3699eee2903 100644 --- a/src/mbgl/layout/symbol_projection.hpp +++ b/src/mbgl/layout/symbol_projection.hpp @@ -60,4 +60,9 @@ namespace mbgl { const mat4& labelPlaneMatrix, const bool returnTileDistance); + void hideGlyphs(std::size_t numGlyphs, gfx::VertexVector>& dynamicVertices); + void addDynamicAttributes(const Point& anchorPoint, + const float angle, + gfx::VertexVector>& dynamicVertices); + } // end namespace mbgl From cefa35b9ab6a5bd7a399c0486c7d3b07f54c1240 Mon Sep 17 00:00:00 2001 From: Alexander Shalamov Date: Wed, 13 Mar 2019 23:17:14 +0200 Subject: [PATCH 06/12] [core] Introduce variable text placement for point labels - Render part --- src/mbgl/programs/symbol_program.cpp | 7 +- src/mbgl/programs/symbol_program.hpp | 2 + .../renderer/layers/render_symbol_layer.cpp | 92 ++++++++++++++++++- src/mbgl/renderer/paint_parameters.hpp | 2 + src/mbgl/text/placement.hpp | 6 ++ 5 files changed, 103 insertions(+), 6 deletions(-) diff --git a/src/mbgl/programs/symbol_program.cpp b/src/mbgl/programs/symbol_program.cpp index 01955061290..d6a7a103681 100644 --- a/src/mbgl/programs/symbol_program.cpp +++ b/src/mbgl/programs/symbol_program.cpp @@ -42,6 +42,7 @@ std::unique_ptr SymbolSizeBinder::create(const float tileZoom, template Values makeValues(const bool isText, + const bool hasVariablePacement, const style::SymbolPropertyValues& values, const Size& texsize, const std::array& pixelsToGLUnits, @@ -71,7 +72,7 @@ Values makeValues(const bool isText, const bool rotateInShader = rotateWithMap && !pitchWithMap && !alongLine; mat4 labelPlaneMatrix; - if (alongLine) { + if (alongLine || (isText && hasVariablePacement)) { // For labels that follow lines the first part of the projection is handled on the cpu. // Pass an identity matrix because no transformation needs to be done in the vertex shader. matrix::identity(labelPlaneMatrix); @@ -106,6 +107,7 @@ Values makeValues(const bool isText, SymbolIconProgram::LayoutUniformValues SymbolIconProgram::layoutUniformValues(const bool isText, + const bool hasVariablePacement, const style::SymbolPropertyValues& values, const Size& texsize, const std::array& pixelsToGLUnits, @@ -115,6 +117,7 @@ SymbolIconProgram::layoutUniformValues(const bool isText, const float symbolFadeChange) { return makeValues( isText, + hasVariablePacement, values, texsize, pixelsToGLUnits, @@ -128,6 +131,7 @@ SymbolIconProgram::layoutUniformValues(const bool isText, template typename SymbolSDFProgram::LayoutUniformValues SymbolSDFProgram::layoutUniformValues(const bool isText, + const bool hasVariablePacement, const style::SymbolPropertyValues& values, const Size& texsize, const std::array& pixelsToGLUnits, @@ -142,6 +146,7 @@ SymbolSDFProgram::layoutUniformValues(const bool isText, return makeValues::LayoutUniformValues>( isText, + hasVariablePacement, values, texsize, pixelsToGLUnits, diff --git a/src/mbgl/programs/symbol_program.hpp b/src/mbgl/programs/symbol_program.hpp index 9c00caef845..d640eb74daa 100644 --- a/src/mbgl/programs/symbol_program.hpp +++ b/src/mbgl/programs/symbol_program.hpp @@ -367,6 +367,7 @@ class SymbolIconProgram : public SymbolProgram< using SymbolProgram::SymbolProgram; static LayoutUniformValues layoutUniformValues(const bool isText, + const bool hasVariablePacement, const style::SymbolPropertyValues&, const Size& texsize, const std::array& pixelsToGLUnits, @@ -436,6 +437,7 @@ class SymbolSDFProgram : public SymbolProgram< using BaseProgram::BaseProgram; static LayoutUniformValues layoutUniformValues(const bool isText, + const bool hasVariablePacement, const style::SymbolPropertyValues&, const Size& texsize, const std::array& pixelsToGLUnits, diff --git a/src/mbgl/renderer/layers/render_symbol_layer.cpp b/src/mbgl/renderer/layers/render_symbol_layer.cpp index 72c641e95aa..83752753767 100644 --- a/src/mbgl/renderer/layers/render_symbol_layer.cpp +++ b/src/mbgl/renderer/layers/render_symbol_layer.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -14,6 +15,7 @@ #include #include #include +#include #include #include @@ -22,6 +24,19 @@ namespace mbgl { using namespace style; +namespace { +Point calculateVariableRenderShift(style::SymbolAnchorType anchor, float width, float height, float radialOffset, float textBoxScale, float renderTextSize) { + AnchorAlignment alignment = AnchorAlignment::getAnchorAlignment(anchor); + float shiftX = -(alignment.horizontalAlign - 0.5f) * width; + float shiftY = -(alignment.verticalAlign - 0.5f) * height; + Point offset = SymbolLayout::evaluateRadialOffset(anchor, radialOffset); + return Point( + (shiftX / textBoxScale + offset.x) * renderTextSize, + (shiftY / textBoxScale + offset.y) * renderTextSize + ); +} +} // namespace + RenderSymbolLayer::RenderSymbolLayer(Immutable _impl) : RenderLayer(std::move(_impl)), unevaluated(impl().paint.untransitioned()) { @@ -168,7 +183,7 @@ void RenderSymbolLayer::render(PaintParameters& parameters, RenderSource*) { if (bucket.sdfIcons) { if (values.hasHalo) { draw(parameters.programs.getSymbolLayerPrograms().symbolIconSDF, - SymbolSDFIconProgram::layoutUniformValues(false, values, texsize, parameters.pixelsToGLUnits, alongLine, tile, parameters.state, parameters.symbolFadeChange, SymbolSDFPart::Halo), + SymbolSDFIconProgram::layoutUniformValues(false, false, values, texsize, parameters.pixelsToGLUnits, alongLine, tile, parameters.state, parameters.symbolFadeChange, SymbolSDFPart::Halo), bucket.icon, bucket.iconSizeBinder, values, @@ -181,7 +196,7 @@ void RenderSymbolLayer::render(PaintParameters& parameters, RenderSource*) { if (values.hasFill) { draw(parameters.programs.getSymbolLayerPrograms().symbolIconSDF, - SymbolSDFIconProgram::layoutUniformValues(false, values, texsize, parameters.pixelsToGLUnits, alongLine, tile, parameters.state, parameters.symbolFadeChange, SymbolSDFPart::Fill), + SymbolSDFIconProgram::layoutUniformValues(false, false, values, texsize, parameters.pixelsToGLUnits, alongLine, tile, parameters.state, parameters.symbolFadeChange, SymbolSDFPart::Fill), bucket.icon, bucket.iconSizeBinder, values, @@ -193,7 +208,7 @@ void RenderSymbolLayer::render(PaintParameters& parameters, RenderSource*) { } } else { draw(parameters.programs.getSymbolLayerPrograms().symbolIcon, - SymbolIconProgram::layoutUniformValues(false, values, texsize, parameters.pixelsToGLUnits, alongLine, tile, parameters.state, parameters.symbolFadeChange), + SymbolIconProgram::layoutUniformValues(false, false, values, texsize, parameters.pixelsToGLUnits, alongLine, tile, parameters.state, parameters.symbolFadeChange), bucket.icon, bucket.iconSizeBinder, values, @@ -211,6 +226,7 @@ void RenderSymbolLayer::render(PaintParameters& parameters, RenderSource*) { auto values = textPropertyValues(evaluated_, layout); const auto& paintPropertyValues = textPaintProperties(evaluated_); + bool hasVariablePacement = false; const bool alongLine = layout.get() != SymbolPlacementType::Point && layout.get() == AlignmentType::Map; @@ -225,13 +241,79 @@ void RenderSymbolLayer::render(PaintParameters& parameters, RenderSource*) { parameters.state); parameters.context.updateVertexBuffer(*bucket.text.dynamicVertexBuffer, std::move(bucket.text.dynamicVertices)); + } else if (!layout.get().empty()) { + bucket.text.dynamicVertices.clear(); + + const auto partiallyEvaluatedSize = bucket.textSizeBinder->evaluateForZoom(parameters.state.getZoom()); + const float tileScale = std::pow(2, parameters.state.getZoom() - tile.tile.id.overscaledZ); + const bool rotateWithMap = layout.get() == AlignmentType::Map; + const bool pitchWithMap = layout.get() == AlignmentType::Map; + const float pixelsToTileUnits = tile.id.pixelsToTileUnits(1.0, parameters.state.getZoom()); + const auto labelPlaneMatrix = getLabelPlaneMatrix(tile.matrix, pitchWithMap, rotateWithMap, parameters.state, pixelsToTileUnits); + + for (const PlacedSymbol& symbol : bucket.text.placedSymbols) { + optional variableOffset; + if (!symbol.hidden && symbol.crossTileID != 0u) { + auto it = parameters.variableOffsets.find(symbol.crossTileID); + if (it != parameters.variableOffsets.end()) { + variableOffset = it->second; + hasVariablePacement |= true; + } + } + + if (!variableOffset) { + // These symbols are from a justification that is not being used, or a label that wasn't placed + // so we don't need to do the extra math to figure out what incremental shift to apply. + hideGlyphs(symbol.glyphOffsets.size(), bucket.text.dynamicVertices); + } else { + const Point tileAnchor = symbol.anchorPoint; + const auto projectedAnchor = project(tileAnchor, pitchWithMap ? tile.matrix : labelPlaneMatrix); + const float perspectiveRatio = 0.5f + 0.5f * (parameters.state.getCameraToCenterDistance() / projectedAnchor.second); + float renderTextSize = evaluateSizeForFeature(partiallyEvaluatedSize, symbol) * perspectiveRatio / util::ONE_EM; + if (pitchWithMap) { + // Go from size in pixels to equivalent size in tile units + renderTextSize *= bucket.tilePixelRatio / tileScale; + } + + auto shift = calculateVariableRenderShift( + (*variableOffset).anchor, + (*variableOffset).width, + (*variableOffset).height, + (*variableOffset).radialOffset, + (*variableOffset).textBoxScale, + renderTextSize); + + // Usual case is that we take the projected anchor and add the pixel-based shift + // calculated above. In the (somewhat weird) case of pitch-aligned text, we add an equivalent + // tile-unit based shift to the anchor before projecting to the label plane. + Point shiftedAnchor; + if (pitchWithMap) { + shiftedAnchor = project(Point(tileAnchor.x + shift.x, tileAnchor.y + shift.y), + labelPlaneMatrix).first; + } else { + if (rotateWithMap) { + auto rotated = util::rotate(shift, -parameters.state.getPitch()); + shiftedAnchor = Point(projectedAnchor.first.x + rotated.x, + projectedAnchor.first.y + rotated.y); + } else { + shiftedAnchor = Point(projectedAnchor.first.x + shift.x, + projectedAnchor.first.y + shift.y); + } + } + + for (std::size_t i = 0; i < symbol.glyphOffsets.size(); i++) { + addDynamicAttributes(shiftedAnchor, 0, bucket.text.dynamicVertices); + } + } + } + parameters.context.updateVertexBuffer(*bucket.text.dynamicVertexBuffer, std::move(bucket.text.dynamicVertices)); } const Size texsize = geometryTile.glyphAtlasTexture->size; if (values.hasHalo) { draw(parameters.programs.getSymbolLayerPrograms().symbolGlyph, - SymbolSDFTextProgram::layoutUniformValues(true, values, texsize, parameters.pixelsToGLUnits, alongLine, tile, parameters.state, parameters.symbolFadeChange, SymbolSDFPart::Halo), + SymbolSDFTextProgram::layoutUniformValues(true, hasVariablePacement, values, texsize, parameters.pixelsToGLUnits, alongLine, tile, parameters.state, parameters.symbolFadeChange, SymbolSDFPart::Halo), bucket.text, bucket.textSizeBinder, values, @@ -244,7 +326,7 @@ void RenderSymbolLayer::render(PaintParameters& parameters, RenderSource*) { if (values.hasFill) { draw(parameters.programs.getSymbolLayerPrograms().symbolGlyph, - SymbolSDFTextProgram::layoutUniformValues(true, values, texsize, parameters.pixelsToGLUnits, alongLine, tile, parameters.state, parameters.symbolFadeChange, SymbolSDFPart::Fill), + SymbolSDFTextProgram::layoutUniformValues(true, hasVariablePacement, values, texsize, parameters.pixelsToGLUnits, alongLine, tile, parameters.state, parameters.symbolFadeChange, SymbolSDFPart::Fill), bucket.text, bucket.textSizeBinder, values, diff --git a/src/mbgl/renderer/paint_parameters.hpp b/src/mbgl/renderer/paint_parameters.hpp index 847093166ac..cb257a656e7 100644 --- a/src/mbgl/renderer/paint_parameters.hpp +++ b/src/mbgl/renderer/paint_parameters.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include @@ -56,6 +57,7 @@ class PaintParameters { TimePoint timePoint; float pixelRatio; + std::unordered_map variableOffsets; std::array pixelsToGLUnits; algorithm::ClipIDGenerator clipIDGenerator; diff --git a/src/mbgl/text/placement.hpp b/src/mbgl/text/placement.hpp index 32310f723e6..673ea59c240 100644 --- a/src/mbgl/text/placement.hpp +++ b/src/mbgl/text/placement.hpp @@ -138,4 +138,10 @@ class Placement { CollisionGroups collisionGroups; }; +Point calculateVariableLayoutOffset(style::SymbolAnchorType anchor, + float width, + float height, + float radialOffset, + float textBoxScale); + } // namespace mbgl From ab1f8200b18a686dbaebcb6aa2388a7903bd25b7 Mon Sep 17 00:00:00 2001 From: Mikhail Pozdnyakov Date: Thu, 14 Mar 2019 10:25:53 +0200 Subject: [PATCH 07/12] [core] Introduce variable text placement for point labels - Placement part --- src/mbgl/layout/symbol_layout.cpp | 2 +- src/mbgl/programs/collision_box_program.hpp | 18 +- .../renderer/layers/render_symbol_layer.cpp | 4 +- src/mbgl/renderer/paint_parameters.cpp | 4 +- src/mbgl/renderer/paint_parameters.hpp | 5 +- src/mbgl/renderer/renderer_impl.cpp | 16 +- src/mbgl/text/collision_index.cpp | 9 +- src/mbgl/text/collision_index.hpp | 1 + src/mbgl/text/placement.cpp | 263 +++++++++++++++--- src/mbgl/text/placement.hpp | 18 +- 10 files changed, 267 insertions(+), 73 deletions(-) diff --git a/src/mbgl/layout/symbol_layout.cpp b/src/mbgl/layout/symbol_layout.cpp index 52ec2aa8432..f691d406f19 100644 --- a/src/mbgl/layout/symbol_layout.cpp +++ b/src/mbgl/layout/symbol_layout.cpp @@ -739,7 +739,7 @@ void SymbolLayout::addToDebugBuffers(SymbolBucket& bucket) { // Dynamic vertices are initialized so that the vertex count always agrees with // the layout vertex buffer, but they will always be updated before rendering happens - auto dynamicVertex = CollisionBoxProgram::dynamicVertex(false, false); + auto dynamicVertex = CollisionBoxProgram::dynamicVertex(false, false, {}); collisionBuffer.dynamicVertices.emplace_back(dynamicVertex); collisionBuffer.dynamicVertices.emplace_back(dynamicVertex); collisionBuffer.dynamicVertices.emplace_back(dynamicVertex); diff --git a/src/mbgl/programs/collision_box_program.hpp b/src/mbgl/programs/collision_box_program.hpp index 0fa7ea4b4f4..677704b1549 100644 --- a/src/mbgl/programs/collision_box_program.hpp +++ b/src/mbgl/programs/collision_box_program.hpp @@ -13,10 +13,9 @@ namespace mbgl { using CollisionBoxLayoutAttributes = TypeList< attributes::pos, attributes::anchor_pos, - attributes::extrude, - attributes::shift>; + attributes::extrude>; -using CollisionBoxDynamicAttributes = TypeList; +using CollisionBoxDynamicAttributes = TypeList; class CollisionBoxProgram : public Program< CollisionBoxProgram, @@ -45,17 +44,14 @@ class CollisionBoxProgram : public Program< {{ static_cast(::round(o.x)), static_cast(::round(o.y)) - }}, - {{ - 0.0f, - 0.0f }} }; } - static gfx::Vertex dynamicVertex(bool placed, bool notUsed) { + static gfx::Vertex dynamicVertex(bool placed, bool notUsed, Point shift) { return { - {{ static_cast(placed), static_cast(notUsed) }} + {{ static_cast(placed), static_cast(notUsed) }}, + {{ shift.x, shift.y }} }; } @@ -139,10 +135,6 @@ class CollisionCircleProgram : public Program< {{ static_cast(::round(o.x)), static_cast(::round(o.y)) - }}, - {{ - 0.0f, - 0.0f }} }; } diff --git a/src/mbgl/renderer/layers/render_symbol_layer.cpp b/src/mbgl/renderer/layers/render_symbol_layer.cpp index 83752753767..743e94447e2 100644 --- a/src/mbgl/renderer/layers/render_symbol_layer.cpp +++ b/src/mbgl/renderer/layers/render_symbol_layer.cpp @@ -254,8 +254,8 @@ void RenderSymbolLayer::render(PaintParameters& parameters, RenderSource*) { for (const PlacedSymbol& symbol : bucket.text.placedSymbols) { optional variableOffset; if (!symbol.hidden && symbol.crossTileID != 0u) { - auto it = parameters.variableOffsets.find(symbol.crossTileID); - if (it != parameters.variableOffsets.end()) { + auto it = parameters.variableOffsets.get().find(symbol.crossTileID); + if (it != parameters.variableOffsets.get().end()) { variableOffset = it->second; hasVariablePacement |= true; } diff --git a/src/mbgl/renderer/paint_parameters.cpp b/src/mbgl/renderer/paint_parameters.cpp index 639041202f4..55dbf704729 100644 --- a/src/mbgl/renderer/paint_parameters.cpp +++ b/src/mbgl/renderer/paint_parameters.cpp @@ -13,7 +13,8 @@ PaintParameters::PaintParameters(gfx::Context& context_, const EvaluatedLight& evaluatedLight_, RenderStaticData& staticData_, ImageManager& imageManager_, - LineAtlas& lineAtlas_) + LineAtlas& lineAtlas_, + Placement::VariableOffsets variableOffsets_) : context(context_), backend(backend_), state(updateParameters.transformState), @@ -26,6 +27,7 @@ PaintParameters::PaintParameters(gfx::Context& context_, contextMode(contextMode_), timePoint(updateParameters.timePoint), pixelRatio(pixelRatio_), + variableOffsets(variableOffsets_), #ifndef NDEBUG programs((debugOptions & MapDebugOptions::Overdraw) ? staticData_.overdrawPrograms : staticData_.programs) #else diff --git a/src/mbgl/renderer/paint_parameters.hpp b/src/mbgl/renderer/paint_parameters.hpp index cb257a656e7..537158a68ea 100644 --- a/src/mbgl/renderer/paint_parameters.hpp +++ b/src/mbgl/renderer/paint_parameters.hpp @@ -38,7 +38,8 @@ class PaintParameters { const EvaluatedLight&, RenderStaticData&, ImageManager&, - LineAtlas&); + LineAtlas&, + Placement::VariableOffsets); gfx::Context& context; RendererBackend& backend; @@ -57,7 +58,7 @@ class PaintParameters { TimePoint timePoint; float pixelRatio; - std::unordered_map variableOffsets; + Placement::VariableOffsets variableOffsets; std::array pixelsToGLUnits; algorithm::ClipIDGenerator clipIDGenerator; diff --git a/src/mbgl/renderer/renderer_impl.cpp b/src/mbgl/renderer/renderer_impl.cpp index 311008507bd..61fe7e91c63 100644 --- a/src/mbgl/renderer/renderer_impl.cpp +++ b/src/mbgl/renderer/renderer_impl.cpp @@ -263,7 +263,8 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { renderLight.getEvaluated(), *staticData, *imageManager, - *lineAtlas + *lineAtlas, + placement->getVariableOffsets() }; bool loaded = updateParameters.styleLoaded && isLoaded(); @@ -344,26 +345,25 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { bool symbolBucketsChanged = false; const bool placementChanged = !placement->stillRecent(parameters.timePoint); - std::unique_ptr newPlacement; std::set usedSymbolLayers; if (placementChanged) { - newPlacement = std::make_unique(parameters.state, parameters.mapMode, updateParameters.transitionOptions, updateParameters.crossSourceCollisions); + placement = std::make_unique(parameters.state, parameters.mapMode, updateParameters.transitionOptions, updateParameters.crossSourceCollisions, std::move(placement)); } for (const auto& item : renderItemsWithSymbols) { if (crossTileSymbolIndex.addLayer(*item.layer.getSymbolInterface(), parameters.state.getLatLng().longitude())) symbolBucketsChanged = true; - if (newPlacement) { + if (placementChanged) { usedSymbolLayers.insert(item.layer.getID()); - newPlacement->placeLayer(*item.layer.getSymbolInterface(), parameters.projMatrix, parameters.debugOptions & MapDebugOptions::Collision); + placement->placeLayer(*item.layer.getSymbolInterface(), parameters.projMatrix, parameters.debugOptions & MapDebugOptions::Collision); } } - if (newPlacement) { - newPlacement->commit(*placement, parameters.timePoint); + if (placementChanged) { + placement->commit(parameters.timePoint); crossTileSymbolIndex.pruneUnusedLayers(usedSymbolLayers); - placement = std::move(newPlacement); + parameters.variableOffsets = placement->getVariableOffsets(); updateFadingTiles(); } else { placement->setStale(); diff --git a/src/mbgl/text/collision_index.cpp b/src/mbgl/text/collision_index.cpp index 90acb2b441c..88e59bf51c9 100644 --- a/src/mbgl/text/collision_index.cpp +++ b/src/mbgl/text/collision_index.cpp @@ -80,6 +80,7 @@ bool CollisionIndex::isInsideTile(const CollisionBox& box, const CollisionTileBo std::pair CollisionIndex::placeFeature(CollisionFeature& feature, + Point shift, const mat4& posMatrix, const mat4& labelPlaneMatrix, const float textPixelRatio, @@ -95,10 +96,10 @@ std::pair CollisionIndex::placeFeature(CollisionFeature& feature, CollisionBox& box = feature.boxes.front(); const auto projectedPoint = projectAndGetPerspectiveRatio(posMatrix, box.anchor); const float tileToViewport = textPixelRatio * projectedPoint.second; - box.px1 = box.x1 * tileToViewport + projectedPoint.first.x; - box.py1 = box.y1 * tileToViewport + projectedPoint.first.y; - box.px2 = box.x2 * tileToViewport + projectedPoint.first.x; - box.py2 = box.y2 * tileToViewport + projectedPoint.first.y; + box.px1 = (box.x1 + shift.x) * tileToViewport + projectedPoint.first.x; + box.py1 = (box.y1 + shift.y) * tileToViewport + projectedPoint.first.y; + box.px2 = (box.x2 + shift.x) * tileToViewport + projectedPoint.first.x; + box.py2 = (box.y2 + shift.y) * tileToViewport + projectedPoint.first.y; if ((avoidEdges && !isInsideTile(box, *avoidEdges)) || diff --git a/src/mbgl/text/collision_index.hpp b/src/mbgl/text/collision_index.hpp index dac0aa0bf73..dd160c945c4 100644 --- a/src/mbgl/text/collision_index.hpp +++ b/src/mbgl/text/collision_index.hpp @@ -23,6 +23,7 @@ class CollisionIndex { explicit CollisionIndex(const TransformState&); std::pair placeFeature(CollisionFeature& feature, + Point shift, const mat4& posMatrix, const mat4& labelPlaneMatrix, const float textPixelRatio, diff --git a/src/mbgl/text/placement.cpp b/src/mbgl/text/placement.cpp index bb7e2d1a6c1..9d0fcf7b326 100644 --- a/src/mbgl/text/placement.cpp +++ b/src/mbgl/text/placement.cpp @@ -1,10 +1,13 @@ #include + +#include #include #include #include #include #include #include +#include namespace mbgl { @@ -55,13 +58,18 @@ const CollisionGroups::CollisionGroup& CollisionGroups::get(const std::string& s } } -Placement::Placement(const TransformState& state_, MapMode mapMode_, style::TransitionOptions transitionOptions_, const bool crossSourceCollisions) +Placement::Placement(const TransformState& state_, MapMode mapMode_, style::TransitionOptions transitionOptions_, const bool crossSourceCollisions, std::unique_ptr prevPlacement_) : collisionIndex(state_) , state(state_) , mapMode(mapMode_) , transitionOptions(transitionOptions_) , collisionGroups(crossSourceCollisions) -{} + , prevPlacement(std::move(prevPlacement_)) +{ + if (prevPlacement) { + prevPlacement->prevPlacement.reset(); // Only hold on to one placement back + } +} void Placement::placeLayer(const RenderLayerSymbolInterface& symbolInterface, const mat4& projMatrix, bool showCollisionBoxes) { @@ -121,6 +129,19 @@ void Placement::placeLayer(const RenderLayerSymbolInterface& symbolInterface, co } } +namespace { +Point calculateVariableLayoutOffset(style::SymbolAnchorType anchor, float width, float height, float radialOffset, float textBoxScale) { + AnchorAlignment alignment = AnchorAlignment::getAnchorAlignment(anchor); + float shiftX = -(alignment.horizontalAlign - 0.5f) * width; + float shiftY = -(alignment.verticalAlign - 0.5f) * height; + Point offset = SymbolLayout::evaluateRadialOffset(anchor, radialOffset); + return Point( + shiftX + offset.x * textBoxScale, + shiftY + offset.y * textBoxScale + ); +} +} // namespace + void Placement::placeLayerBucket( SymbolBucket& bucket, const mat4& posMatrix, @@ -161,8 +182,11 @@ void Placement::placeLayerBucket( // See https://github.com/mapbox/mapbox-gl-native/issues/12683 const bool alwaysShowText = textAllowOverlap && (iconAllowOverlap || !bucket.hasIconData() || bucket.layout.get()); const bool alwaysShowIcon = iconAllowOverlap && (textAllowOverlap || !bucket.hasTextData() || bucket.layout.get()); + std::vector variableTextAnchors = bucket.layout.get(); + const bool rotateWithMap = bucket.layout.get() == style::AlignmentType::Map; + const bool pitchWithMap = bucket.layout.get() == style::AlignmentType::Map; - for (auto& symbolInstance : bucket.symbolInstances) { + for (SymbolInstance& symbolInstance : bucket.symbolInstances) { if (seenCrossTileIDs.count(symbolInstance.crossTileID) == 0) { if (holdingForFade) { @@ -175,30 +199,111 @@ void Placement::placeLayerBucket( bool placeText = false; bool placeIcon = false; bool offscreen = true; - - if (symbolInstance.placedRightTextIndex) { - PlacedSymbol& placedSymbol = bucket.text.placedSymbols.at(*symbolInstance.placedRightTextIndex); + optional horizontalTextIndex = symbolInstance.getDefaultHorizontalPlacedTextIndex(); + if (horizontalTextIndex) { + CollisionFeature& textCollisionFeature = symbolInstance.textCollisionFeature; + PlacedSymbol& placedSymbol = bucket.text.placedSymbols.at(*horizontalTextIndex); const float fontSize = evaluateSizeForFeature(partiallyEvaluatedTextSize, placedSymbol); - - auto placed = collisionIndex.placeFeature(symbolInstance.textCollisionFeature, - posMatrix, textLabelPlaneMatrix, textPixelRatio, - placedSymbol, scale, fontSize, - bucket.layout.get(), - bucket.layout.get() == style::AlignmentType::Map, - showCollisionBoxes, avoidEdges, collisionGroup.second); - placeText = placed.first; - offscreen &= placed.second; + if (variableTextAnchors.empty()) { + auto placed = collisionIndex.placeFeature(textCollisionFeature, {}, + posMatrix, textLabelPlaneMatrix, textPixelRatio, + placedSymbol, scale, fontSize, + bucket.layout.get(), + pitchWithMap, + showCollisionBoxes, avoidEdges, collisionGroup.second); + placeText = placed.first; + offscreen &= placed.second; + } else if (!textCollisionFeature.alongLine && !textCollisionFeature.boxes.empty()) { + const CollisionBox& textBox = symbolInstance.textCollisionFeature.boxes[0]; + const float width = textBox.x2 - textBox.x1; + const float height = textBox.y2 - textBox.y1; + const float textBoxScale = symbolInstance.textBoxScale; + + // If this symbol was in the last placement, shift the previously used + // anchor to the front of the anchor list. + if (prevPlacement) { + auto prevOffset = prevPlacement->variableOffsets.find(symbolInstance.crossTileID); + if (prevOffset != prevPlacement->variableOffsets.end() && + variableTextAnchors.front() != prevOffset->second.anchor) { + std::vector filtered; + filtered.reserve(variableTextAnchors.size()); + filtered.push_back(prevOffset->second.anchor); + for (auto anchor : variableTextAnchors) { + if (anchor != prevOffset->second.anchor) { + filtered.push_back(anchor); + } + } + variableTextAnchors = std::move(filtered); + } + } + + for (auto anchor : variableTextAnchors) { + Point shift = calculateVariableLayoutOffset(anchor, width, height, symbolInstance.radialTextOffset, textBoxScale); + if (rotateWithMap) { + float angle = pitchWithMap ? state.getBearing() : -state.getBearing(); + shift = util::rotate(shift, angle); + } + + auto placed = collisionIndex.placeFeature(textCollisionFeature, shift, + posMatrix, mat4(), textPixelRatio, + placedSymbol, scale, fontSize, + bucket.layout.get(), + pitchWithMap, + showCollisionBoxes, avoidEdges, collisionGroup.second); + + if (placed.first) { + assert(symbolInstance.crossTileID != 0u); + optional prevAnchor; + + // If this label was placed in the previous placement, record the anchor position + // to allow us to animate the transition + if (prevPlacement) { + auto prevOffset = prevPlacement->variableOffsets.find(symbolInstance.crossTileID); + auto prevPlacements = prevPlacement->placements.find(symbolInstance.crossTileID); + if (prevOffset != prevPlacement->variableOffsets.end() && + prevPlacements != prevPlacement->placements.end() && + prevPlacements->second.text) { + prevAnchor = prevOffset->second.anchor; + } + } + + variableOffsets.insert(std::make_pair(symbolInstance.crossTileID, VariableOffset{ + symbolInstance.radialTextOffset, + width, + height, + anchor, + textBoxScale, + prevAnchor + })); + markUsedJustification(bucket, anchor, symbolInstance); + + placeText = placed.first; + offscreen &= placed.second; + break; + } + } + + // If we didn't get placed, we still need to copy our position from the last placement for + // fade animations + if (prevPlacement && variableOffsets.find(symbolInstance.crossTileID) == variableOffsets.end()) { + auto prevOffset = prevPlacement->variableOffsets.find(symbolInstance.crossTileID); + if (prevOffset != prevPlacement->variableOffsets.end()) { + variableOffsets[symbolInstance.crossTileID] = prevOffset->second; + markUsedJustification(bucket, prevOffset->second.anchor, symbolInstance); + } + } + } } if (symbolInstance.placedIconIndex) { PlacedSymbol& placedSymbol = bucket.icon.placedSymbols.at(*symbolInstance.placedIconIndex); const float fontSize = evaluateSizeForFeature(partiallyEvaluatedIconSize, placedSymbol); - auto placed = collisionIndex.placeFeature(symbolInstance.iconCollisionFeature, + auto placed = collisionIndex.placeFeature(symbolInstance.iconCollisionFeature, {}, posMatrix, iconLabelPlaneMatrix, textPixelRatio, placedSymbol, scale, fontSize, bucket.layout.get(), - bucket.layout.get() == style::AlignmentType::Map, + pitchWithMap, showCollisionBoxes, avoidEdges, collisionGroup.second); placeIcon = placed.first; offscreen &= placed.second; @@ -240,7 +345,8 @@ void Placement::placeLayerBucket( bucket.justReloaded = false; } -void Placement::commit(const Placement& prevPlacement, TimePoint now) { +void Placement::commit(TimePoint now) { + assert(prevPlacement); commitTime = now; bool placementChanged = false; @@ -248,13 +354,13 @@ void Placement::commit(const Placement& prevPlacement, TimePoint now) { float increment = mapMode == MapMode::Continuous && transitionOptions.enablePlacementTransitions && transitionOptions.duration.value_or(util::DEFAULT_TRANSITION_DURATION) > Milliseconds(0) ? - std::chrono::duration(commitTime - prevPlacement.commitTime) / transitionOptions.duration.value_or(util::DEFAULT_TRANSITION_DURATION) : + std::chrono::duration(commitTime - prevPlacement->commitTime) / transitionOptions.duration.value_or(util::DEFAULT_TRANSITION_DURATION) : 1.0; // add the opacities from the current placement, and copy their current values from the previous placement for (auto& jointPlacement : placements) { - auto prevOpacity = prevPlacement.opacities.find(jointPlacement.first); - if (prevOpacity != prevPlacement.opacities.end()) { + auto prevOpacity = prevPlacement->opacities.find(jointPlacement.first); + if (prevOpacity != prevPlacement->opacities.end()) { opacities.emplace(jointPlacement.first, JointOpacityState(prevOpacity->second, increment, jointPlacement.second.text, jointPlacement.second.icon)); placementChanged = placementChanged || jointPlacement.second.icon != prevOpacity->second.icon.placed || @@ -266,7 +372,7 @@ void Placement::commit(const Placement& prevPlacement, TimePoint now) { } // copy and update values from the previous placement that aren't in the current placement but haven't finished fading - for (auto& prevOpacity : prevPlacement.opacities) { + for (auto& prevOpacity : prevPlacement->opacities) { if (opacities.find(prevOpacity.first) == opacities.end()) { JointOpacityState jointOpacity(prevOpacity.second, increment, false, false); if (!jointOpacity.isHidden()) { @@ -276,7 +382,16 @@ void Placement::commit(const Placement& prevPlacement, TimePoint now) { } } - fadeStartTime = placementChanged ? commitTime : prevPlacement.fadeStartTime; + for (auto& prevOffset : prevPlacement->variableOffsets) { + const uint32_t crossTileID = prevOffset.first; + auto foundOffset = variableOffsets.find(crossTileID); + auto foundOpacity = opacities.find(crossTileID); + if (foundOffset == variableOffsets.end() && foundOpacity != opacities.end() && !foundOpacity->second.isHidden()) { + variableOffsets[prevOffset.first] = prevOffset.second; + } + } + + fadeStartTime = placementChanged ? commitTime : prevPlacement->fadeStartTime; } void Placement::updateLayerOpacities(const RenderLayerSymbolInterface& symbolInterface) { @@ -310,7 +425,10 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, std::set& const bool textAllowOverlap = bucket.layout.get(); const bool iconAllowOverlap = bucket.layout.get(); - + const bool variablePlacement = !bucket.layout.get().empty(); + const bool rotateWithMap = bucket.layout.get() == style::AlignmentType::Map; + const bool pitchWithMap = bucket.layout.get() == style::AlignmentType::Map; + // If allow-overlap is true, we can show symbols before placement runs on them // But we have to wait for placement if we potentially depend on a paired icon/text // with allow-overlap: false. @@ -339,19 +457,38 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, std::set& if (symbolInstance.hasText) { auto opacityVertex = SymbolSDFTextProgram::opacityVertex(opacityState.text.placed, opacityState.text.opacity); - for (size_t i = 0; i < symbolInstance.rightJustifiedGlyphQuads.size() * 4; i++) { - bucket.text.opacityVertices.emplace_back(opacityVertex); - } - for (size_t i = 0; i < symbolInstance.verticalGlyphQuads.size() * 4; i++) { - bucket.text.opacityVertices.emplace_back(opacityVertex); - } if (symbolInstance.placedRightTextIndex) { + for (size_t i = 0; i < symbolInstance.rightJustifiedGlyphQuads.size() * 4; i++) { + bucket.text.opacityVertices.emplace_back(opacityVertex); + } PlacedSymbol& placed = bucket.text.placedSymbols[*symbolInstance.placedRightTextIndex]; placed.hidden = opacityState.isHidden(); } + if (symbolInstance.placedCenterTextIndex) { + for (size_t i = 0; i < symbolInstance.centerJustifiedGlyphQuads.size() * 4; i++) { + bucket.text.opacityVertices.emplace_back(opacityVertex); + } + PlacedSymbol& placed = bucket.text.placedSymbols[*symbolInstance.placedCenterTextIndex]; + placed.hidden = opacityState.isHidden(); + } + if (symbolInstance.placedLeftTextIndex) { + for (size_t i = 0; i < symbolInstance.leftJustifiedGlyphQuads.size() * 4; i++) { + bucket.text.opacityVertices.emplace_back(opacityVertex); + } + PlacedSymbol& placed = bucket.text.placedSymbols[*symbolInstance.placedLeftTextIndex]; + placed.hidden = opacityState.isHidden(); + } if (symbolInstance.placedVerticalTextIndex) { + for (size_t i = 0; i < symbolInstance.verticalGlyphQuads.size() * 4; i++) { + bucket.text.opacityVertices.emplace_back(opacityVertex); + } bucket.text.placedSymbols[*symbolInstance.placedVerticalTextIndex].hidden = opacityState.isHidden(); } + + auto prevOffset = variableOffsets.find(symbolInstance.crossTileID); + if (prevOffset != variableOffsets.end()) { + markUsedJustification(bucket, prevOffset->second.anchor, symbolInstance); + } } if (symbolInstance.hasIcon) { auto opacityVertex = SymbolIconProgram::opacityVertex(opacityState.icon.placed, opacityState.icon.opacity); @@ -370,7 +507,42 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, std::set& if (feature.alongLine) { return; } - auto dynamicVertex = CollisionBoxProgram::dynamicVertex(placed, false); + auto dynamicVertex = CollisionBoxProgram::dynamicVertex(placed, false, {}); + for (size_t i = 0; i < feature.boxes.size() * 4; i++) { + bucket.collisionBox.dynamicVertices.emplace_back(dynamicVertex); + } + }; + + auto updateCollisionTextBox = [this, &bucket, &symbolInstance, variablePlacement, rotateWithMap, pitchWithMap](const auto& feature, const bool placed) { + if (feature.alongLine) { + return; + } + Point shift; + bool used = true; + if (variablePlacement) { + auto foundOffset = variableOffsets.find(symbolInstance.crossTileID); + if (foundOffset != variableOffsets.end()) { + const VariableOffset& variableOffset = foundOffset->second; + // This will show either the currently placed position or the last + // successfully placed position (so you can visualize what collision + // just made the symbol disappear, and the most likely place for the + // symbol to come back) + shift = calculateVariableLayoutOffset(variableOffset.anchor, + variableOffset.width, + variableOffset.height, + variableOffset.radialOffset, + variableOffset.textBoxScale); + if (rotateWithMap) { + shift = util::rotate(shift, pitchWithMap ? state.getBearing() : -state.getBearing()); + } + } else { + // No offset -> this symbol hasn't been placed since coming on-screen + // No single box is particularly meaningful and all of them would be too noisy + // Use the center box just to show something's there, but mark it "not used" + used = false; + } + } + auto dynamicVertex = CollisionBoxProgram::dynamicVertex(placed, !used, shift); for (size_t i = 0; i < feature.boxes.size() * 4; i++) { bucket.collisionBox.dynamicVertices.emplace_back(dynamicVertex); } @@ -381,7 +553,7 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, std::set& return; } for (const CollisionBox& box : feature.boxes) { - auto dynamicVertex = CollisionBoxProgram::dynamicVertex(placed, !box.used); + auto dynamicVertex = CollisionBoxProgram::dynamicVertex(placed, !box.used, {}); bucket.collisionCircle.dynamicVertices.emplace_back(dynamicVertex); bucket.collisionCircle.dynamicVertices.emplace_back(dynamicVertex); bucket.collisionCircle.dynamicVertices.emplace_back(dynamicVertex); @@ -390,7 +562,7 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, std::set& }; if (bucket.hasCollisionBoxData()) { - updateCollisionBox(symbolInstance.textCollisionFeature, opacityState.text.placed); + updateCollisionTextBox(symbolInstance.textCollisionFeature, opacityState.text.placed); updateCollisionBox(symbolInstance.iconCollisionFeature, opacityState.icon.placed); } if (bucket.hasCollisionCircleData()) { @@ -407,6 +579,31 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, std::set& } } +void Placement::markUsedJustification(SymbolBucket& bucket, style::TextVariableAnchorType placedAnchor, SymbolInstance& symbolInstance) { + std::map> justificationToIndex { + {style::TextJustifyType::Right, symbolInstance.placedRightTextIndex}, + {style::TextJustifyType::Center, symbolInstance.placedCenterTextIndex}, + {style::TextJustifyType::Left, symbolInstance.placedLeftTextIndex}, + }; + style::TextJustifyType justify = getAnchorJustification(placedAnchor); + assert(justify == style::TextJustifyType::Right || justify == style::TextJustifyType::Center || justify == style::TextJustifyType::Left); + const optional autoIndex = justificationToIndex[justify]; + + for (auto& pair : justificationToIndex) { + const optional index = pair.second; + if (index) { + assert(bucket.text.placedSymbols.size() > *index); + if (autoIndex && *index != *autoIndex) { + // There are multiple justifications and this one isn't it: shift offscreen + bucket.text.placedSymbols.at(*index).crossTileID = 0u; + } else { + // Either this is the chosen justification or the justification is hardwired: use this one + bucket.text.placedSymbols.at(*index).crossTileID = symbolInstance.crossTileID; + } + } + } +} + float Placement::symbolFadeChange(TimePoint now) const { if (mapMode == MapMode::Continuous && transitionOptions.enablePlacementTransitions && transitionOptions.duration.value_or(util::DEFAULT_TRANSITION_DURATION) > Milliseconds(0)) { diff --git a/src/mbgl/text/placement.hpp b/src/mbgl/text/placement.hpp index 673ea59c240..3f2a7b8a035 100644 --- a/src/mbgl/text/placement.hpp +++ b/src/mbgl/text/placement.hpp @@ -12,6 +12,7 @@ namespace mbgl { class RenderLayerSymbolInterface; class SymbolBucket; +class SymbolInstance; class OpacityState { public: @@ -90,9 +91,9 @@ class CollisionGroups { class Placement { public: - Placement(const TransformState&, MapMode, style::TransitionOptions, const bool crossSourceCollisions); + Placement(const TransformState&, MapMode, style::TransitionOptions, const bool crossSourceCollisions, std::unique_ptr prevPlacementOrNull = nullptr); void placeLayer(const RenderLayerSymbolInterface&, const mat4&, bool showCollisionBoxes); - void commit(const Placement& prevPlacement, TimePoint); + void commit(TimePoint); void updateLayerOpacities(const RenderLayerSymbolInterface&); float symbolFadeChange(TimePoint now) const; bool hasTransitions(TimePoint now) const; @@ -104,8 +105,10 @@ class Placement { void setStale(); const RetainedQueryData& getQueryData(uint32_t bucketInstanceId) const; -private: + using VariableOffsets = std::reference_wrapper>; + VariableOffsets getVariableOffsets() const { return std::cref(variableOffsets); } +private: void placeLayerBucket( SymbolBucket&, const mat4& posMatrix, @@ -119,6 +122,7 @@ class Placement { const CollisionGroups::CollisionGroup& collisionGroup); void updateBucketOpacities(SymbolBucket&, std::set&); + void markUsedJustification(SymbolBucket&, style::TextVariableAnchorType, SymbolInstance&); CollisionIndex collisionIndex; @@ -131,17 +135,13 @@ class Placement { std::unordered_map placements; std::unordered_map opacities; + std::unordered_map variableOffsets; bool stale = false; std::unordered_map retainedQueryData; CollisionGroups collisionGroups; + std::unique_ptr prevPlacement; }; -Point calculateVariableLayoutOffset(style::SymbolAnchorType anchor, - float width, - float height, - float radialOffset, - float textBoxScale); - } // namespace mbgl From 9b1def0e7e5bf68f7a05ec0eff2b5d4540a883de Mon Sep 17 00:00:00 2001 From: Mikhail Pozdnyakov Date: Wed, 20 Mar 2019 19:25:16 +0200 Subject: [PATCH 08/12] Enable text-variable-anchor render tests --- platform/node/test/ignores.json | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/platform/node/test/ignores.json b/platform/node/test/ignores.json index d0becfc58ae..11dff4d7163 100644 --- a/platform/node/test/ignores.json +++ b/platform/node/test/ignores.json @@ -149,24 +149,9 @@ "render-tests/symbol-sort-key/text-expression": "https://github.com/mapbox/mapbox-gl-native/issues/14028", "render-tests/symbol-sort-key/text-placement": "https://github.com/mapbox/mapbox-gl-native/issues/14028", "render-tests/fill-opacity/opaque-fill-over-symbol-layer": "skip - port https://github.com/mapbox/mapbox-gl-js/pull/7612", - "render-tests/text-radial-offset/basic": "skip - port of https://github.com/mapbox/mapbox-gl-js/pull/7596", - "render-tests/text-variable-anchor/all-anchors-offset": "skip - port of https://github.com/mapbox/mapbox-gl-js/pull/7596", - "render-tests/text-variable-anchor/all-anchors": "skip - port of https://github.com/mapbox/mapbox-gl-js/pull/7596", - "render-tests/text-variable-anchor/icon-image-all-anchors": "skip - port of https://github.com/mapbox/mapbox-gl-js/pull/7596", - "render-tests/text-variable-anchor/icon-image": "skip - port of https://github.com/mapbox/mapbox-gl-js/pull/7596", - "render-tests/text-variable-anchor/no-animate-zoom": "skip - port of https://github.com/mapbox/mapbox-gl-js/pull/7596", - "render-tests/text-variable-anchor/pitched-offset": "skip - port of https://github.com/mapbox/mapbox-gl-js/pull/7596", - "render-tests/text-variable-anchor/pitched-rotated-debug": "skip - port of https://github.com/mapbox/mapbox-gl-js/pull/7596", - "render-tests/text-variable-anchor/pitched-with-map": "skip - port of https://github.com/mapbox/mapbox-gl-js/pull/7596", - "render-tests/text-variable-anchor/pitched": "skip - port of https://github.com/mapbox/mapbox-gl-js/pull/7596", - "render-tests/text-variable-anchor/remember-last-placement": "skip - port of https://github.com/mapbox/mapbox-gl-js/pull/7596", - "render-tests/text-variable-anchor/rotated-offset": "skip - port of https://github.com/mapbox/mapbox-gl-js/pull/7596", - "render-tests/text-variable-anchor/rotated-with-map": "skip - port of https://github.com/mapbox/mapbox-gl-js/pull/7596", - "render-tests/text-variable-anchor/rotated": "skip - port of https://github.com/mapbox/mapbox-gl-js/pull/7596", - "render-tests/text-variable-anchor/single-justification": "skip - port of https://github.com/mapbox/mapbox-gl-js/pull/7596", - "render-tests/text-variable-anchor/single-line": "skip - port of https://github.com/mapbox/mapbox-gl-js/pull/7596", - "render-tests/text-variable-anchor/top-bottom-left-right": "skip - port of https://github.com/mapbox/mapbox-gl-js/pull/7596", - "render-tests/text-justify/auto": "skip - port of https://github.com/mapbox/mapbox-gl-js/pull/7596", + "render-tests/text-variable-anchor/pitched-rotated-debug": "skip - fails on gl-native due to worse accuracy at collision detection (float vs double) - needs issue", + "render-tests/text-variable-anchor/rotated-offset": "skip - fails on gl-native due to worse accuracy at collision detection (float vs double) - needs issue", + "render-tests/text-variable-anchor/remember-last-placement": "skip - fails on gl-native, as symbol index is not functional at static map mode - needs issue", "render-tests/geojson/clustered-properties": "https://github.com/mapbox/mapbox-gl-native/issues/14043", "render-tests/remove-feature-state/composite-expression": "https://github.com/mapbox/mapbox-gl-native/issues/12413", "render-tests/remove-feature-state/data-expression": "https://github.com/mapbox/mapbox-gl-native/issues/12413", From 26d0a743b8ac74dcf9f36e5bf4888f4281d12275 Mon Sep 17 00:00:00 2001 From: Mikhail Pozdnyakov Date: Thu, 21 Mar 2019 17:20:39 +0200 Subject: [PATCH 09/12] [android] Fix style code generation if array default value is missing --- .../com/mapbox/mapboxsdk/testapp/style/SymbolLayerTest.java | 2 +- platform/android/scripts/generate-style-code.js | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) 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 94e6303c3a8..ae2c6d98f65 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 @@ -598,7 +598,7 @@ public void testTextVariableAnchorAsConstant() { assertNull(layer.getTextVariableAnchor().getValue()); // Set and Get - String[] propertyValue = [undefined]; + String[] propertyValue = new String[0]; layer.setProperties(textVariableAnchor(propertyValue)); assertEquals(layer.getTextVariableAnchor().getValue(), propertyValue); } diff --git a/platform/android/scripts/generate-style-code.js b/platform/android/scripts/generate-style-code.js index 6f64a38a674..688a3aa5273 100755 --- a/platform/android/scripts/generate-style-code.js +++ b/platform/android/scripts/generate-style-code.js @@ -198,7 +198,11 @@ global.defaultValueJava = function(property) { switch (property.value) { case 'string': case 'enum': - return '[' + property['default'] + "]"; + if (property['default'] !== undefined) { + return '[' + property['default'] + ']'; + } else { + return 'new String[0]'; + } case 'number': var result ='new Float[] {'; for (var i = 0; i < property.length; i++) { From cc6cc890bf2aadc8b0ca394f25a90b4c8281033a Mon Sep 17 00:00:00 2001 From: Mikhail Pozdnyakov Date: Fri, 22 Mar 2019 20:49:38 +0200 Subject: [PATCH 10/12] [core] Single line optimization for variable label placement --- platform/node/test/ignores.json | 4 +-- src/mbgl/layout/symbol_instance.cpp | 27 ++++++++++++----- src/mbgl/layout/symbol_instance.hpp | 5 +++- src/mbgl/layout/symbol_layout.cpp | 33 +++++++++++++-------- src/mbgl/renderer/buckets/symbol_bucket.cpp | 4 +-- src/mbgl/style/conversion/constant.cpp | 5 ---- src/mbgl/text/placement.cpp | 4 +-- src/mbgl/text/shaping.cpp | 1 - 8 files changed, 50 insertions(+), 33 deletions(-) diff --git a/platform/node/test/ignores.json b/platform/node/test/ignores.json index 11dff4d7163..060f4935a60 100644 --- a/platform/node/test/ignores.json +++ b/platform/node/test/ignores.json @@ -149,8 +149,8 @@ "render-tests/symbol-sort-key/text-expression": "https://github.com/mapbox/mapbox-gl-native/issues/14028", "render-tests/symbol-sort-key/text-placement": "https://github.com/mapbox/mapbox-gl-native/issues/14028", "render-tests/fill-opacity/opaque-fill-over-symbol-layer": "skip - port https://github.com/mapbox/mapbox-gl-js/pull/7612", - "render-tests/text-variable-anchor/pitched-rotated-debug": "skip - fails on gl-native due to worse accuracy at collision detection (float vs double) - needs issue", - "render-tests/text-variable-anchor/rotated-offset": "skip - fails on gl-native due to worse accuracy at collision detection (float vs double) - needs issue", + "render-tests/text-variable-anchor/pitched-rotated-debug": "https://github.com/mapbox/mapbox-gl-native/issues/14211", + "render-tests/text-variable-anchor/rotated-offset": "https://github.com/mapbox/mapbox-gl-native/issues/14211", "render-tests/text-variable-anchor/remember-last-placement": "skip - fails on gl-native, as symbol index is not functional at static map mode - needs issue", "render-tests/geojson/clustered-properties": "https://github.com/mapbox/mapbox-gl-native/issues/14043", "render-tests/remove-feature-state/composite-expression": "https://github.com/mapbox/mapbox-gl-native/issues/12413", diff --git a/src/mbgl/layout/symbol_instance.cpp b/src/mbgl/layout/symbol_instance.cpp index fddaaf7c2de..0197df10664 100644 --- a/src/mbgl/layout/symbol_instance.cpp +++ b/src/mbgl/layout/symbol_instance.cpp @@ -54,26 +54,37 @@ SymbolInstance::SymbolInstance(Anchor& anchor_, iconOffset(iconOffset_), key(key_), textBoxScale(textBoxScale_), - radialTextOffset(radialTextOffset_) { + radialTextOffset(radialTextOffset_), + singleLine(shapedTextOrientations.singleLine) { // Create the quads used for rendering the icon and glyphs. if (shapedIcon) { iconQuad = getIconQuad(*shapedIcon, layout, layoutTextSize, shapedTextOrientations.horizontal); } - - if (shapedTextOrientations.right) { + + bool singleLineInitialized = false; + const auto initHorizontalGlyphQuads = [&] (SymbolQuads& quads, const Shaping& shaping) { writingModes |= WritingModeType::Horizontal; - rightJustifiedGlyphQuads = getGlyphQuads(shapedTextOrientations.right, textOffset, layout, textPlacement, positions); + if (!singleLine) { + quads = getGlyphQuads(shaping, textOffset, layout, textPlacement, positions); + return; + } + if (!singleLineInitialized) { + rightJustifiedGlyphQuads = getGlyphQuads(shaping, textOffset, layout, textPlacement, positions); + singleLineInitialized = true; + } + }; + + if (shapedTextOrientations.right) { + initHorizontalGlyphQuads(rightJustifiedGlyphQuads, shapedTextOrientations.right); } if (shapedTextOrientations.center) { - writingModes |= WritingModeType::Horizontal; - centerJustifiedGlyphQuads = getGlyphQuads(shapedTextOrientations.center, textOffset, layout, textPlacement, positions); + initHorizontalGlyphQuads(centerJustifiedGlyphQuads, shapedTextOrientations.center); } if (shapedTextOrientations.left) { - writingModes |= WritingModeType::Horizontal; - leftJustifiedGlyphQuads = getGlyphQuads(shapedTextOrientations.left, textOffset, layout, textPlacement, positions); + initHorizontalGlyphQuads(leftJustifiedGlyphQuads, shapedTextOrientations.left); } if (shapedTextOrientations.vertical) { diff --git a/src/mbgl/layout/symbol_instance.hpp b/src/mbgl/layout/symbol_instance.hpp index 44d81ae1e5f..5169b16adbe 100644 --- a/src/mbgl/layout/symbol_instance.hpp +++ b/src/mbgl/layout/symbol_instance.hpp @@ -18,6 +18,7 @@ struct ShapedTextOrientations { Shaping& right = horizontal; Shaping center; Shaping left; + bool singleLine = false; }; class SymbolInstance { @@ -45,14 +46,15 @@ class SymbolInstance { float radialTextOffset); optional getDefaultHorizontalPlacedTextIndex() const; - Anchor anchor; GeometryCoordinates line; bool hasText; bool hasIcon; + // Note: When singleLine == true, only `rightJustifiedGlyphQuads` is populated. SymbolQuads rightJustifiedGlyphQuads; SymbolQuads centerJustifiedGlyphQuads; SymbolQuads leftJustifiedGlyphQuads; + SymbolQuads verticalGlyphQuads; optional iconQuad; @@ -72,6 +74,7 @@ class SymbolInstance { optional placedIconIndex; float textBoxScale; float radialTextOffset; + bool singleLine; uint32_t crossTileID = 0; }; diff --git a/src/mbgl/layout/symbol_layout.cpp b/src/mbgl/layout/symbol_layout.cpp index f691d406f19..d1c50d77738 100644 --- a/src/mbgl/layout/symbol_layout.cpp +++ b/src/mbgl/layout/symbol_layout.cpp @@ -312,7 +312,6 @@ void SymbolLayout::prepareSymbols(const GlyphMap& glyphMap, const GlyphPositions justifications.push_back(getAnchorJustification(anchor)); } } - for (TextJustifyType justification: justifications) { Shaping& shapingForJustification = shapingForTextJustifyType(shapedTextOrientations, justification); if (shapingForJustification) { @@ -323,8 +322,11 @@ void SymbolLayout::prepareSymbols(const GlyphMap& glyphMap, const GlyphPositions Shaping shaping = applyShaping(*feature.formattedText, WritingModeType::Horizontal, SymbolAnchorType::Center, justification); if (shaping) { shapingForJustification = std::move(shaping); + if (shaping.lineCount == 1u) { + shapedTextOrientations.singleLine = true; + break; + } } - // TODO: use 'singleLine' optimization. } } else { if (textJustify == TextJustifyType::Auto) { @@ -547,9 +549,9 @@ void SymbolLayout::createBucket(const ImagePositions&, std::unique_ptr(layout, layerPaintProperties, textSize, iconSize, zoom, sdfIcons, iconsNeedLinear, sortFeaturesByY, bucketLeaderID, std::move(symbolInstances), tilePixelRatio); for (SymbolInstance &symbolInstance : bucket->symbolInstances) { - const bool hasText = symbolInstance.hasText; const bool hasIcon = symbolInstance.hasIcon; + const bool singleLine = symbolInstance.singleLine; const auto& feature = features.at(symbolInstance.layoutFeatureIndex); @@ -557,16 +559,23 @@ void SymbolLayout::createBucket(const ImagePositions&, std::unique_ptr lastAddedSection; - if (!symbolInstance.rightJustifiedGlyphQuads.empty()) { - lastAddedSection = addSymbolGlyphQuads(*bucket, symbolInstance, feature, symbolInstance.writingModes, symbolInstance.placedRightTextIndex, symbolInstance.rightJustifiedGlyphQuads, lastAddedSection); - } - if (!symbolInstance.centerJustifiedGlyphQuads.empty()) { - lastAddedSection = addSymbolGlyphQuads(*bucket, symbolInstance, feature, symbolInstance.writingModes, symbolInstance.placedCenterTextIndex, symbolInstance.centerJustifiedGlyphQuads, lastAddedSection); - } - if (!symbolInstance.leftJustifiedGlyphQuads.empty()) { - lastAddedSection = addSymbolGlyphQuads(*bucket, symbolInstance, feature, symbolInstance.writingModes, symbolInstance.placedLeftTextIndex, symbolInstance.leftJustifiedGlyphQuads, lastAddedSection); + if (singleLine) { + optional placedTextIndex; + lastAddedSection = addSymbolGlyphQuads(*bucket, symbolInstance, feature, symbolInstance.writingModes, placedTextIndex, symbolInstance.rightJustifiedGlyphQuads, lastAddedSection); + symbolInstance.placedRightTextIndex = placedTextIndex; + symbolInstance.placedCenterTextIndex = placedTextIndex; + symbolInstance.placedLeftTextIndex = placedTextIndex; + } else { + if (!symbolInstance.rightJustifiedGlyphQuads.empty()) { + lastAddedSection = addSymbolGlyphQuads(*bucket, symbolInstance, feature, symbolInstance.writingModes, symbolInstance.placedRightTextIndex, symbolInstance.rightJustifiedGlyphQuads, lastAddedSection); + } + if (!symbolInstance.centerJustifiedGlyphQuads.empty()) { + lastAddedSection = addSymbolGlyphQuads(*bucket, symbolInstance, feature, symbolInstance.writingModes, symbolInstance.placedCenterTextIndex, symbolInstance.centerJustifiedGlyphQuads, lastAddedSection); + } + if (!symbolInstance.leftJustifiedGlyphQuads.empty()) { + lastAddedSection = addSymbolGlyphQuads(*bucket, symbolInstance, feature, symbolInstance.writingModes, symbolInstance.placedLeftTextIndex, symbolInstance.leftJustifiedGlyphQuads, lastAddedSection); + } } - if (symbolInstance.writingModes & WritingModeType::Vertical) { lastAddedSection = addSymbolGlyphQuads(*bucket, symbolInstance, feature, WritingModeType::Vertical, symbolInstance.placedVerticalTextIndex, symbolInstance.verticalGlyphQuads, lastAddedSection); } diff --git a/src/mbgl/renderer/buckets/symbol_bucket.cpp b/src/mbgl/renderer/buckets/symbol_bucket.cpp index 38342b44eeb..68f683c8d69 100644 --- a/src/mbgl/renderer/buckets/symbol_bucket.cpp +++ b/src/mbgl/renderer/buckets/symbol_bucket.cpp @@ -224,11 +224,11 @@ void SymbolBucket::sortFeatures(const float angle) { addPlacedSymbol(text.triangles, text.placedSymbols[*symbolInstance.placedRightTextIndex]); } - if (symbolInstance.placedCenterTextIndex) { + if (symbolInstance.placedCenterTextIndex && !symbolInstance.singleLine) { addPlacedSymbol(text.triangles, text.placedSymbols[*symbolInstance.placedCenterTextIndex]); } - if (symbolInstance.placedLeftTextIndex) { + if (symbolInstance.placedLeftTextIndex && !symbolInstance.singleLine) { addPlacedSymbol(text.triangles, text.placedSymbols[*symbolInstance.placedLeftTextIndex]); } diff --git a/src/mbgl/style/conversion/constant.cpp b/src/mbgl/style/conversion/constant.cpp index 1942779aaac..0fcaab433b1 100644 --- a/src/mbgl/style/conversion/constant.cpp +++ b/src/mbgl/style/conversion/constant.cpp @@ -147,11 +147,6 @@ optional> Converter>::operator()(const Con return result; } - -namespace { - -} // namespace - optional> Converter>::operator()(const Convertible& value, Error& error) const { if (!isArray(value)) { error.message = "value must be an array"; diff --git a/src/mbgl/text/placement.cpp b/src/mbgl/text/placement.cpp index 9d0fcf7b326..f6c4ac1eb6b 100644 --- a/src/mbgl/text/placement.cpp +++ b/src/mbgl/text/placement.cpp @@ -464,14 +464,14 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, std::set& PlacedSymbol& placed = bucket.text.placedSymbols[*symbolInstance.placedRightTextIndex]; placed.hidden = opacityState.isHidden(); } - if (symbolInstance.placedCenterTextIndex) { + if (symbolInstance.placedCenterTextIndex && !symbolInstance.singleLine) { for (size_t i = 0; i < symbolInstance.centerJustifiedGlyphQuads.size() * 4; i++) { bucket.text.opacityVertices.emplace_back(opacityVertex); } PlacedSymbol& placed = bucket.text.placedSymbols[*symbolInstance.placedCenterTextIndex]; placed.hidden = opacityState.isHidden(); } - if (symbolInstance.placedLeftTextIndex) { + if (symbolInstance.placedLeftTextIndex && !symbolInstance.singleLine) { for (size_t i = 0; i < symbolInstance.leftJustifiedGlyphQuads.size() * 4; i++) { bucket.text.opacityVertices.emplace_back(opacityVertex); } diff --git a/src/mbgl/text/shaping.cpp b/src/mbgl/text/shaping.cpp index 348c2ddccc0..ba21b97389c 100644 --- a/src/mbgl/text/shaping.cpp +++ b/src/mbgl/text/shaping.cpp @@ -363,7 +363,6 @@ const Shaping getShaping(const TaggedString& formattedString, const style::TextJustifyType textJustify, const float spacing, const Point& translate, - //const float verticalHeight, const WritingModeType writingMode, BiDi& bidi, const GlyphMap& glyphs) { From 9bc4994715b527df56189ca55619b64e8ccbcc3f Mon Sep 17 00:00:00 2001 From: Mikhail Pozdnyakov Date: Tue, 26 Mar 2019 15:02:13 +0200 Subject: [PATCH 11/12] [darwin] Support for variable text placement API --- .../darwin/scripts/generate-style-code.js | 18 ++- platform/darwin/src/MGLStyleValue_Private.h | 24 +--- platform/darwin/src/MGLSymbolStyleLayer.h | 60 ++++++++- platform/darwin/src/MGLSymbolStyleLayer.mm | 37 ++++++ .../darwin/test/MGLSymbolStyleLayerTests.mm | 116 ++++++++++++++++++ 5 files changed, 228 insertions(+), 27 deletions(-) diff --git a/platform/darwin/scripts/generate-style-code.js b/platform/darwin/scripts/generate-style-code.js index a8bdec78654..1e7b2a3973a 100755 --- a/platform/darwin/scripts/generate-style-code.js +++ b/platform/darwin/scripts/generate-style-code.js @@ -137,6 +137,8 @@ global.objCTestValue = function (property, layerType, arraysAsStructs, indent) { } return '@"{1, 1}"'; } + case 'anchor': + return `@"{'top','bottom'}"`; default: throw new Error(`unknown array type for ${property.name}`); } @@ -185,6 +187,8 @@ global.mbglTestValue = function (property, layerType) { case 'offset': case 'translate': return '{ 1, 1 }'; + case 'anchor': + return '{ mbgl::style::SymbolAnchorType::Top, mbgl::style::SymbolAnchorType::Bottom }'; default: throw new Error(`unknown array type for ${property.name}`); } @@ -200,6 +204,13 @@ global.mbglExpressionTestValue = function (property, layerType) { return `"${_.last(_.keys(property.values))}"`; case 'color': return 'mbgl::Color(1, 0, 0, 1)'; + case 'array': + switch (arrayType(property)) { + case 'anchor': + return `{"top", "bottom"}`; + default: + break; + } default: return global.mbglTestValue(property, layerType); } @@ -539,11 +550,12 @@ global.propertyType = function (property) { case 'font': return 'NSArray *'; case 'padding': - return 'NSValue *'; case 'position': case 'offset': case 'translate': return 'NSValue *'; + case 'anchor': + return 'NSArray *'; default: throw new Error(`unknown array type for ${property.name}`); } @@ -588,6 +600,8 @@ global.valueTransformerArguments = function (property) { case 'offset': case 'translate': return ['std::array', objCType]; + case 'anchor': + return ['std::vector', objCType, 'mbgl::style::SymbolAnchorType', 'MGLTextAnchor']; default: throw new Error(`unknown array type for ${property.name}`); } @@ -637,6 +651,8 @@ global.mbglType = function(property) { return 'std::array'; case 'position': return 'mbgl::style::Position'; + case 'anchor': + return 'std::vector'; default: throw new Error(`unknown array type for ${property.name}`); } diff --git a/platform/darwin/src/MGLStyleValue_Private.h b/platform/darwin/src/MGLStyleValue_Private.h index 84d7ccccec3..fee34b4b712 100644 --- a/platform/darwin/src/MGLStyleValue_Private.h +++ b/platform/darwin/src/MGLStyleValue_Private.h @@ -307,10 +307,8 @@ class MGLStyleValueTransformer { // Enumerations template - static NSValue *toMGLRawStyleValue(const MBGLEnum &value) { - auto str = mbgl::Enum::toString(value); - MGLEnum mglType = *mbgl::Enum::toEnum(str); - return [NSValue value:&mglType withObjCType:@encode(MGLEnum)]; + static NSString *toMGLRawStyleValue(const MBGLEnum &value) { + return @(mbgl::Enum::toString(value)); } /// Converts all types of mbgl property values into an equivalent NSExpression. @@ -320,15 +318,6 @@ class MGLStyleValueTransformer { return nil; } - /** - As hack to allow converting enum => string values, we accept a second, dummy parameter in - the toRawStyleSpecValue() methods for converting 'atomic' (non-style-function) values. - This allows us to use `std::enable_if` to test (at compile time) whether or not MBGLType is an Enum. - */ - template ::value>::type, - typename MGLEnum = ObjCEnum, - class = typename std::enable_if::value>::type> NSExpression *operator()(const MBGLType &value) const { id constantValue = toMGLRawStyleValue(value); if ([constantValue isKindOfClass:[NSArray class]]) { @@ -337,15 +326,6 @@ class MGLStyleValueTransformer { return [NSExpression expressionForConstantValue:constantValue]; } - template ::value>::type, - typename MGLEnum = ObjCEnum, - class = typename std::enable_if::value>::type> - NSExpression *operator()(const MBGLEnum &value) const { - NSString *constantValue = @(mbgl::Enum::toString(value)); - return [NSExpression expressionForConstantValue:constantValue]; - } - NSExpression *operator()(const mbgl::style::PropertyExpression &mbglValue) const { return [NSExpression expressionWithMGLJSONObject:MGLJSONObjectFromMBGLExpression(mbglValue.getExpression())]; } diff --git a/platform/darwin/src/MGLSymbolStyleLayer.h b/platform/darwin/src/MGLSymbolStyleLayer.h index ee8afb1fb29..ad792880e76 100644 --- a/platform/darwin/src/MGLSymbolStyleLayer.h +++ b/platform/darwin/src/MGLSymbolStyleLayer.h @@ -222,6 +222,10 @@ typedef NS_ENUM(NSUInteger, MGLTextAnchor) { property. */ typedef NS_ENUM(NSUInteger, MGLTextJustification) { + /** + The text is aligned towards the anchor position. + */ + MGLTextJustificationAuto, /** The text is aligned to the left. */ @@ -1279,6 +1283,7 @@ MGL_EXPORT * Constant `MGLTextJustification` values * Any of the following constant string values: + * `auto`: The text is aligned towards the anchor position. * `left`: The text is aligned to the left. * `center`: The text is centered. * `right`: The text is aligned to the right. @@ -1349,8 +1354,8 @@ MGL_EXPORT `NSValue` object containing a `CGVector` struct set to 0 ems rightward and 0 ems downward. Set this property to `nil` to reset it to the default value. - This property is only applied to the style if `text` is non-`nil`. Otherwise, - it is ignored. + This property is only applied to the style if `text` is non-`nil`, and + `textRadialOffset` is set to `nil`. Otherwise, it is ignored. You can set this property to an expression containing any of the following: @@ -1372,8 +1377,8 @@ MGL_EXPORT `NSValue` object containing a `CGVector` struct set to 0 ems rightward and 0 ems upward. Set this property to `nil` to reset it to the default value. - This property is only applied to the style if `text` is non-`nil`. Otherwise, - it is ignored. + This property is only applied to the style if `text` is non-`nil`, and + `textRadialOffset` is set to `nil`. Otherwise, it is ignored. You can set this property to an expression containing any of the following: @@ -1463,6 +1468,27 @@ MGL_EXPORT */ @property (nonatomic, null_resettable) NSExpression *textPitchAlignment; +/** + Radial offset of text, in the direction of the symbol's anchor. Useful in + combination with `textVariableAnchor`, which doesn't support the + two-dimensional `textOffset`. + + This property is measured in ems. + + This property is only applied to the style if `textOffset` is set to `nil`. + Otherwise, it is ignored. + + You can set this property to an expression containing any of the following: + + * Constant numeric values + * Predefined functions, including mathematical and string operators + * Conditional expressions + * Variable assignments and references to assigned variables + * Interpolation and step functions applied to the `$zoomLevel` variable and/or + feature attributes + */ +@property (nonatomic, null_resettable) NSExpression *textRadialOffset; + /** Rotates the text clockwise. @@ -1549,6 +1575,32 @@ MGL_EXPORT */ @property (nonatomic, null_resettable) NSExpression *textTransform; +/** + To increase the chance of placing high-priority labels on the map, you can + provide an array of `textAnchor` locations: the render will attempt to place + the label at each location, in order, before moving onto the next label. Use + `textJustify: auto` to choose justification based on anchor position. To apply + an offset, use the `textRadialOffset` instead of the two-dimensional + `textOffset`. + + This property is only applied to the style if `textAnchor` is set to `nil`, and + `textOffset` is set to `nil`, and `symbolPlacement` is set to an expression + that evaluates to or `MGLSymbolPlacementPoint`. Otherwise, it is ignored. + + You can set this property to an expression containing any of the following: + + * Constant array values + * Predefined functions, including mathematical and string operators + * Conditional expressions + * Variable assignments and references to assigned variables + * Step functions applied to the `$zoomLevel` variable + + This property does not support applying interpolation functions to the + `$zoomLevel` variable or applying interpolation or step functions to feature + attributes. + */ +@property (nonatomic, null_resettable) NSExpression *textVariableAnchor; + #pragma mark - Accessing the Paint Attributes #if TARGET_OS_IPHONE diff --git a/platform/darwin/src/MGLSymbolStyleLayer.mm b/platform/darwin/src/MGLSymbolStyleLayer.mm index 60fc4d6881f..6d91bbe87ff 100644 --- a/platform/darwin/src/MGLSymbolStyleLayer.mm +++ b/platform/darwin/src/MGLSymbolStyleLayer.mm @@ -71,6 +71,7 @@ }); MBGL_DEFINE_ENUM(MGLTextJustification, { + { MGLTextJustificationAuto, "auto" }, { MGLTextJustificationLeft, "left" }, { MGLTextJustificationCenter, "center" }, { MGLTextJustificationRight, "right" }, @@ -906,6 +907,24 @@ - (NSExpression *)textPitchAlignment { return MGLStyleValueTransformer().toExpression(propertyValue); } +- (void)setTextRadialOffset:(NSExpression *)textRadialOffset { + MGLAssertStyleLayerIsValid(); + MGLLogDebug(@"Setting textRadialOffset: %@", textRadialOffset); + + auto mbglValue = MGLStyleValueTransformer().toPropertyValue>(textRadialOffset, true); + self.rawLayer->setTextRadialOffset(mbglValue); +} + +- (NSExpression *)textRadialOffset { + MGLAssertStyleLayerIsValid(); + + auto propertyValue = self.rawLayer->getTextRadialOffset(); + if (propertyValue.isUndefined()) { + propertyValue = self.rawLayer->getDefaultTextRadialOffset(); + } + return MGLStyleValueTransformer().toExpression(propertyValue); +} + - (void)setTextRotation:(NSExpression *)textRotation { MGLAssertStyleLayerIsValid(); MGLLogDebug(@"Setting textRotation: %@", textRotation); @@ -967,6 +986,24 @@ - (NSExpression *)textTransform { return MGLStyleValueTransformer().toExpression(propertyValue); } +- (void)setTextVariableAnchor:(NSExpression *)textVariableAnchor { + MGLAssertStyleLayerIsValid(); + MGLLogDebug(@"Setting textVariableAnchor: %@", textVariableAnchor); + + auto mbglValue = MGLStyleValueTransformer, NSArray *, mbgl::style::SymbolAnchorType, MGLTextAnchor>().toPropertyValue>>(textVariableAnchor, false); + self.rawLayer->setTextVariableAnchor(mbglValue); +} + +- (NSExpression *)textVariableAnchor { + MGLAssertStyleLayerIsValid(); + + auto propertyValue = self.rawLayer->getTextVariableAnchor(); + if (propertyValue.isUndefined()) { + propertyValue = self.rawLayer->getDefaultTextVariableAnchor(); + } + return MGLStyleValueTransformer, NSArray *, mbgl::style::SymbolAnchorType, MGLTextAnchor>().toExpression(propertyValue); +} + #pragma mark - Accessing the Paint Attributes - (void)setIconColor:(NSExpression *)iconColor { diff --git a/platform/darwin/test/MGLSymbolStyleLayerTests.mm b/platform/darwin/test/MGLSymbolStyleLayerTests.mm index 2f4206a96b3..083b12bcc30 100644 --- a/platform/darwin/test/MGLSymbolStyleLayerTests.mm +++ b/platform/darwin/test/MGLSymbolStyleLayerTests.mm @@ -1741,6 +1741,75 @@ - (void)testProperties { XCTAssertThrowsSpecificNamed(layer.textPitchAlignment = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); } + // text-radial-offset + { + XCTAssertTrue(rawLayer->getTextRadialOffset().isUndefined(), + @"text-radial-offset should be unset initially."); + NSExpression *defaultExpression = layer.textRadialOffset; + + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"1"]; + layer.textRadialOffset = constantExpression; + mbgl::style::PropertyValue propertyValue = { 1.0 }; + XCTAssertEqual(rawLayer->getTextRadialOffset(), propertyValue, + @"Setting textRadialOffset to a constant value expression should update text-radial-offset."); + XCTAssertEqualObjects(layer.textRadialOffset, constantExpression, + @"textRadialOffset should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"1"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.textRadialOffset = functionExpression; + + { + using namespace mbgl::style::expression::dsl; + propertyValue = mbgl::style::PropertyExpression( + step(zoom(), literal(1.0), 18.0, literal(1.0)) + ); + } + + XCTAssertEqual(rawLayer->getTextRadialOffset(), propertyValue, + @"Setting textRadialOffset to a camera expression should update text-radial-offset."); + XCTAssertEqualObjects(layer.textRadialOffset, functionExpression, + @"textRadialOffset should round-trip camera expressions."); + + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}]; + layer.textRadialOffset = functionExpression; + + { + using namespace mbgl::style::expression::dsl; + propertyValue = mbgl::style::PropertyExpression( + interpolate(linear(), number(get("keyName")), 18.0, literal(1.0)) + ); + } + + XCTAssertEqual(rawLayer->getTextRadialOffset(), propertyValue, + @"Setting textRadialOffset to a data expression should update text-radial-offset."); + NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}]; + XCTAssertEqualObjects(layer.textRadialOffset, pedanticFunctionExpression, + @"textRadialOffset should round-trip data expressions."); + + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + layer.textRadialOffset = functionExpression; + + { + using namespace mbgl::style::expression::dsl; + propertyValue = mbgl::style::PropertyExpression( + interpolate(linear(), zoom(), 10.0, interpolate(linear(), number(get("keyName")), 18.0, literal(1.0))) + ); + } + + XCTAssertEqual(rawLayer->getTextRadialOffset(), propertyValue, + @"Setting textRadialOffset to a camera-data expression should update text-radial-offset."); + pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}]; + XCTAssertEqualObjects(layer.textRadialOffset, pedanticFunctionExpression, + @"textRadialOffset should round-trip camera-data expressions."); + + layer.textRadialOffset = nil; + XCTAssertTrue(rawLayer->getTextRadialOffset().isUndefined(), + @"Unsetting textRadialOffset should return text-radial-offset to the default value."); + XCTAssertEqualObjects(layer.textRadialOffset, defaultExpression, + @"textRadialOffset should return the default value after being unset."); + } + // text-rotate { XCTAssertTrue(rawLayer->getTextRotate().isUndefined(), @@ -1892,6 +1961,50 @@ - (void)testProperties { @"textTransform should return the default value after being unset."); } + // text-variable-anchor + { + XCTAssertTrue(rawLayer->getTextVariableAnchor().isUndefined(), + @"text-variable-anchor should be unset initially."); + NSExpression *defaultExpression = layer.textVariableAnchor; + + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"{'top','bottom'}"]; + layer.textVariableAnchor = constantExpression; + mbgl::style::PropertyValue> propertyValue = { { mbgl::style::SymbolAnchorType::Top, mbgl::style::SymbolAnchorType::Bottom } }; + XCTAssertEqual(rawLayer->getTextVariableAnchor(), propertyValue, + @"Setting textVariableAnchor to a constant value expression should update text-variable-anchor."); + XCTAssertEqualObjects(layer.textVariableAnchor, constantExpression, + @"textVariableAnchor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"{'top','bottom'}"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.textVariableAnchor = functionExpression; + + { + using namespace mbgl::style::expression::dsl; + propertyValue = mbgl::style::PropertyExpression>( + step(zoom(), literal({"top", "bottom"}), 18.0, literal({"top", "bottom"})) + ); + } + + XCTAssertEqual(rawLayer->getTextVariableAnchor(), propertyValue, + @"Setting textVariableAnchor to a camera expression should update text-variable-anchor."); + XCTAssertEqualObjects(layer.textVariableAnchor, functionExpression, + @"textVariableAnchor should round-trip camera expressions."); + + + layer.textVariableAnchor = nil; + XCTAssertTrue(rawLayer->getTextVariableAnchor().isUndefined(), + @"Unsetting textVariableAnchor should return text-variable-anchor to the default value."); + XCTAssertEqualObjects(layer.textVariableAnchor, defaultExpression, + @"textVariableAnchor should return the default value after being unset."); + + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.textVariableAnchor = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.textVariableAnchor = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + } + // icon-color { XCTAssertTrue(rawLayer->getIconColor().isUndefined(), @@ -2896,9 +3009,11 @@ - (void)testPropertyNames { [self testPropertyName:@"is-text-optional" isBoolean:YES]; [self testPropertyName:@"text-padding" isBoolean:NO]; [self testPropertyName:@"text-pitch-alignment" isBoolean:NO]; + [self testPropertyName:@"text-radial-offset" isBoolean:NO]; [self testPropertyName:@"text-rotation" isBoolean:NO]; [self testPropertyName:@"text-rotation-alignment" isBoolean:NO]; [self testPropertyName:@"text-transform" isBoolean:NO]; + [self testPropertyName:@"text-variable-anchor" isBoolean:NO]; [self testPropertyName:@"icon-color" isBoolean:NO]; [self testPropertyName:@"icon-halo-blur" isBoolean:NO]; [self testPropertyName:@"icon-halo-color" isBoolean:NO]; @@ -2949,6 +3064,7 @@ - (void)testValueAdditions { XCTAssertEqual([NSValue valueWithMGLTextAnchor:MGLTextAnchorTopRight].MGLTextAnchorValue, MGLTextAnchorTopRight); XCTAssertEqual([NSValue valueWithMGLTextAnchor:MGLTextAnchorBottomLeft].MGLTextAnchorValue, MGLTextAnchorBottomLeft); XCTAssertEqual([NSValue valueWithMGLTextAnchor:MGLTextAnchorBottomRight].MGLTextAnchorValue, MGLTextAnchorBottomRight); + XCTAssertEqual([NSValue valueWithMGLTextJustification:MGLTextJustificationAuto].MGLTextJustificationValue, MGLTextJustificationAuto); XCTAssertEqual([NSValue valueWithMGLTextJustification:MGLTextJustificationLeft].MGLTextJustificationValue, MGLTextJustificationLeft); XCTAssertEqual([NSValue valueWithMGLTextJustification:MGLTextJustificationCenter].MGLTextJustificationValue, MGLTextJustificationCenter); XCTAssertEqual([NSValue valueWithMGLTextJustification:MGLTextJustificationRight].MGLTextJustificationValue, MGLTextJustificationRight); From 4cad71a8ae281c74568ea8e9753faa6916837620 Mon Sep 17 00:00:00 2001 From: Mikhail Pozdnyakov Date: Thu, 28 Mar 2019 12:06:43 +0200 Subject: [PATCH 12/12] [Darwin] Improve documentation for enum array properties --- platform/darwin/scripts/generate-style-code.js | 5 +++++ platform/darwin/src/MGLSymbolStyleLayer.h | 17 ++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/platform/darwin/scripts/generate-style-code.js b/platform/darwin/scripts/generate-style-code.js index 1e7b2a3973a..2eabd0a92b2 100755 --- a/platform/darwin/scripts/generate-style-code.js +++ b/platform/darwin/scripts/generate-style-code.js @@ -352,6 +352,9 @@ global.propertyDoc = function (propertyName, property, layerType, kind) { if (property.type === 'enum') { doc += '* Any of the following constant string values:\n'; doc += Object.keys(property.values).map(value => ' * `' + value + '`: ' + property.values[value].doc).join('\n') + '\n'; + } else if (property.type === 'array' && property.value === 'enum') { + doc += '* Constant array, whose each element is any of the following constant string values:\n'; + doc += Object.keys(property.values).map(value => ' * `' + value + '`: ' + property.values[value].doc).join('\n') + '\n'; } doc += '* Predefined functions, including mathematical and string operators\n' + '* Conditional expressions\n' + @@ -426,6 +429,8 @@ global.describeType = function (property) { return '`CGVector`'; case 'position': return '`MGLSphericalPosition`'; + case 'anchor': + return '`MGLTextAnchor` array'; default: return 'array'; } diff --git a/platform/darwin/src/MGLSymbolStyleLayer.h b/platform/darwin/src/MGLSymbolStyleLayer.h index ad792880e76..cf2c1466e7b 100644 --- a/platform/darwin/src/MGLSymbolStyleLayer.h +++ b/platform/darwin/src/MGLSymbolStyleLayer.h @@ -1589,7 +1589,22 @@ MGL_EXPORT You can set this property to an expression containing any of the following: - * Constant array values + * Constant `MGLTextAnchor` array values + * Constant array, whose each element is any of the following constant string + values: + * `center`: The center of the text is placed closest to the anchor. + * `left`: The left side of the text is placed closest to the anchor. + * `right`: The right side of the text is placed closest to the anchor. + * `top`: The top of the text is placed closest to the anchor. + * `bottom`: The bottom of the text is placed closest to the anchor. + * `top-left`: The top left corner of the text is placed closest to the + anchor. + * `top-right`: The top right corner of the text is placed closest to the + anchor. + * `bottom-left`: The bottom left corner of the text is placed closest to the + anchor. + * `bottom-right`: The bottom right corner of the text is placed closest to + the anchor. * Predefined functions, including mathematical and string operators * Conditional expressions * Variable assignments and references to assigned variables