Add support for text range selectors (#2518)
This adds support for the [text range selector](https://lottiefiles.github.io/lottie-docs/text/#text-range-selector) type, which allows a [text style](https://lottiefiles.github.io/lottie-docs/text/#text-style) to be applied to a certain range of the text, defined by a start, end, and offset.
The text range selector implementation currently has the following limitations:
- Only text layers drawn using a Font are supported. Adding support for the glyph draw path may be possible, but I don't have a sample Lottie file.
- Only one text range is currently supported per text layer, as the parser [drops all other entries in the array](https://github.com/airbnb/lottie-android/blob/c4cb2254eca3c70199f1de5e39e3872c8c42e473/lottie/src/main/java/com/airbnb/lottie/parser/LayerParser.java#L194-L196).
- Only index-based ranges are supported - percentage-based ranges are also allowed in the spec.
- Only ranges based on characters are supported. The [spec](https://lottiefiles.github.io/lottie-docs/constants/#text-based) allows characters, characters excluding spaces, words, and lines.
- Other options like easing (allows styling characters that are partially inside the range), randomize, and [shape](https://lottiefiles.github.io/lottie-docs/constants/#text-shape) of the range are not currently supported.
This also adds support for the opacity as an animatable text property which is applied multiplicatively with the transform opacity and the parent's alpha.
Partially addresses https://github.com/airbnb/lottie-android/issues/485.
https://github.com/user-attachments/assets/bcfad060-482d-48d9-a578-297c4f143ba9
https://github.com/user-attachments/assets/211dc574-5ea1-4fa3-9f78-f87ee104ce85
Co-authored-by: Allen Chen <allen.chen@airbnb.com>
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableTextProperties.java b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableTextProperties.java
index 1b72bb3..c67cb48 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableTextProperties.java
+++ b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableTextProperties.java
@@ -4,17 +4,13 @@
public class AnimatableTextProperties {
- @Nullable public final AnimatableColorValue color;
- @Nullable public final AnimatableColorValue stroke;
- @Nullable public final AnimatableFloatValue strokeWidth;
- @Nullable public final AnimatableFloatValue tracking;
+ @Nullable public final AnimatableTextStyle textStyle;
+ @Nullable public final AnimatableTextRangeSelector rangeSelector;
- public AnimatableTextProperties(@Nullable AnimatableColorValue color,
- @Nullable AnimatableColorValue stroke, @Nullable AnimatableFloatValue strokeWidth,
- @Nullable AnimatableFloatValue tracking) {
- this.color = color;
- this.stroke = stroke;
- this.strokeWidth = strokeWidth;
- this.tracking = tracking;
+ public AnimatableTextProperties(
+ @Nullable AnimatableTextStyle textStyle,
+ @Nullable AnimatableTextRangeSelector rangeSelector) {
+ this.textStyle = textStyle;
+ this.rangeSelector = rangeSelector;
}
}
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableTextRangeSelector.java b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableTextRangeSelector.java
new file mode 100644
index 0000000..413008b
--- /dev/null
+++ b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableTextRangeSelector.java
@@ -0,0 +1,25 @@
+package com.airbnb.lottie.model.animatable;
+
+import androidx.annotation.Nullable;
+import com.airbnb.lottie.model.content.TextRangeUnits;
+
+/**
+ * Defines an animated range of text that should have an [AnimatableTextProperties] applied to it.
+ */
+public class AnimatableTextRangeSelector {
+ @Nullable public final AnimatableIntegerValue start;
+ @Nullable public final AnimatableIntegerValue end;
+ @Nullable public final AnimatableIntegerValue offset;
+ public final TextRangeUnits units;
+
+ public AnimatableTextRangeSelector(
+ @Nullable AnimatableIntegerValue start,
+ @Nullable AnimatableIntegerValue end,
+ @Nullable AnimatableIntegerValue offset,
+ TextRangeUnits units) {
+ this.start = start;
+ this.end = end;
+ this.offset = offset;
+ this.units = units;
+ }
+}
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableTextStyle.java b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableTextStyle.java
new file mode 100644
index 0000000..24c3421
--- /dev/null
+++ b/lottie/src/main/java/com/airbnb/lottie/model/animatable/AnimatableTextStyle.java
@@ -0,0 +1,25 @@
+package com.airbnb.lottie.model.animatable;
+
+import androidx.annotation.Nullable;
+
+public class AnimatableTextStyle {
+
+ @Nullable public final AnimatableColorValue color;
+ @Nullable public final AnimatableColorValue stroke;
+ @Nullable public final AnimatableFloatValue strokeWidth;
+ @Nullable public final AnimatableFloatValue tracking;
+ @Nullable public final AnimatableIntegerValue opacity;
+
+ public AnimatableTextStyle(
+ @Nullable AnimatableColorValue color,
+ @Nullable AnimatableColorValue stroke,
+ @Nullable AnimatableFloatValue strokeWidth,
+ @Nullable AnimatableFloatValue tracking,
+ @Nullable AnimatableIntegerValue opacity) {
+ this.color = color;
+ this.stroke = stroke;
+ this.strokeWidth = strokeWidth;
+ this.tracking = tracking;
+ this.opacity = opacity;
+ }
+}
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/content/TextRangeUnits.java b/lottie/src/main/java/com/airbnb/lottie/model/content/TextRangeUnits.java
new file mode 100644
index 0000000..6e4f0c8
--- /dev/null
+++ b/lottie/src/main/java/com/airbnb/lottie/model/content/TextRangeUnits.java
@@ -0,0 +1,6 @@
+package com.airbnb.lottie.model.content;
+
+public enum TextRangeUnits {
+ PERCENT,
+ INDEX
+}
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/layer/TextLayer.java b/lottie/src/main/java/com/airbnb/lottie/model/layer/TextLayer.java
index 211f1d6..309950c 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/layer/TextLayer.java
+++ b/lottie/src/main/java/com/airbnb/lottie/model/layer/TextLayer.java
@@ -25,6 +25,7 @@
import com.airbnb.lottie.model.FontCharacter;
import com.airbnb.lottie.model.animatable.AnimatableTextProperties;
import com.airbnb.lottie.model.content.ShapeGroup;
+import com.airbnb.lottie.model.content.TextRangeUnits;
import com.airbnb.lottie.utils.Utils;
import com.airbnb.lottie.value.LottieValueCallback;
@@ -56,6 +57,7 @@
private final TextKeyframeAnimation textAnimation;
private final LottieDrawable lottieDrawable;
private final LottieComposition composition;
+ private TextRangeUnits textRangeUnits = TextRangeUnits.INDEX;
@Nullable
private BaseKeyframeAnimation<Integer, Integer> colorAnimation;
@Nullable
@@ -73,9 +75,17 @@
@Nullable
private BaseKeyframeAnimation<Float, Float> trackingCallbackAnimation;
@Nullable
+ private BaseKeyframeAnimation<Integer, Integer> opacityAnimation;
+ @Nullable
private BaseKeyframeAnimation<Float, Float> textSizeCallbackAnimation;
@Nullable
private BaseKeyframeAnimation<Typeface, Typeface> typefaceCallbackAnimation;
+ @Nullable
+ private BaseKeyframeAnimation<Integer, Integer> textRangeStartAnimation;
+ @Nullable
+ private BaseKeyframeAnimation<Integer, Integer> textRangeEndAnimation;
+ @Nullable
+ private BaseKeyframeAnimation<Integer, Integer> textRangeOffsetAnimation;
TextLayer(LottieDrawable lottieDrawable, Layer layerModel) {
super(lottieDrawable, layerModel);
@@ -87,29 +97,57 @@
addAnimation(textAnimation);
AnimatableTextProperties textProperties = layerModel.getTextProperties();
- if (textProperties != null && textProperties.color != null) {
- colorAnimation = textProperties.color.createAnimation();
+ if (textProperties != null && textProperties.textStyle != null && textProperties.textStyle.color != null) {
+ colorAnimation = textProperties.textStyle.color.createAnimation();
colorAnimation.addUpdateListener(this);
addAnimation(colorAnimation);
}
- if (textProperties != null && textProperties.stroke != null) {
- strokeColorAnimation = textProperties.stroke.createAnimation();
+ if (textProperties != null && textProperties.textStyle != null && textProperties.textStyle.stroke != null) {
+ strokeColorAnimation = textProperties.textStyle.stroke.createAnimation();
strokeColorAnimation.addUpdateListener(this);
addAnimation(strokeColorAnimation);
}
- if (textProperties != null && textProperties.strokeWidth != null) {
- strokeWidthAnimation = textProperties.strokeWidth.createAnimation();
+ if (textProperties != null && textProperties.textStyle != null && textProperties.textStyle.strokeWidth != null) {
+ strokeWidthAnimation = textProperties.textStyle.strokeWidth.createAnimation();
strokeWidthAnimation.addUpdateListener(this);
addAnimation(strokeWidthAnimation);
}
- if (textProperties != null && textProperties.tracking != null) {
- trackingAnimation = textProperties.tracking.createAnimation();
+ if (textProperties != null && textProperties.textStyle != null && textProperties.textStyle.tracking != null) {
+ trackingAnimation = textProperties.textStyle.tracking.createAnimation();
trackingAnimation.addUpdateListener(this);
addAnimation(trackingAnimation);
}
+
+ if (textProperties != null && textProperties.textStyle != null && textProperties.textStyle.opacity != null) {
+ opacityAnimation = textProperties.textStyle.opacity.createAnimation();
+ opacityAnimation.addUpdateListener(this);
+ addAnimation(opacityAnimation);
+ }
+
+ if (textProperties != null && textProperties.rangeSelector != null && textProperties.rangeSelector.start != null) {
+ textRangeStartAnimation = textProperties.rangeSelector.start.createAnimation();
+ textRangeStartAnimation.addUpdateListener(this);
+ addAnimation(textRangeStartAnimation);
+ }
+
+ if (textProperties != null && textProperties.rangeSelector != null && textProperties.rangeSelector.end != null) {
+ textRangeEndAnimation = textProperties.rangeSelector.end.createAnimation();
+ textRangeEndAnimation.addUpdateListener(this);
+ addAnimation(textRangeEndAnimation);
+ }
+
+ if (textProperties != null && textProperties.rangeSelector != null && textProperties.rangeSelector.offset != null) {
+ textRangeOffsetAnimation = textProperties.rangeSelector.offset.createAnimation();
+ textRangeOffsetAnimation.addUpdateListener(this);
+ addAnimation(textRangeOffsetAnimation);
+ }
+
+ if (textProperties != null && textProperties.rangeSelector != null) {
+ textRangeUnits = textProperties.rangeSelector.units;
+ }
}
@Override
@@ -129,49 +167,86 @@
canvas.save();
canvas.concat(parentMatrix);
- configurePaint(documentData, parentAlpha);
+ configurePaint(documentData, parentAlpha, 0);
if (lottieDrawable.useTextGlyphs()) {
- drawTextWithGlyphs(documentData, parentMatrix, font, canvas);
+ drawTextWithGlyphs(documentData, parentMatrix, font, canvas, parentAlpha);
} else {
- drawTextWithFont(documentData, font, canvas);
+ drawTextWithFont(documentData, font, canvas, parentAlpha);
}
canvas.restore();
}
- private void configurePaint(DocumentData documentData, int parentAlpha) {
- if (colorCallbackAnimation != null) {
+ /**
+ * Configures the [fillPaint] and [strokePaint] used for drawing based on currently active text ranges.
+ *
+ * @param parentAlpha A value from 0 to 255 indicating the alpha of the parented layer.
+ */
+ private void configurePaint(DocumentData documentData, int parentAlpha, int indexInDocument) {
+ if (colorCallbackAnimation != null) { // dynamic property takes priority
fillPaint.setColor(colorCallbackAnimation.getValue());
- } else if (colorAnimation != null) {
+ } else if (colorAnimation != null && isIndexInRangeSelection(indexInDocument)) {
fillPaint.setColor(colorAnimation.getValue());
- } else {
+ } else { // fall back to the document color
fillPaint.setColor(documentData.color);
}
if (strokeColorCallbackAnimation != null) {
strokePaint.setColor(strokeColorCallbackAnimation.getValue());
- } else if (strokeColorAnimation != null) {
+ } else if (strokeColorAnimation != null && isIndexInRangeSelection(indexInDocument)) {
strokePaint.setColor(strokeColorAnimation.getValue());
} else {
strokePaint.setColor(documentData.strokeColor);
}
- int opacity = transform.getOpacity() == null ? 100 : transform.getOpacity().getValue();
- int alpha = opacity * 255 / 100 * parentAlpha / 255;
+
+ // These opacity values are in the range 0 to 100
+ int transformOpacity = transform.getOpacity() == null ? 100 : transform.getOpacity().getValue();
+ int textRangeOpacity = opacityAnimation != null && isIndexInRangeSelection(indexInDocument) ? opacityAnimation.getValue() : 100;
+
+ // This alpha value needs to be in the range 0 to 255 to be applied to the Paint instances.
+ // We map the layer transform's opacity into that range and multiply it by the fractional opacity of the text range and the parent.
+ int alpha = Math.round((transformOpacity * 255f / 100f)
+ * (textRangeOpacity / 100f)
+ * parentAlpha / 255f);
fillPaint.setAlpha(alpha);
strokePaint.setAlpha(alpha);
if (strokeWidthCallbackAnimation != null) {
strokePaint.setStrokeWidth(strokeWidthCallbackAnimation.getValue());
- } else if (strokeWidthAnimation != null) {
+ } else if (strokeWidthAnimation != null && isIndexInRangeSelection(indexInDocument)) {
strokePaint.setStrokeWidth(strokeWidthAnimation.getValue());
} else {
strokePaint.setStrokeWidth(documentData.strokeWidth * Utils.dpScale());
}
}
+ private boolean isIndexInRangeSelection(int indexInDocument) {
+ int textLength = textAnimation.getValue().text.length();
+ if (textRangeStartAnimation != null && textRangeEndAnimation != null) {
+ // After effects supports reversed text ranges where the start index is greater than the end index.
+ // For the purposes of determining if the given index is inside of the range, we take the start as the smaller value.
+ int rangeStart = Math.min(textRangeStartAnimation.getValue(), textRangeEndAnimation.getValue());
+ int rangeEnd = Math.max(textRangeStartAnimation.getValue(), textRangeEndAnimation.getValue());
+
+ if (textRangeOffsetAnimation != null) {
+ int offset = textRangeOffsetAnimation.getValue();
+ rangeStart += offset;
+ rangeEnd += offset;
+ }
+
+ if (textRangeUnits == TextRangeUnits.INDEX) {
+ return indexInDocument >= rangeStart && indexInDocument < rangeEnd;
+ } else {
+ float currentIndexAsPercent = indexInDocument / (float) textLength * 100;
+ return currentIndexAsPercent >= rangeStart && currentIndexAsPercent < rangeEnd;
+ }
+ }
+ return true;
+ }
+
private void drawTextWithGlyphs(
- DocumentData documentData, Matrix parentMatrix, Font font, Canvas canvas) {
+ DocumentData documentData, Matrix parentMatrix, Font font, Canvas canvas, int parentAlpha) {
float textSize;
if (textSizeCallbackAnimation != null) {
textSize = textSizeCallbackAnimation.getValue();
@@ -205,7 +280,7 @@
canvas.save();
if (offsetCanvas(canvas, documentData, lineIndex, line.width)) {
- drawGlyphTextLine(line.text, documentData, font, canvas, parentScale, fontScale, tracking);
+ drawGlyphTextLine(line.text, documentData, font, canvas, parentScale, fontScale, tracking, parentAlpha);
}
canvas.restore();
@@ -214,7 +289,7 @@
}
private void drawGlyphTextLine(String text, DocumentData documentData,
- Font font, Canvas canvas, float parentScale, float fontScale, float tracking) {
+ Font font, Canvas canvas, float parentScale, float fontScale, float tracking, int parentAlpha) {
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
int characterHash = FontCharacter.hashFor(c, font.getFamily(), font.getStyle());
@@ -223,13 +298,13 @@
// Something is wrong. Potentially, they didn't export the text as a glyph.
continue;
}
- drawCharacterAsGlyph(character, fontScale, documentData, canvas);
+ drawCharacterAsGlyph(character, fontScale, documentData, canvas, i, parentAlpha);
float tx = (float) character.getWidth() * fontScale * Utils.dpScale() + tracking;
canvas.translate(tx, 0);
}
}
- private void drawTextWithFont(DocumentData documentData, Font font, Canvas canvas) {
+ private void drawTextWithFont(DocumentData documentData, Font font, Canvas canvas, int parentAlpha) {
Typeface typeface = getTypeface(font);
if (typeface == null) {
return;
@@ -263,6 +338,7 @@
List<String> textLines = getTextLines(text);
int textLineCount = textLines.size();
int lineIndex = -1;
+ int characterIndexAtStartOfLine = 0;
for (int i = 0; i < textLineCount; i++) {
String textLine = textLines.get(i);
float boxWidth = documentData.boxSize == null ? 0f : documentData.boxSize.x;
@@ -274,9 +350,11 @@
canvas.save();
if (offsetCanvas(canvas, documentData, lineIndex, line.width)) {
- drawFontTextLine(line.text, documentData, canvas, tracking);
+ drawFontTextLine(line.text, documentData, canvas, tracking, characterIndexAtStartOfLine, parentAlpha);
}
+ characterIndexAtStartOfLine += line.text.length();
+
canvas.restore();
}
}
@@ -331,14 +409,23 @@
return Arrays.asList(textLinesArray);
}
- private void drawFontTextLine(String text, DocumentData documentData, Canvas canvas, float tracking) {
+ /**
+ * @param characterIndexAtStartOfLine The index within the overall document of the character at the start of the line
+ * @param parentAlpha
+ */
+ private void drawFontTextLine(String text,
+ DocumentData documentData,
+ Canvas canvas,
+ float tracking,
+ int characterIndexAtStartOfLine,
+ int parentAlpha) {
for (int i = 0; i < text.length(); ) {
String charString = codePointToString(text, i);
- i += charString.length();
- drawCharacterFromFont(charString, documentData, canvas);
+ drawCharacterFromFont(charString, documentData, canvas, characterIndexAtStartOfLine + i, parentAlpha);
float charWidth = fillPaint.measureText(charString);
float tx = charWidth + tracking;
canvas.translate(tx, 0);
+ i += charString.length();
}
}
@@ -430,7 +517,10 @@
FontCharacter character,
float fontScale,
DocumentData documentData,
- Canvas canvas) {
+ Canvas canvas,
+ int indexInDocument,
+ int parentAlpha) {
+ configurePaint(documentData, parentAlpha, indexInDocument);
List<ContentGroup> contentGroups = getContentsForCharacter(character);
for (int j = 0; j < contentGroups.size(); j++) {
Path path = contentGroups.get(j).getPath();
@@ -459,7 +549,8 @@
canvas.drawPath(path, paint);
}
- private void drawCharacterFromFont(String character, DocumentData documentData, Canvas canvas) {
+ private void drawCharacterFromFont(String character, DocumentData documentData, Canvas canvas, int indexInDocument, int parentAlpha) {
+ configurePaint(documentData, parentAlpha, indexInDocument);
if (documentData.strokeOverFill) {
drawCharacter(character, fillPaint, canvas);
drawCharacter(character, strokePaint, canvas);
diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/AnimatableTextPropertiesParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/AnimatableTextPropertiesParser.java
index 54a111b..c76ad86 100644
--- a/lottie/src/main/java/com/airbnb/lottie/parser/AnimatableTextPropertiesParser.java
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/AnimatableTextPropertiesParser.java
@@ -1,21 +1,37 @@
package com.airbnb.lottie.parser;
+import androidx.annotation.Nullable;
import com.airbnb.lottie.LottieComposition;
import com.airbnb.lottie.model.animatable.AnimatableColorValue;
import com.airbnb.lottie.model.animatable.AnimatableFloatValue;
+import com.airbnb.lottie.model.animatable.AnimatableIntegerValue;
import com.airbnb.lottie.model.animatable.AnimatableTextProperties;
+import com.airbnb.lottie.model.animatable.AnimatableTextRangeSelector;
+import com.airbnb.lottie.model.animatable.AnimatableTextStyle;
+import com.airbnb.lottie.model.content.LBlendMode;
+import com.airbnb.lottie.model.content.TextRangeUnits;
import com.airbnb.lottie.parser.moshi.JsonReader;
+import com.airbnb.lottie.value.Keyframe;
import java.io.IOException;
+import java.util.Collections;
public class AnimatableTextPropertiesParser {
- private static final JsonReader.Options PROPERTIES_NAMES = JsonReader.Options.of("a");
+ private static final JsonReader.Options PROPERTIES_NAMES = JsonReader.Options.of("s", "a");
+
+ private static final JsonReader.Options ANIMATABLE_RANGE_PROPERTIES_NAMES = JsonReader.Options.of(
+ "s", // start
+ "e", // end
+ "o", // offset
+ "r" // text range units (percent or index)
+ );
private static final JsonReader.Options ANIMATABLE_PROPERTIES_NAMES = JsonReader.Options.of(
"fc",
"sc",
"sw",
- "t"
+ "t",
+ "o" // opacity
);
private AnimatableTextPropertiesParser() {
@@ -23,13 +39,17 @@
public static AnimatableTextProperties parse(
JsonReader reader, LottieComposition composition) throws IOException {
- AnimatableTextProperties anim = null;
+ @Nullable AnimatableTextStyle textStyle = null;
+ @Nullable AnimatableTextRangeSelector rangeSelector = null;
reader.beginObject();
while (reader.hasNext()) {
switch (reader.selectName(PROPERTIES_NAMES)) {
- case 0:
- anim = parseAnimatableTextProperties(reader, composition);
+ case 0: // Range selector
+ rangeSelector = parseAnimatableTextRangeSelector(reader, composition);
+ break;
+ case 1: // Text style for this range
+ textStyle = parseAnimatableTextStyle(reader, composition);
break;
default:
reader.skipName();
@@ -37,19 +57,60 @@
}
}
reader.endObject();
- if (anim == null) {
- // Not sure if this is possible.
- return new AnimatableTextProperties(null, null, null, null);
- }
- return anim;
+
+ return new AnimatableTextProperties(textStyle, rangeSelector);
}
- private static AnimatableTextProperties parseAnimatableTextProperties(
+ private static AnimatableTextRangeSelector parseAnimatableTextRangeSelector(JsonReader reader, LottieComposition composition) throws IOException {
+ // TODO These may need to be floats in the future if we want to support percentage based ranges.
+ AnimatableIntegerValue start = null;
+ AnimatableIntegerValue end = null;
+ AnimatableIntegerValue offset = null;
+ TextRangeUnits units = null;
+
+ reader.beginObject();
+ while (reader.hasNext()) {
+ switch (reader.selectName(ANIMATABLE_RANGE_PROPERTIES_NAMES)) {
+ case 0: // start
+ start = AnimatableValueParser.parseInteger(reader, composition);
+ break;
+ case 1: // end
+ end = AnimatableValueParser.parseInteger(reader, composition);
+ break;
+ case 2: // offset
+ offset = AnimatableValueParser.parseInteger(reader, composition);
+ break;
+ case 3: // text range units (percent or index)
+ int textRangeUnits = reader.nextInt();
+ if (textRangeUnits != 1 && textRangeUnits != 2) {
+ composition.addWarning("Unsupported text range units: " + textRangeUnits);
+ units = TextRangeUnits.INDEX;
+ break;
+ }
+ units = textRangeUnits == 1 ? TextRangeUnits.PERCENT : TextRangeUnits.INDEX;
+ break;
+ default:
+ reader.skipName();
+ reader.skipValue();
+ }
+ }
+ reader.endObject();
+
+ // If no start value is provided, default to a non-animated value of 0 to match After Effects/Bodymovin.
+ if (start == null && end != null) {
+ start = new AnimatableIntegerValue(Collections.singletonList(new Keyframe<>(0)));
+ }
+
+ return new AnimatableTextRangeSelector(start, end, offset, units);
+ }
+
+ private static AnimatableTextStyle parseAnimatableTextStyle(
JsonReader reader, LottieComposition composition) throws IOException {
AnimatableColorValue color = null;
AnimatableColorValue stroke = null;
AnimatableFloatValue strokeWidth = null;
AnimatableFloatValue tracking = null;
+ AnimatableIntegerValue opacity = null;
reader.beginObject();
while (reader.hasNext()) {
@@ -66,6 +127,9 @@
case 3:
tracking = AnimatableValueParser.parseFloat(reader, composition);
break;
+ case 4: // opacity
+ opacity = AnimatableValueParser.parseInteger(reader, composition);
+ break;
default:
reader.skipName();
reader.skipValue();
@@ -73,6 +137,6 @@
}
reader.endObject();
- return new AnimatableTextProperties(color, stroke, strokeWidth, tracking);
+ return new AnimatableTextStyle(color, stroke, strokeWidth, tracking, opacity);
}
}
diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/LayerParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/LayerParser.java
index c56e213..c4ebd59 100644
--- a/lottie/src/main/java/com/airbnb/lottie/parser/LayerParser.java
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/LayerParser.java
@@ -179,23 +179,25 @@
}
reader.endArray();
break;
- case 12:
+ case 12: // Text data
reader.beginObject();
while (reader.hasNext()) {
switch (reader.selectName(TEXT_NAMES)) {
- case 0:
+ case 0: // "d", Text contents
text = AnimatableValueParser.parseDocumentData(reader, composition);
break;
- case 1:
+ case 1: // "a", Text ranges with custom animations and style
reader.beginArray();
if (reader.hasNext()) {
textProperties = AnimatableTextPropertiesParser.parse(reader, composition);
}
+ // TODO support more than one text range
while (reader.hasNext()) {
reader.skipValue();
}
reader.endArray();
break;
+ // TODO support Text follow path and Text alignment
default:
reader.skipName();
reader.skipValue();
diff --git a/snapshot-tests/src/main/assets/Tests/text_range_test.json b/snapshot-tests/src/main/assets/Tests/text_range_test.json
new file mode 100644
index 0000000..be0c5ca
--- /dev/null
+++ b/snapshot-tests/src/main/assets/Tests/text_range_test.json
@@ -0,0 +1,406 @@
+{
+ "v": "5.9.6",
+ "fr": 60,
+ "ip": 0,
+ "op": 327,
+ "w": 900,
+ "h": 325,
+ "nm": "Lottie Type Animation",
+ "ddd": 0,
+ "assets": [],
+ "fonts": {
+ "list": [
+ {
+ "fName": "Roboto-Regular",
+ "fFamily": "Roboto",
+ "fStyle": "Regular",
+ "ascent": 75
+ }
+ ]
+ },
+ "layers": [
+ {
+ "ddd": 0,
+ "ind": 1,
+ "ty": 5,
+ "nm": "Paris, FR",
+ "sr": 1,
+ "ks": {
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 11
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 10
+ },
+ "p": {
+ "a": 0,
+ "k": [
+ 90.042,
+ 220.965,
+ 0
+ ],
+ "ix": 2,
+ "l": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [
+ -318.2,
+ -1.622,
+ 0
+ ],
+ "ix": 1,
+ "l": 2
+ },
+ "s": {
+ "a": 0,
+ "k": [
+ 91.581,
+ 91.581,
+ 100
+ ],
+ "ix": 6,
+ "l": 2
+ }
+ },
+ "ao": 0,
+ "t": {
+ "d": {
+ "k": [
+ {
+ "s": {
+ "sz": [
+ 1383.20715332031,
+ 126
+ ],
+ "ps": [
+ -328,
+ -130
+ ],
+ "s": 140,
+ "f": "Roboto-Regular",
+ "t": "Paris, France",
+ "ca": 0,
+ "j": 0,
+ "tr": -13,
+ "lh": 168,
+ "ls": 0,
+ "fc": [
+ 0.133,
+ 0.133,
+ 0.133
+ ]
+ },
+ "t": 0
+ }
+ ]
+ },
+ "p": {},
+ "m": {
+ "g": 1,
+ "a": {
+ "a": 0,
+ "k": [
+ 0,
+ 0
+ ],
+ "ix": 2
+ }
+ },
+ "a": [
+ {
+ "nm": "Animator 1",
+ "s": {
+ "t": 0,
+ "xe": {
+ "a": 0,
+ "k": 0,
+ "ix": 7
+ },
+ "ne": {
+ "a": 0,
+ "k": 0,
+ "ix": 8
+ },
+ "a": {
+ "a": 0,
+ "k": 100,
+ "ix": 4
+ },
+ "b": 1,
+ "rn": 0,
+ "sh": 1,
+ "sm": {
+ "a": 0,
+ "k": 0,
+ "ix": 6
+ },
+ "s": {
+ "a": 1,
+ "k": [
+ {
+ "i": {
+ "x": [
+ 0.833
+ ],
+ "y": [
+ 0.833
+ ]
+ },
+ "o": {
+ "x": [
+ 0.167
+ ],
+ "y": [
+ 0.167
+ ]
+ },
+ "t": 56,
+ "s": [
+ 13
+ ]
+ },
+ {
+ "i": {
+ "x": [
+ 0.833
+ ],
+ "y": [
+ 0.833
+ ]
+ },
+ "o": {
+ "x": [
+ 0.167
+ ],
+ "y": [
+ 0.167
+ ]
+ },
+ "t": 86,
+ "s": [
+ 0
+ ]
+ },
+ {
+ "i": {
+ "x": [
+ 0.833
+ ],
+ "y": [
+ 0.833
+ ]
+ },
+ "o": {
+ "x": [
+ 0.167
+ ],
+ "y": [
+ 0.167
+ ]
+ },
+ "t": 116,
+ "s": [
+ 0
+ ]
+ },
+ {
+ "i": {
+ "x": [
+ 0.833
+ ],
+ "y": [
+ 1
+ ]
+ },
+ "o": {
+ "x": [
+ 0.167
+ ],
+ "y": [
+ 0
+ ]
+ },
+ "t": 146,
+ "s": [
+ 6
+ ]
+ },
+ {
+ "i": {
+ "x": [
+ 0
+ ],
+ "y": [
+ 1
+ ]
+ },
+ "o": {
+ "x": [
+ 0.2
+ ],
+ "y": [
+ 0
+ ]
+ },
+ "t": 236,
+ "s": [
+ 6
+ ]
+ },
+ {
+ "t": 296,
+ "s": [
+ 13
+ ]
+ }
+ ],
+ "ix": 4
+ },
+ "e": {
+ "a": 1,
+ "k": [
+ {
+ "i": {
+ "x": [
+ 0.833
+ ],
+ "y": [
+ 0.833
+ ]
+ },
+ "o": {
+ "x": [
+ 0.167
+ ],
+ "y": [
+ 0.167
+ ]
+ },
+ "t": 0,
+ "s": [
+ 0
+ ]
+ },
+ {
+ "i": {
+ "x": [
+ 0.833
+ ],
+ "y": [
+ 0.833
+ ]
+ },
+ "o": {
+ "x": [
+ 0.167
+ ],
+ "y": [
+ 0.167
+ ]
+ },
+ "t": 26,
+ "s": [
+ 13
+ ]
+ },
+ {
+ "i": {
+ "x": [
+ 0.833
+ ],
+ "y": [
+ 0.833
+ ]
+ },
+ "o": {
+ "x": [
+ 0.167
+ ],
+ "y": [
+ 0.167
+ ]
+ },
+ "t": 176,
+ "s": [
+ 13
+ ]
+ },
+ {
+ "i": {
+ "x": [
+ 0.833
+ ],
+ "y": [
+ 1
+ ]
+ },
+ "o": {
+ "x": [
+ 0.167
+ ],
+ "y": [
+ 0
+ ]
+ },
+ "t": 206,
+ "s": [
+ 6
+ ]
+ },
+ {
+ "i": {
+ "x": [
+ 0
+ ],
+ "y": [
+ 1
+ ]
+ },
+ "o": {
+ "x": [
+ 0.2
+ ],
+ "y": [
+ 0
+ ]
+ },
+ "t": 236,
+ "s": [
+ 6
+ ]
+ },
+ {
+ "t": 296,
+ "s": [
+ 0
+ ]
+ }
+ ],
+ "ix": 5
+ },
+ "r": 2
+ },
+ "a": {
+ "o": {
+ "a": 0,
+ "k": 0,
+ "ix": 9
+ }
+ }
+ }
+ ]
+ },
+ "ip": 0,
+ "op": 568,
+ "st": 0,
+ "ct": 1,
+ "bm": 0
+ }
+ ],
+ "markers": []
+}
diff --git a/snapshot-tests/src/main/assets/Tests/text_range_test_2.json b/snapshot-tests/src/main/assets/Tests/text_range_test_2.json
new file mode 100644
index 0000000..f1c16ff
--- /dev/null
+++ b/snapshot-tests/src/main/assets/Tests/text_range_test_2.json
@@ -0,0 +1,1290 @@
+{
+ "v": "5.9.6",
+ "fr": 60,
+ "ip": 0,
+ "op": 403,
+ "w": 500,
+ "h": 106,
+ "nm": "Lottie Type Animation - v2",
+ "ddd": 0,
+ "assets": [],
+ "fonts": {
+ "list": [
+ {
+ "fName": "Roboto-Regular",
+ "fFamily": "Roboto",
+ "fStyle": "Regular",
+ "ascent": 75
+ }
+ ]
+ },
+ "layers": [
+ {
+ "ddd": 0,
+ "ind": 1,
+ "ty": 5,
+ "nm": "Düsseldorf, Germany",
+ "sr": 1,
+ "ks": {
+ "o": {
+ "a": 1,
+ "k": [
+ {
+ "i": {
+ "x": [
+ 0.833
+ ],
+ "y": [
+ 0.833
+ ]
+ },
+ "o": {
+ "x": [
+ 0.167
+ ],
+ "y": [
+ 0.167
+ ]
+ },
+ "t": 330,
+ "s": [
+ 0
+ ]
+ },
+ {
+ "i": {
+ "x": [
+ 0.833
+ ],
+ "y": [
+ 0.833
+ ]
+ },
+ "o": {
+ "x": [
+ 0.167
+ ],
+ "y": [
+ 0.167
+ ]
+ },
+ "t": 342,
+ "s": [
+ 100
+ ]
+ },
+ {
+ "i": {
+ "x": [
+ 0.833
+ ],
+ "y": [
+ 0.833
+ ]
+ },
+ "o": {
+ "x": [
+ 0.167
+ ],
+ "y": [
+ 0.167
+ ]
+ },
+ "t": 392,
+ "s": [
+ 100
+ ]
+ },
+ {
+ "t": 402,
+ "s": [
+ 0
+ ]
+ }
+ ],
+ "ix": 11
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 10
+ },
+ "p": {
+ "a": 0,
+ "k": [
+ 34.475,
+ 78.183,
+ 0
+ ],
+ "ix": 2,
+ "l": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [
+ -318.2,
+ -1.622,
+ 0
+ ],
+ "ix": 1,
+ "l": 2
+ },
+ "s": {
+ "a": 0,
+ "k": [
+ 32.258,
+ 32.258,
+ 100
+ ],
+ "ix": 6,
+ "l": 2
+ }
+ },
+ "ao": 0,
+ "t": {
+ "d": {
+ "k": [
+ {
+ "s": {
+ "sz": [
+ 1383.20715332031,
+ 126
+ ],
+ "ps": [
+ -328,
+ -130
+ ],
+ "s": 140,
+ "f": "Roboto-Regular",
+ "t": "Düsseldorf, Germany",
+ "ca": 0,
+ "j": 0,
+ "tr": -13,
+ "lh": 168,
+ "ls": 0,
+ "fc": [
+ 0.443,
+ 0.443,
+ 0.443
+ ]
+ },
+ "t": 0
+ }
+ ]
+ },
+ "p": {},
+ "m": {
+ "g": 1,
+ "a": {
+ "a": 0,
+ "k": [
+ 0,
+ 0
+ ],
+ "ix": 2
+ }
+ },
+ "a": [
+ {
+ "nm": "Animator 1",
+ "s": {
+ "t": 0,
+ "xe": {
+ "a": 0,
+ "k": 0,
+ "ix": 7
+ },
+ "ne": {
+ "a": 0,
+ "k": 0,
+ "ix": 8
+ },
+ "a": {
+ "a": 0,
+ "k": 100,
+ "ix": 4
+ },
+ "b": 1,
+ "rn": 0,
+ "sh": 1,
+ "sm": {
+ "a": 0,
+ "k": 0,
+ "ix": 6
+ },
+ "e": {
+ "a": 0,
+ "k": 3,
+ "ix": 5
+ },
+ "r": 2
+ },
+ "a": {
+ "o": {
+ "a": 0,
+ "k": 0,
+ "ix": 9
+ }
+ }
+ }
+ ]
+ },
+ "ip": 268,
+ "op": 403,
+ "st": 268,
+ "ct": 1,
+ "bm": 0
+ },
+ {
+ "ddd": 0,
+ "ind": 2,
+ "ty": 5,
+ "nm": "Düsseldorf, Germany",
+ "sr": 1,
+ "ks": {
+ "o": {
+ "a": 1,
+ "k": [
+ {
+ "i": {
+ "x": [
+ 0.833
+ ],
+ "y": [
+ 0.833
+ ]
+ },
+ "o": {
+ "x": [
+ 0.167
+ ],
+ "y": [
+ 0.167
+ ]
+ },
+ "t": 392,
+ "s": [
+ 100
+ ]
+ },
+ {
+ "t": 402,
+ "s": [
+ 0
+ ]
+ }
+ ],
+ "ix": 11
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 10
+ },
+ "p": {
+ "a": 0,
+ "k": [
+ 31.249,
+ 78.183,
+ 0
+ ],
+ "ix": 2,
+ "l": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [
+ -318.2,
+ -1.622,
+ 0
+ ],
+ "ix": 1,
+ "l": 2
+ },
+ "s": {
+ "a": 0,
+ "k": [
+ 32.258,
+ 32.258,
+ 100
+ ],
+ "ix": 6,
+ "l": 2
+ }
+ },
+ "ao": 0,
+ "t": {
+ "d": {
+ "k": [
+ {
+ "s": {
+ "sz": [
+ 1383.20715332031,
+ 126
+ ],
+ "ps": [
+ -328,
+ -130
+ ],
+ "s": 140,
+ "f": "Roboto-Regular",
+ "t": "Düsseldorf, Germany",
+ "ca": 0,
+ "j": 0,
+ "tr": -13,
+ "lh": 168,
+ "ls": 0,
+ "fc": [
+ 0.133,
+ 0.133,
+ 0.133
+ ]
+ },
+ "t": 0
+ }
+ ]
+ },
+ "p": {},
+ "m": {
+ "g": 1,
+ "a": {
+ "a": 0,
+ "k": [
+ 0,
+ 0
+ ],
+ "ix": 2
+ }
+ },
+ "a": [
+ {
+ "nm": "Animator 1",
+ "s": {
+ "t": 0,
+ "xe": {
+ "a": 0,
+ "k": 0,
+ "ix": 7
+ },
+ "ne": {
+ "a": 0,
+ "k": 0,
+ "ix": 8
+ },
+ "a": {
+ "a": 0,
+ "k": 100,
+ "ix": 4
+ },
+ "b": 1,
+ "rn": 0,
+ "sh": 1,
+ "sm": {
+ "a": 0,
+ "k": 0,
+ "ix": 6
+ },
+ "s": {
+ "a": 0,
+ "k": 200,
+ "ix": 4
+ },
+ "e": {
+ "a": 1,
+ "k": [
+ {
+ "i": {
+ "x": [
+ 0.833
+ ],
+ "y": [
+ 0.833
+ ]
+ },
+ "o": {
+ "x": [
+ 0.167
+ ],
+ "y": [
+ 0.167
+ ]
+ },
+ "t": 297,
+ "s": [
+ 0
+ ]
+ },
+ {
+ "t": 323,
+ "s": [
+ 3
+ ]
+ }
+ ],
+ "ix": 5
+ },
+ "r": 2
+ },
+ "a": {
+ "o": {
+ "a": 0,
+ "k": 0,
+ "ix": 9
+ }
+ }
+ }
+ ]
+ },
+ "ip": 268,
+ "op": 403,
+ "st": 268,
+ "ct": 1,
+ "bm": 0
+ },
+ {
+ "ddd": 0,
+ "ind": 3,
+ "ty": 5,
+ "nm": "Montreal, Canada",
+ "sr": 1,
+ "ks": {
+ "o": {
+ "a": 1,
+ "k": [
+ {
+ "i": {
+ "x": [
+ 0.833
+ ],
+ "y": [
+ 0.833
+ ]
+ },
+ "o": {
+ "x": [
+ 0.167
+ ],
+ "y": [
+ 0.167
+ ]
+ },
+ "t": 196,
+ "s": [
+ 0
+ ]
+ },
+ {
+ "i": {
+ "x": [
+ 0.833
+ ],
+ "y": [
+ 0.833
+ ]
+ },
+ "o": {
+ "x": [
+ 0.167
+ ],
+ "y": [
+ 0.167
+ ]
+ },
+ "t": 208,
+ "s": [
+ 100
+ ]
+ },
+ {
+ "i": {
+ "x": [
+ 0.833
+ ],
+ "y": [
+ 0.833
+ ]
+ },
+ "o": {
+ "x": [
+ 0.167
+ ],
+ "y": [
+ 0.167
+ ]
+ },
+ "t": 258,
+ "s": [
+ 100
+ ]
+ },
+ {
+ "t": 268,
+ "s": [
+ 0
+ ]
+ }
+ ],
+ "ix": 11
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 10
+ },
+ "p": {
+ "a": 0,
+ "k": [
+ 34.475,
+ 78.183,
+ 0
+ ],
+ "ix": 2,
+ "l": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [
+ -318.2,
+ -1.622,
+ 0
+ ],
+ "ix": 1,
+ "l": 2
+ },
+ "s": {
+ "a": 0,
+ "k": [
+ 32.258,
+ 32.258,
+ 100
+ ],
+ "ix": 6,
+ "l": 2
+ }
+ },
+ "ao": 0,
+ "t": {
+ "d": {
+ "k": [
+ {
+ "s": {
+ "sz": [
+ 1383.20715332031,
+ 126
+ ],
+ "ps": [
+ -328,
+ -130
+ ],
+ "s": 140,
+ "f": "Roboto-Regular",
+ "t": "Montreal, Canada",
+ "ca": 0,
+ "j": 0,
+ "tr": -13,
+ "lh": 168,
+ "ls": 0,
+ "fc": [
+ 0.443,
+ 0.443,
+ 0.443
+ ]
+ },
+ "t": 0
+ }
+ ]
+ },
+ "p": {},
+ "m": {
+ "g": 1,
+ "a": {
+ "a": 0,
+ "k": [
+ 0,
+ 0
+ ],
+ "ix": 2
+ }
+ },
+ "a": [
+ {
+ "nm": "Animator 1",
+ "s": {
+ "t": 0,
+ "xe": {
+ "a": 0,
+ "k": 0,
+ "ix": 7
+ },
+ "ne": {
+ "a": 0,
+ "k": 0,
+ "ix": 8
+ },
+ "a": {
+ "a": 0,
+ "k": 100,
+ "ix": 4
+ },
+ "b": 1,
+ "rn": 0,
+ "sh": 1,
+ "sm": {
+ "a": 0,
+ "k": 0,
+ "ix": 6
+ },
+ "e": {
+ "a": 0,
+ "k": 3,
+ "ix": 5
+ },
+ "r": 2
+ },
+ "a": {
+ "o": {
+ "a": 0,
+ "k": 0,
+ "ix": 9
+ }
+ }
+ }
+ ]
+ },
+ "ip": 134,
+ "op": 269,
+ "st": 134,
+ "ct": 1,
+ "bm": 0
+ },
+ {
+ "ddd": 0,
+ "ind": 4,
+ "ty": 5,
+ "nm": "Montreal, Canada",
+ "sr": 1,
+ "ks": {
+ "o": {
+ "a": 1,
+ "k": [
+ {
+ "i": {
+ "x": [
+ 0.833
+ ],
+ "y": [
+ 0.833
+ ]
+ },
+ "o": {
+ "x": [
+ 0.167
+ ],
+ "y": [
+ 0.167
+ ]
+ },
+ "t": 258,
+ "s": [
+ 100
+ ]
+ },
+ {
+ "t": 268,
+ "s": [
+ 0
+ ]
+ }
+ ],
+ "ix": 11
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 10
+ },
+ "p": {
+ "a": 0,
+ "k": [
+ 31.249,
+ 78.183,
+ 0
+ ],
+ "ix": 2,
+ "l": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [
+ -318.2,
+ -1.622,
+ 0
+ ],
+ "ix": 1,
+ "l": 2
+ },
+ "s": {
+ "a": 0,
+ "k": [
+ 32.258,
+ 32.258,
+ 100
+ ],
+ "ix": 6,
+ "l": 2
+ }
+ },
+ "ao": 0,
+ "t": {
+ "d": {
+ "k": [
+ {
+ "s": {
+ "sz": [
+ 1383.20715332031,
+ 126
+ ],
+ "ps": [
+ -328,
+ -130
+ ],
+ "s": 140,
+ "f": "Roboto-Regular",
+ "t": "Montreal, Canada",
+ "ca": 0,
+ "j": 0,
+ "tr": -13,
+ "lh": 168,
+ "ls": 0,
+ "fc": [
+ 0.133,
+ 0.133,
+ 0.133
+ ]
+ },
+ "t": 0
+ }
+ ]
+ },
+ "p": {},
+ "m": {
+ "g": 1,
+ "a": {
+ "a": 0,
+ "k": [
+ 0,
+ 0
+ ],
+ "ix": 2
+ }
+ },
+ "a": [
+ {
+ "nm": "Animator 1",
+ "s": {
+ "t": 0,
+ "xe": {
+ "a": 0,
+ "k": 0,
+ "ix": 7
+ },
+ "ne": {
+ "a": 0,
+ "k": 0,
+ "ix": 8
+ },
+ "a": {
+ "a": 0,
+ "k": 100,
+ "ix": 4
+ },
+ "b": 1,
+ "rn": 0,
+ "sh": 1,
+ "sm": {
+ "a": 0,
+ "k": 0,
+ "ix": 6
+ },
+ "s": {
+ "a": 0,
+ "k": 200,
+ "ix": 4
+ },
+ "e": {
+ "a": 1,
+ "k": [
+ {
+ "i": {
+ "x": [
+ 0.833
+ ],
+ "y": [
+ 0.833
+ ]
+ },
+ "o": {
+ "x": [
+ 0.167
+ ],
+ "y": [
+ 0.167
+ ]
+ },
+ "t": 163,
+ "s": [
+ 0
+ ]
+ },
+ {
+ "t": 189,
+ "s": [
+ 3
+ ]
+ }
+ ],
+ "ix": 5
+ },
+ "r": 2
+ },
+ "a": {
+ "o": {
+ "a": 0,
+ "k": 0,
+ "ix": 9
+ }
+ }
+ }
+ ]
+ },
+ "ip": 134,
+ "op": 269,
+ "st": 134,
+ "ct": 1,
+ "bm": 0
+ },
+ {
+ "ddd": 0,
+ "ind": 5,
+ "ty": 5,
+ "nm": "Paris, France",
+ "sr": 1,
+ "ks": {
+ "o": {
+ "a": 1,
+ "k": [
+ {
+ "i": {
+ "x": [
+ 0.833
+ ],
+ "y": [
+ 0.833
+ ]
+ },
+ "o": {
+ "x": [
+ 0.167
+ ],
+ "y": [
+ 0.167
+ ]
+ },
+ "t": 62,
+ "s": [
+ 0
+ ]
+ },
+ {
+ "i": {
+ "x": [
+ 0.833
+ ],
+ "y": [
+ 0.833
+ ]
+ },
+ "o": {
+ "x": [
+ 0.167
+ ],
+ "y": [
+ 0.167
+ ]
+ },
+ "t": 74,
+ "s": [
+ 100
+ ]
+ },
+ {
+ "i": {
+ "x": [
+ 0.833
+ ],
+ "y": [
+ 0.833
+ ]
+ },
+ "o": {
+ "x": [
+ 0.167
+ ],
+ "y": [
+ 0.167
+ ]
+ },
+ "t": 124,
+ "s": [
+ 100
+ ]
+ },
+ {
+ "t": 134,
+ "s": [
+ 0
+ ]
+ }
+ ],
+ "ix": 11
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 10
+ },
+ "p": {
+ "a": 0,
+ "k": [
+ 34.475,
+ 78.183,
+ 0
+ ],
+ "ix": 2,
+ "l": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [
+ -318.2,
+ -1.622,
+ 0
+ ],
+ "ix": 1,
+ "l": 2
+ },
+ "s": {
+ "a": 0,
+ "k": [
+ 32.258,
+ 32.258,
+ 100
+ ],
+ "ix": 6,
+ "l": 2
+ }
+ },
+ "ao": 0,
+ "t": {
+ "d": {
+ "k": [
+ {
+ "s": {
+ "sz": [
+ 1383.20715332031,
+ 126
+ ],
+ "ps": [
+ -328,
+ -130
+ ],
+ "s": 140,
+ "f": "Roboto-Regular",
+ "t": "Paris, France",
+ "ca": 0,
+ "j": 0,
+ "tr": -13,
+ "lh": 168,
+ "ls": 0,
+ "fc": [
+ 0.443,
+ 0.443,
+ 0.443
+ ]
+ },
+ "t": 0
+ }
+ ]
+ },
+ "p": {},
+ "m": {
+ "g": 1,
+ "a": {
+ "a": 0,
+ "k": [
+ 0,
+ 0
+ ],
+ "ix": 2
+ }
+ },
+ "a": [
+ {
+ "nm": "Animator 1",
+ "s": {
+ "t": 0,
+ "xe": {
+ "a": 0,
+ "k": 0,
+ "ix": 7
+ },
+ "ne": {
+ "a": 0,
+ "k": 0,
+ "ix": 8
+ },
+ "a": {
+ "a": 0,
+ "k": 100,
+ "ix": 4
+ },
+ "b": 1,
+ "rn": 0,
+ "sh": 1,
+ "sm": {
+ "a": 0,
+ "k": 0,
+ "ix": 6
+ },
+ "e": {
+ "a": 0,
+ "k": 3,
+ "ix": 5
+ },
+ "r": 2
+ },
+ "a": {
+ "o": {
+ "a": 0,
+ "k": 0,
+ "ix": 9
+ }
+ }
+ }
+ ]
+ },
+ "ip": 0,
+ "op": 135,
+ "st": 0,
+ "ct": 1,
+ "bm": 0
+ },
+ {
+ "ddd": 0,
+ "ind": 6,
+ "ty": 5,
+ "nm": "Paris, France",
+ "sr": 1,
+ "ks": {
+ "o": {
+ "a": 1,
+ "k": [
+ {
+ "i": {
+ "x": [
+ 0.833
+ ],
+ "y": [
+ 0.833
+ ]
+ },
+ "o": {
+ "x": [
+ 0.167
+ ],
+ "y": [
+ 0.167
+ ]
+ },
+ "t": 124,
+ "s": [
+ 100
+ ]
+ },
+ {
+ "t": 134,
+ "s": [
+ 0
+ ]
+ }
+ ],
+ "ix": 11
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 10
+ },
+ "p": {
+ "a": 0,
+ "k": [
+ 31.249,
+ 78.183,
+ 0
+ ],
+ "ix": 2,
+ "l": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [
+ -318.2,
+ -1.622,
+ 0
+ ],
+ "ix": 1,
+ "l": 2
+ },
+ "s": {
+ "a": 0,
+ "k": [
+ 32.258,
+ 32.258,
+ 100
+ ],
+ "ix": 6,
+ "l": 2
+ }
+ },
+ "ao": 0,
+ "t": {
+ "d": {
+ "k": [
+ {
+ "s": {
+ "sz": [
+ 1383.20715332031,
+ 126
+ ],
+ "ps": [
+ -328,
+ -130
+ ],
+ "s": 140,
+ "f": "Roboto-Regular",
+ "t": "Paris, France",
+ "ca": 0,
+ "j": 0,
+ "tr": -13,
+ "lh": 168,
+ "ls": 0,
+ "fc": [
+ 0.133,
+ 0.133,
+ 0.133
+ ]
+ },
+ "t": 0
+ }
+ ]
+ },
+ "p": {},
+ "m": {
+ "g": 1,
+ "a": {
+ "a": 0,
+ "k": [
+ 0,
+ 0
+ ],
+ "ix": 2
+ }
+ },
+ "a": [
+ {
+ "nm": "Animator 1",
+ "s": {
+ "t": 0,
+ "xe": {
+ "a": 0,
+ "k": 0,
+ "ix": 7
+ },
+ "ne": {
+ "a": 0,
+ "k": 0,
+ "ix": 8
+ },
+ "a": {
+ "a": 0,
+ "k": 100,
+ "ix": 4
+ },
+ "b": 1,
+ "rn": 0,
+ "sh": 1,
+ "sm": {
+ "a": 0,
+ "k": 0,
+ "ix": 6
+ },
+ "s": {
+ "a": 0,
+ "k": 200,
+ "ix": 4
+ },
+ "e": {
+ "a": 1,
+ "k": [
+ {
+ "i": {
+ "x": [
+ 0.833
+ ],
+ "y": [
+ 0.833
+ ]
+ },
+ "o": {
+ "x": [
+ 0.167
+ ],
+ "y": [
+ 0.167
+ ]
+ },
+ "t": 29,
+ "s": [
+ 0
+ ]
+ },
+ {
+ "t": 55,
+ "s": [
+ 3
+ ]
+ }
+ ],
+ "ix": 5
+ },
+ "r": 2
+ },
+ "a": {
+ "o": {
+ "a": 0,
+ "k": 0,
+ "ix": 9
+ }
+ }
+ }
+ ]
+ },
+ "ip": 0,
+ "op": 135,
+ "st": 0,
+ "ct": 1,
+ "bm": 0
+ }
+ ],
+ "markers": []
+}
diff --git a/snapshot-tests/src/main/assets/Tests/text_range_test_glyphs.json b/snapshot-tests/src/main/assets/Tests/text_range_test_glyphs.json
new file mode 100644
index 0000000..cdddb20
--- /dev/null
+++ b/snapshot-tests/src/main/assets/Tests/text_range_test_glyphs.json
@@ -0,0 +1,1840 @@
+{
+ "v": "5.12.2",
+ "fr": 29.9700012207031,
+ "ip": 0,
+ "op": 120.0000048877,
+ "w": 400,
+ "h": 400,
+ "nm": "Comp 1",
+ "ddd": 0,
+ "assets": [],
+ "fonts": {
+ "list": [
+ {
+ "fName": "Helvetica",
+ "fFamily": "Helvetica",
+ "fStyle": "Regular",
+ "ascent": 71.8994140625
+ }
+ ]
+ },
+ "layers": [
+ {
+ "ddd": 0,
+ "ind": 1,
+ "ty": 5,
+ "nm": "Hello World",
+ "sr": 1,
+ "ks": {
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 11
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 10
+ },
+ "p": {
+ "a": 0,
+ "k": [
+ 42,
+ 168,
+ 0
+ ],
+ "ix": 2,
+ "l": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [
+ 0,
+ 0,
+ 0
+ ],
+ "ix": 1,
+ "l": 2
+ },
+ "s": {
+ "a": 0,
+ "k": [
+ 100,
+ 100,
+ 100
+ ],
+ "ix": 6,
+ "l": 2
+ }
+ },
+ "ao": 0,
+ "t": {
+ "d": {
+ "k": [
+ {
+ "s": {
+ "s": 36,
+ "f": "Helvetica",
+ "t": "Hello World",
+ "ca": 0,
+ "j": 0,
+ "tr": 0,
+ "lh": 43.2000007629395,
+ "ls": 0,
+ "fc": [
+ 0.132,
+ 0.132,
+ 0.132
+ ]
+ },
+ "t": 0
+ }
+ ]
+ },
+ "p": {},
+ "m": {
+ "g": 1,
+ "a": {
+ "a": 0,
+ "k": [
+ 0,
+ 0
+ ],
+ "ix": 2
+ }
+ },
+ "a": [
+ {
+ "nm": "Animator 1",
+ "s": {
+ "t": 0,
+ "xe": {
+ "a": 0,
+ "k": 0,
+ "ix": 7
+ },
+ "ne": {
+ "a": 0,
+ "k": 0,
+ "ix": 8
+ },
+ "a": {
+ "a": 0,
+ "k": 100,
+ "ix": 4
+ },
+ "b": 1,
+ "rn": 0,
+ "sh": 1,
+ "sm": {
+ "a": 0,
+ "k": 100,
+ "ix": 6
+ },
+ "s": {
+ "a": 1,
+ "k": [
+ {
+ "i": {
+ "x": [
+ 0.833
+ ],
+ "y": [
+ 0.833
+ ]
+ },
+ "o": {
+ "x": [
+ 0.167
+ ],
+ "y": [
+ 0.167
+ ]
+ },
+ "t": 0,
+ "s": [
+ 0
+ ]
+ },
+ {
+ "t": 119.000004846969,
+ "s": [
+ 100
+ ]
+ }
+ ],
+ "ix": 1
+ },
+ "e": {
+ "a": 1,
+ "k": [
+ {
+ "i": {
+ "x": [
+ 0.833
+ ],
+ "y": [
+ 0.833
+ ]
+ },
+ "o": {
+ "x": [
+ 0.167
+ ],
+ "y": [
+ 0.167
+ ]
+ },
+ "t": 0,
+ "s": [
+ 0
+ ]
+ },
+ {
+ "t": 119.000004846969,
+ "s": [
+ 0
+ ]
+ }
+ ],
+ "ix": 2
+ },
+ "r": 1
+ },
+ "a": {
+ "o": {
+ "a": 0,
+ "k": 0,
+ "ix": 9
+ }
+ }
+ }
+ ]
+ },
+ "ip": 0,
+ "op": 120.0000048877,
+ "st": 0,
+ "ct": 1,
+ "bm": 0
+ }
+ ],
+ "markers": [],
+ "props": {},
+ "chars": [
+ {
+ "ch": "H",
+ "size": 36,
+ "style": "Regular",
+ "w": 72.22,
+ "data": {
+ "shapes": [
+ {
+ "ty": "gr",
+ "it": [
+ {
+ "ind": 0,
+ "ty": "sh",
+ "ix": 1,
+ "ks": {
+ "a": 0,
+ "k": {
+ "i": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ]
+ ],
+ "o": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ]
+ ],
+ "v": [
+ [
+ 7.861,
+ 0
+ ],
+ [
+ 17.676,
+ 0
+ ],
+ [
+ 17.676,
+ -33.545
+ ],
+ [
+ 54.541,
+ -33.545
+ ],
+ [
+ 54.541,
+ 0
+ ],
+ [
+ 64.355,
+ 0
+ ],
+ [
+ 64.355,
+ -71.729
+ ],
+ [
+ 54.541,
+ -71.729
+ ],
+ [
+ 54.541,
+ -42.09
+ ],
+ [
+ 17.676,
+ -42.09
+ ],
+ [
+ 17.676,
+ -71.729
+ ],
+ [
+ 7.861,
+ -71.729
+ ]
+ ],
+ "c": true
+ },
+ "ix": 2
+ },
+ "nm": "H",
+ "mn": "ADBE Vector Shape - Group",
+ "hd": false
+ }
+ ],
+ "nm": "H",
+ "np": 3,
+ "cix": 2,
+ "bm": 0,
+ "ix": 1,
+ "mn": "ADBE Vector Group",
+ "hd": false
+ }
+ ]
+ },
+ "fFamily": "Helvetica"
+ },
+ {
+ "ch": "e",
+ "size": 36,
+ "style": "Regular",
+ "w": 55.62,
+ "data": {
+ "shapes": [
+ {
+ "ty": "gr",
+ "it": [
+ {
+ "ind": 0,
+ "ty": "sh",
+ "ix": 1,
+ "ks": {
+ "a": 0,
+ "k": {
+ "i": [
+ [
+ 3.688,
+ 0
+ ],
+ [
+ 4.529,
+ -5.241
+ ],
+ [
+ 0,
+ -8.398
+ ],
+ [
+ -4.497,
+ -4.736
+ ],
+ [
+ -6.47,
+ 0
+ ],
+ [
+ -2.1,
+ 0.52
+ ],
+ [
+ -2.65,
+ 2.605
+ ],
+ [
+ -1.286,
+ 2.361
+ ],
+ [
+ -0.228,
+ 1.921
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 1.518,
+ -1.765
+ ],
+ [
+ 4.554,
+ 0
+ ],
+ [
+ 2.325,
+ 3.215
+ ],
+ [
+ 0.162,
+ 5.321
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0.517,
+ 2.409
+ ],
+ [
+ 1.747,
+ 2.637
+ ],
+ [
+ 3.461,
+ 1.742
+ ]
+ ],
+ "o": [
+ [
+ -7.312,
+ 0
+ ],
+ [
+ -4.53,
+ 5.241
+ ],
+ [
+ 0,
+ 8.529
+ ],
+ [
+ 4.497,
+ 4.736
+ ],
+ [
+ 2.649,
+ 0
+ ],
+ [
+ 3.909,
+ -0.912
+ ],
+ [
+ 1.582,
+ -1.497
+ ],
+ [
+ 1.286,
+ -2.36
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ -0.633,
+ 2.322
+ ],
+ [
+ -2.713,
+ 3.041
+ ],
+ [
+ -4.877,
+ 0
+ ],
+ [
+ -2.325,
+ -3.215
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ -5.273
+ ],
+ [
+ -0.583,
+ -3.516
+ ],
+ [
+ -1.812,
+ -2.766
+ ],
+ [
+ -3.462,
+ -1.741
+ ]
+ ],
+ "v": [
+ [
+ 28.022,
+ -53.467
+ ],
+ [
+ 10.261,
+ -45.605
+ ],
+ [
+ 3.467,
+ -25.146
+ ],
+ [
+ 10.211,
+ -5.249
+ ],
+ [
+ 26.661,
+ 1.855
+ ],
+ [
+ 33.784,
+ 1.074
+ ],
+ [
+ 43.622,
+ -4.199
+ ],
+ [
+ 47.925,
+ -9.985
+ ],
+ [
+ 50.195,
+ -16.406
+ ],
+ [
+ 41.553,
+ -16.406
+ ],
+ [
+ 38.326,
+ -10.275
+ ],
+ [
+ 27.425,
+ -5.713
+ ],
+ [
+ 16.621,
+ -10.535
+ ],
+ [
+ 12.891,
+ -23.34
+ ],
+ [
+ 50.928,
+ -23.34
+ ],
+ [
+ 50.151,
+ -34.863
+ ],
+ [
+ 46.657,
+ -44.092
+ ],
+ [
+ 38.747,
+ -50.854
+ ]
+ ],
+ "c": true
+ },
+ "ix": 2
+ },
+ "nm": "e",
+ "mn": "ADBE Vector Shape - Group",
+ "hd": false
+ },
+ {
+ "ind": 1,
+ "ty": "sh",
+ "ix": 2,
+ "ks": {
+ "a": 0,
+ "k": {
+ "i": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -2.711,
+ 2.914
+ ],
+ [
+ -4.002,
+ 0
+ ],
+ [
+ -2.389,
+ -4.231
+ ],
+ [
+ -0.356,
+ -3.809
+ ]
+ ],
+ "o": [
+ [
+ 0.161,
+ -4.492
+ ],
+ [
+ 2.711,
+ -2.913
+ ],
+ [
+ 5.584,
+ 0
+ ],
+ [
+ 1.291,
+ 2.279
+ ],
+ [
+ 0,
+ 0
+ ]
+ ],
+ "v": [
+ [
+ 13.086,
+ -30.322
+ ],
+ [
+ 17.395,
+ -41.431
+ ],
+ [
+ 27.466,
+ -45.801
+ ],
+ [
+ 39.425,
+ -39.453
+ ],
+ [
+ 41.895,
+ -30.322
+ ]
+ ],
+ "c": true
+ },
+ "ix": 2
+ },
+ "nm": "e",
+ "mn": "ADBE Vector Shape - Group",
+ "hd": false
+ }
+ ],
+ "nm": "e",
+ "np": 5,
+ "cix": 2,
+ "bm": 0,
+ "ix": 1,
+ "mn": "ADBE Vector Group",
+ "hd": false
+ }
+ ]
+ },
+ "fFamily": "Helvetica"
+ },
+ {
+ "ch": "l",
+ "size": 36,
+ "style": "Regular",
+ "w": 22.22,
+ "data": {
+ "shapes": [
+ {
+ "ty": "gr",
+ "it": [
+ {
+ "ind": 0,
+ "ty": "sh",
+ "ix": 1,
+ "ks": {
+ "a": 0,
+ "k": {
+ "i": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ]
+ ],
+ "o": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ]
+ ],
+ "v": [
+ [
+ 6.689,
+ 0
+ ],
+ [
+ 15.479,
+ 0
+ ],
+ [
+ 15.479,
+ -71.729
+ ],
+ [
+ 6.689,
+ -71.729
+ ]
+ ],
+ "c": true
+ },
+ "ix": 2
+ },
+ "nm": "l",
+ "mn": "ADBE Vector Shape - Group",
+ "hd": false
+ }
+ ],
+ "nm": "l",
+ "np": 3,
+ "cix": 2,
+ "bm": 0,
+ "ix": 1,
+ "mn": "ADBE Vector Group",
+ "hd": false
+ }
+ ]
+ },
+ "fFamily": "Helvetica"
+ },
+ {
+ "ch": "o",
+ "size": 36,
+ "style": "Regular",
+ "w": 55.62,
+ "data": {
+ "shapes": [
+ {
+ "ty": "gr",
+ "it": [
+ {
+ "ind": 0,
+ "ty": "sh",
+ "ix": 1,
+ "ks": {
+ "a": 0,
+ "k": {
+ "i": [
+ [
+ 5.761,
+ 0
+ ],
+ [
+ 2.414,
+ 3.706
+ ],
+ [
+ 0,
+ 5.56
+ ],
+ [
+ -2.414,
+ 4.097
+ ],
+ [
+ -5.312,
+ 0
+ ],
+ [
+ -2.446,
+ -4.812
+ ],
+ [
+ 0,
+ -4.877
+ ],
+ [
+ 2.141,
+ -4.405
+ ]
+ ],
+ "o": [
+ [
+ -5.247,
+ 0
+ ],
+ [
+ -2.414,
+ -3.706
+ ],
+ [
+ 0,
+ -5.787
+ ],
+ [
+ 2.414,
+ -4.097
+ ],
+ [
+ 5.987,
+ 0
+ ],
+ [
+ 1.545,
+ 3.056
+ ],
+ [
+ 0,
+ 5.397
+ ],
+ [
+ -2.141,
+ 4.405
+ ]
+ ],
+ "v": [
+ [
+ 27.026,
+ -5.713
+ ],
+ [
+ 15.535,
+ -11.272
+ ],
+ [
+ 11.914,
+ -25.172
+ ],
+ [
+ 15.535,
+ -39.997
+ ],
+ [
+ 27.122,
+ -46.143
+ ],
+ [
+ 39.772,
+ -38.924
+ ],
+ [
+ 42.09,
+ -27.025
+ ],
+ [
+ 38.879,
+ -12.321
+ ]
+ ],
+ "c": true
+ },
+ "ix": 2
+ },
+ "nm": "o",
+ "mn": "ADBE Vector Shape - Group",
+ "hd": false
+ },
+ {
+ "ind": 1,
+ "ty": "sh",
+ "ix": 2,
+ "ks": {
+ "a": 0,
+ "k": {
+ "i": [
+ [
+ 6.691,
+ 0
+ ],
+ [
+ 4.428,
+ -5.203
+ ],
+ [
+ 0,
+ -8.781
+ ],
+ [
+ -4.202,
+ -4.862
+ ],
+ [
+ -7.144,
+ 0
+ ],
+ [
+ -4.073,
+ 5.496
+ ],
+ [
+ 0,
+ 8.424
+ ],
+ [
+ 4.622,
+ 4.488
+ ]
+ ],
+ "o": [
+ [
+ -7.467,
+ 0
+ ],
+ [
+ -4.428,
+ 5.204
+ ],
+ [
+ 0,
+ 8.196
+ ],
+ [
+ 4.202,
+ 4.862
+ ],
+ [
+ 8.566,
+ 0
+ ],
+ [
+ 4.073,
+ -5.496
+ ],
+ [
+ 0,
+ -8.716
+ ],
+ [
+ -4.623,
+ -4.488
+ ]
+ ],
+ "v": [
+ [
+ 27.366,
+ -53.809
+ ],
+ [
+ 9.523,
+ -46.003
+ ],
+ [
+ 2.881,
+ -25.025
+ ],
+ [
+ 9.184,
+ -5.437
+ ],
+ [
+ 26.202,
+ 1.855
+ ],
+ [
+ 45.16,
+ -6.389
+ ],
+ [
+ 51.27,
+ -27.27
+ ],
+ [
+ 44.336,
+ -47.076
+ ]
+ ],
+ "c": true
+ },
+ "ix": 2
+ },
+ "nm": "o",
+ "mn": "ADBE Vector Shape - Group",
+ "hd": false
+ }
+ ],
+ "nm": "o",
+ "np": 5,
+ "cix": 2,
+ "bm": 0,
+ "ix": 1,
+ "mn": "ADBE Vector Group",
+ "hd": false
+ }
+ ]
+ },
+ "fFamily": "Helvetica"
+ },
+ {
+ "ch": " ",
+ "size": 36,
+ "style": "Regular",
+ "w": 27.78,
+ "data": {},
+ "fFamily": "Helvetica"
+ },
+ {
+ "ch": "W",
+ "size": 36,
+ "style": "Regular",
+ "w": 94.38,
+ "data": {
+ "shapes": [
+ {
+ "ty": "gr",
+ "it": [
+ {
+ "ind": 0,
+ "ty": "sh",
+ "ix": 1,
+ "ks": {
+ "a": 0,
+ "k": {
+ "i": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ]
+ ],
+ "o": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ]
+ ],
+ "v": [
+ [
+ 1.831,
+ -71.729
+ ],
+ [
+ 20.581,
+ 0
+ ],
+ [
+ 30.786,
+ 0
+ ],
+ [
+ 47.437,
+ -59.473
+ ],
+ [
+ 63.989,
+ 0
+ ],
+ [
+ 74.194,
+ 0
+ ],
+ [
+ 93.042,
+ -71.729
+ ],
+ [
+ 82.39,
+ -71.729
+ ],
+ [
+ 68.867,
+ -13.343
+ ],
+ [
+ 52.617,
+ -71.729
+ ],
+ [
+ 42.114,
+ -71.729
+ ],
+ [
+ 25.9,
+ -13.453
+ ],
+ [
+ 12.379,
+ -71.729
+ ]
+ ],
+ "c": true
+ },
+ "ix": 2
+ },
+ "nm": "W",
+ "mn": "ADBE Vector Shape - Group",
+ "hd": false
+ }
+ ],
+ "nm": "W",
+ "np": 3,
+ "cix": 2,
+ "bm": 0,
+ "ix": 1,
+ "mn": "ADBE Vector Group",
+ "hd": false
+ }
+ ]
+ },
+ "fFamily": "Helvetica"
+ },
+ {
+ "ch": "r",
+ "size": 36,
+ "style": "Regular",
+ "w": 33.3,
+ "data": {
+ "shapes": [
+ {
+ "ty": "gr",
+ "it": [
+ {
+ "ind": 0,
+ "ty": "sh",
+ "ix": 1,
+ "ks": {
+ "a": 0,
+ "k": {
+ "i": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ -2.377,
+ 2.849
+ ],
+ [
+ -4.427,
+ 0
+ ],
+ [
+ -0.439,
+ -0.032
+ ],
+ [
+ -0.521,
+ -0.098
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0.391,
+ 0.033
+ ],
+ [
+ 0.163,
+ 0
+ ],
+ [
+ 2.669,
+ -2.522
+ ],
+ [
+ 0.684,
+ -1.758
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ]
+ ],
+ "o": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ -3.711
+ ],
+ [
+ 2.376,
+ -2.848
+ ],
+ [
+ 0.52,
+ 0
+ ],
+ [
+ 0.439,
+ 0.033
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ -0.945,
+ -0.098
+ ],
+ [
+ -0.391,
+ -0.032
+ ],
+ [
+ -3.484,
+ 0
+ ],
+ [
+ -2.67,
+ 2.523
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ]
+ ],
+ "v": [
+ [
+ 6.689,
+ 0
+ ],
+ [
+ 15.479,
+ 0
+ ],
+ [
+ 15.479,
+ -30.078
+ ],
+ [
+ 19.043,
+ -39.917
+ ],
+ [
+ 29.248,
+ -44.189
+ ],
+ [
+ 30.688,
+ -44.141
+ ],
+ [
+ 32.129,
+ -43.945
+ ],
+ [
+ 32.129,
+ -53.223
+ ],
+ [
+ 30.127,
+ -53.418
+ ],
+ [
+ 29.297,
+ -53.467
+ ],
+ [
+ 20.068,
+ -49.683
+ ],
+ [
+ 15.039,
+ -43.262
+ ],
+ [
+ 15.039,
+ -52.295
+ ],
+ [
+ 6.689,
+ -52.295
+ ]
+ ],
+ "c": true
+ },
+ "ix": 2
+ },
+ "nm": "r",
+ "mn": "ADBE Vector Shape - Group",
+ "hd": false
+ }
+ ],
+ "nm": "r",
+ "np": 3,
+ "cix": 2,
+ "bm": 0,
+ "ix": 1,
+ "mn": "ADBE Vector Group",
+ "hd": false
+ }
+ ]
+ },
+ "fFamily": "Helvetica"
+ },
+ {
+ "ch": "d",
+ "size": 36,
+ "style": "Regular",
+ "w": 55.62,
+ "data": {
+ "shapes": [
+ {
+ "ty": "gr",
+ "it": [
+ {
+ "ind": 0,
+ "ty": "sh",
+ "ix": 1,
+ "ks": {
+ "a": 0,
+ "k": {
+ "i": [
+ [
+ 0,
+ 5.599
+ ],
+ [
+ -2.737,
+ 3.386
+ ],
+ [
+ -4.405,
+ 0
+ ],
+ [
+ -2.689,
+ -3.174
+ ],
+ [
+ 0,
+ -6.608
+ ],
+ [
+ 2.607,
+ -3.499
+ ],
+ [
+ 4.048,
+ 0
+ ],
+ [
+ 2.364,
+ 3.777
+ ]
+ ],
+ "o": [
+ [
+ 0,
+ -6.575
+ ],
+ [
+ 2.737,
+ -3.385
+ ],
+ [
+ 3.951,
+ 0
+ ],
+ [
+ 2.688,
+ 3.174
+ ],
+ [
+ 0,
+ 6.543
+ ],
+ [
+ -2.608,
+ 3.5
+ ],
+ [
+ -5.215,
+ 0
+ ],
+ [
+ -2.364,
+ -3.776
+ ]
+ ],
+ "v": [
+ [
+ 12.012,
+ -25.537
+ ],
+ [
+ 16.116,
+ -40.479
+ ],
+ [
+ 26.829,
+ -45.557
+ ],
+ [
+ 36.788,
+ -40.796
+ ],
+ [
+ 40.82,
+ -26.123
+ ],
+ [
+ 36.909,
+ -11.06
+ ],
+ [
+ 26.926,
+ -5.811
+ ],
+ [
+ 15.558,
+ -11.475
+ ]
+ ],
+ "c": true
+ },
+ "ix": 2
+ },
+ "nm": "d",
+ "mn": "ADBE Vector Shape - Group",
+ "hd": false
+ },
+ {
+ "ind": 1,
+ "ty": "sh",
+ "ix": 2,
+ "ks": {
+ "a": 0,
+ "k": {
+ "i": [
+ [
+ 3.989,
+ 0
+ ],
+ [
+ 3.875,
+ -5.582
+ ],
+ [
+ 0,
+ -7.617
+ ],
+ [
+ -4.248,
+ -4.899
+ ],
+ [
+ -5.804,
+ 0
+ ],
+ [
+ -2.789,
+ 1.432
+ ],
+ [
+ -2.043,
+ 3.223
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 1.556,
+ 0.977
+ ]
+ ],
+ "o": [
+ [
+ -7.199,
+ 0
+ ],
+ [
+ -3.875,
+ 5.583
+ ],
+ [
+ 0,
+ 8.138
+ ],
+ [
+ 4.248,
+ 4.9
+ ],
+ [
+ 3.6,
+ 0
+ ],
+ [
+ 2.789,
+ -1.432
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ -1.978,
+ -2.441
+ ],
+ [
+ -2.692,
+ -1.692
+ ]
+ ],
+ "v": [
+ [
+ 25.157,
+ -53.223
+ ],
+ [
+ 8.546,
+ -44.849
+ ],
+ [
+ 2.734,
+ -25.049
+ ],
+ [
+ 9.106,
+ -5.493
+ ],
+ [
+ 24.185,
+ 1.855
+ ],
+ [
+ 33.768,
+ -0.293
+ ],
+ [
+ 41.016,
+ -7.275
+ ],
+ [
+ 41.016,
+ 0
+ ],
+ [
+ 48.926,
+ 0
+ ],
+ [
+ 48.926,
+ -71.973
+ ],
+ [
+ 40.479,
+ -71.973
+ ],
+ [
+ 40.479,
+ -45.557
+ ],
+ [
+ 35.177,
+ -50.684
+ ]
+ ],
+ "c": true
+ },
+ "ix": 2
+ },
+ "nm": "d",
+ "mn": "ADBE Vector Shape - Group",
+ "hd": false
+ }
+ ],
+ "nm": "d",
+ "np": 5,
+ "cix": 2,
+ "bm": 0,
+ "ix": 1,
+ "mn": "ADBE Vector Group",
+ "hd": false
+ }
+ ]
+ },
+ "fFamily": "Helvetica"
+ }
+ ]
+}