From 5f61dd81ff128761f46adcbe407b9a0df702e5dd Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 5 Jan 2018 12:40:54 -0800 Subject: [PATCH 1/2] [core, ios, macos, android] Add data-driven-styling support for `text-font` --- .../mbgl/style/expression/array_assertion.hpp | 4 + include/mbgl/style/expression/assertion.hpp | 5 +- include/mbgl/style/expression/at.hpp | 4 + .../style/expression/boolean_operator.hpp | 5 +- include/mbgl/style/expression/case.hpp | 2 + include/mbgl/style/expression/coalesce.hpp | 4 +- include/mbgl/style/expression/coercion.hpp | 5 ++ .../style/expression/compound_expression.hpp | 8 +- include/mbgl/style/expression/equals.hpp | 1 + include/mbgl/style/expression/expression.hpp | 18 +++-- include/mbgl/style/expression/interpolate.hpp | 2 + include/mbgl/style/expression/let.hpp | 6 +- include/mbgl/style/expression/literal.hpp | 6 +- include/mbgl/style/expression/match.hpp | 7 +- include/mbgl/style/expression/step.hpp | 2 + include/mbgl/style/expression/value.hpp | 9 +++ .../mbgl/style/function/camera_function.hpp | 7 +- .../style/function/composite_function.hpp | 4 + include/mbgl/style/function/convert.hpp | 4 + .../mbgl/style/function/source_function.hpp | 4 + include/mbgl/style/layers/symbol_layer.hpp | 6 +- include/mbgl/util/optional.hpp | 3 + .../style/layers/PropertyFactory.java | 6 +- .../testapp/style/SymbolLayerTest.java | 57 ++++++++++++++ platform/darwin/src/MGLSymbolStyleLayer.h | 14 +++- platform/darwin/src/MGLSymbolStyleLayer.mm | 6 +- .../darwin/test/MGLSymbolStyleLayerTests.mm | 7 +- platform/node/test/ignores.json | 1 - src/mbgl/layout/symbol_layout.cpp | 29 ++++--- .../conversion/make_property_setters.hpp | 2 +- src/mbgl/style/expression/assertion.cpp | 10 +++ .../style/expression/boolean_operator.cpp | 8 ++ src/mbgl/style/expression/case.cpp | 13 ++++ src/mbgl/style/expression/coalesce.cpp | 10 +++ src/mbgl/style/expression/coercion.cpp | 10 +++ src/mbgl/style/expression/equals.cpp | 4 + src/mbgl/style/expression/interpolate.cpp | 10 +++ src/mbgl/style/expression/let.cpp | 8 ++ src/mbgl/style/expression/match.cpp | 14 ++++ src/mbgl/style/expression/step.cpp | 10 +++ src/mbgl/style/layers/symbol_layer.cpp | 6 +- .../style/layers/symbol_layer_properties.hpp | 2 +- src/mbgl/style/parser.cpp | 29 +++---- test/style/style_parser.test.cpp | 75 +++++++++++++++++++ 44 files changed, 379 insertions(+), 68 deletions(-) diff --git a/include/mbgl/style/expression/array_assertion.hpp b/include/mbgl/style/expression/array_assertion.hpp index 2516eea024c..7f36f8aac2f 100644 --- a/include/mbgl/style/expression/array_assertion.hpp +++ b/include/mbgl/style/expression/array_assertion.hpp @@ -30,6 +30,10 @@ class ArrayAssertion : public Expression { return false; } + std::vector> possibleOutputs() const override { + return input->possibleOutputs(); + } + private: std::unique_ptr input; }; diff --git a/include/mbgl/style/expression/assertion.hpp b/include/mbgl/style/expression/assertion.hpp index 504d49f4e56..43ea73f2ba1 100644 --- a/include/mbgl/style/expression/assertion.hpp +++ b/include/mbgl/style/expression/assertion.hpp @@ -1,7 +1,9 @@ #pragma once + #include #include #include + #include #include @@ -23,6 +25,8 @@ class Assertion : public Expression { bool operator==(const Expression& e) const override; + std::vector> possibleOutputs() const override; + private: std::vector> inputs; }; @@ -30,4 +34,3 @@ class Assertion : public Expression { } // namespace expression } // namespace style } // namespace mbgl - diff --git a/include/mbgl/style/expression/at.hpp b/include/mbgl/style/expression/at.hpp index e3eefa4fe82..27fccc761f7 100644 --- a/include/mbgl/style/expression/at.hpp +++ b/include/mbgl/style/expression/at.hpp @@ -28,6 +28,10 @@ class At : public Expression { return false; } + std::vector> possibleOutputs() const override { + return { nullopt }; + } + private: std::unique_ptr index; std::unique_ptr input; diff --git a/include/mbgl/style/expression/boolean_operator.hpp b/include/mbgl/style/expression/boolean_operator.hpp index 01231d706b9..115a0966653 100644 --- a/include/mbgl/style/expression/boolean_operator.hpp +++ b/include/mbgl/style/expression/boolean_operator.hpp @@ -2,6 +2,7 @@ #include #include + #include namespace mbgl { @@ -20,6 +21,7 @@ class Any : public Expression { EvaluationResult evaluate(const EvaluationContext& params) const override; void eachChild(const std::function& visit) const override; bool operator==(const Expression& e) const override; + std::vector> possibleOutputs() const override; private: std::vector> inputs; @@ -36,8 +38,8 @@ class All : public Expression { EvaluationResult evaluate(const EvaluationContext& params) const override; void eachChild(const std::function& visit) const override; - bool operator==(const Expression& e) const override; + std::vector> possibleOutputs() const override; private: std::vector> inputs; @@ -46,4 +48,3 @@ class All : public Expression { } // namespace expression } // namespace style } // namespace mbgl - diff --git a/include/mbgl/style/expression/case.hpp b/include/mbgl/style/expression/case.hpp index ece2fe03298..e61a55fc6d2 100644 --- a/include/mbgl/style/expression/case.hpp +++ b/include/mbgl/style/expression/case.hpp @@ -26,6 +26,8 @@ class Case : public Expression { bool operator==(const Expression& e) const override; + std::vector> possibleOutputs() const override; + private: std::vector branches; std::unique_ptr otherwise; diff --git a/include/mbgl/style/expression/coalesce.hpp b/include/mbgl/style/expression/coalesce.hpp index 4e6a9b37931..52d9498cbdf 100644 --- a/include/mbgl/style/expression/coalesce.hpp +++ b/include/mbgl/style/expression/coalesce.hpp @@ -27,7 +27,9 @@ class Coalesce : public Expression { void eachChild(const std::function& visit) const override; bool operator==(const Expression& e) const override; - + + std::vector> possibleOutputs() const override; + std::size_t getLength() const { return args.size(); } diff --git a/include/mbgl/style/expression/coercion.hpp b/include/mbgl/style/expression/coercion.hpp index 665bb7ce7cc..40d2490186e 100644 --- a/include/mbgl/style/expression/coercion.hpp +++ b/include/mbgl/style/expression/coercion.hpp @@ -1,6 +1,8 @@ #pragma once + #include #include + #include #include @@ -23,6 +25,9 @@ class Coercion : public Expression { void eachChild(const std::function& visit) const override; bool operator==(const Expression& e) const override; + + std::vector> possibleOutputs() const override; + private: EvaluationResult (*coerceSingleValue) (const Value& v); std::vector> inputs; diff --git a/include/mbgl/style/expression/compound_expression.hpp b/include/mbgl/style/expression/compound_expression.hpp index fc3edbfd4a6..8b740275786 100644 --- a/include/mbgl/style/expression/compound_expression.hpp +++ b/include/mbgl/style/expression/compound_expression.hpp @@ -72,7 +72,11 @@ class CompoundExpressionBase : public Expression { [&](const std::vector& p) -> optional { return p.size(); } ); } - + + std::vector> possibleOutputs() const override { + return { nullopt }; + } + private: std::string name; variant, VarargsType> params; @@ -107,7 +111,7 @@ class CompoundExpression : public CompoundExpressionBase { } return false; } - + private: Signature signature; typename Signature::Args args; diff --git a/include/mbgl/style/expression/equals.hpp b/include/mbgl/style/expression/equals.hpp index 3c0c024294e..80550bd59dd 100644 --- a/include/mbgl/style/expression/equals.hpp +++ b/include/mbgl/style/expression/equals.hpp @@ -19,6 +19,7 @@ class Equals : public Expression { void eachChild(const std::function& visit) const override; bool operator==(const Expression&) const override; EvaluationResult evaluate(const EvaluationContext&) const override; + std::vector> possibleOutputs() const override; private: std::unique_ptr lhs; diff --git a/include/mbgl/style/expression/expression.hpp b/include/mbgl/style/expression/expression.hpp index a22fc287240..cf9fa0cb21d 100644 --- a/include/mbgl/style/expression/expression.hpp +++ b/include/mbgl/style/expression/expression.hpp @@ -1,8 +1,5 @@ #pragma once -#include -#include -#include #include #include #include @@ -10,6 +7,10 @@ #include #include +#include +#include +#include + namespace mbgl { class GeometryTileFeature; @@ -38,7 +39,7 @@ class EvaluationContext { optional heatmapDensity; }; -template +template class Result : private variant { public: using variant::variant; @@ -128,6 +129,13 @@ class Expression { EvaluationResult evaluate(optional zoom, const Feature& feature, optional heatmapDensity) const; + /** + * Statically analyze the expression, attempting to enumerate possible outputs. Returns + * an array of values plus the sentinel null optional value, used to indicate that the + * complete set of outputs is statically undecidable. + */ + virtual std::vector> possibleOutputs() const = 0; + protected: template static bool childrenEqual(const T& lhs, const T& rhs) { @@ -161,8 +169,6 @@ class Expression { const std::pair, std::unique_ptr>& rhs) { return *(lhs.first) == *(rhs.first) && *(lhs.second) == *(rhs.second); } - - private: type::Type type; diff --git a/include/mbgl/style/expression/interpolate.hpp b/include/mbgl/style/expression/interpolate.hpp index 439122f91c8..9cc7c879572 100644 --- a/include/mbgl/style/expression/interpolate.hpp +++ b/include/mbgl/style/expression/interpolate.hpp @@ -89,6 +89,8 @@ class InterpolateBase : public Expression { ); } + std::vector> possibleOutputs() const override; + protected: const Interpolator interpolator; const std::unique_ptr input; diff --git a/include/mbgl/style/expression/let.hpp b/include/mbgl/style/expression/let.hpp index aaa16ca0c24..6829ded9b88 100644 --- a/include/mbgl/style/expression/let.hpp +++ b/include/mbgl/style/expression/let.hpp @@ -33,6 +33,8 @@ class Let : public Expression { return false; } + std::vector> possibleOutputs() const override; + Expression* getResult() const { return result.get(); } @@ -61,7 +63,9 @@ class Var : public Expression { } return false; } - + + std::vector> possibleOutputs() const override; + private: std::string name; std::shared_ptr value; diff --git a/include/mbgl/style/expression/literal.hpp b/include/mbgl/style/expression/literal.hpp index a0819c7e730..82983d78afd 100644 --- a/include/mbgl/style/expression/literal.hpp +++ b/include/mbgl/style/expression/literal.hpp @@ -28,7 +28,11 @@ class Literal : public Expression { } return false; } - + + std::vector> possibleOutputs() const override { + return {{ value }}; + } + private: Value value; }; diff --git a/include/mbgl/style/expression/match.hpp b/include/mbgl/style/expression/match.hpp index e17fe96bfe4..682d784b0fe 100644 --- a/include/mbgl/style/expression/match.hpp +++ b/include/mbgl/style/expression/match.hpp @@ -25,14 +25,15 @@ class Match : public Expression { otherwise(std::move(otherwise_)) {} + EvaluationResult evaluate(const EvaluationContext& params) const override; + void eachChild(const std::function& visit) const override; bool operator==(const Expression& e) const override; - EvaluationResult evaluate(const EvaluationContext& params) const override; - + std::vector> possibleOutputs() const override; + private: - std::unique_ptr input; Branches branches; std::unique_ptr otherwise; diff --git a/include/mbgl/style/expression/step.hpp b/include/mbgl/style/expression/step.hpp index e3c49bc6098..4a0a724d7ca 100644 --- a/include/mbgl/style/expression/step.hpp +++ b/include/mbgl/style/expression/step.hpp @@ -33,6 +33,8 @@ class Step : public Expression { bool operator==(const Expression& e) const override; + std::vector> possibleOutputs() const override; + static ParseResult parse(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx); private: diff --git a/include/mbgl/style/expression/value.hpp b/include/mbgl/style/expression/value.hpp index 8baa9d2dba1..be5be647523 100644 --- a/include/mbgl/style/expression/value.hpp +++ b/include/mbgl/style/expression/value.hpp @@ -148,6 +148,15 @@ struct ValueConverter::value >> { static optional fromExpressionValue(const Value& value); }; +template +std::vector> fromExpressionValues(const std::vector>& values) { + std::vector> result; + for (const auto& value : values) { + result.push_back(value ? fromExpressionValue(*value) : nullopt); + } + return result; +} + } // namespace expression } // namespace style } // namespace mbgl diff --git a/include/mbgl/style/function/camera_function.hpp b/include/mbgl/style/function/camera_function.hpp index 25b38e36168..015abd3e624 100644 --- a/include/mbgl/style/function/camera_function.hpp +++ b/include/mbgl/style/function/camera_function.hpp @@ -12,7 +12,6 @@ #include #include - namespace mbgl { namespace style { @@ -66,7 +65,11 @@ class CameraFunction { [&](auto z) { return z->getCoveringStops(lower, upper); } ); } - + + std::vector> possibleOutputs() const { + return expression::fromExpressionValues(expression->possibleOutputs()); + } + friend bool operator==(const CameraFunction& lhs, const CameraFunction& rhs) { return *lhs.expression == *rhs.expression; diff --git a/include/mbgl/style/function/composite_function.hpp b/include/mbgl/style/function/composite_function.hpp index b44bf8e6fe9..24578f599c7 100644 --- a/include/mbgl/style/function/composite_function.hpp +++ b/include/mbgl/style/function/composite_function.hpp @@ -102,6 +102,10 @@ class CompositeFunction { ); } + std::vector> possibleOutputs() const { + return expression::fromExpressionValues(expression->possibleOutputs()); + } + friend bool operator==(const CompositeFunction& lhs, const CompositeFunction& rhs) { return *lhs.expression == *rhs.expression; diff --git a/include/mbgl/style/function/convert.hpp b/include/mbgl/style/function/convert.hpp index 9f7b7ed1f8f..8e544d3ad59 100644 --- a/include/mbgl/style/function/convert.hpp +++ b/include/mbgl/style/function/convert.hpp @@ -45,6 +45,10 @@ class ErrorExpression : public Expression { return EvaluationError{message}; } + std::vector> possibleOutputs() const override { + return {}; + } + private: std::string message; }; diff --git a/include/mbgl/style/function/source_function.hpp b/include/mbgl/style/function/source_function.hpp index 02e4b604e23..bd7b109fd86 100644 --- a/include/mbgl/style/function/source_function.hpp +++ b/include/mbgl/style/function/source_function.hpp @@ -57,6 +57,10 @@ class SourceFunction { return defaultValue ? *defaultValue : finalDefaultValue; } + std::vector> possibleOutputs() const { + return expression::fromExpressionValues(expression->possibleOutputs()); + } + friend bool operator==(const SourceFunction& lhs, const SourceFunction& rhs) { return *lhs.expression == *rhs.expression; diff --git a/include/mbgl/style/layers/symbol_layer.hpp b/include/mbgl/style/layers/symbol_layer.hpp index a72baa0b4ed..f068e2d060f 100644 --- a/include/mbgl/style/layers/symbol_layer.hpp +++ b/include/mbgl/style/layers/symbol_layer.hpp @@ -118,9 +118,9 @@ class SymbolLayer : public Layer { DataDrivenPropertyValue getTextField() const; void setTextField(DataDrivenPropertyValue); - static PropertyValue> getDefaultTextFont(); - PropertyValue> getTextFont() const; - void setTextFont(PropertyValue>); + static DataDrivenPropertyValue> getDefaultTextFont(); + DataDrivenPropertyValue> getTextFont() const; + void setTextFont(DataDrivenPropertyValue>); static DataDrivenPropertyValue getDefaultTextSize(); DataDrivenPropertyValue getTextSize() const; diff --git a/include/mbgl/util/optional.hpp b/include/mbgl/util/optional.hpp index a9374a1b536..abec02dca94 100644 --- a/include/mbgl/util/optional.hpp +++ b/include/mbgl/util/optional.hpp @@ -7,4 +7,7 @@ namespace mbgl { template using optional = std::experimental::optional; +using nullopt_t = std::experimental::nullopt_t; +constexpr nullopt_t nullopt = std::experimental::nullopt; + } // namespace mbgl 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 8247577bd48..67596fb4284 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 @@ -2888,11 +2888,11 @@ public static PropertyValue textFont(Expression value) { /** * Font stack to use for displaying text. * - * @param the zoom parameter type - * @param function a wrapper {@link CameraFunction} for String[] + * @param the function input type + * @param function a wrapper function for String[] * @return property wrapper around a String[] function */ - public static PropertyValue> textFont(CameraFunction function) { + public static PropertyValue> textFont(Function function) { return new LayoutPropertyValue<>("text-font", function); } 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 f8248ae4a7e..781307862d5 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 @@ -1615,6 +1615,63 @@ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { }); } + @Test + public void testTextFontAsIdentitySourceFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("text-font"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + textFont(property("FeaturePropertyA", Stops.identity())) + ); + + // Verify + assertNotNull(layer.getTextFont()); + assertNotNull(layer.getTextFont().getFunction()); + assertEquals(SourceFunction.class, layer.getTextFont().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((SourceFunction) layer.getTextFont().getFunction()).getProperty()); + assertEquals(IdentityStops.class, layer.getTextFont().getFunction().getStops().getClass()); + } + }); + } + + @Test + public void testTextFontAsIntervalSourceFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("text-font"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + textFont( + property( + "FeaturePropertyA", + interval( + stop(1, textFont(new String[]{"Open Sans Regular", "Arial Unicode MS Regular"})) + ) + ) + ) + ); + + // Verify + assertNotNull(layer.getTextFont()); + assertNotNull(layer.getTextFont().getFunction()); + assertEquals(SourceFunction.class, layer.getTextFont().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((SourceFunction) layer.getTextFont().getFunction()).getProperty()); + assertEquals(IntervalStops.class, layer.getTextFont().getFunction().getStops().getClass()); + } + }); + } + @Test public void testTextSizeAsConstant() { validateTestSetup(); diff --git a/platform/darwin/src/MGLSymbolStyleLayer.h b/platform/darwin/src/MGLSymbolStyleLayer.h index f6d5323e222..84d32cd0b1e 100644 --- a/platform/darwin/src/MGLSymbolStyleLayer.h +++ b/platform/darwin/src/MGLSymbolStyleLayer.h @@ -1054,8 +1054,18 @@ MGL_EXPORT You can set this property to an instance of: * `MGLConstantStyleValue` - * `MGLCameraStyleFunction` with an interpolation mode of - `MGLInterpolationModeInterval` + * `MGLCameraStyleFunction` with an interpolation mode of: + * `MGLInterpolationModeExponential` + * `MGLInterpolationModeInterval` + * `MGLSourceStyleFunction` with an interpolation mode of: + * `MGLInterpolationModeExponential` + * `MGLInterpolationModeInterval` + * `MGLInterpolationModeCategorical` + * `MGLInterpolationModeIdentity` + * `MGLCompositeStyleFunction` with an interpolation mode of: + * `MGLInterpolationModeExponential` + * `MGLInterpolationModeInterval` + * `MGLInterpolationModeCategorical` */ @property (nonatomic, null_resettable) MGLStyleValue *> *textFontNames; diff --git a/platform/darwin/src/MGLSymbolStyleLayer.mm b/platform/darwin/src/MGLSymbolStyleLayer.mm index 1990c826699..3f206aac19b 100644 --- a/platform/darwin/src/MGLSymbolStyleLayer.mm +++ b/platform/darwin/src/MGLSymbolStyleLayer.mm @@ -632,7 +632,7 @@ - (void)setTextAnchor:(MGLStyleValue *)textAnchor { - (void)setTextFontNames:(MGLStyleValue *> *)textFontNames { MGLAssertStyleLayerIsValid(); - auto mbglValue = MGLStyleValueTransformer, NSArray *, std::string>().toPropertyValue(textFontNames); + auto mbglValue = MGLStyleValueTransformer, NSArray *, std::string>().toDataDrivenPropertyValue(textFontNames); self.rawLayer->setTextFont(mbglValue); } @@ -641,9 +641,9 @@ - (void)setTextFontNames:(MGLStyleValue *> *)textFontNames { auto propertyValue = self.rawLayer->getTextFont(); if (propertyValue.isUndefined()) { - return MGLStyleValueTransformer, NSArray *, std::string>().toStyleValue(self.rawLayer->getDefaultTextFont()); + return MGLStyleValueTransformer, NSArray *, std::string>().toDataDrivenStyleValue(self.rawLayer->getDefaultTextFont()); } - return MGLStyleValueTransformer, NSArray *, std::string>().toStyleValue(propertyValue); + return MGLStyleValueTransformer, NSArray *, std::string>().toDataDrivenStyleValue(propertyValue); } - (void)setTextFont:(MGLStyleValue *> *)textFont { diff --git a/platform/darwin/test/MGLSymbolStyleLayerTests.mm b/platform/darwin/test/MGLSymbolStyleLayerTests.mm index 1ac86dd4029..60d674f2edf 100644 --- a/platform/darwin/test/MGLSymbolStyleLayerTests.mm +++ b/platform/darwin/test/MGLSymbolStyleLayerTests.mm @@ -1017,7 +1017,7 @@ - (void)testProperties { MGLStyleValue *> *constantStyleValue = [MGLStyleValue *> valueWithRawValue:@[@"Text Font", @"Tnof Txet"]]; layer.textFontNames = constantStyleValue; - mbgl::style::PropertyValue> propertyValue = { { "Text Font", "Tnof Txet" } }; + mbgl::style::DataDrivenPropertyValue> propertyValue = { { "Text Font", "Tnof Txet" } }; XCTAssertEqual(rawLayer->getTextFont(), propertyValue, @"Setting textFontNames to a constant value should update text-font."); XCTAssertEqualObjects(layer.textFontNames, constantStyleValue, @@ -1041,11 +1041,6 @@ - (void)testProperties { @"Unsetting textFontNames should return text-font to the default value."); XCTAssertEqualObjects(layer.textFontNames, defaultStyleValue, @"textFontNames should return the default value after being unset."); - - functionStyleValue = [MGLStyleValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.textFontNames = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.textFontNames = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); } // text-size diff --git a/platform/node/test/ignores.json b/platform/node/test/ignores.json index 7cb3560f4d2..0c287212a4f 100644 --- a/platform/node/test/ignores.json +++ b/platform/node/test/ignores.json @@ -60,7 +60,6 @@ "render-tests/runtime-styling/set-style-paint-property-fill-flat-to-extrude": "https://github.com/mapbox/mapbox-gl-native/issues/6745", "render-tests/symbol-placement/line-overscaled": "https://github.com/mapbox/mapbox-gl-js/issues/5654", "render-tests/symbol-visibility/visible": "https://github.com/mapbox/mapbox-gl-native/issues/10409", - "render-tests/text-font/data-expression": "https://github.com/mapbox/mapbox-gl-native/issues/10535", "render-tests/text-pitch-alignment/auto-text-rotation-alignment-map": "https://github.com/mapbox/mapbox-gl-native/issues/9732", "render-tests/text-pitch-alignment/map-text-rotation-alignment-map": "https://github.com/mapbox/mapbox-gl-native/issues/9732", "render-tests/text-pitch-alignment/viewport-text-rotation-alignment-map": "https://github.com/mapbox/mapbox-gl-native/issues/9732", diff --git a/src/mbgl/layout/symbol_layout.cpp b/src/mbgl/layout/symbol_layout.cpp index fc1ede56ffe..a41a98fcaf9 100644 --- a/src/mbgl/layout/symbol_layout.cpp +++ b/src/mbgl/layout/symbol_layout.cpp @@ -32,7 +32,7 @@ using namespace style; template static bool has(const style::SymbolLayoutProperties::PossiblyEvaluated& layout) { return layout.get().match( - [&] (const std::string& s) { return !s.empty(); }, + [&] (const typename Property::Type& t) { return !t.empty(); }, [&] (const auto&) { return true; } ); } @@ -82,7 +82,7 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters, layout.get() = layout.get(); } - const bool hasText = has(layout) && !layout.get().empty(); + const bool hasText = has(layout) && has(layout); const bool hasIcon = has(layout); if (!hasText && !hasIcon) { @@ -143,12 +143,15 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters, && layout.get() == SymbolPlacementType::Line && util::i18n::allowsVerticalWritingMode(*ft.text); + FontStack fontStack = layout.evaluate(zoom, ft); + GlyphIDs& dependencies = glyphDependencies[fontStack]; + // Loop through all characters of this text and collect unique codepoints. for (char16_t chr : *ft.text) { - glyphDependencies[layout.get()].insert(chr); + dependencies.insert(chr); if (canVerticalizeText) { if (char16_t verticalChr = util::i18n::verticalizePunctuation(chr)) { - glyphDependencies[layout.get()].insert(verticalChr); + dependencies.insert(verticalChr); } } } @@ -183,18 +186,20 @@ void SymbolLayout::prepare(const GlyphMap& glyphMap, const GlyphPositions& glyph const bool textAlongLine = layout.get() == AlignmentType::Map && layout.get() == SymbolPlacementType::Line; - auto glyphMapIt = glyphMap.find(layout.get()); - const Glyphs& glyphs = glyphMapIt != glyphMap.end() - ? glyphMapIt->second : Glyphs(); - - auto glyphPositionsIt = glyphPositions.find(layout.get()); - const GlyphPositionMap& glyphPositionMap = glyphPositionsIt != glyphPositions.end() - ? glyphPositionsIt->second : GlyphPositionMap(); - for (auto it = features.begin(); it != features.end(); ++it) { auto& feature = *it; if (feature.geometry.empty()) continue; + FontStack fontStack = layout.evaluate(zoom, feature); + + auto glyphMapIt = glyphMap.find(fontStack); + const Glyphs& glyphs = glyphMapIt != glyphMap.end() + ? glyphMapIt->second : Glyphs(); + + auto glyphPositionsIt = glyphPositions.find(fontStack); + const GlyphPositionMap& glyphPositionMap = glyphPositionsIt != glyphPositions.end() + ? glyphPositionsIt->second : GlyphPositionMap(); + std::pair shapedTextOrientations; optional shapedIcon; diff --git a/src/mbgl/style/conversion/make_property_setters.hpp b/src/mbgl/style/conversion/make_property_setters.hpp index 074d7eb7303..18370df636a 100644 --- a/src/mbgl/style/conversion/make_property_setters.hpp +++ b/src/mbgl/style/conversion/make_property_setters.hpp @@ -49,7 +49,7 @@ inline auto makeLayoutPropertySetters() { result["text-pitch-alignment"] = &setProperty, &SymbolLayer::setTextPitchAlignment>; result["text-rotation-alignment"] = &setProperty, &SymbolLayer::setTextRotationAlignment>; result["text-field"] = &setProperty, &SymbolLayer::setTextField>; - result["text-font"] = &setProperty>, &SymbolLayer::setTextFont>; + result["text-font"] = &setProperty>, &SymbolLayer::setTextFont>; result["text-size"] = &setProperty, &SymbolLayer::setTextSize>; result["text-max-width"] = &setProperty, &SymbolLayer::setTextMaxWidth>; result["text-line-height"] = &setProperty, &SymbolLayer::setTextLineHeight>; diff --git a/src/mbgl/style/expression/assertion.cpp b/src/mbgl/style/expression/assertion.cpp index a17c53cf549..0187921af95 100644 --- a/src/mbgl/style/expression/assertion.cpp +++ b/src/mbgl/style/expression/assertion.cpp @@ -66,6 +66,16 @@ bool Assertion::operator==(const Expression& e) const { return false; } +std::vector> Assertion::possibleOutputs() const { + std::vector> result; + for (const auto& input : inputs) { + for (auto& output : input->possibleOutputs()) { + result.push_back(std::move(output)); + } + } + return result; +} + } // namespace expression } // namespace style } // namespace mbgl diff --git a/src/mbgl/style/expression/boolean_operator.cpp b/src/mbgl/style/expression/boolean_operator.cpp index 88797f965a0..8d277450ba1 100644 --- a/src/mbgl/style/expression/boolean_operator.cpp +++ b/src/mbgl/style/expression/boolean_operator.cpp @@ -26,6 +26,10 @@ bool Any::operator==(const Expression& e) const { return false; } +std::vector> Any::possibleOutputs() const { + return {{ true }, { false }}; +} + EvaluationResult All::evaluate(const EvaluationContext& params) const { for (auto it = inputs.begin(); it != inputs.end(); it++) { @@ -49,6 +53,10 @@ bool All::operator==(const Expression& e) const { return false; } +std::vector> All::possibleOutputs() const { + return {{ true }, { false }}; +} + using namespace mbgl::style::conversion; template diff --git a/src/mbgl/style/expression/case.cpp b/src/mbgl/style/expression/case.cpp index 049f258606d..295e694189c 100644 --- a/src/mbgl/style/expression/case.cpp +++ b/src/mbgl/style/expression/case.cpp @@ -34,6 +34,19 @@ bool Case::operator==(const Expression& e) const { return false; } +std::vector> Case::possibleOutputs() const { + std::vector> result; + for (const auto& branch : branches) { + for (auto& output : branch.second->possibleOutputs()) { + result.push_back(std::move(output)); + } + } + for (auto& output : otherwise->possibleOutputs()) { + result.push_back(std::move(output)); + } + return result; +} + using namespace mbgl::style::conversion; ParseResult Case::parse(const Convertible& value, ParsingContext& ctx) { assert(isArray(value)); diff --git a/src/mbgl/style/expression/coalesce.cpp b/src/mbgl/style/expression/coalesce.cpp index 0373c9626c2..872a9abbefc 100644 --- a/src/mbgl/style/expression/coalesce.cpp +++ b/src/mbgl/style/expression/coalesce.cpp @@ -27,6 +27,16 @@ bool Coalesce::operator==(const Expression& e) const { return false; } +std::vector> Coalesce::possibleOutputs() const { + std::vector> result; + for (const auto& arg : args) { + for (auto& output : arg->possibleOutputs()) { + result.push_back(std::move(output)); + } + } + return result; +} + using namespace mbgl::style::conversion; ParseResult Coalesce::parse(const Convertible& value, ParsingContext& ctx) { assert(isArray(value)); diff --git a/src/mbgl/style/expression/coercion.cpp b/src/mbgl/style/expression/coercion.cpp index 8ed8e160dd1..56ab33fcfd3 100644 --- a/src/mbgl/style/expression/coercion.cpp +++ b/src/mbgl/style/expression/coercion.cpp @@ -136,6 +136,16 @@ bool Coercion::operator==(const Expression& e) const { return false; } +std::vector> Coercion::possibleOutputs() const { + std::vector> result; + for (const auto& input : inputs) { + for (auto& output : input->possibleOutputs()) { + result.push_back(std::move(output)); + } + } + return result; +} + } // namespace expression } // namespace style } // namespace mbgl diff --git a/src/mbgl/style/expression/equals.cpp b/src/mbgl/style/expression/equals.cpp index 08ef85e92bd..6d963cc1d86 100644 --- a/src/mbgl/style/expression/equals.cpp +++ b/src/mbgl/style/expression/equals.cpp @@ -37,6 +37,10 @@ bool Equals::operator==(const Expression& e) const { return false; } +std::vector> Equals::possibleOutputs() const { + return {{ true }, { false }}; +} + static bool isComparableType(const type::Type& type) { return type == type::String || type == type::Number || diff --git a/src/mbgl/style/expression/interpolate.cpp b/src/mbgl/style/expression/interpolate.cpp index 5ddfca8e9fb..4cb22a3e4f1 100644 --- a/src/mbgl/style/expression/interpolate.cpp +++ b/src/mbgl/style/expression/interpolate.cpp @@ -206,6 +206,16 @@ ParseResult parseInterpolate(const Convertible& value, ParsingContext& ctx) { ); } +std::vector> InterpolateBase::possibleOutputs() const { + std::vector> result; + for (const auto& stop : stops) { + for (auto& output : stop.second->possibleOutputs()) { + result.push_back(std::move(output)); + } + } + return result; +} + } // namespace expression } // namespace style } // namespace mbgl diff --git a/src/mbgl/style/expression/let.cpp b/src/mbgl/style/expression/let.cpp index 5c08248eefc..fe48138ac31 100644 --- a/src/mbgl/style/expression/let.cpp +++ b/src/mbgl/style/expression/let.cpp @@ -17,6 +17,10 @@ void Let::eachChild(const std::function& visit) const { visit(*result); } +std::vector> Let::possibleOutputs() const { + return result->possibleOutputs(); +} + using namespace mbgl::style::conversion; ParseResult Let::parse(const Convertible& value, ParsingContext& ctx) { @@ -67,6 +71,10 @@ EvaluationResult Var::evaluate(const EvaluationContext& params) const { void Var::eachChild(const std::function&) const {} +std::vector> Var::possibleOutputs() const { + return { nullopt }; +} + ParseResult Var::parse(const Convertible& value_, ParsingContext& ctx) { assert(isArray(value_)); diff --git a/src/mbgl/style/expression/match.cpp b/src/mbgl/style/expression/match.cpp index 35356747c95..0b2790b6884 100644 --- a/src/mbgl/style/expression/match.cpp +++ b/src/mbgl/style/expression/match.cpp @@ -26,6 +26,20 @@ bool Match::operator==(const Expression& e) const { return false; } +template +std::vector> Match::possibleOutputs() const { + std::vector> result; + for (const auto& branch : branches) { + for (auto& output : branch.second->possibleOutputs()) { + result.push_back(std::move(output)); + } + } + for (auto& output : otherwise->possibleOutputs()) { + result.push_back(std::move(output)); + } + return result; +} + template<> EvaluationResult Match::evaluate(const EvaluationContext& params) const { const EvaluationResult inputValue = input->evaluate(params); diff --git a/src/mbgl/style/expression/step.cpp b/src/mbgl/style/expression/step.cpp index bfcb6fd270f..1fb3bd73cc5 100644 --- a/src/mbgl/style/expression/step.cpp +++ b/src/mbgl/style/expression/step.cpp @@ -40,6 +40,16 @@ bool Step::operator==(const Expression& e) const { return false; } +std::vector> Step::possibleOutputs() const { + std::vector> result; + for (const auto& stop : stops) { + for (auto& output : stop.second->possibleOutputs()) { + result.push_back(std::move(output)); + } + } + return result; +} + Range Step::getCoveringStops(const double lower, const double upper) const { return ::mbgl::style::expression::getCoveringStops(stops, lower, upper); } diff --git a/src/mbgl/style/layers/symbol_layer.cpp b/src/mbgl/style/layers/symbol_layer.cpp index 9a944657cad..d1a1ba246e0 100644 --- a/src/mbgl/style/layers/symbol_layer.cpp +++ b/src/mbgl/style/layers/symbol_layer.cpp @@ -412,15 +412,15 @@ void SymbolLayer::setTextField(DataDrivenPropertyValue value) { baseImpl = std::move(impl_); observer->onLayerChanged(*this); } -PropertyValue> SymbolLayer::getDefaultTextFont() { +DataDrivenPropertyValue> SymbolLayer::getDefaultTextFont() { return TextFont::defaultValue(); } -PropertyValue> SymbolLayer::getTextFont() const { +DataDrivenPropertyValue> SymbolLayer::getTextFont() const { return impl().layout.get(); } -void SymbolLayer::setTextFont(PropertyValue> value) { +void SymbolLayer::setTextFont(DataDrivenPropertyValue> value) { if (value == getTextFont()) return; auto impl_ = mutableImpl(); diff --git a/src/mbgl/style/layers/symbol_layer_properties.hpp b/src/mbgl/style/layers/symbol_layer_properties.hpp index 436b5cbd4f6..e70ac28d598 100644 --- a/src/mbgl/style/layers/symbol_layer_properties.hpp +++ b/src/mbgl/style/layers/symbol_layer_properties.hpp @@ -112,7 +112,7 @@ struct TextField : DataDrivenLayoutProperty { static std::string defaultValue() { return ""; } }; -struct TextFont : LayoutProperty> { +struct TextFont : DataDrivenLayoutProperty> { static constexpr const char * key = "text-font"; static std::vector defaultValue() { return { "Open Sans Regular", "Arial Unicode MS Regular" }; } }; diff --git a/src/mbgl/style/parser.cpp b/src/mbgl/style/parser.cpp index 10fce339860..52d788b3a13 100644 --- a/src/mbgl/style/parser.cpp +++ b/src/mbgl/style/parser.cpp @@ -271,28 +271,29 @@ void Parser::parseLayer(const std::string& id, const JSValue& value, std::unique } std::vector Parser::fontStacks() const { - std::set optional; + std::set result; for (const auto& layer : layers) { if (layer->is()) { - PropertyValue textFont = layer->as()->getTextFont(); - if (textFont.isUndefined()) { - optional.insert({"Open Sans Regular", "Arial Unicode MS Regular"}); - } else if (textFont.isConstant()) { - optional.insert(textFont.asConstant()); - } else if (textFont.isCameraFunction()) { - textFont.asCameraFunction().stops.match( - [&] (const auto& stops) { - for (const auto& stop : stops.stops) { - optional.insert(stop.second); + layer->as()->getTextFont().match( + [&] (Undefined) { + result.insert({"Open Sans Regular", "Arial Unicode MS Regular"}); + }, + [&] (const FontStack& constant) { + result.insert(constant); + }, + [&] (const auto& function) { + for (const auto& value : function.possibleOutputs()) { + if (value) { + result.insert(*value); } } - ); - } + } + ); } } - return std::vector(optional.begin(), optional.end()); + return std::vector(result.begin(), result.end()); } } // namespace style diff --git a/test/style/style_parser.test.cpp b/test/style/style_parser.test.cpp index 5fa81b47e91..7f7c06f4c5a 100644 --- a/test/style/style_parser.test.cpp +++ b/test/style/style_parser.test.cpp @@ -102,3 +102,78 @@ TEST(StyleParser, FontStacks) { ASSERT_EQ(FontStack({"a", "b"}), result[1]); ASSERT_EQ(FontStack({"a", "b", "c"}), result[2]); } + +TEST(StyleParser, FontStacksCaseExpression) { + style::Parser parser; + parser.parse(R"({ + "version": 8, + "layers": [{ + "id": "symbol", + "type": "symbol", + "source": "vector", + "layout": { + "text-font": ["case", ["==", "a", ["string", ["get", "text-font"]]], ["literal", ["Arial"]], ["literal", ["Helvetica"]]] + } + }] + })"); + auto result = parser.fontStacks(); + ASSERT_EQ(2u, result.size()); + ASSERT_EQ(FontStack({"Arial"}), result[0]); + ASSERT_EQ(FontStack({"Helvetica"}), result[1]); +} + +TEST(StyleParser, FontStacksMatchExpression) { + style::Parser parser; + parser.parse(R"({ + "version": 8, + "layers": [{ + "id": "symbol", + "type": "symbol", + "source": "vector", + "layout": { + "text-font": ["match", ["get", "text-font"], "a", ["literal", ["Arial"]], ["literal", ["Helvetica"]]] + } + }] + })"); + auto result = parser.fontStacks(); + ASSERT_EQ(2u, result.size()); + ASSERT_EQ(FontStack({"Arial"}), result[0]); + ASSERT_EQ(FontStack({"Helvetica"}), result[1]); +} + +TEST(StyleParser, FontStacksStepExpression) { + style::Parser parser; + parser.parse(R"({ + "version": 8, + "layers": [{ + "id": "symbol", + "type": "symbol", + "source": "vector", + "layout": { + "text-font": ["array", "string", ["step", ["get", "text-font"], ["literal", ["Arial"]], 0, ["literal", ["Helvetica"]]]] + } + }] + })"); + auto result = parser.fontStacks(); + ASSERT_EQ(2u, result.size()); + ASSERT_EQ(FontStack({"Arial"}), result[0]); + ASSERT_EQ(FontStack({"Helvetica"}), result[1]); +} + +TEST(StyleParser, FontStacksGetExpression) { + // Invalid style, but not currently validated. + style::Parser parser; + parser.parse(R"({ + "version": 8, + "layers": [{ + "id": "symbol", + "type": "symbol", + "source": "vector", + "layout": { + "text-font": ["array", "string", ["get", "text-font"]] + } + }] + })"); + auto result = parser.fontStacks(); + ASSERT_EQ(0u, result.size()); +} From 2ddd492496f4207c45c21acb21d34547c9b2b975 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 10 Jan 2018 14:42:53 -0800 Subject: [PATCH 2/2] [core] Add warning for invalid text-font values --- src/mbgl/style/parser.cpp | 6 + .../fixtures/style_parser/text-font.info.json | 14 ++ .../style_parser/text-font.style.json | 135 ++++++++++++++++++ 3 files changed, 155 insertions(+) create mode 100644 test/fixtures/style_parser/text-font.info.json create mode 100644 test/fixtures/style_parser/text-font.style.json diff --git a/src/mbgl/style/parser.cpp b/src/mbgl/style/parser.cpp index 52d788b3a13..b177f2159cd 100644 --- a/src/mbgl/style/parser.cpp +++ b/src/mbgl/style/parser.cpp @@ -119,6 +119,9 @@ StyleParseResult Parser::parse(const std::string& json) { } } + // Call for side effect of logging warnings for invalid values. + fontStacks(); + return nullptr; } @@ -286,6 +289,9 @@ std::vector Parser::fontStacks() const { for (const auto& value : function.possibleOutputs()) { if (value) { result.insert(*value); + } else { + Log::Warning(Event::ParseStyle, "Layer '%s' has an invalid value for text-font and will not work offline. Output values must be contained as literals within the expression.", layer->getID().c_str()); + break; } } } diff --git a/test/fixtures/style_parser/text-font.info.json b/test/fixtures/style_parser/text-font.info.json new file mode 100644 index 00000000000..0fb96582693 --- /dev/null +++ b/test/fixtures/style_parser/text-font.info.json @@ -0,0 +1,14 @@ +{ + "default": { + "log": [ + [1, "WARNING", "ParseStyle", "Layer 'invalid expression - get' has an invalid value for text-font and will not work offline. Output values must be contained as literals within the expression."], + [1, "WARNING", "ParseStyle", "Layer 'invalid expression - case' has an invalid value for text-font and will not work offline. Output values must be contained as literals within the expression."], + [1, "WARNING", "ParseStyle", "Layer 'invalid expression - match' has an invalid value for text-font and will not work offline. Output values must be contained as literals within the expression."], + [1, "WARNING", "ParseStyle", "Layer 'invalid expression - at' has an invalid value for text-font and will not work offline. Output values must be contained as literals within the expression."], + [1, "WARNING", "ParseStyle", "Layer 'invalid expression - coalesce' has an invalid value for text-font and will not work offline. Output values must be contained as literals within the expression."], + [1, "WARNING", "ParseStyle", "Layer 'invalid expression - step' has an invalid value for text-font and will not work offline. Output values must be contained as literals within the expression."], + [1, "WARNING", "ParseStyle", "Layer 'invalid expression - let/var' has an invalid value for text-font and will not work offline. Output values must be contained as literals within the expression."], + [1, "WARNING", "ParseStyle", "Layer 'invalid expression - identity function' has an invalid value for text-font and will not work offline. Output values must be contained as literals within the expression."] + ] + } +} diff --git a/test/fixtures/style_parser/text-font.style.json b/test/fixtures/style_parser/text-font.style.json new file mode 100644 index 00000000000..215807ca81a --- /dev/null +++ b/test/fixtures/style_parser/text-font.style.json @@ -0,0 +1,135 @@ +{ + "version": 8, + "glyphs": "https://example.com/{fontstack}/{range}", + "sources": { + "vector": { + "type": "vector", + "url": "mapbox://mapbox.mapbox-streets-v5" + } + }, + "layers": [ + { + "id": "minimum", + "type": "symbol", + "source": "vector", + "source-layer": "layer", + "layout": { + "text-font": ["Helvetica"], + "text-field": "{foo}" + } + }, + { + "id": "valid expression - case", + "type": "symbol", + "source": "vector", + "source-layer": "layer", + "layout": { + "text-font": ["case", ["==", "a", ["string", ["get", "text-font"]]], ["literal", ["Arial"]], ["literal", ["Helvetica"]]], + "text-field": "{foo}" + } + }, + { + "id": "valid expression - match", + "type": "symbol", + "source": "vector", + "source-layer": "layer", + "layout": { + "text-font": ["match", ["get", "text-font"], "a", ["literal", ["Arial"]], ["literal", ["Helvetica"]]], + "text-field": "{foo}" + } + }, + { + "id": "valid expression - step", + "type": "symbol", + "source": "vector", + "source-layer": "layer", + "layout": { + "text-font": ["array", "string", ["step", ["get", "text-font"], ["literal", ["Arial"]], 0, ["literal", ["Helvetica"]]]], + "text-field": "{foo}" + } + }, + { + "id": "invalid expression - get", + "type": "symbol", + "source": "vector", + "source-layer": "layer", + "layout": { + "text-font": ["array", "string", ["get", "text-font"]], + "text-field": "{foo}" + } + }, + { + "id": "invalid expression - case", + "type": "symbol", + "source": "vector", + "source-layer": "layer", + "layout": { + "text-font": ["case", ["==", "a", ["string", ["get", "text-font"]]], ["array", "string", ["get", "text-font-a"]], ["literal", ["Helvetica"]]], + "text-field": "{foo}" + } + }, + { + "id": "invalid expression - match", + "type": "symbol", + "source": "vector", + "source-layer": "layer", + "layout": { + "text-font": ["match", ["get", "text-font"], "a", ["array", "string", ["get", "text-font-a"]], ["literal", ["Helvetica"]]], + "text-field": "{foo}" + } + }, + { + "id": "invalid expression - at", + "type": "symbol", + "source": "vector", + "source-layer": "layer", + "layout": { + "text-font": ["array", "string", ["at", 0, ["array", ["get", "text-font"]]]], + "text-field": "{foo}" + } + }, + { + "id": "invalid expression - coalesce", + "type": "symbol", + "source": "vector", + "source-layer": "layer", + "layout": { + "text-font": ["array", "string", ["coalesce", ["get", "text-font"]]], + "text-field": "{foo}" + } + }, + { + "id": "invalid expression - step", + "type": "symbol", + "source": "vector", + "source-layer": "layer", + "layout": { + "text-font": ["step", ["zoom"], ["array", "string", ["get", "text-font-0"]], 0, ["array", "string", ["get", "text-font-1"]]], + "text-field": "{foo}" + } + }, + { + "id": "invalid expression - let/var", + "type": "symbol", + "source": "vector", + "source-layer": "layer", + "layout": { + "text-font": ["let", "p", ["array", "string", ["get", "text-font"]], ["var", "p"]], + "text-field": "{foo}" + } + }, + { + "id": "invalid expression - identity function", + "type": "symbol", + "source": "vector", + "source-layer": "layer", + "layout": { + "text-font": { + "type": "identity", + "property": "text-font" + }, + "text-field": "{foo}" + } + } + ] +}