summaryrefslogtreecommitdiff
path: root/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk
diff options
context:
space:
mode:
Diffstat (limited to 'platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk')
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/expressions/Expression.java319
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyValue.java4
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/SymbolLayer.java28
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/layer.java.ejs34
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/types/Formatted.java54
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/types/FormattedSection.java100
6 files changed, 491 insertions, 48 deletions
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/expressions/Expression.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/expressions/Expression.java
index f586ecfdb2..1184baba65 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/expressions/Expression.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/expressions/Expression.java
@@ -183,7 +183,7 @@ public class Expression {
*/
public static Expression literal(@NonNull Object object) {
if (object.getClass().isArray()) {
- return literal(ExpressionArray.toObjectArray(object));
+ return literal(toObjectArray(object));
} else if (object instanceof Expression) {
throw new RuntimeException("Can't convert an expression to a literal");
}
@@ -197,7 +197,7 @@ public class Expression {
* @return the expression
*/
public static Expression literal(@NonNull Object[] array) {
- return new ExpressionArray(array);
+ return new Expression("literal", new ExpressionLiteralArray(array));
}
/**
@@ -2872,7 +2872,8 @@ public class Expression {
*
* @param expression the expression to evaluate
* @return expression
- * @see <a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-is-supported-script">Style specification</a>
+ * @see <a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-is-supported-script">Style
+ * specification</a>
*/
public static Expression isSupportedScript(Expression expression) {
return new Expression("is-supported-script", expression);
@@ -2902,7 +2903,8 @@ public class Expression {
*
* @param string the string to evaluate
* @return expression
- * @see <a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-is-supported-script">Style specification</a>
+ * @see <a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-is-supported-script">Style
+ * specification</a>
*/
public static Expression isSupportedScript(String string) {
return new Expression("is-supported-script", literal(string));
@@ -3224,9 +3226,121 @@ public class Expression {
return new Expression("collator", new ExpressionMap(map));
}
- public static Expression format(Expression input) {
- Map<String, Expression> map = new HashMap<>();
- return new Expression("format", input, new ExpressionMap(map));
+ /**
+ * Returns formatted text containing annotations for use in mixed-format text-field entries.
+ * <p>
+ * To build the expression, use {@link #formatEntry(Expression, FormatOption...)}.
+ * <p>
+ * "format" expression can be used, for example, with the {@link PropertyFactory#textField(Expression)}
+ * and accepts unlimited numbers of formatted sections.
+ * <p>
+ * Each section consist of the input, the displayed text, and options, like font-scale and text-font.
+ * <p>
+ * Example usage:
+ * </p>
+ * <pre>
+ * {@code
+ * SymbolLayer symbolLayer = new SymbolLayer("layer-id", "source-id");
+ * symbolLayer.setProperties(
+ * textField(
+ * format(
+ * formatEntry(
+ * get("header_property"),
+ * fontScale(2.0),
+ * textFont(new String[] {"DIN Offc Pro Regular", "Arial Unicode MS Regular"})
+ * ),
+ * formatEntry(concat(literal("\n"), get("description_property")), fontScale(1.5)),
+ * }
+ * </pre>
+ *
+ * @param formatEntries format entries
+ * @return expression
+ * @see <a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-types-format">Style specification</a>
+ */
+ public static Expression format(@NonNull FormatEntry... formatEntries) {
+ // for each entry we are going to build an input and parameters
+ Expression[] mappedExpressions = new Expression[formatEntries.length * 2];
+
+ int mappedIndex = 0;
+ for (FormatEntry formatEntry : formatEntries) {
+ // input
+ mappedExpressions[mappedIndex++] = formatEntry.text;
+
+ // parameters
+ Map<String, Expression> map = new HashMap<>();
+
+ if (formatEntry.options != null) {
+ for (FormatOption option : formatEntry.options) {
+ map.put(option.type, option.value);
+ }
+ }
+
+ mappedExpressions[mappedIndex++] = new ExpressionMap(map);
+ }
+
+ return new Expression("format", mappedExpressions);
+ }
+
+ /**
+ * Returns a format entry that can be used in {@link #format(FormatEntry...)} to create formatted text fields.
+ * <p>
+ * Text is required to be of a resulting type string.
+ * <p>
+ * Text is required to be passed; {@link FormatOption}s are optional and will default to the base values defined
+ * for the symbol.
+ *
+ * @param text displayed text
+ * @param formatOptions format options
+ * @return format entry
+ */
+ public static FormatEntry formatEntry(@NonNull Expression text, @Nullable FormatOption... formatOptions) {
+ return new FormatEntry(text, formatOptions);
+ }
+
+ /**
+ * Returns a format entry that can be used in {@link #format(FormatEntry...)} to create formatted text fields.
+ * <p>
+ * Text is required to be of a resulting type string.
+ * <p>
+ * Text is required to be passed; {@link FormatOption}s are optional and will default to the base values defined
+ * for the symbol.
+ *
+ * @param text displayed text
+ * @return format entry
+ */
+ public static FormatEntry formatEntry(@NonNull Expression text) {
+ return new FormatEntry(text, null);
+ }
+
+ /**
+ * Returns a format entry that can be used in {@link #format(FormatEntry...)} to create formatted text fields.
+ * <p>
+ * Text is required to be of a resulting type string.
+ * <p>
+ * Text is required to be passed; {@link FormatOption}s are optional and will default to the base values defined
+ * for the symbol.
+ *
+ * @param text displayed text
+ * @param formatOptions format options
+ * @return format entry
+ */
+ public static FormatEntry formatEntry(@NonNull String text, @Nullable FormatOption... formatOptions) {
+ return new FormatEntry(literal(text), formatOptions);
+ }
+
+ /**
+ * Returns a format entry that can be used in {@link #format(FormatEntry...)} to create formatted text fields.
+ * <p>
+ * Text is required to be of a resulting type string.
+ * <p>
+ * Text is required to be passed; {@link FormatOption}s are optional and will default to the base values defined
+ * for the symbol.
+ *
+ * @param text displayed text
+ * @return format entry
+ */
+ public static FormatEntry formatEntry(@NonNull String text) {
+ return new FormatEntry(literal(text), null);
}
/**
@@ -3981,18 +4095,7 @@ public class Expression {
if (arguments != null) {
for (Object argument : arguments) {
builder.append(", ");
- if (argument instanceof ExpressionLiteral) {
- Object literalValue = ((ExpressionLiteral) argument).toValue();
-
- // special case for handling unusual input like 'rgba(r, g, b, a)'
- if (literalValue instanceof String) {
- builder.append("\"").append(literalValue).append("\"");
- } else {
- builder.append(literalValue);
- }
- } else {
- builder.append(argument.toString());
- }
+ builder.append(argument.toString());
}
}
builder.append("]");
@@ -4231,6 +4334,99 @@ public class Expression {
}
/**
+ * Holds format entries used in a {@link #format(FormatEntry...)} expression.
+ */
+ public static class FormatEntry {
+ @NonNull
+ private Expression text;
+ @Nullable
+ private FormatOption[] options;
+
+ FormatEntry(@NonNull Expression text, @Nullable FormatOption[] options) {
+ this.text = text;
+ this.options = options;
+ }
+ }
+
+ /**
+ * Holds format options used in a {@link #formatEntry(Expression, FormatOption...)} that builds
+ * a {@link #format(FormatEntry...)} expression.
+ * <p>
+ * If an option is not set, it defaults to the base value defined for the symbol.
+ */
+ public static class FormatOption {
+ @NonNull
+ private String type;
+ @NonNull
+ private Expression value;
+
+ FormatOption(@NonNull String type, @NonNull Expression value) {
+ this.type = type;
+ this.value = value;
+ }
+
+ /**
+ * If set, the font-scale argument specifies a scaling factor relative to the text-size
+ * specified in the root layout properties.
+ * <p>
+ * "font-scale" is required to be of a resulting type number.
+ *
+ * @param expression expression
+ * @return format option
+ */
+ @NonNull
+ public static FormatOption fontScale(@NonNull Expression expression) {
+ return new FormatOption("font-scale", expression);
+ }
+
+ /**
+ * If set, the font-scale argument specifies a scaling factor relative to the text-size
+ * specified in the root layout properties.
+ * <p>
+ * "font-scale" is required to be of a resulting type number.
+ *
+ * @param scale value
+ * @return format option
+ */
+ @NonNull
+ public static FormatOption fontScale(double scale) {
+ return new FormatOption("font-scale", literal(scale));
+ }
+
+ /**
+ * If set, the text-font argument overrides the font specified by the root layout properties.
+ * <p>
+ * "text-font" is required to a literal array.
+ * <p>
+ * The requested font stack has to be a part of the used style.
+ * For more information see <a href="https://www.mapbox.com/help/define-font-stack/">the documentation</a>.
+ *
+ * @param expression expression
+ * @return format option
+ */
+ @NonNull
+ public static FormatOption textFont(@NonNull Expression expression) {
+ return new FormatOption("text-font", expression);
+ }
+
+ /**
+ * If set, the text-font argument overrides the font specified by the root layout properties.
+ * <p>
+ * "text-font" is required to a literal array.
+ * <p>
+ * The requested font stack has to be a part of the used style.
+ * For more information see <a href="https://www.mapbox.com/help/define-font-stack/">the documentation</a>.
+ *
+ * @param fontStack value
+ * @return format option
+ */
+ @NonNull
+ public static FormatOption textFont(@NonNull String[] fontStack) {
+ return new FormatOption("text-font", literal(fontStack));
+ }
+ }
+
+ /**
* Converts a JsonArray or a raw expression to a Java expression.
*/
public final static class Converter {
@@ -4251,10 +4447,24 @@ public class Expression {
final String operator = jsonArray.get(0).getAsString();
final List<Expression> arguments = new ArrayList<>();
- JsonElement jsonElement;
for (int i = 1; i < jsonArray.size(); i++) {
- jsonElement = jsonArray.get(i);
- arguments.add(convert(jsonElement));
+ JsonElement jsonElement = jsonArray.get(i);
+ if (operator.equals("literal") && jsonElement instanceof JsonArray) {
+ JsonArray nestedArray = (JsonArray) jsonElement;
+ Object[] array = new Object[nestedArray.size()];
+ for (int j = 0; j < nestedArray.size(); j++) {
+ JsonElement element = nestedArray.get(j);
+ if (element instanceof JsonPrimitive) {
+ array[j] = convertToValue((JsonPrimitive) element);
+ } else {
+ throw new IllegalArgumentException("Nested literal arrays are not supported.");
+ }
+ }
+
+ arguments.add(new ExpressionLiteralArray(array));
+ } else {
+ arguments.add(convert(jsonElement));
+ }
}
return new Expression(operator, arguments.toArray(new Expression[arguments.size()]));
}
@@ -4290,12 +4500,22 @@ public class Expression {
* @return the expression literal
*/
private static Expression convert(@NonNull JsonPrimitive jsonPrimitive) {
+ return new ExpressionLiteral(convertToValue(jsonPrimitive));
+ }
+
+ /**
+ * Converts a JsonPrimitive to value
+ *
+ * @param jsonPrimitive the json primitive to convert
+ * @return the value
+ */
+ private static Object convertToValue(@NonNull JsonPrimitive jsonPrimitive) {
if (jsonPrimitive.isBoolean()) {
- return new Expression.ExpressionLiteral(jsonPrimitive.getAsBoolean());
+ return jsonPrimitive.getAsBoolean();
} else if (jsonPrimitive.isNumber()) {
- return new Expression.ExpressionLiteral(jsonPrimitive.getAsFloat());
+ return jsonPrimitive.getAsFloat();
} else if (jsonPrimitive.isString()) {
- return new Expression.ExpressionLiteral(jsonPrimitive.getAsString());
+ return jsonPrimitive.getAsString();
} else {
throw new RuntimeException("Unsupported literal expression conversion for " + jsonPrimitive.getClass());
}
@@ -4316,20 +4536,15 @@ public class Expression {
/**
* Expression to wrap Object[] as a literal
*/
- private static class ExpressionArray extends Expression {
-
- private Object[] array;
-
- ExpressionArray(Object[] array) {
- this.array = array;
- }
+ private static class ExpressionLiteralArray extends ExpressionLiteral {
- @NonNull
- @Override
- public Object[] toArray() {
- return new Object[] {
- "literal", array
- };
+ /**
+ * Create an expression literal.
+ *
+ * @param object the object to be treated as literal
+ */
+ ExpressionLiteralArray(@NonNull Object[] object) {
+ super(object);
}
/**
@@ -4339,10 +4554,10 @@ public class Expression {
*/
@Override
public String toString() {
- StringBuilder builder = new StringBuilder("[\"literal\"], [");
- Object argument;
+ Object[] array = (Object[]) literal;
+ StringBuilder builder = new StringBuilder("[");
for (int i = 0; i < array.length; i++) {
- argument = array[i];
+ Object argument = array[i];
if (argument instanceof String) {
builder.append("\"").append(argument).append("\"");
} else {
@@ -4353,9 +4568,23 @@ public class Expression {
builder.append(", ");
}
}
- builder.append("]]");
+ builder.append("]");
return builder.toString();
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ ExpressionLiteralArray that = (ExpressionLiteralArray) o;
+
+ return Arrays.equals((Object[]) this.literal, (Object[]) that.literal);
+ }
}
/**
@@ -4373,8 +4602,8 @@ public class Expression {
Map<String, Object> unwrappedMap = new HashMap<>();
for (String key : map.keySet()) {
Expression expression = map.get(key);
- if (expression instanceof Expression.ExpressionLiteral) {
- unwrappedMap.put(key, ((ExpressionLiteral) expression).toValue());
+ if (expression instanceof ValueExpression) {
+ unwrappedMap.put(key, ((ValueExpression) expression).toValue());
} else {
unwrappedMap.put(key, expression.toArray());
}
@@ -4437,7 +4666,7 @@ public class Expression {
* @param object the object to convert to an object array
* @return the converted object array
*/
- static Object[] toObjectArray(Object object) {
+ private static Object[] toObjectArray(Object object) {
// object is a primitive array
int len = java.lang.reflect.Array.getLength(object);
Object[] objects = new Object[len];
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyValue.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyValue.java
index 848165f00f..6936c302b2 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyValue.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyValue.java
@@ -62,7 +62,7 @@ public class PropertyValue<T> {
? Expression.Converter.convert((JsonArray) value)
: (Expression) value;
} else {
- Logger.w(TAG, "not a expression, try value");
+ Logger.w(TAG, "not an expression, try PropertyValue#getValue()");
return null;
}
}
@@ -87,7 +87,7 @@ public class PropertyValue<T> {
// noinspection unchecked
return value;
} else {
- Logger.w(TAG, "not a value, try function");
+ Logger.w(TAG, "not a value, try PropertyValue#getExpression()");
return null;
}
}
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 1d45f34bd3..7b9128343c 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
@@ -13,6 +13,8 @@ import static com.mapbox.mapboxsdk.utils.ColorUtils.rgbaToColor;
import com.google.gson.JsonArray;
import com.mapbox.mapboxsdk.style.expressions.Expression;
import com.mapbox.mapboxsdk.style.layers.TransitionOptions;
+import com.mapbox.mapboxsdk.style.types.Formatted;
+import com.mapbox.mapboxsdk.style.types.FormattedSection;
/**
* An icon or a text label.
@@ -365,7 +367,31 @@ public class SymbolLayer extends Layer {
@SuppressWarnings("unchecked")
public PropertyValue<String> getTextField() {
checkThread();
- return (PropertyValue<String>) new PropertyValue("text-field", nativeGetTextField());
+
+ PropertyValue propertyValue = new PropertyValue<>("text-field", nativeGetTextField());
+ if (propertyValue.isExpression()) {
+ return (PropertyValue<String>) propertyValue;
+ } else {
+ Formatted formatted = (Formatted) nativeGetTextField();
+ StringBuilder builder = new StringBuilder();
+ for (FormattedSection section : formatted.getFormattedSections()) {
+ builder.append(section.getText());
+ }
+
+ return (PropertyValue<String>) new PropertyValue("text-field", builder.toString());
+ }
+ }
+
+ /**
+ * Get the TextField property as {@link Formatted} object
+ *
+ * @return property wrapper value around String
+ * @see Expression#format(Expression...)
+ */
+ @SuppressWarnings("unchecked")
+ public PropertyValue<Formatted> getFormattedTextField() {
+ checkThread();
+ return (PropertyValue<Formatted>) new PropertyValue("text-field", nativeGetTextField());
}
/**
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/layer.java.ejs b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/layer.java.ejs
index 958cb7383d..961991c7a1 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/layer.java.ejs
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/layer.java.ejs
@@ -18,6 +18,10 @@ import static com.mapbox.mapboxsdk.utils.ColorUtils.rgbaToColor;
import com.google.gson.JsonArray;
import com.mapbox.mapboxsdk.style.expressions.Expression;
import com.mapbox.mapboxsdk.style.layers.TransitionOptions;
+<% if (type === 'symbol') { -%>
+import com.mapbox.mapboxsdk.style.types.Formatted;
+import com.mapbox.mapboxsdk.style.types.FormattedSection;
+<% } -%>
/**
* <%- doc %>
@@ -171,8 +175,38 @@ public class <%- camelize(type) %>Layer extends Layer {
@SuppressWarnings("unchecked")
public PropertyValue<<%- propertyType(property) %>> get<%- camelize(property.name) %>() {
checkThread();
+<% if (property.name === 'text-field' && property.type === 'formatted') { -%>
+
+ PropertyValue propertyValue = new PropertyValue<>("text-field", nativeGetTextField());
+ if (propertyValue.isExpression()) {
+ return (PropertyValue<String>) propertyValue;
+ } else {
+ Formatted formatted = (Formatted) nativeGetTextField();
+ StringBuilder builder = new StringBuilder();
+ for (FormattedSection section : formatted.getFormattedSections()) {
+ builder.append(section.getText());
+ }
+
+ return (PropertyValue<String>) new PropertyValue("text-field", builder.toString());
+ }
+<% } else { -%>
return (PropertyValue<<%- propertyType(property) %>>) new PropertyValue("<%- property.name %>", nativeGet<%- camelize(property.name) %>());
+<% } -%>
+ }
+<% if (property.name === 'text-field' && property.type === 'formatted') { -%>
+
+ /**
+ * Get the <%- camelize(property.name) %> property as {@link Formatted} object
+ *
+ * @return property wrapper value around <%- propertyType(property) %>
+ * @see Expression#format(Expression...)
+ */
+ @SuppressWarnings("unchecked")
+ public PropertyValue<Formatted> getFormatted<%- camelize(property.name) %>() {
+ checkThread();
+ return (PropertyValue<Formatted>) new PropertyValue("<%- property.name %>", nativeGet<%- camelize(property.name) %>());
}
+<% } -%>
<% if (property.type == 'color') { -%>
/**
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/types/Formatted.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/types/Formatted.java
new file mode 100644
index 0000000000..b11a1b5bc7
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/types/Formatted.java
@@ -0,0 +1,54 @@
+package com.mapbox.mapboxsdk.style.types;
+
+import android.support.annotation.Keep;
+import android.support.annotation.VisibleForTesting;
+
+import java.util.Arrays;
+
+/**
+ * Represents a string broken into sections annotated with separate formatting options.
+ *
+ * @see <a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#types-formatted">Style specification</a>
+ */
+@Keep
+public class Formatted {
+ private final FormattedSection[] formattedSections;
+
+ /**
+ * Create a new formatted text.
+ *
+ * @param formattedSections sections with formatting options
+ */
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ public Formatted(FormattedSection[] formattedSections) {
+ this.formattedSections = formattedSections;
+ }
+
+ /**
+ * Returns sections with separate formatting options that are a part of this formatted text.
+ *
+ * @return formatted sections
+ */
+ public FormattedSection[] getFormattedSections() {
+ return formattedSections;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ Formatted formatted = (Formatted) o;
+
+ return Arrays.equals(formattedSections, formatted.formattedSections);
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(formattedSections);
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/types/FormattedSection.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/types/FormattedSection.java
new file mode 100644
index 0000000000..b3c36414cc
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/types/FormattedSection.java
@@ -0,0 +1,100 @@
+package com.mapbox.mapboxsdk.style.types;
+
+import android.support.annotation.Keep;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+
+import java.util.Arrays;
+
+/**
+ * A component of the {@link Formatted}.
+ */
+@Keep
+public class FormattedSection {
+ private String text;
+ private double fontScale;
+ private String[] fontStack;
+
+ /**
+ * Creates a formatted section.
+ *
+ * @param text displayed string
+ * @param fontScale scale of the font, 1.0 is default
+ * @param fontStack main and fallback fonts that are a part of the style
+ */
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ public FormattedSection(@NonNull String text, double fontScale, @Nullable String[] fontStack) {
+ this.text = text;
+ this.fontScale = fontScale;
+ this.fontStack = fontStack;
+ }
+
+ /**
+ * Creates a formatted section.
+ *
+ * @param text displayed string
+ * @param fontScale scale of the font, 1.0 is default
+ */
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ public FormattedSection(@NonNull String text, double fontScale) {
+ this.text = text;
+ this.fontScale = fontScale;
+ }
+
+ /**
+ * Returns the displayed text.
+ *
+ * @return text
+ */
+ @NonNull
+ public String getText() {
+ return text;
+ }
+
+ /**
+ * Returns displayed text's font scale.
+ *
+ * @return font scale, defaults to 1.0
+ */
+ public double getFontScale() {
+ return fontScale;
+ }
+
+ /**
+ * Returns the font stack with main and fallback fonts.
+ *
+ * @return font stack
+ */
+ @Nullable
+ public String[] getFontStack() {
+ return fontStack;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ FormattedSection section = (FormattedSection) o;
+
+ return Double.compare(section.fontScale, fontScale) == 0
+ && (text != null ? text.equals(section.text) : section.text == null)
+ && Arrays.equals(fontStack, section.fontStack);
+ }
+
+ @Override
+ public int hashCode() {
+ int result;
+ long temp;
+ result = text != null ? text.hashCode() : 0;
+ temp = Double.doubleToLongBits(fontScale);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ result = 31 * result + Arrays.hashCode(fontStack);
+ return result;
+ }
+}